Added support for method argument nullability

Summary:
This diff adds support for enforcing nullability in the arguments for exported methods.

We previously supported use of the nullable/nonnull attributes on method arguments, but didn't do anything to ensure that they were respected.

Now, if an argument is marked as nonnull, and a null value is sent for that argument, it will display a redbox.

In future, nonnull will be assumed by default, but for now we assume that un-annotated arguments can be null (to avoid breaking existing code).
This commit is contained in:
Nick Lockwood 2015-07-29 05:54:59 -07:00
parent e0ea046092
commit 95b9dd3a88
7 changed files with 397 additions and 235 deletions

View File

@ -15,13 +15,14 @@
134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 134A8A251AACED6A00945AAE /* libRCTGeolocation.a */; };
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A151B53CD440074A87E /* RCTCacheTests.m */; };
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 138D6A161B53CD440074A87E /* RCTShadowViewTests.m */; };
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */; };
139FDEDB1B0651FB00C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDED91B0651EA00C62182 /* libRCTWebSocket.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; };
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */; };
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; };
141FC1211B222EBB004D5FFB /* IntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 141FC1201B222EBB004D5FFB /* IntegrationTests.m */; };
143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; };
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTClipRectTests.m */; };
@ -167,6 +168,7 @@
134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = ../../Libraries/Geolocation/RCTGeolocation.xcodeproj; sourceTree = "<group>"; };
138D6A151B53CD440074A87E /* RCTCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTCacheTests.m; sourceTree = "<group>"; };
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTShadowViewTests.m; sourceTree = "<group>"; };
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
139FDECA1B0651EA00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = ../../Libraries/WebSocket/RCTWebSocket.xcodeproj; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* UIExplorer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UIExplorer.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = UIExplorer/AppDelegate.h; sourceTree = "<group>"; };
@ -177,7 +179,7 @@
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = UIExplorer/main.m; sourceTree = "<group>"; };
13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = "<group>"; };
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = "<group>"; };
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleMethodTests.m; sourceTree = "<group>"; };
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = "<group>"; };
141FC1201B222EBB004D5FFB /* IntegrationTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationTests.m; sourceTree = "<group>"; };
143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = "<group>"; };
@ -362,7 +364,8 @@
1497CFA91B21F5E400C1F8F2 /* RCTEventDispatcherTests.m */,
1300627E1B59179B0043FE5A /* RCTGzipTests.m */,
13DB03471B5D2ED500C27245 /* RCTJSONTests.m */,
13DF61B51B67A45000EDB188 /* RCTModuleMethodTests.m */,
13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */,
1393D0371B68CD1300E1B601 /* RCTModuleMethodTests.m */,
138D6A161B53CD440074A87E /* RCTShadowViewTests.m */,
1497CFAA1B21F5E400C1F8F2 /* RCTSparseArrayTests.m */,
1497CFAB1B21F5E400C1F8F2 /* RCTUIManagerTests.m */,
@ -791,6 +794,7 @@
files = (
1497CFB01B21F5E400C1F8F2 /* RCTConvert_UIFontTests.m in Sources */,
144D21241B2204C5006DB32B /* RCTClipRectTests.m in Sources */,
1393D0381B68CD1300E1B601 /* RCTModuleMethodTests.m in Sources */,
1497CFB21B21F5E400C1F8F2 /* RCTSparseArrayTests.m in Sources */,
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */,
1497CFAF1B21F5E400C1F8F2 /* RCTConvert_NSURLTests.m in Sources */,
@ -801,7 +805,7 @@
138D6A171B53CD440074A87E /* RCTCacheTests.m in Sources */,
13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */,
1497CFAC1B21F5E400C1F8F2 /* RCTAllocationTests.m in Sources */,
13DF61B61B67A45000EDB188 /* RCTModuleMethodTests.m in Sources */,
13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */,
138D6A181B53CD440074A87E /* RCTShadowViewTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -0,0 +1,131 @@
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "RCTModuleMethod.h"
@interface RCTMethodArgumentTests : XCTestCase
@end
@implementation RCTMethodArgumentTests
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
- (void)testOneArgument
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:");
XCTAssertEqual(arguments.count, (NSUInteger)1);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
}
- (void)testTwoArguments
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
- (void)testSpaces
{
NSArray *arguments;
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
- (void)testNewlines
{
NSArray *arguments;
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
- (void)testUnnamedArgs
{
NSArray *arguments;
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo::");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSInteger");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
- (void)testUntypedUnnamedArgs
{
NSArray *arguments;
NSString *methodName = @"foo:foo:bar:bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:::");
XCTAssertEqual(arguments.count, (NSUInteger)3);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"id");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"id");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[2]).type, @"id");
}
- (void)testAttributes
{
NSArray *arguments;
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
- (void)testNullability
{
NSArray *arguments;
NSString *methodName = @"foo:(nullable NSString *)foo bar:(nonnull NSNumber *)bar baz:(id)baz";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:baz:");
XCTAssertEqual(arguments.count, (NSUInteger)3);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"NSNumber");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[2]).type, @"id");
XCTAssertEqual(((RCTMethodArgument *)arguments[0]).nullability, RCTNullable);
XCTAssertEqual(((RCTMethodArgument *)arguments[1]).nullability, RCTNonnullable);
XCTAssertEqual(((RCTMethodArgument *)arguments[2]).nullability, RCTNullabilityUnspecified);
}
- (void)testSemicolonStripping
{
NSArray *arguments;
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
RCTParseObjCMethodName(&methodName, &arguments);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(arguments.count, (NSUInteger)2);
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[0]).type, @"NSString");
XCTAssertEqualObjects(((RCTMethodArgument *)arguments[1]).type, @"BOOL");
}
@end

View File

@ -1,15 +1,22 @@
//
// RCTModuleMethodTests.m
// UIExplorer
//
// Created by Nick Lockwood on 28/07/2015.
// Copyright (c) 2015 Facebook. All rights reserved.
//
/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import <XCTest/XCTest.h>
#import "RCTModuleMethod.h"
#import "RCTLog.h"
@interface RCTModuleMethodTests : XCTestCase
@ -17,94 +24,40 @@
@implementation RCTModuleMethodTests
extern void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes);
- (void)doFooWithBar:(__unused NSString *)bar { }
- (void)testOneArgument
- (void)testNonnull
{
NSArray *argTypes;
NSString *methodName = @"foo:(NSInteger)foo";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:");
XCTAssertEqual(argTypes.count, (NSUInteger)1);
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
}
NSString *methodName = @"doFooWithBar:(nonnull NSString *)bar";
RCTModuleMethod *method = [[RCTModuleMethod alloc] initWithObjCMethodName:methodName
JSMethodName:nil
moduleClass:[self class]];
- (void)testTwoArguments
{
NSArray *argTypes;
NSString *methodName = @"foo:(NSInteger)foo bar:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
}
{
__block BOOL loggedError = NO;
RCTPerformBlockWithLogFunction(^{
[method invokeWithBridge:nil module:self arguments:@[@"Hello World"]];
}, ^(RCTLogLevel level,
__unused NSString *fileName,
__unused NSNumber *lineNumber,
__unused NSString *message) {
loggedError = (level == RCTLogLevelError);
});
XCTAssertFalse(loggedError);
}
- (void)testSpaces
{
NSArray *argTypes;
NSString *methodName = @"foo : (NSInteger)foo bar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
}
- (void)testNewlines
{
NSArray *argTypes;
NSString *methodName = @"foo : (NSInteger)foo\nbar : (BOOL) bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
}
- (void)testUnnamedArgs
{
NSArray *argTypes;
NSString *methodName = @"foo:(NSInteger)foo:(BOOL)bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo::");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSInteger");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
}
- (void)testUntypedUnnamedArgs
{
NSArray *argTypes;
NSString *methodName = @"foo:foo:bar:bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:::");
XCTAssertEqual(argTypes.count, (NSUInteger)3);
XCTAssertEqualObjects(argTypes[0], @"id");
XCTAssertEqualObjects(argTypes[1], @"id");
XCTAssertEqualObjects(argTypes[2], @"id");
}
- (void)testAttributes
{
NSArray *argTypes;
NSString *methodName = @"foo:(__attribute__((nonnull)) NSString *)foo bar:(__unused BOOL)bar";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSString");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
}
- (void)testSemicolonStripping
{
NSArray *argTypes;
NSString *methodName = @"foo:(NSString *)foo bar:(BOOL)bar;";
RCTParseObjCMethodName(&methodName, &argTypes);
XCTAssertEqualObjects(methodName, @"foo:bar:");
XCTAssertEqual(argTypes.count, (NSUInteger)2);
XCTAssertEqualObjects(argTypes[0], @"NSString");
XCTAssertEqualObjects(argTypes[1], @"BOOL");
{
__block BOOL loggedError = NO;
RCTPerformBlockWithLogFunction(^{
[method invokeWithBridge:nil module:self arguments:@[[NSNull null]]];
}, ^(RCTLogLevel level,
__unused NSString *fileName,
__unused NSNumber *lineNumber,
__unused NSString *message) {
loggedError = (level == RCTLogLevelError);
});
XCTAssertTrue(loggedError);
}
}
@end

View File

@ -60,7 +60,7 @@ RCT_EXTERN NSString *RCTFormatLog(
extern RCTLogFunction RCTDefaultLogFunction;
/**
* These methods get and set the current logging threshold. This is the level
* These methods get and set the global logging threshold. This is the level
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
* RCTLogLevelError for production.
*/
@ -68,7 +68,7 @@ RCT_EXTERN void RCTSetLogThreshold(RCTLogLevel threshold);
RCT_EXTERN RCTLogLevel RCTGetLogThreshold(void);
/**
* These methods get and set the current logging function called by the RCTLogXX
* These methods get and set the global logging function called by the RCTLogXX
* macros. You can use these to replace the standard behavior with custom log
* functionality.
*/
@ -82,6 +82,13 @@ RCT_EXTERN RCTLogFunction RCTGetLogFunction(void);
*/
RCT_EXTERN void RCTAddLogFunction(RCTLogFunction logFunction);
/**
* This method temporarily overrides the log function while performing the
* specified block. This is useful for testing purposes (to detect if a given
* function logs something) or to suppress or override logging temporarily.
*/
RCT_EXTERN void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction);
/**
* This method adds a conditional prefix to any messages logged within the scope
* of the passed block. This is useful for adding additional context to log

View File

@ -22,7 +22,7 @@
@end
static NSString *const RCTLogPrefixStack = @"RCTLogPrefixStack";
static NSString *const RCTLogFunctionStack = @"RCTLogFunctionStack";
const char *RCTLogLevels[] = {
"info",
@ -107,17 +107,42 @@ void RCTAddLogFunction(RCTLogFunction logFunction)
}
}
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
/**
* returns the topmost stacked log function for the current thread, which
* may not be the same as the current value of RCTCurrentLogFunction.
*/
static RCTLogFunction RCTGetLocalLogFunction()
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray *prefixStack = threadDictionary[RCTLogPrefixStack];
if (!prefixStack) {
prefixStack = [[NSMutableArray alloc] init];
threadDictionary[RCTLogPrefixStack] = prefixStack;
NSArray *functionStack = threadDictionary[RCTLogFunctionStack];
RCTLogFunction logFunction = [functionStack lastObject];
if (logFunction) {
return logFunction;
}
[prefixStack addObject:prefix];
return RCTCurrentLogFunction;
}
void RCTPerformBlockWithLogFunction(void (^block)(void), RCTLogFunction logFunction)
{
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSMutableArray *functionStack = threadDictionary[RCTLogFunctionStack];
if (!functionStack) {
functionStack = [[NSMutableArray alloc] init];
threadDictionary[RCTLogFunctionStack] = functionStack;
}
[functionStack addObject:logFunction];
block();
[prefixStack removeLastObject];
[functionStack removeLastObject];
}
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
{
RCTLogFunction logFunction = RCTGetLocalLogFunction();
if (logFunction) {
RCTPerformBlockWithLogFunction(block, ^(RCTLogLevel level, NSString *fileName, NSNumber *lineNumber, NSString *message) {
logFunction(level, fileName, lineNumber, [prefix stringByAppendingString:message]);
});
}
}
NSString *RCTFormatLog(
@ -165,8 +190,8 @@ void _RCTLogFormat(
int lineNumber,
NSString *format, ...)
{
BOOL log = RCT_DEBUG || (RCTCurrentLogFunction != nil);
RCTLogFunction logFunction = RCTGetLocalLogFunction();
BOOL log = RCT_DEBUG || (logFunction != nil);
if (log && level >= RCTCurrentLogThreshold) {
// Get message
@ -175,18 +200,10 @@ void _RCTLogFormat(
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
// Add prefix
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
NSArray *prefixStack = threadDictionary[RCTLogPrefixStack];
NSString *prefix = [prefixStack lastObject];
if (prefix) {
message = [prefix stringByAppendingString:message];
}
// Call log function
RCTCurrentLogFunction(
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
);
if (logFunction) {
logFunction(level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message);
}
#if RCT_DEBUG // Red box is only available in debug mode

View File

@ -16,12 +16,25 @@ typedef NS_ENUM(NSUInteger, RCTJavaScriptFunctionKind) {
RCTJavaScriptFunctionKindAsync,
};
typedef NS_ENUM(NSUInteger, RCTNullability) {
RCTNullabilityUnspecified,
RCTNullable,
RCTNonnullable,
};
@interface RCTMethodArgument : NSObject
@property (nonatomic, copy, readonly) NSString *type;
@property (nonatomic, readonly) RCTNullability nullability;
@end
@interface RCTModuleMethod : NSObject
@property (nonatomic, copy, readonly) NSString *JSMethodName;
@property (nonatomic, strong, readonly) Class moduleClass;
@property (nonatomic, assign, readonly) SEL selector;
@property (nonatomic, assign, readonly) RCTJavaScriptFunctionKind functionKind;
@property (nonatomic, readonly) Class moduleClass;
@property (nonatomic, readonly) SEL selector;
@property (nonatomic, readonly) RCTJavaScriptFunctionKind functionKind;
- (instancetype)initWithObjCMethodName:(NSString *)objCMethodName
JSMethodName:(NSString *)JSMethodName

View File

@ -17,6 +17,22 @@
#import "RCTLog.h"
#import "RCTUtils.h"
typedef void (^RCTArgumentBlock)(RCTBridge *, NSInvocation *, NSUInteger, id);
@implementation RCTMethodArgument
- (instancetype)initWithType:(NSString *)type
nullability:(RCTNullability)nullability
{
if ((self = [super init])) {
_type = [type copy];
_nullability = nullability;
}
return self;
}
@end
@interface RCTBridge (RCTModuleMethod)
- (void)_invokeAndProcessModule:(NSString *)module
@ -36,16 +52,17 @@
RCT_NOT_IMPLEMENTED(-init)
void RCTParseObjCMethodName(NSString **, NSArray **);
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **arguments)
{
static NSRegularExpression *typeNameRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *unusedPattern = @"(?:__unused|__attribute__\\(\\(unused\\)\\))";
NSString *constPattern = @"(?:const)";
NSString *nullabilityPattern = @"(?:__nullable|__nonnull|nullable|nonnull|__attribute__\\(\\(nonnull\\)\\)|__attribute__\\(\\(nullable\\)\\))";
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|%@)\\s*)",
unusedPattern, constPattern, nullabilityPattern];
NSString *nullablePattern = @"(?:__nullable|nullable|__attribute__\\(\\(nullable\\)\\))";
NSString *nonnullPattern = @"(?:__nonnull|nonnull|__attribute__\\(\\(nonnull\\)\\))";
NSString *annotationPattern = [NSString stringWithFormat:@"(?:(?:%@|%@|(%@)|(%@))\\s*)",
unusedPattern, constPattern, nullablePattern, nonnullPattern];
NSString *pattern = [NSString stringWithFormat:@"(?<=:)(\\s*\\(%1$@?(\\w+?)(?:\\s*\\*)?%1$@?\\))?\\s*\\w+",
annotationPattern];
typeNameRegex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:NULL];
@ -54,12 +71,15 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
// Extract argument types
NSString *methodName = *objCMethodName;
NSRange methodRange = {0, methodName.length};
NSMutableArray *arguments = [NSMutableArray array];
NSMutableArray *args = [NSMutableArray array];
[typeNameRegex enumerateMatchesInString:methodName options:0 range:methodRange usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) {
NSRange range = [result rangeAtIndex:2];
[arguments addObject:range.length ? [methodName substringWithRange:range] : @"id"];
NSRange typeRange = [result rangeAtIndex:4];
NSString *type = typeRange.length ? [methodName substringWithRange:typeRange] : @"id";
RCTNullability nullability = [result rangeAtIndex:2].length ? RCTNullable :
[result rangeAtIndex:3].length ? RCTNonnullable : RCTNullabilityUnspecified;
[args addObject:[[RCTMethodArgument alloc] initWithType:type nullability:nullability]];
}];
*argTypes = [arguments copy];
*arguments = [args copy];
// Remove the parameter types and names
methodName = [typeNameRegex stringByReplacingMatchesInString:methodName options:0
@ -84,8 +104,8 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
{
if ((self = [super init])) {
NSArray *argTypes;
RCTParseObjCMethodName(&objCMethodName, &argTypes);
NSArray *arguments;
RCTParseObjCMethodName(&objCMethodName, &arguments);
_moduleClass = moduleClass;
_selector = NSSelectorFromString(objCMethodName);
@ -112,7 +132,7 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
[invocation setArgument:&value atIndex:(index) + 2]; \
}];
void (^addBlockArgument)(void) = ^{
@ -143,12 +163,13 @@ void RCTParseObjCMethodName(NSString **objCMethodName, NSArray **argTypes)
};
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
const char *objcType = [_methodSignature getArgumentTypeAtIndex:i];
NSString *selName = argTypes[i - 2];
SEL selector = NSSelectorFromString([selName stringByAppendingString:@":"]);
RCTMethodArgument *argument = arguments[i - 2];
NSString *typeName = argument.type;
SEL selector = NSSelectorFromString([typeName stringByAppendingString:@":"]);
if ([RCTConvert respondsToSelector:selector]) {
switch (argumentType[0]) {
switch (objcType[0]) {
#define RCT_CONVERT_CASE(_value, _type) \
case _value: { \
@ -157,109 +178,125 @@ case _value: { \
break; \
}
RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
RCT_CONVERT_CASE(':', SEL)
RCT_CONVERT_CASE('*', const char *)
RCT_CONVERT_CASE('c', char)
RCT_CONVERT_CASE('C', unsigned char)
RCT_CONVERT_CASE('s', short)
RCT_CONVERT_CASE('S', unsigned short)
RCT_CONVERT_CASE('i', int)
RCT_CONVERT_CASE('I', unsigned int)
RCT_CONVERT_CASE('l', long)
RCT_CONVERT_CASE('L', unsigned long)
RCT_CONVERT_CASE('q', long long)
RCT_CONVERT_CASE('Q', unsigned long long)
RCT_CONVERT_CASE('f', float)
RCT_CONVERT_CASE('d', double)
RCT_CONVERT_CASE('B', BOOL)
RCT_CONVERT_CASE('@', id)
RCT_CONVERT_CASE('^', void *)
case '{': {
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
void *returnValue = malloc(methodSignature.methodReturnLength);
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
[_invocation setArgument:&json atIndex:2];
[_invocation invoke];
[_invocation getReturnValue:returnValue];
case '{': {
[argumentBlocks addObject:^(__unused RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
void *returnValue = malloc(methodSignature.methodReturnLength);
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[_invocation setTarget:[RCTConvert class]];
[_invocation setSelector:selector];
[_invocation setArgument:&json atIndex:2];
[_invocation invoke];
[_invocation getReturnValue:returnValue];
[invocation setArgument:returnValue atIndex:index];
[invocation setArgument:returnValue atIndex:index + 2];
free(returnValue);
}];
break;
}
default:
defaultCase(argumentType);
free(returnValue);
}];
break;
}
} else if ([selName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument();
} else if ([selName isEqualToString:@"RCTResponseErrorBlock"]) {
RCT_ARG_BLOCK(
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSError *error) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
} : ^(__unused NSError *error) {});
)
} else if ([selName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
NSArray *arguments = result ? @[result] : @[];
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, arguments]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else if ([selName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(i == numberOfArguments - 1,
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, @[errorJSON]]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else {
// Unknown argument type
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
" to support this type.", selName, [self methodName]);
default:
defaultCase(objcType);
}
} else if ([typeName isEqualToString:@"RCTResponseSenderBlock"]) {
addBlockArgument();
} else if ([typeName isEqualToString:@"RCTResponseErrorBlock"]) {
RCT_ARG_BLOCK(
if (RCT_DEBUG && json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSError *error) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, @[RCTJSErrorFromNSError(error)]]];
} : ^(__unused NSError *error) {});
)
} else if ([typeName isEqualToString:@"RCTPromiseResolveBlock"]) {
RCTAssert(i == numberOfArguments - 2,
@"The RCTPromiseResolveBlock must be the second to last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise resolver ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseResolveBlock value = (^(id result) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, result ? @[result] : @[]]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else if ([typeName isEqualToString:@"RCTPromiseRejectBlock"]) {
RCTAssert(i == numberOfArguments - 1,
@"The RCTPromiseRejectBlock must be the last parameter in -[%@ %@]",
_moduleClass, objCMethodName);
RCT_ARG_BLOCK(
if (RCT_DEBUG && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ must be a promise rejecter ID", index,
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing RCTPromiseRejectBlock value = (^(NSError *error) {
NSDictionary *errorJSON = RCTJSErrorFromNSError(error);
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, @[errorJSON]]];
});
)
_functionKind = RCTJavaScriptFunctionKindAsync;
} else {
// Unknown argument type
RCTLogError(@"Unknown argument type '%@' in method %@. Extend RCTConvert"
" to support this type.", typeName, [self methodName]);
}
if (RCT_DEBUG) {
RCTNullability nullability = argument.nullability;
if (nullability == RCTNonnullable) {
RCTArgumentBlock oldBlock = argumentBlocks[i - 2];
argumentBlocks[i - 2] = ^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) {
if (json == nil || json == (id)kCFNull) {
RCTLogError(@"Argument %tu of %@.%@ must not be null", index,
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
id null = nil;
[invocation setArgument:&null atIndex:index + 2];
} else {
oldBlock(bridge, invocation, index, json);
}
};
}
}
}
_argumentBlocks = [argumentBlocks copy];
@ -305,8 +342,8 @@ case _value: { \
NSUInteger index = 0;
for (id json in arguments) {
id arg = RCTNilIfNull(json);
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
block(bridge, invocation, index + 2, arg);
RCTArgumentBlock block = _argumentBlocks[index];
block(bridge, invocation, index, arg);
index++;
}