We should support CreateThis in the FTL
[WebKit-https.git] / Source / JavaScriptCore / API / tests / testapi.mm
1 /*
2  * Copyright (C) 2013-2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "JSExportMacros.h"
27 #import <JavaScriptCore/JavaScriptCore.h>
28
29 #undef NS_AVAILABLE
30 #define NS_AVAILABLE(_mac, _ios)
31
32 #import "CurrentThisInsideBlockGetterTest.h"
33 #import "DFGWorklist.h"
34 #import "DateTests.h"
35 #import "JSExportTests.h"
36 #import "JSVirtualMachinePrivate.h"
37 #import "JSWrapperMapTests.h"
38 #import "Regress141275.h"
39 #import "Regress141809.h"
40
41 #import <pthread.h>
42 #import <vector>
43 #import <wtf/MemoryFootprint.h>
44 #import <wtf/Optional.h>
45
46 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
47 extern "C" void JSSynchronousEdenCollectForDebugging(JSContextRef);
48
49 extern "C" bool _Block_has_signature(id);
50 extern "C" const char * _Block_signature(id);
51
52 extern int failed;
53 extern "C" void testObjectiveCAPI(void);
54 extern "C" void checkResult(NSString *, bool);
55
56 #if JSC_OBJC_API_ENABLED
57
58 @interface UnexportedObject : NSObject
59 @end
60
61 @implementation UnexportedObject
62 @end
63
64 @protocol ParentObject <JSExport>
65 @end
66
67 @interface ParentObject : NSObject<ParentObject>
68 + (NSString *)parentTest;
69 @end
70
71 @implementation ParentObject
72 + (NSString *)parentTest
73 {
74     return [self description];
75 }
76 @end
77
78 @protocol TestObject <JSExport>
79 - (id)init;
80 @property int variable;
81 @property (readonly) int six;
82 @property CGPoint point;
83 + (NSString *)classTest;
84 + (NSString *)parentTest;
85 - (NSString *)getString;
86 JSExportAs(testArgumentTypes,
87 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
88 );
89 - (void)callback:(JSValue *)function;
90 - (void)bogusCallback:(void(^)(int))function;
91 @end
92
93 @interface TestObject : ParentObject <TestObject>
94 @property int six;
95 + (id)testObject;
96 @end
97
98 @implementation TestObject
99 @synthesize variable;
100 @synthesize six;
101 @synthesize point;
102 + (id)testObject
103 {
104     return [[TestObject alloc] init];
105 }
106 + (NSString *)classTest
107 {
108     return @"classTest - okay";
109 }
110 - (NSString *)getString
111 {
112     return @"42";
113 }
114 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
115 {
116     return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
117 }
118 - (void)callback:(JSValue *)function
119 {
120     [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
121 }
122 - (void)bogusCallback:(void(^)(int))function
123 {
124     function(42);
125 }
126 @end
127
128 bool testXYZTested = false;
129
130 @protocol TextXYZ <JSExport>
131 - (id)initWithString:(NSString*)string;
132 @property int x;
133 @property (readonly) int y;
134 @property (assign) JSValue *onclick;
135 @property (assign) JSValue *weakOnclick;
136 - (void)test:(NSString *)message;
137 @end
138
139 @interface TextXYZ : NSObject <TextXYZ>
140 @property int x;
141 @property int y;
142 @property int z;
143 - (void)click;
144 @end
145
146 @implementation TextXYZ {
147     JSManagedValue *m_weakOnclickHandler;
148     JSManagedValue *m_onclickHandler;
149 }
150 @synthesize x;
151 @synthesize y;
152 @synthesize z;
153 - (id)initWithString:(NSString*)string
154 {
155     self = [super init];
156     if (!self)
157         return nil;
158
159     NSLog(@"%@", string);
160
161     return self;
162 }
163 - (void)test:(NSString *)message
164 {
165     testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
166 }
167 - (void)setWeakOnclick:(JSValue *)value
168 {
169     m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
170 }
171
172 - (void)setOnclick:(JSValue *)value
173 {
174     m_onclickHandler = [JSManagedValue managedValueWithValue:value];
175     [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self];
176 }
177 - (JSValue *)weakOnclick
178 {
179     return [m_weakOnclickHandler value];
180 }
181 - (JSValue *)onclick
182 {
183     return [m_onclickHandler value];
184 }
185 - (void)click
186 {
187     if (!m_onclickHandler)
188         return;
189
190     JSValue *function = [m_onclickHandler value];
191     [function callWithArguments:[NSArray array]];
192 }
193 @end
194
195 @class TinyDOMNode;
196
197 @protocol TinyDOMNode <JSExport>
198 - (void)appendChild:(TinyDOMNode *)child;
199 - (NSUInteger)numberOfChildren;
200 - (TinyDOMNode *)childAtIndex:(NSUInteger)index;
201 - (void)removeChildAtIndex:(NSUInteger)index;
202 @end
203
204 @interface TinyDOMNode : NSObject<TinyDOMNode>
205 @end
206
207 @implementation TinyDOMNode {
208     NSMutableArray *m_children;
209     JSVirtualMachine *m_sharedVirtualMachine;
210 }
211
212 - (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine
213 {
214     self = [super init];
215     if (!self)
216         return nil;
217
218     m_children = [[NSMutableArray alloc] initWithCapacity:0];
219     m_sharedVirtualMachine = virtualMachine;
220 #if !__has_feature(objc_arc)
221     [m_sharedVirtualMachine retain];
222 #endif
223
224     return self;
225 }
226
227 - (void)appendChild:(TinyDOMNode *)child
228 {
229     [m_sharedVirtualMachine addManagedReference:child withOwner:self];
230     [m_children addObject:child];
231 }
232
233 - (NSUInteger)numberOfChildren
234 {
235     return [m_children count];
236 }
237
238 - (TinyDOMNode *)childAtIndex:(NSUInteger)index
239 {
240     if (index >= [m_children count])
241         return nil;
242     return [m_children objectAtIndex:index];
243 }
244
245 - (void)removeChildAtIndex:(NSUInteger)index
246 {
247     if (index >= [m_children count])
248         return;
249     [m_sharedVirtualMachine removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
250     [m_children removeObjectAtIndex:index];
251 }
252
253 @end
254
255 @interface JSCollection : NSObject
256 - (void)setValue:(JSValue *)value forKey:(NSString *)key;
257 - (JSValue *)valueForKey:(NSString *)key;
258 @end
259
260 @implementation JSCollection {
261     NSMutableDictionary *_dict;
262 }
263 - (id)init
264 {
265     self = [super init];
266     if (!self)
267         return nil;
268
269     _dict = [[NSMutableDictionary alloc] init];
270
271     return self;
272 }
273
274 - (void)setValue:(JSValue *)value forKey:(NSString *)key
275 {
276     JSManagedValue *oldManagedValue = [_dict objectForKey:key];
277     if (oldManagedValue) {
278         JSValue* oldValue = [oldManagedValue value];
279         if (oldValue)
280             [oldValue.context.virtualMachine removeManagedReference:oldManagedValue withOwner:self];
281     }
282     JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value];
283     [value.context.virtualMachine addManagedReference:managedValue withOwner:self];
284     [_dict setObject:managedValue forKey:key];
285 }
286
287 - (JSValue *)valueForKey:(NSString *)key
288 {
289     JSManagedValue *managedValue = [_dict objectForKey:key];
290     if (!managedValue)
291         return nil;
292     return [managedValue value];
293 }
294 @end
295
296 @protocol InitA <JSExport>
297 - (id)initWithA:(int)a;
298 - (int)initialize;
299 @end
300
301 @protocol InitB <JSExport>
302 - (id)initWithA:(int)a b:(int)b;
303 @end
304
305 @protocol InitC <JSExport>
306 - (id)_init;
307 @end
308
309 @interface ClassA : NSObject<InitA>
310 @end
311
312 @interface ClassB : ClassA<InitB>
313 @end
314
315 @interface ClassC : ClassB<InitA, InitB>
316 @end
317
318 @interface ClassCPrime : ClassB<InitA, InitC>
319 @end
320
321 @interface ClassD : NSObject<InitA>
322 - (id)initWithA:(int)a;
323 @end
324
325 @interface ClassE : ClassD
326 - (id)initWithA:(int)a;
327 @end
328
329 @implementation ClassA {
330     int _a;
331 }
332 - (id)initWithA:(int)a
333 {
334     self = [super init];
335     if (!self)
336         return nil;
337
338     _a = a;
339
340     return self;
341 }
342 - (int)initialize
343 {
344     return 42;
345 }
346 @end
347
348 @implementation ClassB {
349     int _b;
350 }
351 - (id)initWithA:(int)a b:(int)b
352 {
353     self = [super initWithA:a];
354     if (!self)
355         return nil;
356
357     _b = b;
358
359     return self;
360 }
361 @end
362
363 @implementation ClassC {
364     int _c;
365 }
366 - (id)initWithA:(int)a
367 {
368     return [self initWithA:a b:0];
369 }
370 - (id)initWithA:(int)a b:(int)b
371 {
372     self = [super initWithA:a b:b];
373     if (!self)
374         return nil;
375
376     _c = a + b;
377
378     return self;
379 }
380 @end
381
382 @implementation ClassCPrime
383 - (id)initWithA:(int)a
384 {
385     self = [super initWithA:a b:0];
386     if (!self)
387         return nil;
388     return self;
389 }
390 - (id)_init
391 {
392     return [self initWithA:42];
393 }
394 @end
395
396 @implementation ClassD
397
398 - (id)initWithA:(int)a
399 {
400     self = nil;
401     return [[ClassE alloc] initWithA:a];
402 }
403 - (int)initialize
404 {
405     return 0;
406 }
407 @end
408
409 @implementation ClassE {
410     int _a;
411 }
412
413 - (id)initWithA:(int)a
414 {
415     self = [super init];
416     if (!self)
417         return nil;
418
419     _a = a;
420
421     return self;
422 }
423 @end
424
425 static bool evilAllocationObjectWasDealloced = false;
426
427 @interface EvilAllocationObject : NSObject
428 - (JSValue *)doEvilThingsWithContext:(JSContext *)context;
429 @end
430
431 @implementation EvilAllocationObject {
432     JSContext *m_context;
433 }
434 - (id)initWithContext:(JSContext *)context
435 {
436     self = [super init];
437     if (!self)
438         return nil;
439
440     m_context = context;
441
442     return self;
443 }
444 - (void)dealloc
445 {
446     [self doEvilThingsWithContext:m_context];
447     evilAllocationObjectWasDealloced = true;
448 #if !__has_feature(objc_arc)
449     [super dealloc];
450 #endif
451 }
452
453 - (JSValue *)doEvilThingsWithContext:(JSContext *)context
454 {
455     JSValue *result = [context evaluateScript:@" \
456         (function() { \
457             var a = []; \
458             var sum = 0; \
459             for (var i = 0; i < 10000; ++i) { \
460                 sum += i; \
461                 a[i] = sum; \
462             } \
463             return sum; \
464         })()"];
465
466     JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
467     return result;
468 }
469 @end
470
471 extern "C" void checkResult(NSString *description, bool passed)
472 {
473     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
474     if (!passed)
475         failed = 1;
476 }
477
478 static bool blockSignatureContainsClass()
479 {
480     static bool containsClass = ^{
481         id block = ^(NSString *string){ return string; };
482         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
483     }();
484     return containsClass;
485 }
486
487 static void* threadMain(void* contextPtr)
488 {
489     JSContext *context = (__bridge JSContext*)contextPtr;
490
491     // Do something to enter the VM.
492     TestObject *testObject = [TestObject testObject];
493     context[@"testObject"] = testObject;
494     return nullptr;
495 }
496
497 static void* multiVMThreadMain(void* okPtr)
498 {
499     bool& ok = *static_cast<bool*>(okPtr);
500     JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
501     JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
502     [context evaluateScript:
503         @"var array = [{}];\n"
504          "for (var i = 0; i < 20; ++i) {\n"
505          "    var newArray = new Array(array.length * 2);\n"
506          "    for (var j = 0; j < newArray.length; ++j)\n"
507          "        newArray[j] = {parent: array[j / 2]};\n"
508          "    array = newArray;\n"
509          "}\n"];
510     if (context.exception) {
511         NSLog(@"Uncaught exception.\n");
512         ok = false;
513     }
514     if (![context.globalObject valueForProperty:@"array"].toObject) {
515         NSLog(@"Did not find \"array\" variable.\n");
516         ok = false;
517     }
518     JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
519     return nullptr;
520 }
521
522 static void runJITThreadLimitTests()
523 {
524 #if ENABLE(DFG_JIT)
525     auto testDFG = [] {
526         unsigned defaultNumberOfThreads = JSC::Options::numberOfDFGCompilerThreads();
527         unsigned targetNumberOfThreads = 1;
528         unsigned initialNumberOfThreads = [JSVirtualMachine setNumberOfDFGCompilerThreads:1];
529         checkResult(@"Initial number of DFG threads should be the value provided through Options", initialNumberOfThreads == defaultNumberOfThreads);
530         unsigned updatedNumberOfThreads = [JSVirtualMachine setNumberOfDFGCompilerThreads:initialNumberOfThreads];
531         checkResult(@"Number of DFG threads should have been updated", updatedNumberOfThreads == targetNumberOfThreads);
532     };
533
534     auto testFTL = [] {
535         unsigned defaultNumberOfThreads = JSC::Options::numberOfFTLCompilerThreads();
536         unsigned targetNumberOfThreads = 3;
537         unsigned initialNumberOfThreads = [JSVirtualMachine setNumberOfFTLCompilerThreads:1];
538         checkResult(@"Initial number of FTL threads should be the value provided through Options", initialNumberOfThreads == defaultNumberOfThreads);
539         unsigned updatedNumberOfThreads = [JSVirtualMachine setNumberOfFTLCompilerThreads:initialNumberOfThreads];
540         checkResult(@"Number of FTL threads should have been updated", updatedNumberOfThreads == targetNumberOfThreads);
541     };
542
543     checkResult(@"runJITThreadLimitTests() must run at the very beginning to test the case where the global JIT worklist was not initialized yet", !JSC::DFG::existingGlobalDFGWorklistOrNull() && !JSC::DFG::existingGlobalFTLWorklistOrNull());
544
545     testDFG();
546     JSC::DFG::ensureGlobalDFGWorklist();
547     testDFG();
548
549     testFTL();
550     JSC::DFG::ensureGlobalFTLWorklist();
551     testFTL();
552 #endif // ENABLE(DFG_JIT)
553 }
554
555 static void testObjectiveCAPIMain()
556 {
557     // FIXME: Make this work again.
558     // https://bugs.webkit.org/show_bug.cgi?id=187886
559     if (false)
560         runJITThreadLimitTests();
561
562     @autoreleasepool {
563         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
564         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
565         [context evaluateScript:@"bad"];
566     }
567
568     @autoreleasepool {
569         JSContext *context = [[JSContext alloc] init];
570         JSValue *result = [context evaluateScript:@"2 + 2"];
571         checkResult(@"2 + 2", result.isNumber && [result toInt32] == 4);
572     }
573
574     @autoreleasepool {
575         JSContext *context = [[JSContext alloc] init];
576         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
577         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
578     }
579
580     @autoreleasepool {
581         JSContext *context = [[JSContext alloc] init];
582         context[@"message"] = @"Hello";
583         JSValue *result = [context evaluateScript:@"message + ', World!'"];
584         checkResult(@"Hello, World!", result.isString && [result isEqualToObject:@"Hello, World!"]);
585     }
586
587     @autoreleasepool {
588         JSContext *context = [[JSContext alloc] init];
589         checkResult(@"Promise is exposed", ![context[@"Promise"] isUndefined]);
590         JSValue *result = [context evaluateScript:@"typeof Promise"];
591         checkResult(@"typeof Promise is 'function'", result.isString && [result isEqualToObject:@"function"]);
592     }
593
594     @autoreleasepool {
595         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
596         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
597         [context evaluateScript:@"result = 0; Promise.resolve(42).then(function (value) { result = value; });"];
598         checkResult(@"Microtask is drained", [context[@"result"]  isEqualToObject:@42]);
599     }
600
601     @autoreleasepool {
602         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
603         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
604         TestObject* testObject = [TestObject testObject];
605         context[@"testObject"] = testObject;
606         [context evaluateScript:@"result = 0; callbackResult = 0; Promise.resolve(42).then(function (value) { result = value; }); callbackResult = testObject.getString();"];
607         checkResult(@"Microtask is drained with same VM", [context[@"result"]  isEqualToObject:@42] && [context[@"callbackResult"] isEqualToObject:@"42"]);
608     }
609
610     @autoreleasepool {
611         JSContext *context = [[JSContext alloc] init];
612         JSValue *result = [context evaluateScript:@"({ x:42 })"];
613         checkResult(@"({ x:42 })", result.isObject && [result[@"x"] isEqualToObject:@42]);
614         id obj = [result toObject];
615         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
616         id num = (NSDictionary *)obj[@"x"];
617         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
618     }
619
620     @autoreleasepool {
621         JSContext *context = [[JSContext alloc] init];
622         JSValue *result = [context evaluateScript:@"[ ]"];
623         checkResult(@"[ ]", result.isArray);
624     }
625
626     @autoreleasepool {
627         JSContext *context = [[JSContext alloc] init];
628         JSValue *result = [context evaluateScript:@"new Date"];
629         checkResult(@"new Date", result.isDate);
630     }
631
632     @autoreleasepool {
633         JSCollection* myPrivateProperties = [[JSCollection alloc] init];
634
635         @autoreleasepool {
636             JSContext* context = [[JSContext alloc] init];
637             TestObject* rootObject = [TestObject testObject];
638             context[@"root"] = rootObject;
639             [context.virtualMachine addManagedReference:myPrivateProperties withOwner:rootObject];
640             [myPrivateProperties setValue:[JSValue valueWithBool:true inContext:context] forKey:@"is_ham"];
641             [myPrivateProperties setValue:[JSValue valueWithObject:@"hello!" inContext:context] forKey:@"message"];
642             [myPrivateProperties setValue:[JSValue valueWithInt32:42 inContext:context] forKey:@"my_number"];
643             [myPrivateProperties setValue:[JSValue valueWithNullInContext:context] forKey:@"definitely_null"];
644             [myPrivateProperties setValue:[JSValue valueWithUndefinedInContext:context] forKey:@"not_sure_if_undefined"];
645
646             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
647
648             JSValue *isHam = [myPrivateProperties valueForKey:@"is_ham"];
649             JSValue *message = [myPrivateProperties valueForKey:@"message"];
650             JSValue *myNumber = [myPrivateProperties valueForKey:@"my_number"];
651             JSValue *definitelyNull = [myPrivateProperties valueForKey:@"definitely_null"];
652             JSValue *notSureIfUndefined = [myPrivateProperties valueForKey:@"not_sure_if_undefined"];
653             checkResult(@"is_ham is true", isHam.isBoolean && [isHam toBool]);
654             checkResult(@"message is hello!", message.isString && [@"hello!" isEqualToString:[message toString]]);
655             checkResult(@"my_number is 42", myNumber.isNumber && [myNumber toInt32] == 42);
656             checkResult(@"definitely_null is null", definitelyNull.isNull);
657             checkResult(@"not_sure_if_undefined is undefined", notSureIfUndefined.isUndefined);
658         }
659
660         checkResult(@"is_ham is nil", ![myPrivateProperties valueForKey:@"is_ham"]);
661         checkResult(@"message is nil", ![myPrivateProperties valueForKey:@"message"]);
662         checkResult(@"my_number is 42", ![myPrivateProperties valueForKey:@"my_number"]);
663         checkResult(@"definitely_null is null", ![myPrivateProperties valueForKey:@"definitely_null"]);
664         checkResult(@"not_sure_if_undefined is undefined", ![myPrivateProperties valueForKey:@"not_sure_if_undefined"]);
665     }
666
667     @autoreleasepool {
668         JSContext *context = [[JSContext alloc] init];
669         JSValue *message = [JSValue valueWithObject:@"hello" inContext:context];
670         TestObject *rootObject = [TestObject testObject];
671         JSCollection *collection = [[JSCollection alloc] init];
672         context[@"root"] = rootObject;
673         @autoreleasepool {
674             JSValue *jsCollection = [JSValue valueWithObject:collection inContext:context];
675             JSManagedValue *weakCollection = [JSManagedValue managedValueWithValue:jsCollection andOwner:rootObject];
676             [context.virtualMachine addManagedReference:weakCollection withOwner:message];
677             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
678         }
679     }
680
681     @autoreleasepool {
682         JSContext *context = [[JSContext alloc] init];
683         __block int result;
684         context[@"blockCallback"] = ^(int value){
685             result = value;
686         };
687         [context evaluateScript:@"blockCallback(42)"];
688         checkResult(@"blockCallback", result == 42);
689     }
690
691     if (blockSignatureContainsClass()) {
692         @autoreleasepool {
693             JSContext *context = [[JSContext alloc] init];
694             __block bool result = false;
695             context[@"blockCallback"] = ^(NSString *value){
696                 result = [@"42" isEqualToString:value] == YES;
697             };
698             [context evaluateScript:@"blockCallback(42)"];
699             checkResult(@"blockCallback(NSString *)", result);
700         }
701     } else
702         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
703
704     @autoreleasepool {
705         JSContext *context = [[JSContext alloc] init];
706         checkResult(@"!context.exception", !context.exception);
707         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
708         checkResult(@"context.exception", context.exception);
709     }
710
711     @autoreleasepool {
712         JSContext *context = [[JSContext alloc] init];
713         __block bool caught = false;
714         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
715             (void)context;
716             (void)exception;
717             caught = true;
718         };
719         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
720         checkResult(@"JSContext.exceptionHandler", caught);
721     }
722
723     @autoreleasepool {
724         JSContext *context = [[JSContext alloc] init];
725         __block int expectedExceptionLineNumber = 1;
726         __block bool sawExpectedExceptionLineNumber = false;
727         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
728             sawExpectedExceptionLineNumber = [exception[@"line"] toInt32] == expectedExceptionLineNumber;
729         };
730         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
731         checkResult(@"evaluteScript exception on line 1", sawExpectedExceptionLineNumber);
732
733         expectedExceptionLineNumber = 2;
734         sawExpectedExceptionLineNumber = false;
735         [context evaluateScript:@"// Line 1\n!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
736         checkResult(@"evaluteScript exception on line 2", sawExpectedExceptionLineNumber);
737     }
738
739     @autoreleasepool {
740         JSContext *context = [[JSContext alloc] init];
741         __block bool emptyExceptionSourceURL = false;
742         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
743             emptyExceptionSourceURL = exception[@"sourceURL"].isUndefined;
744         };
745         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
746         checkResult(@"evaluteScript: exception has no sourceURL", emptyExceptionSourceURL);
747
748         __block NSString *exceptionSourceURL = nil;
749         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
750             exceptionSourceURL = [exception[@"sourceURL"] toString];
751         };
752         NSURL *url = [NSURL fileURLWithPath:@"/foo/bar.js" isDirectory:NO];
753         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()" withSourceURL:url];
754         checkResult(@"evaluateScript:withSourceURL: exception has expected sourceURL", [exceptionSourceURL isEqualToString:[url absoluteString]]);
755     }
756
757     @autoreleasepool {
758         JSContext *context = [[JSContext alloc] init];
759         context[@"callback"] = ^{
760             JSContext *context = [JSContext currentContext];
761             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
762         };
763         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
764         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
765         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
766     }
767
768     @autoreleasepool {
769         JSContext *context = [[JSContext alloc] init];
770         context[@"callback"] = ^{
771             JSContext *context = [JSContext currentContext];
772             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
773         };
774         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
775         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
776         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
777     }
778
779     @autoreleasepool {
780         JSContext *context = [[JSContext alloc] init];
781         [context evaluateScript:
782             @"function sum(array) { \
783                 var result = 0; \
784                 for (var i in array) \
785                     result += array[i]; \
786                 return result; \
787             }"];
788         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
789         JSValue *sumFunction = context[@"sum"];
790         JSValue *result = [sumFunction callWithArguments:@[ array ]];
791         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
792     }
793
794     @autoreleasepool {
795         JSContext *context = [[JSContext alloc] init];
796         JSValue *mulAddFunction = [context evaluateScript:
797             @"(function(array, object) { \
798                 var result = []; \
799                 for (var i in array) \
800                     result.push(array[i] * object.x + object.y); \
801                 return result; \
802             })"];
803         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
804         checkResult(@"mulAddFunction", result.isObject && [[result toString] isEqual:@"43,44,46"]);
805     }
806
807     @autoreleasepool {
808         JSContext *context = [[JSContext alloc] init];        
809         JSValue *array = [JSValue valueWithNewArrayInContext:context];
810         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
811         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
812         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
813         NSUInteger lowIndex = 5;
814         NSUInteger maxLength = UINT_MAX;
815
816         [array setValue:value1 atIndex:lowIndex];
817         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
818
819         [array setValue:value1 atIndex:(maxLength - 1)];
820         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
821
822         [array setValue:value2 atIndex:maxLength];
823         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
824
825         [array setValue:value2 atIndex:(maxLength + 1)];
826         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
827
828         if (sizeof(NSUInteger) == 8)
829             checkResult(@"valueAtIndex:0 is undefined", [array valueAtIndex:0].isUndefined);
830         else
831             checkResult(@"valueAtIndex:0", [[array valueAtIndex:0] toInt32] == 24);
832         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
833         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
834         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
835         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
836     }
837
838     @autoreleasepool {
839         JSContext *context = [[JSContext alloc] init];
840         JSValue *object = [JSValue valueWithNewObjectInContext:context];
841
842         object[@"point"] = @{ @"x":@1, @"y":@2 };
843         object[@"point"][@"x"] = @3;
844         CGPoint point = [object[@"point"] toPoint];
845         checkResult(@"toPoint", point.x == 3 && point.y == 2);
846
847         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
848         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
849
850         object[[@"foobar" substringToIndex:3]] = @"bar";
851         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
852     }
853
854     @autoreleasepool {
855         JSContext *context = [[JSContext alloc] init];
856         TextXYZ *testXYZ = [[TextXYZ alloc] init];
857         context[@"testXYZ"] = testXYZ;
858         testXYZ.x = 3;
859         testXYZ.y = 4;
860         testXYZ.z = 5;
861         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
862         [context evaluateScript:@"testXYZ.test('test')"];
863         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
864         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
865         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
866     }
867
868     @autoreleasepool {
869         JSContext *context = [[JSContext alloc] init];
870         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
871             JSPropertyDescriptorGetKey:^{
872                 return [JSContext currentThis][@"x"];
873             }
874         }];
875         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
876         int result = [object [@"getterProperty"] toInt32];
877         checkResult(@"getterProperty", result == 101);
878     }
879
880     @autoreleasepool {
881         JSContext *context = [[JSContext alloc] init];
882         context[@"concatenate"] = ^{
883             NSArray *arguments = [JSContext currentArguments];
884             if (![arguments count])
885                 return @"";
886             NSString *message = [arguments[0] description];
887             for (NSUInteger index = 1; index < [arguments count]; ++index)
888                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
889             return message;
890         };
891         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
892         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
893     }
894
895     @autoreleasepool {
896         JSContext *context = [[JSContext alloc] init];
897         context[@"foo"] = @YES;
898         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
899         JSValue *result = [context evaluateScript:@"typeof foo"];
900         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
901     }
902
903     @autoreleasepool {
904         JSContext *context = [[JSContext alloc] init];
905         JSValue *result = [context evaluateScript:@"String(console)"];
906         checkResult(@"String(console)", [result isEqualToObject:@"[object Console]"]);
907         result = [context evaluateScript:@"typeof console.log"];
908         checkResult(@"typeof console.log", [result isEqualToObject:@"function"]);
909     }
910
911     @autoreleasepool {
912         JSContext *context = [[JSContext alloc] init];
913         TestObject* testObject = [TestObject testObject];
914         context[@"testObject"] = testObject;
915         JSValue *result = [context evaluateScript:@"String(testObject)"];
916         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
917     }
918
919     @autoreleasepool {
920         JSContext *context = [[JSContext alloc] init];
921         TestObject* testObject = [TestObject testObject];
922         context[@"testObject"] = testObject;
923         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
924         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
925     }
926
927     @autoreleasepool {
928         JSContext *context = [[JSContext alloc] init];
929         context[@"TestObject"] = [TestObject class];
930         JSValue *result = [context evaluateScript:@"String(TestObject)"];
931         checkResult(@"String(TestObject)", [result isEqualToObject:@"function TestObject() {\n    [native code]\n}"]);
932     }
933
934     @autoreleasepool {
935         JSContext *context = [[JSContext alloc] init];
936         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
937         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
938     }
939
940     @autoreleasepool {
941         JSContext *context = [[JSContext alloc] init];
942         context[@"TestObject"] = [TestObject class];
943         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
944         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
945     }
946
947     @autoreleasepool {
948         JSContext *context = [[JSContext alloc] init];
949         TestObject* testObject = [TestObject testObject];
950         context[@"testObjectA"] = testObject;
951         context[@"testObjectB"] = testObject;
952         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
953         checkResult(@"testObjectA == testObjectB", result.isBoolean && [result toBool]);
954     }
955
956     @autoreleasepool {
957         JSContext *context = [[JSContext alloc] init];
958         TestObject* testObject = [TestObject testObject];
959         context[@"testObject"] = testObject;
960         testObject.point = (CGPoint){3,4};
961         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
962         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
963         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
964     }
965
966     @autoreleasepool {
967         JSContext *context = [[JSContext alloc] init];
968         TestObject* testObject = [TestObject testObject];
969         testObject.six = 6;
970         context[@"testObject"] = testObject;
971         context[@"mul"] = ^(int x, int y){ return x * y; };
972         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
973         checkResult(@"mul(testObject.six, 7)", result.isNumber && [result toInt32] == 42);
974     }
975
976     @autoreleasepool {
977         JSContext *context = [[JSContext alloc] init];
978         TestObject* testObject = [TestObject testObject];
979         context[@"testObject"] = testObject;
980         context[@"testObject"][@"variable"] = @4;
981         [context evaluateScript:@"++testObject.variable"];
982         checkResult(@"++testObject.variable", testObject.variable == 5);
983     }
984
985     @autoreleasepool {
986         JSContext *context = [[JSContext alloc] init];
987         context[@"point"] = @{ @"x":@6, @"y":@7 };
988         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
989         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
990     }
991
992     @autoreleasepool {
993         JSContext *context = [[JSContext alloc] init];
994         context[@"point"] = @{ @"x":@6, @"y":@7 };
995         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
996         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
997     }
998
999     @autoreleasepool {
1000         JSContext *context = [[JSContext alloc] init];
1001         TestObject* testObject = [TestObject testObject];
1002         context[@"testObject"] = testObject;
1003         JSValue *result = [context evaluateScript:@"testObject.getString()"];
1004         checkResult(@"testObject.getString()", result.isString && [result toInt32] == 42);
1005     }
1006
1007     @autoreleasepool {
1008         JSContext *context = [[JSContext alloc] init];
1009         TestObject* testObject = [TestObject testObject];
1010         context[@"testObject"] = testObject;
1011         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
1012         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
1013     }
1014
1015     @autoreleasepool {
1016         JSContext *context = [[JSContext alloc] init];
1017         TestObject* testObject = [TestObject testObject];
1018         context[@"testObject"] = testObject;
1019         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
1020         checkResult(@"testObject.getString.call(testObject)", result.isString && [result toInt32] == 42);
1021     }
1022
1023     @autoreleasepool {
1024         JSContext *context = [[JSContext alloc] init];
1025         TestObject* testObject = [TestObject testObject];
1026         context[@"testObject"] = testObject;
1027         checkResult(@"testObject.getString.call({}) pre", !context.exception);
1028         [context evaluateScript:@"testObject.getString.call({})"];
1029         checkResult(@"testObject.getString.call({}) post", context.exception);
1030     }
1031
1032     @autoreleasepool {
1033         JSContext *context = [[JSContext alloc] init];
1034         TestObject* testObject = [TestObject testObject];
1035         context[@"testObject"] = testObject;
1036         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
1037         checkResult(@"testObject.callback", result.isNumber && [result toInt32] == 42);
1038         result = [context evaluateScript:@"testObject.bogusCallback"];
1039         checkResult(@"testObject.bogusCallback == undefined", result.isUndefined);
1040     }
1041
1042     @autoreleasepool {
1043         JSContext *context = [[JSContext alloc] init];
1044         TestObject *testObject = [TestObject testObject];
1045         context[@"testObject"] = testObject;
1046         JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
1047         checkResult(@"Function.prototype.toString", !context.exception && !result.isUndefined);
1048     }
1049
1050     @autoreleasepool {
1051         JSContext *context1 = [[JSContext alloc] init];
1052         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
1053         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
1054         context1[@"passValueBetweenContexts"] = value;
1055         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
1056         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
1057     }
1058
1059     @autoreleasepool {
1060         JSContext *context = [[JSContext alloc] init];
1061         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
1062             NSDictionary *expectedDict = @{
1063                 @"foo" : [NSNumber numberWithInt:1],
1064                 @"bar" : @{
1065                     @"baz": [NSNumber numberWithInt:2]
1066                 }
1067             };
1068             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
1069         };
1070         [context evaluateScript:@"var myDict = { \
1071             'foo': 1, \
1072             'bar': {'baz': 2} \
1073         }; \
1074         handleTheDictionary(myDict);"];
1075
1076         context[@"handleTheArray"] = ^(NSArray *array) {
1077             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
1078             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
1079         };
1080         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
1081     }
1082
1083     @autoreleasepool {
1084         JSContext *context = [[JSContext alloc] init];
1085         TestObject *testObject = [TestObject testObject];
1086         @autoreleasepool {
1087             context[@"testObject"] = testObject;
1088             [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
1089             [context evaluateScript:@"testObject = undefined"];
1090         }
1091         
1092         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1093
1094         @autoreleasepool {
1095             context[@"testObject"] = testObject;
1096         }
1097     }
1098
1099     @autoreleasepool {
1100         JSContext *context = [[JSContext alloc] init];
1101         TextXYZ *testXYZ = [[TextXYZ alloc] init];
1102
1103         @autoreleasepool {
1104             context[@"testXYZ"] = testXYZ;
1105
1106             [context evaluateScript:@" \
1107                 didClick = false; \
1108                 testXYZ.onclick = function() { \
1109                     didClick = true; \
1110                 }; \
1111                  \
1112                 testXYZ.weakOnclick = function() { \
1113                     return 'foo'; \
1114                 }; \
1115             "];
1116         }
1117
1118         @autoreleasepool {
1119             [testXYZ click];
1120             JSValue *result = [context evaluateScript:@"didClick"];
1121             checkResult(@"Event handler onclick", [result toBool]);
1122         }
1123
1124         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1125
1126         @autoreleasepool {
1127             JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
1128             checkResult(@"onclick still around after GC", !(result.isNull || result.isUndefined));
1129         }
1130
1131
1132         @autoreleasepool {
1133             JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
1134             checkResult(@"weakOnclick not around after GC", result.isNull || result.isUndefined);
1135         }
1136
1137         @autoreleasepool {
1138             [context evaluateScript:@" \
1139                 didClick = false; \
1140                 testXYZ = null; \
1141             "];
1142         }
1143
1144         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1145
1146         @autoreleasepool {
1147             [testXYZ click];
1148             JSValue *result = [context evaluateScript:@"didClick"];
1149             checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
1150         }
1151     }
1152
1153     @autoreleasepool {
1154         JSContext *context = [[JSContext alloc] init];
1155         TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1156         TinyDOMNode *lastNode = root;
1157         for (NSUInteger i = 0; i < 3; i++) {
1158             TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1159             [lastNode appendChild:newNode];
1160             lastNode = newNode;
1161         }
1162
1163         @autoreleasepool {
1164             context[@"root"] = root;
1165             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
1166                 TinyDOMNode *lastNode = nil;
1167                 while (head) {
1168                     lastNode = head;
1169                     head = [lastNode childAtIndex:0];
1170                 }
1171                 return lastNode;
1172             };
1173             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
1174         }
1175
1176         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1177
1178         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
1179         checkResult(@"My custom property == 42", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
1180     }
1181
1182     @autoreleasepool {
1183         JSContext *context = [[JSContext alloc] init];
1184         TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1185         TinyDOMNode *lastNode = root;
1186         for (NSUInteger i = 0; i < 3; i++) {
1187             TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1188             [lastNode appendChild:newNode];
1189             lastNode = newNode;
1190         }
1191
1192         @autoreleasepool {
1193             context[@"root"] = root;
1194             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
1195                 TinyDOMNode *lastNode = nil;
1196                 while (head) {
1197                     lastNode = head;
1198                     head = [lastNode childAtIndex:0];
1199                 }
1200                 return lastNode;
1201             };
1202             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
1203
1204             [root appendChild:[root childAtIndex:0]];
1205             [root removeChildAtIndex:0];
1206         }
1207
1208         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1209
1210         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
1211         checkResult(@"duplicate calls to addManagedReference don't cause things to die", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
1212     }
1213
1214     @autoreleasepool {
1215         JSContext *context = [[JSContext alloc] init];
1216         JSValue *o = [JSValue valueWithNewObjectInContext:context];
1217         o[@"foo"] = @"foo";
1218         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1219
1220         checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]);
1221     }
1222
1223     @autoreleasepool {
1224         JSContext *context = [[JSContext alloc] init];
1225         TestObject *testObject = [TestObject testObject];
1226         context[@"testObject"] = testObject;
1227         [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"];
1228         checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception);
1229     }
1230
1231     @autoreleasepool {
1232         static const unsigned count = 100;
1233         NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
1234         JSContext *context = [[JSContext alloc] init];
1235         @autoreleasepool {
1236             for (unsigned i = 0; i < count; ++i) {
1237                 JSValue *object = [JSValue valueWithNewObjectInContext:context];
1238                 JSManagedValue *managedObject = [JSManagedValue managedValueWithValue:object];
1239                 [array addObject:managedObject];
1240             }
1241         }
1242         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1243         for (unsigned i = 0; i < count; ++i)
1244             [context.virtualMachine addManagedReference:array[i] withOwner:array];
1245     }
1246
1247     @autoreleasepool {
1248         TestObject *testObject = [TestObject testObject];
1249         JSManagedValue *managedTestObject;
1250         @autoreleasepool {
1251             JSContext *context = [[JSContext alloc] init];
1252             context[@"testObject"] = testObject;
1253             managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]];
1254             [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
1255         }
1256     }
1257
1258     @autoreleasepool {
1259         JSContext *context = [[JSContext alloc] init];
1260         TestObject *testObject = [TestObject testObject];
1261         context[@"testObject"] = testObject;
1262         JSManagedValue *managedValue = nil;
1263         @autoreleasepool {
1264             JSValue *object = [JSValue valueWithNewObjectInContext:context];
1265             managedValue = [JSManagedValue managedValueWithValue:object andOwner:testObject];
1266             [context.virtualMachine addManagedReference:managedValue withOwner:testObject];
1267         }
1268         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1269     }
1270
1271     @autoreleasepool {
1272         JSContext *context = [[JSContext alloc] init];
1273         context[@"MyClass"] = ^{
1274             JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1275             JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1276             JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1277             JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyClass"][@"prototype"] JSValueRef]);
1278             return newThis;
1279         };
1280
1281         context[@"MyOtherClass"] = ^{
1282             JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1283             JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1284             JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1285             JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyOtherClass"][@"prototype"] JSValueRef]);
1286             return newThis;
1287         };
1288
1289         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
1290             NSLog(@"EXCEPTION: %@", [exception toString]);
1291             context.exception = nil;
1292         };
1293
1294         JSValue *constructor1 = context[@"MyClass"];
1295         JSValue *constructor2 = context[@"MyOtherClass"];
1296
1297         JSValue *value1 = [context evaluateScript:@"new MyClass()"];
1298         checkResult(@"value1 instanceof MyClass", [value1 isInstanceOf:constructor1]);
1299         checkResult(@"!(value1 instanceof MyOtherClass)", ![value1 isInstanceOf:constructor2]);
1300         checkResult(@"MyClass.prototype.constructor === MyClass", [[context evaluateScript:@"MyClass.prototype.constructor === MyClass"] toBool]);
1301         checkResult(@"MyClass instanceof Function", [[context evaluateScript:@"MyClass instanceof Function"] toBool]);
1302
1303         JSValue *value2 = [context evaluateScript:@"new MyOtherClass()"];
1304         checkResult(@"value2 instanceof MyOtherClass", [value2 isInstanceOf:constructor2]);
1305         checkResult(@"!(value2 instanceof MyClass)", ![value2 isInstanceOf:constructor1]);
1306         checkResult(@"MyOtherClass.prototype.constructor === MyOtherClass", [[context evaluateScript:@"MyOtherClass.prototype.constructor === MyOtherClass"] toBool]);
1307         checkResult(@"MyOtherClass instanceof Function", [[context evaluateScript:@"MyOtherClass instanceof Function"] toBool]);
1308     }
1309
1310     @autoreleasepool {
1311         JSContext *context = [[JSContext alloc] init];
1312         context[@"MyClass"] = ^{
1313             NSLog(@"I'm intentionally not returning anything.");
1314         };
1315         JSValue *result = [context evaluateScript:@"new MyClass()"];
1316         checkResult(@"result === undefined", result.isUndefined);
1317         checkResult(@"exception.message is correct'", context.exception 
1318             && [@"Objective-C blocks called as constructors must return an object." isEqualToString:[context.exception[@"message"] toString]]);
1319     }
1320
1321     @autoreleasepool {
1322         checkResult(@"[JSContext currentThis] == nil outside of callback", ![JSContext currentThis]);
1323         checkResult(@"[JSContext currentArguments] == nil outside of callback", ![JSContext currentArguments]);
1324         if ([JSContext currentCallee])
1325             checkResult(@"[JSContext currentCallee] == nil outside of callback", ![JSContext currentCallee]);
1326     }
1327
1328     if ([JSContext currentCallee]) {
1329         @autoreleasepool {
1330             JSContext *context = [[JSContext alloc] init];
1331             context[@"testFunction"] = ^{
1332                 checkResult(@"testFunction.foo === 42", [[JSContext currentCallee][@"foo"] toInt32] == 42);
1333             };
1334             context[@"testFunction"][@"foo"] = @42;
1335             [context[@"testFunction"] callWithArguments:nil];
1336
1337             context[@"TestConstructor"] = ^{
1338                 JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1339                 JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1340                 JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1341                 JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentCallee][@"prototype"] JSValueRef]);
1342                 return newThis;
1343             };
1344             checkResult(@"(new TestConstructor) instanceof TestConstructor", [context evaluateScript:@"(new TestConstructor) instanceof TestConstructor"]);
1345         }
1346     }
1347
1348     @autoreleasepool {
1349         JSContext *context = [[JSContext alloc] init];
1350         context[@"TestObject"] = [TestObject class];
1351         JSValue *testObject = [context evaluateScript:@"(new TestObject())"];
1352         checkResult(@"testObject instanceof TestObject", [testObject isInstanceOf:context[@"TestObject"]]);
1353
1354         context[@"TextXYZ"] = [TextXYZ class];
1355         JSValue *textObject = [context evaluateScript:@"(new TextXYZ(\"Called TextXYZ constructor!\"))"];
1356         checkResult(@"textObject instanceof TextXYZ", [textObject isInstanceOf:context[@"TextXYZ"]]);
1357     }
1358
1359     @autoreleasepool {
1360         JSContext *context = [[JSContext alloc] init];
1361         context[@"ClassA"] = [ClassA class];
1362         context[@"ClassB"] = [ClassB class];
1363         context[@"ClassC"] = [ClassC class]; // Should print error message about too many inits found.
1364         context[@"ClassCPrime"] = [ClassCPrime class]; // Ditto.
1365
1366         JSValue *a = [context evaluateScript:@"(new ClassA(42))"];
1367         checkResult(@"a instanceof ClassA", [a isInstanceOf:context[@"ClassA"]]);
1368         checkResult(@"a.initialize() is callable", [[a invokeMethod:@"initialize" withArguments:@[]] toInt32] == 42);
1369
1370         JSValue *b = [context evaluateScript:@"(new ClassB(42, 53))"];
1371         checkResult(@"b instanceof ClassB", [b isInstanceOf:context[@"ClassB"]]);
1372
1373         JSValue *canConstructClassC = [context evaluateScript:@"(function() { \
1374             try { \
1375                 (new ClassC(1, 2)); \
1376                 return true; \
1377             } catch(e) { \
1378                 return false; \
1379             } \
1380         })()"];
1381         checkResult(@"shouldn't be able to construct ClassC", ![canConstructClassC toBool]);
1382
1383         JSValue *canConstructClassCPrime = [context evaluateScript:@"(function() { \
1384             try { \
1385                 (new ClassCPrime(1)); \
1386                 return true; \
1387             } catch(e) { \
1388                 return false; \
1389             } \
1390         })()"];
1391         checkResult(@"shouldn't be able to construct ClassCPrime", ![canConstructClassCPrime toBool]);
1392     }
1393
1394     @autoreleasepool {
1395         JSContext *context = [[JSContext alloc] init];
1396         context[@"ClassA"] = [ClassA class];
1397         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
1398             NSLog(@"%@", [exception toString]);
1399             context.exception = exception;
1400         };
1401
1402         checkResult(@"ObjC Constructor without 'new' pre", !context.exception);
1403         [context evaluateScript:@"ClassA(42)"];
1404         checkResult(@"ObjC Constructor without 'new' post", context.exception);
1405     }
1406
1407     @autoreleasepool {
1408         JSContext *context = [[JSContext alloc] init];
1409         context[@"ClassD"] = [ClassD class];
1410         context[@"ClassE"] = [ClassE class];
1411        
1412         JSValue *d = [context evaluateScript:@"(new ClassD())"];
1413         checkResult(@"Returning instance of ClassE from ClassD's init has correct class", [d isInstanceOf:context[@"ClassE"]]);
1414     }
1415
1416     @autoreleasepool {
1417         JSContext *context = [[JSContext alloc] init];
1418         while (!evilAllocationObjectWasDealloced) {
1419             @autoreleasepool {
1420                 EvilAllocationObject *evilObject = [[EvilAllocationObject alloc] initWithContext:context];
1421                 context[@"evilObject"] = evilObject;
1422                 context[@"evilObject"] = nil;
1423             }
1424         }
1425         checkResult(@"EvilAllocationObject was successfully dealloced without crashing", evilAllocationObjectWasDealloced);
1426     }
1427
1428     @autoreleasepool {
1429         JSContext *context = [[JSContext alloc] init];
1430         checkResult(@"default context.name is nil", context.name == nil);
1431         NSString *name1 = @"Name1";
1432         NSString *name2 = @"Name2";
1433         context.name = name1;
1434         NSString *fetchedName1 = context.name;
1435         context.name = name2;
1436         NSString *fetchedName2 = context.name;
1437         context.name = nil;
1438         NSString *fetchedName3 = context.name;
1439         checkResult(@"fetched context.name was expected", [fetchedName1 isEqualToString:name1]);
1440         checkResult(@"fetched context.name was expected", [fetchedName2 isEqualToString:name2]);
1441         checkResult(@"fetched context.name was expected", ![fetchedName1 isEqualToString:fetchedName2]);
1442         checkResult(@"fetched context.name was expected", fetchedName3 == nil);
1443     }
1444
1445     @autoreleasepool {
1446         JSContext *context = [[JSContext alloc] init];
1447         context[@"UnexportedObject"] = [UnexportedObject class];
1448         context[@"makeObject"] = ^{
1449             return [[UnexportedObject alloc] init];
1450         };
1451         JSValue *result = [context evaluateScript:@"(makeObject() instanceof UnexportedObject)"];
1452         checkResult(@"makeObject() instanceof UnexportedObject", result.isBoolean && [result toBool]);
1453     }
1454
1455     @autoreleasepool {
1456         JSContext *context = [[JSContext alloc] init];
1457         [[JSValue valueWithInt32:42 inContext:context] toDictionary];
1458         [[JSValue valueWithInt32:42 inContext:context] toArray];
1459     }
1460
1461     @autoreleasepool {
1462         JSContext *context = [[JSContext alloc] init];
1463
1464         // Create the root, make it reachable from JS, and force an EdenCollection
1465         // so that we scan the external object graph.
1466         TestObject *root = [TestObject testObject];
1467         @autoreleasepool {
1468             context[@"root"] = root;
1469         }
1470         JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
1471
1472         // Create a new Obj-C object only reachable via the external object graph
1473         // through the object we already scanned during the EdenCollection.
1474         TestObject *child = [TestObject testObject];
1475         [context.virtualMachine addManagedReference:child withOwner:root];
1476
1477         // Create a new managed JSValue that will only be kept alive if we properly rescan
1478         // the external object graph.
1479         JSManagedValue *managedJSObject = nil;
1480         @autoreleasepool {
1481             JSValue *jsObject = [JSValue valueWithObject:@"hello" inContext:context];
1482             managedJSObject = [JSManagedValue managedValueWithValue:jsObject];
1483             [context.virtualMachine addManagedReference:managedJSObject withOwner:child];
1484         }
1485
1486         // Force another EdenCollection. It should rescan the new part of the external object graph.
1487         JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
1488         
1489         // Check that the managed JSValue is still alive.
1490         checkResult(@"EdenCollection doesn't reclaim new managed values", [managedJSObject value] != nil);
1491     }
1492
1493     @autoreleasepool {
1494         JSContext *context = [[JSContext alloc] init];
1495         
1496         pthread_t threadID;
1497         pthread_create(&threadID, NULL, &threadMain, (__bridge void*)context);
1498         pthread_join(threadID, nullptr);
1499         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1500
1501         checkResult(@"Did not crash after entering the VM from another thread", true);
1502     }
1503     
1504     @autoreleasepool {
1505         std::vector<pthread_t> threads;
1506         bool ok = true;
1507         for (unsigned i = 0; i < 5; ++i) {
1508             pthread_t threadID;
1509             pthread_create(&threadID, nullptr, multiVMThreadMain, &ok);
1510             threads.push_back(threadID);
1511         }
1512
1513         for (pthread_t thread : threads)
1514             pthread_join(thread, nullptr);
1515
1516         checkResult(@"Ran code in five concurrent VMs that GC'd", ok);
1517     }
1518
1519     currentThisInsideBlockGetterTest();
1520     runDateTests();
1521     runJSExportTests();
1522     runJSWrapperMapTests();
1523     runRegress141275();
1524     runRegress141809();
1525 }
1526
1527 @protocol NumberProtocol <JSExport>
1528
1529 @property (nonatomic) NSInteger number;
1530
1531 @end
1532
1533 @interface NumberObject : NSObject <NumberProtocol>
1534
1535 @property (nonatomic) NSInteger number;
1536
1537 @end
1538
1539 @implementation NumberObject
1540
1541 @end
1542
1543 // Check that negative NSIntegers retain the correct value when passed into JS code.
1544 static void checkNegativeNSIntegers()
1545 {
1546     NumberObject *container = [[NumberObject alloc] init];
1547     container.number = -1;
1548     JSContext *context = [[JSContext alloc] init];
1549     context[@"container"] = container;
1550     NSString *jsID = @"var getContainerNumber = function() { return container.number }";
1551     [context evaluateScript:jsID];
1552     JSValue *jsFunction = context[@"getContainerNumber"];
1553     JSValue *result = [jsFunction callWithArguments:@[]];
1554     
1555     checkResult(@"Negative number maintained its original value", [[result toString] isEqualToString:@"-1"]);
1556 }
1557
1558
1559 void testObjectiveCAPI()
1560 {
1561     NSLog(@"Testing Objective-C API");
1562     checkNegativeNSIntegers();
1563     testObjectiveCAPIMain();
1564 }
1565
1566 #else
1567
1568 void testObjectiveCAPI()
1569 {
1570 }
1571
1572 #endif // JSC_OBJC_API_ENABLED