Skip to content

Commit 9b0e314

Browse files
committed
Merge branch 'dmaclach-block_fix'
2 parents f7141a3 + 30e6dc9 commit 9b0e314

6 files changed

+161
-34
lines changed

Source/OCMock.xcodeproj/project.pbxproj

+6
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@
279279
817EB15C1BD765130047E85A /* OCMBlockArgCaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 2FA2891034E7B73AA3511D17 /* OCMBlockArgCaller.h */; };
280280
817EB15D1BD765130047E85A /* OCMArgAction.h in Headers */ = {isa = PBXBuildFile; fileRef = 2FA2833B48908EAD36444671 /* OCMArgAction.h */; };
281281
817EB1661BD7674D0047E85A /* OCMFunctionsPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 03F370CA1BAA1DE800CAD3E8 /* OCMFunctionsPrivate.h */; };
282+
8BF73E53246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
283+
8BF73E54246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */; settings = {COMPILER_FLAGS = "-Xclang -fexperimental-optimized-noescape"; }; };
282284
8DE97C5522B43EE60098C63F /* OCMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3159E146333BF0052CD09 /* OCMockObject.m */; };
283285
8DE97C5622B43EE60098C63F /* OCClassMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B3158C146333BF0052CD09 /* OCClassMockObject.m */; };
284286
8DE97C5722B43EE60098C63F /* OCPartialMockObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 03B315AA146333BF0052CD09 /* OCPartialMockObject.m */; };
@@ -569,6 +571,7 @@
569571
3CFBDD751BB3DB200050D9C5 /* TestClassWithCustomReferenceCounting.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestClassWithCustomReferenceCounting.h; sourceTree = "<group>"; };
570572
3CFBDD761BB3DB200050D9C5 /* TestClassWithCustomReferenceCounting.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TestClassWithCustomReferenceCounting.m; sourceTree = "<group>"; };
571573
817EB1621BD765130047E85A /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
574+
8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OCMNoEscapeBlockTests.m; sourceTree = "<group>"; };
572575
8DE97CA022B43EE60098C63F /* OCMock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OCMock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
573576
A02926811CA0725A00594AAF /* TestObjects.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = TestObjects.xcdatamodel; sourceTree = "<group>"; };
574577
D31108AD1828DB8700737925 /* OCMockLibTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OCMockLibTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -743,6 +746,7 @@
743746
03B316231463350E0052CD09 /* OCMockObjectHamcrestTests.m */,
744747
038599F623807B06002B3ABE /* OCMockObjectInternalTests.m */,
745748
2FA2813F93050582D83E1499 /* OCMockObjectRuntimeTests.m */,
749+
8BF73E52246CA75E00B9A52C /* OCMNoEscapeBlockTests.m */,
746750
03B316271463350E0052CD09 /* OCMStubRecorderTests.m */,
747751
037ECD5318FAD84100AF0E4C /* OCMInvocationMatcherTests.m */,
748752
031E50571BB4A56300E257C3 /* OCMBoxedReturnValueProviderTests.m */,
@@ -1501,6 +1505,7 @@
15011505
2FA28FA53C57236B6DD64E82 /* OCMockObjectRuntimeTests.m in Sources */,
15021506
2FA2839F33289795284C32FB /* OCMockObjectTests.m in Sources */,
15031507
038599F723807B06002B3ABE /* OCMockObjectInternalTests.m in Sources */,
1508+
8BF73E53246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */,
15041509
2FA28AB33F01A7D980F2C705 /* OCMockObjectDynamicPropertyMockingTests.m in Sources */,
15051510
031E50581BB4A56300E257C3 /* OCMBoxedReturnValueProviderTests.m in Sources */,
15061511
);
@@ -1613,6 +1618,7 @@
16131618
A06930951CA1BFC900513023 /* TestObjects.xcdatamodeld in Sources */,
16141619
2FA28295E1F58F40A77D7448 /* OCMockObjectRuntimeTests.m in Sources */,
16151620
038599F823807B06002B3ABE /* OCMockObjectInternalTests.m in Sources */,
1621+
8BF73E54246CA75E00B9A52C /* OCMNoEscapeBlockTests.m in Sources */,
16161622
2FA28246CD449A01717B1CEC /* OCMockObjectTests.m in Sources */,
16171623
2FA28F12AAD384A8CB16094B /* OCMockObjectDynamicPropertyMockingTests.m in Sources */,
16181624
);

