testapi.mm   [plain text]


/*
 * 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
 * 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. ``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
 * 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 <JavaScriptCore/JavaScriptCore.h>

#import "CurrentThisInsideBlockGetterTest.h"
#import "DateTests.h"
#import "JSExportTests.h"
#import "Regress141275.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

@interface UnexportedObject : NSObject
@end

@implementation UnexportedObject
@end

@protocol ParentObject <JSExport>
@end

@interface ParentObject : NSObject<ParentObject>
+ (NSString *)parentTest;
@end

@implementation ParentObject
+ (NSString *)parentTest
{
    return [self description];
}
@end

@protocol TestObject <JSExport>
- (id)init;
@property int variable;
@property (readonly) int six;
@property CGPoint point;
+ (NSString *)classTest;
+ (NSString *)parentTest;
- (NSString *)getString;
JSExportAs(testArgumentTypes,
- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
);
- (void)callback:(JSValue *)function;
- (void)bogusCallback:(void(^)(int))function;
@end

@interface TestObject : ParentObject <TestObject>
@property int six;
+ (id)testObject;
@end

@implementation TestObject
@synthesize variable;
@synthesize six;
@synthesize point;
+ (id)testObject
{
    return [[TestObject alloc] init];
}
+ (NSString *)classTest
{
    return @"classTest - okay";
}
- (NSString *)getString
{
    return @"42";
}
- (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
{
    return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
}
- (void)callback:(JSValue *)function
{
    [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
}
- (void)bogusCallback:(void(^)(int))function
{
    function(42);
}
@end

bool testXYZTested = false;

@protocol TextXYZ <JSExport>
- (id)initWithString:(NSString*)string;
@property int x;
@property (readonly) int y;
@property (assign) JSValue *onclick;
@property (assign) JSValue *weakOnclick;
- (void)test:(NSString *)message;
@end

@interface TextXYZ : NSObject <TextXYZ>
@property int x;
@property int y;
@property int z;
- (void)click;
@end

@implementation TextXYZ {
    JSManagedValue *m_weakOnclickHandler;
    JSManagedValue *m_onclickHandler;
}
@synthesize x;
@synthesize y;
@synthesize z;
- (id)initWithString:(NSString*)string
{
    self = [super init];
    if (!self)
        return nil;

    NSLog(@"%@", string);

    return self;
}
- (void)test:(NSString *)message
{
    testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
}
- (void)setWeakOnclick:(JSValue *)value
{
    m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
}

- (void)setOnclick:(JSValue *)value
{
    m_onclickHandler = [JSManagedValue managedValueWithValue:value];
    [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self];
}
- (JSValue *)weakOnclick
{
    return [m_weakOnclickHandler value];
}
- (JSValue *)onclick
{
    return [m_onclickHandler value];
}
- (void)click
{
    if (!m_onclickHandler)
        return;

    JSValue *function = [m_onclickHandler value];
    [function callWithArguments:[NSArray array]];
}
@end

@class TinyDOMNode;

@protocol TinyDOMNode <JSExport>
- (void)appendChild:(TinyDOMNode *)child;
- (NSUInteger)numberOfChildren;
- (TinyDOMNode *)childAtIndex:(NSUInteger)index;
- (void)removeChildAtIndex:(NSUInteger)index;
@end

@interface TinyDOMNode : NSObject<TinyDOMNode>
@end

@implementation TinyDOMNode {
    NSMutableArray *m_children;
    JSVirtualMachine *m_sharedVirtualMachine;
}

- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine
{
    self = [super init];
    if (!self)
        return nil;

    m_children = [[NSMutableArray alloc] initWithCapacity:0];
    m_sharedVirtualMachine = virtualMachine;
#if !__has_feature(objc_arc)
    [m_sharedVirtualMachine retain];
#endif

    return self;
}

- (void)appendChild:(TinyDOMNode *)child
{
    [m_sharedVirtualMachine addManagedReference:child withOwner:self];
    [m_children addObject:child];
}

- (NSUInteger)numberOfChildren
{
    return [m_children count];
}

- (TinyDOMNode *)childAtIndex:(NSUInteger)index
{
    if (index >= [m_children count])
        return nil;
    return [m_children objectAtIndex:index];
}

- (void)removeChildAtIndex:(NSUInteger)index
{
    if (index >= [m_children count])
        return;
    [m_sharedVirtualMachine removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
    [m_children removeObjectAtIndex:index];
}

@end

@interface JSCollection : NSObject
- (void)setValue:(JSValue *)value forKey:(NSString *)key;
- (JSValue *)valueForKey:(NSString *)key;
@end

@implementation JSCollection {
    NSMutableDictionary *_dict;
}
- (id)init
{
    self = [super init];
    if (!self)
        return nil;

    _dict = [[NSMutableDictionary alloc] init];

    return self;
}

- (void)setValue:(JSValue *)value forKey:(NSString *)key
{
    JSManagedValue *oldManagedValue = [_dict objectForKey:key];
    if (oldManagedValue) {
        JSValue* oldValue = [oldManagedValue value];
        if (oldValue)
            [oldValue.context.virtualMachine removeManagedReference:oldManagedValue withOwner:self];
    }
    JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value];
    [value.context.virtualMachine addManagedReference:managedValue withOwner:self];
    [_dict setObject:managedValue forKey:key];
}

- (JSValue *)valueForKey:(NSString *)key
{
    JSManagedValue *managedValue = [_dict objectForKey:key];
    if (!managedValue)
        return nil;
    return [managedValue value];
}
@end

@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 ClassB : ClassA<InitB>
@end

@interface ClassC : ClassB<InitA, InitB>
@end

@interface ClassCPrime : ClassB<InitA, InitC>
@end

@interface ClassD : NSObject<InitA>
- (id)initWithA:(int)a;
@end

@interface ClassE : ClassD
- (id)initWithA:(int)a;
@end

@implementation ClassA {
    int _a;
}
- (id)initWithA:(int)a
{
    self = [super init];
    if (!self)
        return nil;

    _a = a;

    return self;
}
- (int)initialize
{
    return 42;
}
@end

@implementation ClassB {
    int _b;
}
- (id)initWithA:(int)a b:(int)b
{
    self = [super initWithA:a];
    if (!self)
        return nil;

    _b = b;

    return self;
}
@end

@implementation ClassC {
    int _c;
}
- (id)initWithA:(int)a
{
    return [self initWithA:a b:0];
}
- (id)initWithA:(int)a b:(int)b
{
    self = [super initWithA:a b:b];
    if (!self)
        return nil;

    _c = a + b;

    return self;
}
@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 {
    int _a;
}

- (id)initWithA:(int)a
{
    self = [super init];
    if (!self)
        return nil;

    _a = a;

    return self;
}
@end

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)
        failed = 1;
}

static bool blockSignatureContainsClass()
{
    static bool containsClass = ^{
        id block = ^(NSString *string){ return string; };
        return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
    }();
    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);
}

// This test is flaky. Since GC marks C stack and registers as roots conservatively,
// objects not referenced logically can be accidentally marked and alive.
// To avoid this situation as possible as we can,
// 1. run this test first before stack is polluted,
// 2. extract this test as a function to suppress stack height.
static void testWeakValue()
{
    @autoreleasepool {
        JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
        TestObject *testObject = [TestObject testObject];
        JSManagedValue *weakValue;
        @autoreleasepool {
            JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
            context[@"testObject"] = testObject;
            weakValue = [[JSManagedValue alloc] initWithValue:context[@"testObject"]];
        }

        @autoreleasepool {
            JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm];
            context[@"testObject"] = testObject;
            JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
            checkResult(@"weak value == nil", ![weakValue value]);
            checkResult(@"root is still alive", !context[@"testObject"].isUndefined);
        }
    }
}

static void testObjectiveCAPIMain()
{
    @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(@"2 + 2", result.isNumber && [result toInt32] == 4);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
        checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"message"] = @"Hello";
        JSValue *result = [context evaluateScript:@"message + ', World!'"];
        checkResult(@"Hello, World!", result.isString && [result isEqualToObject:@"Hello, World!"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        checkResult(@"Promise is not exposed", [context[@"Promise"] isUndefined]);
        JSValue *result = [context evaluateScript:@"typeof Promise"];
        checkResult(@"typeof Promise is 'undefined'", result.isString && [result isEqualToObject:@"undefined"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *result = [context evaluateScript:@"({ x:42 })"];
        checkResult(@"({ x:42 })", result.isObject && [result[@"x"] isEqualToObject:@42]);
        id obj = [result toObject];
        checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
        id num = (NSDictionary *)obj[@"x"];
        checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *result = [context evaluateScript:@"[ ]"];
        checkResult(@"[ ]", result.isArray);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *result = [context evaluateScript:@"new Date"];
        checkResult(@"new Date", result.isDate);
    }

    @autoreleasepool {
        JSCollection* myPrivateProperties = [[JSCollection alloc] init];

        @autoreleasepool {
            JSContext* context = [[JSContext alloc] init];
            TestObject* rootObject = [TestObject testObject];
            context[@"root"] = rootObject;
            [context.virtualMachine addManagedReference:myPrivateProperties withOwner:rootObject];
            [myPrivateProperties setValue:[JSValue valueWithBool:true inContext:context] forKey:@"is_ham"];
            [myPrivateProperties setValue:[JSValue valueWithObject:@"hello!" inContext:context] forKey:@"message"];
            [myPrivateProperties setValue:[JSValue valueWithInt32:42 inContext:context] forKey:@"my_number"];
            [myPrivateProperties setValue:[JSValue valueWithNullInContext:context] forKey:@"definitely_null"];
            [myPrivateProperties setValue:[JSValue valueWithUndefinedInContext:context] forKey:@"not_sure_if_undefined"];

            JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

            JSValue *isHam = [myPrivateProperties valueForKey:@"is_ham"];
            JSValue *message = [myPrivateProperties valueForKey:@"message"];
            JSValue *myNumber = [myPrivateProperties valueForKey:@"my_number"];
            JSValue *definitelyNull = [myPrivateProperties valueForKey:@"definitely_null"];
            JSValue *notSureIfUndefined = [myPrivateProperties valueForKey:@"not_sure_if_undefined"];
            checkResult(@"is_ham is true", isHam.isBoolean && [isHam toBool]);
            checkResult(@"message is hello!", message.isString && [@"hello!" isEqualToString:[message toString]]);
            checkResult(@"my_number is 42", myNumber.isNumber && [myNumber toInt32] == 42);
            checkResult(@"definitely_null is null", definitelyNull.isNull);
            checkResult(@"not_sure_if_undefined is undefined", notSureIfUndefined.isUndefined);
        }

        checkResult(@"is_ham is nil", ![myPrivateProperties valueForKey:@"is_ham"]);
        checkResult(@"message is nil", ![myPrivateProperties valueForKey:@"message"]);
        checkResult(@"my_number is 42", ![myPrivateProperties valueForKey:@"my_number"]);
        checkResult(@"definitely_null is null", ![myPrivateProperties valueForKey:@"definitely_null"]);
        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;
        context[@"blockCallback"] = ^(int value){
            result = value;
        };
        [context evaluateScript:@"blockCallback(42)"];
        checkResult(@"blockCallback", result == 42);
    }

    if (blockSignatureContainsClass()) {
        @autoreleasepool {
            JSContext *context = [[JSContext alloc] init];
            __block bool result = false;
            context[@"blockCallback"] = ^(NSString *value){
                result = [@"42" isEqualToString:value] == YES;
            };
            [context evaluateScript:@"blockCallback(42)"];
            checkResult(@"blockCallback(NSString *)", result);
        }
    } else
        NSLog(@"Skipping 'blockCallback(NSString *)' test case");

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        checkResult(@"!context.exception", !context.exception);
        [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
        checkResult(@"context.exception", context.exception);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        __block bool caught = false;
        context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
            (void)context;
            (void)exception;
            caught = true;
        };
        [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
        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"] = ^{
            JSContext *context = [JSContext currentContext];
            context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
        };
        JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
        checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
        checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"callback"] = ^{
            JSContext *context = [JSContext currentContext];
            [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
        };
        JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
        checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
        checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        [context evaluateScript:
            @"function sum(array) { \
                var result = 0; \
                for (var i in array) \
                    result += array[i]; \
                return result; \
            }"];
        JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
        JSValue *sumFunction = context[@"sum"];
        JSValue *result = [sumFunction callWithArguments:@[ array ]];
        checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *mulAddFunction = [context evaluateScript:
            @"(function(array, object) { \
                var result = []; \
                for (var i in array) \
                    result.push(array[i] * object.x + object.y); \
                return result; \
            })"];
        JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
        checkResult(@"mulAddFunction", result.isObject && [[result toString] isEqual:@"43,44,46"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];        
        JSValue *array = [JSValue valueWithNewArrayInContext:context];
        checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
        JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
        JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
        NSUInteger lowIndex = 5;
        NSUInteger maxLength = UINT_MAX;

        [array setValue:value1 atIndex:lowIndex];
        checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));

        [array setValue:value1 atIndex:(maxLength - 1)];
        checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);

        [array setValue:value2 atIndex:maxLength];
        checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);

        [array setValue:value2 atIndex:(maxLength + 1)];
        checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);

        if (sizeof(NSUInteger) == 8)
            checkResult(@"valueAtIndex:0 is undefined", [array valueAtIndex:0].isUndefined);
        else
            checkResult(@"valueAtIndex:0", [[array valueAtIndex:0] toInt32] == 24);
        checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
        checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
        checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
        checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *object = [JSValue valueWithNewObjectInContext:context];

        object[@"point"] = @{ @"x":@1, @"y":@2 };
        object[@"point"][@"x"] = @3;
        CGPoint point = [object[@"point"] toPoint];
        checkResult(@"toPoint", point.x == 3 && point.y == 2);

        object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
        checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);

        object[[@"foobar" substringToIndex:3]] = @"bar";
        checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TextXYZ *testXYZ = [[TextXYZ alloc] init];
        context[@"testXYZ"] = testXYZ;
        testXYZ.x = 3;
        testXYZ.y = 4;
        testXYZ.z = 5;
        [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
        [context evaluateScript:@"testXYZ.test('test')"];
        checkResult(@"TextXYZ - testXYZTested", testXYZTested);
        JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
        checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
            JSPropertyDescriptorGetKey:^{
                return [JSContext currentThis][@"x"];
            }
        }];
        JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
        int result = [object [@"getterProperty"] toInt32];
        checkResult(@"getterProperty", result == 101);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"concatenate"] = ^{
            NSArray *arguments = [JSContext currentArguments];
            if (![arguments count])
                return @"";
            NSString *message = [arguments[0] description];
            for (NSUInteger index = 1; index < [arguments count]; ++index)
                message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
            return message;
        };
        JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
        checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"foo"] = @YES;
        checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
        JSValue *result = [context evaluateScript:@"typeof foo"];
        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];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"String(testObject)"];
        checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
        checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"TestObject"] = [TestObject class];
        JSValue *result = [context evaluateScript:@"String(TestObject)"];
        checkResult(@"String(TestObject)", [result isEqualToObject:@"function TestObject() {\n    [native code]\n}"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
        checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"TestObject"] = [TestObject class];
        JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
        checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObjectA"] = testObject;
        context[@"testObjectB"] = testObject;
        JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
        checkResult(@"testObjectA == testObjectB", result.isBoolean && [result toBool]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        testObject.point = (CGPoint){3,4};
        JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
        checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
        checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        testObject.six = 6;
        context[@"testObject"] = testObject;
        context[@"mul"] = ^(int x, int y){ return x * y; };
        JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
        checkResult(@"mul(testObject.six, 7)", result.isNumber && [result toInt32] == 42);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        context[@"testObject"][@"variable"] = @4;
        [context evaluateScript:@"++testObject.variable"];
        checkResult(@"++testObject.variable", testObject.variable == 5);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"point"] = @{ @"x":@6, @"y":@7 };
        JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
        checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"point"] = @{ @"x":@6, @"y":@7 };
        JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
        checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"testObject.getString()"];
        checkResult(@"testObject.getString()", result.isString && [result toInt32] == 42);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
        checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
        checkResult(@"testObject.getString.call(testObject)", result.isString && [result toInt32] == 42);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        checkResult(@"testObject.getString.call({}) pre", !context.exception);
        [context evaluateScript:@"testObject.getString.call({})"];
        checkResult(@"testObject.getString.call({}) post", context.exception);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject* testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
        checkResult(@"testObject.callback", result.isNumber && [result toInt32] == 42);
        result = [context evaluateScript:@"testObject.bogusCallback"];
        checkResult(@"testObject.bogusCallback == undefined", result.isUndefined);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject *testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
        checkResult(@"Function.prototype.toString", !context.exception && !result.isUndefined);
    }

    @autoreleasepool {
        JSContext *context1 = [[JSContext alloc] init];
        JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
        JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
        context1[@"passValueBetweenContexts"] = value;
        JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
        checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
    }

    @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];
        TestObject *testObject = [TestObject testObject];
        @autoreleasepool {
            context[@"testObject"] = testObject;
            [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
            [context evaluateScript:@"testObject = undefined"];
        }
        
        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        @autoreleasepool {
            context[@"testObject"] = testObject;
        }
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TextXYZ *testXYZ = [[TextXYZ alloc] init];

        @autoreleasepool {
            context[@"testXYZ"] = testXYZ;

            [context evaluateScript:@" \
                didClick = false; \
                testXYZ.onclick = function() { \
                    didClick = true; \
                }; \
                 \
                testXYZ.weakOnclick = function() { \
                    return 'foo'; \
                }; \
            "];
        }

        @autoreleasepool {
            [testXYZ click];
            JSValue *result = [context evaluateScript:@"didClick"];
            checkResult(@"Event handler onclick", [result toBool]);
        }

        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        @autoreleasepool {
            JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
            checkResult(@"onclick still around after GC", !(result.isNull || result.isUndefined));
        }


        @autoreleasepool {
            JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
            checkResult(@"weakOnclick not around after GC", result.isNull || result.isUndefined);
        }

        @autoreleasepool {
            [context evaluateScript:@" \
                didClick = false; \
                testXYZ = null; \
            "];
        }

        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        @autoreleasepool {
            [testXYZ click];
            JSValue *result = [context evaluateScript:@"didClick"];
            checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
        }
    }

    @autoreleasepool {
        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] initWithVirtualMachine:context.virtualMachine];
            [lastNode appendChild:newNode];
            lastNode = newNode;
        }

        @autoreleasepool {
            context[@"root"] = root;
            context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
                TinyDOMNode *lastNode = nil;
                while (head) {
                    lastNode = head;
                    head = [lastNode childAtIndex:0];
                }
                return lastNode;
            };
            [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
        }

        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
        checkResult(@"My custom property == 42", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
    }

    @autoreleasepool {
        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] initWithVirtualMachine:context.virtualMachine];
            [lastNode appendChild:newNode];
            lastNode = newNode;
        }

        @autoreleasepool {
            context[@"root"] = root;
            context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
                TinyDOMNode *lastNode = nil;
                while (head) {
                    lastNode = head;
                    head = [lastNode childAtIndex:0];
                }
                return lastNode;
            };
            [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];

            [root appendChild:[root childAtIndex:0]];
            [root removeChildAtIndex:0];
        }

        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
        checkResult(@"duplicate calls to addManagedReference don't cause things to die", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        JSValue *o = [JSValue valueWithNewObjectInContext:context];
        o[@"foo"] = @"foo";
        JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);

        checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        TestObject *testObject = [TestObject testObject];
        context[@"testObject"] = testObject;
        [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"];
        checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception);
    }

    @autoreleasepool {
        TestObject *testObject = [TestObject testObject];
        JSManagedValue *managedTestObject;
        @autoreleasepool {
            JSContext *context = [[JSContext alloc] init];
            context[@"testObject"] = testObject;
            managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]];
            [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
        }
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        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 {
        JSContext *context = [[JSContext alloc] init];
        context[@"MyClass"] = ^{
            JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
            JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
            JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
            JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyClass"][@"prototype"] JSValueRef]);
            return newThis;
        };

        context[@"MyOtherClass"] = ^{
            JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
            JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
            JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
            JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyOtherClass"][@"prototype"] JSValueRef]);
            return newThis;
        };

        context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
            NSLog(@"EXCEPTION: %@", [exception toString]);
            context.exception = nil;
        };

        JSValue *constructor1 = context[@"MyClass"];
        JSValue *constructor2 = context[@"MyOtherClass"];

        JSValue *value1 = [context evaluateScript:@"new MyClass()"];
        checkResult(@"value1 instanceof MyClass", [value1 isInstanceOf:constructor1]);
        checkResult(@"!(value1 instanceof MyOtherClass)", ![value1 isInstanceOf:constructor2]);
        checkResult(@"MyClass.prototype.constructor === MyClass", [[context evaluateScript:@"MyClass.prototype.constructor === MyClass"] toBool]);
        checkResult(@"MyClass instanceof Function", [[context evaluateScript:@"MyClass instanceof Function"] toBool]);

        JSValue *value2 = [context evaluateScript:@"new MyOtherClass()"];
        checkResult(@"value2 instanceof MyOtherClass", [value2 isInstanceOf:constructor2]);
        checkResult(@"!(value2 instanceof MyClass)", ![value2 isInstanceOf:constructor1]);
        checkResult(@"MyOtherClass.prototype.constructor === MyOtherClass", [[context evaluateScript:@"MyOtherClass.prototype.constructor === MyOtherClass"] toBool]);
        checkResult(@"MyOtherClass instanceof Function", [[context evaluateScript:@"MyOtherClass instanceof Function"] toBool]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        context[@"MyClass"] = ^{
            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];
        JSValue *testObject = [context evaluateScript:@"(new TestObject())"];
        checkResult(@"testObject instanceof TestObject", [testObject isInstanceOf:context[@"TestObject"]]);

        context[@"TextXYZ"] = [TextXYZ class];
        JSValue *textObject = [context evaluateScript:@"(new TextXYZ(\"Called TextXYZ constructor!\"))"];
        checkResult(@"textObject instanceof TextXYZ", [textObject isInstanceOf:context[@"TextXYZ"]]);
    }

    @autoreleasepool {
        JSContext *context = [[JSContext alloc] init];
        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"]]);

        JSValue *canConstructClassC = [context evaluateScript:@"(function() { \
            try { \
                (new ClassC(1, 2)); \
                return true; \
            } catch(e) { \
                return false; \
            } \
        })()"];
        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 {
        JSContext *context = [[JSContext alloc] init];
        context[@"ClassD"] = [ClassD class];
        context[@"ClassE"] = [ClassE class];
       
        JSValue *d = [context evaluateScript:@"(new ClassD())"];
        checkResult(@"Returning instance of ClassE from ClassD's init has correct class", [d isInstanceOf:context[@"ClassE"]]);
    }

    @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();
    runRegress141275();
    runRegress141809();
}

@protocol NumberProtocol <JSExport>

@property (nonatomic) NSInteger number;

@end

@interface NumberObject : NSObject <NumberProtocol>

@property (nonatomic) NSInteger number;

@end

@implementation NumberObject

@end

// Check that negative NSIntegers retain the correct value when passed into JS code.
static void checkNegativeNSIntegers()
{
    NumberObject *container = [[NumberObject alloc] init];
    container.number = -1;
    JSContext *context = [[JSContext alloc] init];
    context[@"container"] = container;
    NSString *jsID = @"var getContainerNumber = function() { return container.number }";
    [context evaluateScript:jsID];
    JSValue *jsFunction = context[@"getContainerNumber"];
    JSValue *result = [jsFunction callWithArguments:@[]];
    
    checkResult(@"Negative number maintained its original value", [[result toString] isEqualToString:@"-1"]);
}

void testObjectiveCAPI()
{
    NSLog(@"Testing Objective-C API");
    checkNegativeNSIntegers();
    testWeakValue();
    testObjectiveCAPIMain();
}

#else

void testObjectiveCAPI()
{
}

#endif // JSC_OBJC_API_ENABLED