/*
- * Copyright (C) 2013 Apple Inc. All rights reserved.
+ * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 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.
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import <JavaScriptCore/JavaScriptCore.h>
+#import "CurrentThisInsideBlockGetterTest.h"
+#import "DateTests.h"
+#import "JSExportTests.h"
+#import "Regress141809.h"
+
+#import <pthread.h>
+
extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
+extern "C" void JSSynchronousEdenCollectForDebugging(JSContextRef);
extern "C" bool _Block_has_signature(id);
extern "C" const char * _Block_signature(id);
extern int failed;
extern "C" void testObjectiveCAPI(void);
+extern "C" void checkResult(NSString *, bool);
#if JSC_OBJC_API_ENABLED
JSValue *function = [m_onclickHandler value];
[function callWithArguments:[NSArray array]];
}
-- (void)dealloc
-{
- [[m_onclickHandler value].context.virtualMachine removeManagedReference:m_onclickHandler withOwner:self];
-}
@end
@class TinyDOMNode;
@end
@interface TinyDOMNode : NSObject<TinyDOMNode>
-+ (JSVirtualMachine *)sharedVirtualMachine;
-+ (void)clearSharedVirtualMachine;
@end
@implementation TinyDOMNode {
NSMutableArray *m_children;
+ JSVirtualMachine *m_sharedVirtualMachine;
}
-static JSVirtualMachine *sharedInstance = nil;
-
-+ (JSVirtualMachine *)sharedVirtualMachine
-{
- if (!sharedInstance)
- sharedInstance = [[JSVirtualMachine alloc] init];
- return sharedInstance;
-}
-
-+ (void)clearSharedVirtualMachine
-{
- sharedInstance = nil;
-}
-
-- (id)init
+- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine
{
self = [super init];
if (!self)
return nil;
m_children = [[NSMutableArray alloc] initWithCapacity:0];
-
- return self;
-}
-
-- (void)dealloc
-{
- NSEnumerator *enumerator = [m_children objectEnumerator];
- id nextChild;
- while ((nextChild = [enumerator nextObject]))
- [[TinyDOMNode sharedVirtualMachine] removeManagedReference:nextChild withOwner:self];
-
+ m_sharedVirtualMachine = virtualMachine;
#if !__has_feature(objc_arc)
- [super dealloc];
+ [m_sharedVirtualMachine retain];
#endif
+
+ return self;
}
- (void)appendChild:(TinyDOMNode *)child
{
- [[TinyDOMNode sharedVirtualMachine] addManagedReference:child withOwner:self];
+ [m_sharedVirtualMachine addManagedReference:child withOwner:self];
[m_children addObject:child];
}
{
if (index >= [m_children count])
return;
- [[TinyDOMNode sharedVirtualMachine] removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
+ [m_sharedVirtualMachine removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
[m_children removeObjectAtIndex:index];
}
@protocol InitA <JSExport>
- (id)initWithA:(int)a;
+- (int)initialize;
@end
@protocol InitB <JSExport>
- (id)initWithA:(int)a b:(int)b;
@end
+@protocol InitC <JSExport>
+- (id)_init;
+@end
+
@interface ClassA : NSObject<InitA>
@end
@interface ClassC : ClassB<InitA, InitB>
@end
+@interface ClassCPrime : ClassB<InitA, InitC>
+@end
+
@interface ClassD : NSObject<InitA>
- (id)initWithA:(int)a;
@end
return self;
}
+- (int)initialize
+{
+ return 42;
+}
@end
@implementation ClassB {
}
@end
+@implementation ClassCPrime
+- (id)initWithA:(int)a
+{
+ self = [super initWithA:a b:0];
+ if (!self)
+ return nil;
+ return self;
+}
+- (id)_init
+{
+ return [self initWithA:42];
+}
+@end
+
@implementation ClassD
- (id)initWithA:(int)a
self = nil;
return [[ClassE alloc] initWithA:a];
}
+- (int)initialize
+{
+ return 0;
+}
@end
@implementation ClassE {
return self;
}
@end
-static void checkResult(NSString *description, bool passed)
+
+static bool evilAllocationObjectWasDealloced = false;
+
+@interface EvilAllocationObject : NSObject
+- (JSValue *)doEvilThingsWithContext:(JSContext *)context;
+@end
+
+@implementation EvilAllocationObject {
+ JSContext *m_context;
+}
+- (id)initWithContext:(JSContext *)context
+{
+ self = [super init];
+ if (!self)
+ return nil;
+
+ m_context = context;
+
+ return self;
+}
+- (void)dealloc
+{
+ [self doEvilThingsWithContext:m_context];
+ evilAllocationObjectWasDealloced = true;
+#if !__has_feature(objc_arc)
+ [super dealloc];
+#endif
+}
+
+- (JSValue *)doEvilThingsWithContext:(JSContext *)context
+{
+ JSValue *result = [context evaluateScript:@" \
+ (function() { \
+ var a = []; \
+ var sum = 0; \
+ for (var i = 0; i < 10000; ++i) { \
+ sum += i; \
+ a[i] = sum; \
+ } \
+ return sum; \
+ })()"];
+
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+ return result;
+}
+@end
+
+extern "C" void checkResult(NSString *description, bool passed)
{
NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
if (!passed)
return containsClass;
}
+static void* threadMain(void* contextPtr)
+{
+ JSContext *context = (__bridge JSContext*)contextPtr;
+
+ // Do something to enter the VM.
+ TestObject *testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ pthread_exit(nullptr);
+}
+
void testObjectiveCAPI()
{
NSLog(@"Testing Objective-C API");
+ @autoreleasepool {
+ JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
+ JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
+ [context evaluateScript:@"bad"];
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
JSValue *result = [context evaluateScript:@"2 + 2"];
checkResult(@"not_sure_if_undefined is undefined", ![myPrivateProperties valueForKey:@"not_sure_if_undefined"]);
}
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *message = [JSValue valueWithObject:@"hello" inContext:context];
+ TestObject *rootObject = [TestObject testObject];
+ JSCollection *collection = [[JSCollection alloc] init];
+ context[@"root"] = rootObject;
+ @autoreleasepool {
+ JSValue *jsCollection = [JSValue valueWithObject:collection inContext:context];
+ JSManagedValue *weakCollection = [JSManagedValue managedValueWithValue:jsCollection andOwner:rootObject];
+ [context.virtualMachine addManagedReference:weakCollection withOwner:message];
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+ }
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
__block int result;
checkResult(@"JSContext.exceptionHandler", caught);
}
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ __block int expectedExceptionLineNumber = 1;
+ __block bool sawExpectedExceptionLineNumber = false;
+ context.exceptionHandler = ^(JSContext *, JSValue *exception) {
+ sawExpectedExceptionLineNumber = [exception[@"line"] toInt32] == expectedExceptionLineNumber;
+ };
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ checkResult(@"evaluteScript exception on line 1", sawExpectedExceptionLineNumber);
+
+ expectedExceptionLineNumber = 2;
+ sawExpectedExceptionLineNumber = false;
+ [context evaluateScript:@"// Line 1\n!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ checkResult(@"evaluteScript exception on line 2", sawExpectedExceptionLineNumber);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ __block bool emptyExceptionSourceURL = false;
+ context.exceptionHandler = ^(JSContext *, JSValue *exception) {
+ emptyExceptionSourceURL = [exception[@"sourceURL"] isUndefined];
+ };
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
+ checkResult(@"evaluteScript: exception has no sourceURL", emptyExceptionSourceURL);
+
+ __block NSString *exceptionSourceURL = nil;
+ context.exceptionHandler = ^(JSContext *, JSValue *exception) {
+ exceptionSourceURL = [exception[@"sourceURL"] toString];
+ };
+ NSURL *url = [NSURL fileURLWithPath:@"/foo/bar.js"];
+ [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()" withSourceURL:url];
+ checkResult(@"evaluateScript:withSourceURL: exception has expected sourceURL", [exceptionSourceURL isEqualToString:[url absoluteString]]);
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
context[@"callback"] = ^{
checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
}
- @autoreleasepool {
- JSContext *context = [[JSContext alloc] init];
- context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
- NSDictionary *expectedDict = @{
- @"foo" : [NSNumber numberWithInt:1],
- @"bar" : @{
- @"baz": [NSNumber numberWithInt:2]
- }
- };
- checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
- };
- [context evaluateScript:@"var myDict = { \
- 'foo': 1, \
- 'bar': {'baz': 2} \
- }; \
- handleTheDictionary(myDict);"];
-
- context[@"handleTheArray"] = ^(NSArray *array) {
- NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
- checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
- };
- [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
- }
-
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
TextXYZ *testXYZ = [[TextXYZ alloc] init];
checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
}
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ JSValue *result = [context evaluateScript:@"String(console)"];
+ checkResult(@"String(console)", [result isEqualToObject:@"[object Console]"]);
+ result = [context evaluateScript:@"typeof console.log"];
+ checkResult(@"typeof console.log", [result isEqualToObject:@"function"]);
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
TestObject* testObject = [TestObject testObject];
}
@autoreleasepool {
- JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
- JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
- TinyDOMNode *root = [[TinyDOMNode alloc] init];
+ JSContext *context = [[JSContext alloc] init];
+ TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
TinyDOMNode *lastNode = root;
for (NSUInteger i = 0; i < 3; i++) {
- TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
+ TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
[lastNode appendChild:newNode];
lastNode = newNode;
}
JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
checkResult(@"My custom property == 42", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
-
- [TinyDOMNode clearSharedVirtualMachine];
}
@autoreleasepool {
- JSVirtualMachine *vm = [TinyDOMNode sharedVirtualMachine];
- JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
- TinyDOMNode *root = [[TinyDOMNode alloc] init];
+ JSContext *context = [[JSContext alloc] init];
+ TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
TinyDOMNode *lastNode = root;
for (NSUInteger i = 0; i < 3; i++) {
- TinyDOMNode *newNode = [[TinyDOMNode alloc] init];
+ TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
[lastNode appendChild:newNode];
lastNode = newNode;
}
JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
checkResult(@"duplicate calls to addManagedReference don't cause things to die", [myCustomProperty isNumber] && [myCustomProperty toInt32] == 42);
-
- [TinyDOMNode clearSharedVirtualMachine];
}
@autoreleasepool {
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
- context[@"UnexportedObject"] = [UnexportedObject class];
- context[@"makeObject"] = ^{
- return [[UnexportedObject alloc] init];
- };
- JSValue *result = [context evaluateScript:@"(makeObject() instanceof UnexportedObject)"];
- checkResult(@"makeObject() instanceof UnexportedObject", [result isBoolean] && [result toBool]);
+ TestObject *testObject = [TestObject testObject];
+ context[@"testObject"] = testObject;
+ JSManagedValue *managedValue = nil;
+ @autoreleasepool {
+ JSValue *object = [JSValue valueWithNewObjectInContext:context];
+ managedValue = [JSManagedValue managedValueWithValue:object andOwner:testObject];
+ [context.virtualMachine addManagedReference:managedValue withOwner:testObject];
+ }
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
}
@autoreleasepool {
NSLog(@"I'm intentionally not returning anything.");
};
JSValue *result = [context evaluateScript:@"new MyClass()"];
+ checkResult(@"result === undefined", [result isUndefined]);
+ checkResult(@"exception.message is correct'", context.exception
+ && [@"Objective-C blocks called as constructors must return an object." isEqualToString:[context.exception[@"message"] toString]]);
+ }
+
+ @autoreleasepool {
+ checkResult(@"[JSContext currentThis] == nil outside of callback", ![JSContext currentThis]);
+ checkResult(@"[JSContext currentArguments] == nil outside of callback", ![JSContext currentArguments]);
+ if ([JSContext currentCallee])
+ checkResult(@"[JSContext currentCallee] == nil outside of callback", ![JSContext currentCallee]);
+ }
+
+ if ([JSContext currentCallee]) {
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"testFunction"] = ^{
+ checkResult(@"testFunction.foo === 42", [[JSContext currentCallee][@"foo"] toInt32] == 42);
+ };
+ context[@"testFunction"][@"foo"] = @42;
+ [context[@"testFunction"] callWithArguments:nil];
+
+ context[@"TestConstructor"] = ^{
+ JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
+ JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
+ JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
+ JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentCallee][@"prototype"] JSValueRef]);
+ return newThis;
+ };
+ checkResult(@"(new TestConstructor) instanceof TestConstructor", [context evaluateScript:@"(new TestConstructor) instanceof TestConstructor"]);
+ }
+ }
+
@autoreleasepool {
JSContext *context = [[JSContext alloc] init];
context[@"TestObject"] = [TestObject class];
context[@"ClassA"] = [ClassA class];
context[@"ClassB"] = [ClassB class];
context[@"ClassC"] = [ClassC class]; // Should print error message about too many inits found.
+ context[@"ClassCPrime"] = [ClassCPrime class]; // Ditto.
JSValue *a = [context evaluateScript:@"(new ClassA(42))"];
checkResult(@"a instanceof ClassA", [a isInstanceOf:context[@"ClassA"]]);
+ checkResult(@"a.initialize() is callable", [[a invokeMethod:@"initialize" withArguments:@[]] toInt32] == 42);
JSValue *b = [context evaluateScript:@"(new ClassB(42, 53))"];
checkResult(@"b instanceof ClassB", [b isInstanceOf:context[@"ClassB"]]);
} \
})()"];
checkResult(@"shouldn't be able to construct ClassC", ![canConstructClassC toBool]);
+ JSValue *canConstructClassCPrime = [context evaluateScript:@"(function() { \
+ try { \
+ (new ClassCPrime(1)); \
+ return true; \
+ } catch(e) { \
+ return false; \
+ } \
+ })()"];
+ checkResult(@"shouldn't be able to construct ClassCPrime", ![canConstructClassCPrime toBool]);
}
@autoreleasepool {
checkResult(@"Returning instance of ClassE from ClassD's init has correct class", [d isInstanceOf:context[@"ClassE"]]);
}
- checkResult(@"result === undefined", [result isUndefined]);
- checkResult(@"exception.message is correct'", context.exception
- && [@"Objective-C blocks called as constructors must return an object." isEqualToString:[context.exception[@"message"] toString]]);
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ while (!evilAllocationObjectWasDealloced) {
+ @autoreleasepool {
+ EvilAllocationObject *evilObject = [[EvilAllocationObject alloc] initWithContext:context];
+ context[@"evilObject"] = evilObject;
+ context[@"evilObject"] = nil;
+ }
+ }
+ checkResult(@"EvilAllocationObject was successfully dealloced without crashing", evilAllocationObjectWasDealloced);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ checkResult(@"default context.name is nil", context.name == nil);
+ NSString *name1 = @"Name1";
+ NSString *name2 = @"Name2";
+ context.name = name1;
+ NSString *fetchedName1 = context.name;
+ context.name = name2;
+ NSString *fetchedName2 = context.name;
+ context.name = nil;
+ NSString *fetchedName3 = context.name;
+ checkResult(@"fetched context.name was expected", [fetchedName1 isEqualToString:name1]);
+ checkResult(@"fetched context.name was expected", [fetchedName2 isEqualToString:name2]);
+ checkResult(@"fetched context.name was expected", ![fetchedName1 isEqualToString:fetchedName2]);
+ checkResult(@"fetched context.name was expected", fetchedName3 == nil);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ context[@"UnexportedObject"] = [UnexportedObject class];
+ context[@"makeObject"] = ^{
+ return [[UnexportedObject alloc] init];
+ };
+ JSValue *result = [context evaluateScript:@"(makeObject() instanceof UnexportedObject)"];
+ checkResult(@"makeObject() instanceof UnexportedObject", [result isBoolean] && [result toBool]);
}
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+ [[JSValue valueWithInt32:42 inContext:context] toDictionary];
+ [[JSValue valueWithInt32:42 inContext:context] toArray];
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+
+ // Create the root, make it reachable from JS, and force an EdenCollection
+ // so that we scan the external object graph.
+ TestObject *root = [TestObject testObject];
+ @autoreleasepool {
+ context[@"root"] = root;
+ }
+ JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
+
+ // Create a new Obj-C object only reachable via the external object graph
+ // through the object we already scanned during the EdenCollection.
+ TestObject *child = [TestObject testObject];
+ [context.virtualMachine addManagedReference:child withOwner:root];
+
+ // Create a new managed JSValue that will only be kept alive if we properly rescan
+ // the external object graph.
+ JSManagedValue *managedJSObject = nil;
+ @autoreleasepool {
+ JSValue *jsObject = [JSValue valueWithObject:@"hello" inContext:context];
+ managedJSObject = [JSManagedValue managedValueWithValue:jsObject];
+ [context.virtualMachine addManagedReference:managedJSObject withOwner:child];
+ }
+
+ // Force another EdenCollection. It should rescan the new part of the external object graph.
+ JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
+
+ // Check that the managed JSValue is still alive.
+ checkResult(@"EdenCollection doesn't reclaim new managed values", [managedJSObject value] != nil);
+ }
+
+ @autoreleasepool {
+ JSContext *context = [[JSContext alloc] init];
+
+ pthread_t threadID;
+ pthread_create(&threadID, NULL, &threadMain, (__bridge void*)context);
+ pthread_join(threadID, nullptr);
+ JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+ checkResult(@"Did not crash after entering the VM from another thread", true);
+ }
+
+ currentThisInsideBlockGetterTest();
+ runDateTests();
+ runJSExportTests();
+ runRegress141809();
}
#else
{
}
-#endif
+#endif // JSC_OBJC_API_ENABLED