Source/OCMock/NSInvocation+OCMAdditions.m

+19-7
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,18 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
8888
{
8989
if(OCMIsBlockType(argumentType))
9090
{
91-
// block types need to be copied in case they're stack blocks
92-
id blockArgument = [argument copy];
93-
[retainedArguments addObject:blockArgument];
94-
[blockArgument release];
91+
// Block types need to be copied because they could be stack blocks.
92+
// However, non-escaping blocks have a lifetime that is stack-based and they
93+
// treat copy/release as a no-op. For details see:
94+
// https://reviews.llvm.org/rGdbfa453e4138bb977644929c69d1c71e5e8b4bee
95+
// If we keep a reference to a non-escaping block in retainedArguments, it
96+
// will end up as dangling pointer, resulting in a crash later.
97+
if(OCMIsNonEscapingBlock(argument) == NO)
98+
{
99+
id blockArgument = [argument copy];
100+
[retainedArguments addObject:blockArgument];
101+
[blockArgument release];
102+
}
95103
}
96104
else if(OCMIsClassType(argumentType) && object_isClass(argument))
97105
{
@@ -116,9 +124,13 @@ - (void)retainObjectArgumentsExcludingObject:(id)objectToExclude
116124
{
117125
if(OCMIsBlockType(returnType))
118126
{
119-
id blockReturnValue = [returnValue copy];
120-
[retainedArguments addObject:blockReturnValue];
121-
[blockReturnValue release];
127+
// See above for an explanation
128+
if(OCMIsNonEscapingBlock(returnValue) == NO)
129+
{
130+
id blockReturnValue = [returnValue copy];
131+
[retainedArguments addObject:blockReturnValue];
132+
[blockReturnValue release];
133+
}
122134
}
123135
else
124136
{

Source/OCMock/NSMethodSignature+OCMAdditions.m

+3-27
Original file line numberDiff line numberDiff line change
@@ -111,38 +111,14 @@ + (objc_property_t)propertyMatchingSelector:(SEL)selector inClass:(Class)aClass
111111

112112
#pragma mark Signatures for blocks
113113

114-
struct OCMBlockDef
115-
{
116-
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
117-
int flags;
118-
int reserved;
119-
void (*invoke)(void *, ...);
120-
struct block_descriptor {
121-
unsigned long int reserved; // NULL
122-
unsigned long int size; // sizeof(struct Block_literal_1)
123-
// optional helper functions
124-
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
125-
void (*dispose_helper)(void *src); // IFF (1<<25)
126-
// required ABI.2010.3.16
127-
const char *signature; // IFF (1<<30)
128-
} *descriptor;
129-
};
130-
131-
enum
132-
{
133-
OCMBlockDescriptionFlagsHasCopyDispose = (1 << 25),
134-
OCMBlockDescriptionFlagsHasSignature = (1 << 30)
135-
};
136-
137-
138114
+ (NSMethodSignature *)signatureForBlock:(id)block
139115
{
140116
/* For a more complete implementation of parsing the block data structure see:
141117
*
142118
* https://github.com/ebf/CTObjectiveCRuntimeAdditions/tree/master/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions
143119
*/
144120

145-
struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *)block;
121+
struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *) block;
146122

147123
if(!(blockRef->flags & OCMBlockDescriptionFlagsHasSignature))
148124
return nil;
@@ -152,11 +128,11 @@ + (NSMethodSignature *)signatureForBlock:(id)block
152128
signatureLocation += sizeof(unsigned long int);
153129
if(blockRef->flags & OCMBlockDescriptionFlagsHasCopyDispose)
154130
{
155-
signatureLocation += sizeof(void(*)(void *dst, void *src));
131+
signatureLocation += sizeof(void (*)(void *dst, void *src));
156132
signatureLocation += sizeof(void (*)(void *src));
157133
}
158134

159-
const char *signature = (*(const char **)signatureLocation);
135+
const char *signature = (*(const char **) signatureLocation);
160136
return [NSMethodSignature signatureWithObjCTypes:signature];
161137
}
162138

Source/OCMock/OCMFunctions.m

+8
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,14 @@ BOOL OCMIsApplePrivateMethod(Class cls, SEL sel)
310310
([selName hasPrefix:@"_"] || [selName hasSuffix:@"_"]);
311311
}
312312

313+
314+
BOOL OCMIsNonEscapingBlock(id block)
315+
{
316+
struct OCMBlockDef *blockRef = (__bridge struct OCMBlockDef *)block;
317+
return (blockRef->flags & OCMBlockIsNoEscape) != 0;
318+
}
319+
320+
313321
#pragma mark Creating classes
314322

315323
Class OCMCreateSubclass(Class class, void *ref)

Source/OCMock/OCMFunctionsPrivate.h

+28
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,31 @@ OCPartialMockObject *OCMGetAssociatedMockForObject(id anObject);
4545

4646
void OCMReportFailure(OCMLocation *loc, NSString *description);
4747

48+
BOOL OCMIsNonEscapingBlock(id block);
49+
50+
51+
52+
struct OCMBlockDef
53+
{
54+
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
55+
int flags;
56+
int reserved;
57+
void (*invoke)(void *, ...);
58+
struct block_descriptor {
59+
unsigned long int reserved; // NULL
60+
unsigned long int size; // sizeof(struct Block_literal_1)
61+
// optional helper functions
62+
void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
63+
void (*dispose_helper)(void *src); // IFF (1<<25)
64+
// required ABI.2010.3.16
65+
const char *signature; // IFF (1<<30)
66+
} *descriptor;
67+
};
68+
69+
enum
70+
{
71+
OCMBlockIsNoEscape = (1 << 23),
72+
OCMBlockDescriptionFlagsHasCopyDispose = (1 << 25),
73+
OCMBlockDescriptionFlagsHasSignature = (1 << 30)
74+
};
75+
+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2020 Erik Doernenburg and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
* not use these files except in compliance with the License. You may obtain
6+
* a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
#import <XCTest/XCTest.h>
18+
#import <OCMock/OCMock.h>
19+
#import "OCMFunctionsPrivate.h"
20+
21+
@interface NSString(NoEscapeBlock)
22+
@end
23+
24+
@implementation NSString(NoEscapeBlock)
25+
26+
- (void)methodWithNoEscapeBlock:(void(NS_NOESCAPE ^)(void))block
27+
{
28+
}
29+
30+
@end
31+
32+
// Verifies that the block being passed in is a noescape block.
33+
@interface BlockCapturer : NSProxy
34+
@end
35+
36+
@implementation BlockCapturer
37+
{
38+
XCTestExpectation *expectation;
39+
}
40+
41+
- (instancetype)initWithExpectation:(XCTestExpectation *)anExpectation
42+
{
43+
expectation = anExpectation;
44+
return self;
45+
}
46+
47+
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
48+
{
49+
return [NSString instanceMethodSignatureForSelector:selector];
50+
}
51+
52+
- (void)forwardInvocation:(NSInvocation *)invocation
53+
{
54+
__unsafe_unretained id block;
55+
[invocation getArgument:&block atIndex:2];
56+
if(OCMIsNonEscapingBlock(block))
57+
{
58+
[expectation fulfill];
59+
}
60+
}
61+
62+
@end
63+
64+
65+
@interface OCMNoEscapeBlockTests : XCTestCase
66+
@end
67+
68+
@implementation OCMNoEscapeBlockTests
69+
70+
- (void)testThatBlocksAreNoEscape
71+
{
72+
// This tests that this file is compiled with
73+
// `-Xclang -fexperimental-optimized-noescape` or equivalent.
74+
XCTestExpectation *expectation = [self expectationWithDescription:@"Block should be noescape"];
75+
id blockCapturer = [[BlockCapturer alloc] initWithExpectation:expectation];
76+
int i = 0;
77+
[blockCapturer methodWithNoEscapeBlock:^{
78+
// Force i to be pulled into the closure.
79+
(void)i;
80+
}];
81+
[self waitForExpectationsWithTimeout:0 handler:nil];
82+
}
83+
84+
- (void)testNoEscapeBlocksAreNotRetained
85+
{
86+
// This tests that OCMock can handle noescape blocks.
87+
// It crashes if it fails
88+
id mock = [OCMockObject mockForClass:[NSString class]];
89+
[[mock stub] methodWithNoEscapeBlock:[OCMArg invokeBlock]];
90+
int i = 0;
91+
[mock methodWithNoEscapeBlock:^{
92+
// Force i to be pulled into the closure.
93+
(void)i;
94+
}];
95+
}
96+
97+
@end

0 commit comments

Comments
 (0)