/* * Copyright (C) 2014, 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "JSExportTests.h" #import <objc/runtime.h> #import <objc/objc.h> #if JSC_OBJC_API_ENABLED extern "C" void checkResult(NSString *description, bool passed); @interface JSExportTests : NSObject + (void) exportInstanceMethodWithIdProtocolTest; + (void) exportInstanceMethodWithClassProtocolTest; + (void) exportDynamicallyGeneratedProtocolTest; @end @protocol TruthTeller - (BOOL) returnTrue; @end @interface TruthTeller : NSObject<TruthTeller> @end @implementation TruthTeller - (BOOL) returnTrue { return true; } @end @protocol ExportMethodWithIdProtocol <JSExport> - (void) methodWithIdProtocol:(id<TruthTeller>)object; @end @interface ExportMethodWithIdProtocol : NSObject<ExportMethodWithIdProtocol> @end @implementation ExportMethodWithIdProtocol - (void) methodWithIdProtocol:(id<TruthTeller>)object { checkResult(@"Exporting a method with id<protocol> in the type signature", [object returnTrue]); } @end @protocol ExportMethodWithClassProtocol <JSExport> - (void) methodWithClassProtocol:(NSObject<TruthTeller> *)object; @end @interface ExportMethodWithClassProtocol : NSObject<ExportMethodWithClassProtocol> @end @implementation ExportMethodWithClassProtocol - (void) methodWithClassProtocol:(NSObject<TruthTeller> *)object { checkResult(@"Exporting a method with class<protocol> in the type signature", [object returnTrue]); } @end @interface NoUnderscorePrefix : NSObject @end @implementation NoUnderscorePrefix @end @interface _UnderscorePrefixNoExport : NoUnderscorePrefix @end @implementation _UnderscorePrefixNoExport @end @protocol Initializing <JSExport> - (instancetype)init; @end @interface _UnderscorePrefixWithExport : NoUnderscorePrefix <Initializing> @end @implementation _UnderscorePrefixWithExport @end @implementation JSExportTests + (void) exportInstanceMethodWithIdProtocolTest { JSContext *context = [[JSContext alloc] init]; context[@"ExportMethodWithIdProtocol"] = [ExportMethodWithIdProtocol class]; context[@"makeTestObject"] = ^{ return [[ExportMethodWithIdProtocol alloc] init]; }; context[@"opaqueObject"] = [[TruthTeller alloc] init]; [context evaluateScript:@"makeTestObject().methodWithIdProtocol(opaqueObject);"]; checkResult(@"Successfully exported instance method", !context.exception); } + (void) exportInstanceMethodWithClassProtocolTest { JSContext *context = [[JSContext alloc] init]; context[@"ExportMethodWithClassProtocol"] = [ExportMethodWithClassProtocol class]; context[@"makeTestObject"] = ^{ return [[ExportMethodWithClassProtocol alloc] init]; }; context[@"opaqueObject"] = [[TruthTeller alloc] init]; [context evaluateScript:@"makeTestObject().methodWithClassProtocol(opaqueObject);"]; checkResult(@"Successfully exported instance method", !context.exception); } + (void) exportDynamicallyGeneratedProtocolTest { JSContext *context = [[JSContext alloc] init]; Protocol *dynProtocol = objc_allocateProtocol("NSStringJSExport"); Protocol *jsExportProtocol = @protocol(JSExport); protocol_addProtocol(dynProtocol, jsExportProtocol); Method method = class_getInstanceMethod([NSString class], @selector(boolValue)); protocol_addMethodDescription(dynProtocol, @selector(boolValue), method_getTypeEncoding(method), YES, YES); NSLog(@"type encoding = %s", method_getTypeEncoding(method)); protocol_addMethodDescription(dynProtocol, @selector(boolValue), "B@:", YES, YES); objc_registerProtocol(dynProtocol); class_addProtocol([NSString class], dynProtocol); context[@"NSString"] = [NSString class]; context[@"myString"] = @"YES"; JSValue *value = [context evaluateScript:@"myString.boolValue()"]; checkResult(@"Dynamically generated JSExport-ed protocols are ignored", [value isUndefined] && !!context.exception); } + (void)classNamePrefixedWithUnderscoreTest { JSContext *context = [[JSContext alloc] init]; context[@"_UnderscorePrefixNoExport"] = [_UnderscorePrefixNoExport class]; context[@"_UnderscorePrefixWithExport"] = [_UnderscorePrefixWithExport class]; checkResult(@"Non-underscore-prefixed ancestor class used when there are no exports", [context[@"_UnderscorePrefixNoExport"] toObject] == [NoUnderscorePrefix class]); checkResult(@"Underscore-prefixed class used when there are exports", [context[@"_UnderscorePrefixWithExport"] toObject] == [_UnderscorePrefixWithExport class]); JSValue *withExportInstance = [context evaluateScript:@"new _UnderscorePrefixWithExport()"]; checkResult(@"Exports present on underscore-prefixed class", !context.exception && !withExportInstance.isUndefined); } @end @protocol AJSExport <JSExport> - (instancetype)init; @end @interface A : NSObject <AJSExport> @end @implementation A @end static void wrapperLifetimeIsTiedToGlobalObject() { JSGlobalContextRef contextRef; @autoreleasepool { JSContext *context = [[JSContext alloc] init]; contextRef = JSGlobalContextRetain(context.JSGlobalContextRef); context[@"A"] = A.class; checkResult(@"Initial wrapper's constructor is itself", [[context evaluateScript:@"new A().constructor === A"] toBool]); } @autoreleasepool { JSContext *context = [JSContext contextWithJSGlobalContextRef:contextRef]; checkResult(@"New context's wrapper's constructor is itself", [[context evaluateScript:@"new A().constructor === A"] toBool]); } JSGlobalContextRelease(contextRef); } static void wrapperForNSObjectisObject() { @autoreleasepool { JSContext *context = [[JSContext alloc] init]; context[@"Object"] = [[NSNull alloc] init]; context.exception = nil; context[@"A"] = NSObject.class; checkResult(@"Should not throw an exception when wrapping NSObject and Object has been changed", ![context exception]); } } void runJSExportTests() { @autoreleasepool { [JSExportTests exportInstanceMethodWithIdProtocolTest]; [JSExportTests exportInstanceMethodWithClassProtocolTest]; [JSExportTests exportDynamicallyGeneratedProtocolTest]; [JSExportTests classNamePrefixedWithUnderscoreTest]; } wrapperLifetimeIsTiedToGlobalObject(); wrapperForNSObjectisObject(); } #endif // JSC_OBJC_API_ENABLED