Add API to generate and consume cached bytecode
[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 "config.h"
27 #import "JSExportMacros.h"
28 #import <JavaScriptCore/JavaScriptCore.h>
29
30 #import "CurrentThisInsideBlockGetterTest.h"
31 #import "DFGWorklist.h"
32 #import "DateTests.h"
33 #import "JSCast.h"
34 #import "JSContextPrivate.h"
35 #import "JSExportTests.h"
36 #import "JSScript.h"
37 #import "JSValuePrivate.h"
38 #import "JSVirtualMachineInternal.h"
39 #import "JSVirtualMachinePrivate.h"
40 #import "JSWrapperMapTests.h"
41 #import "Regress141275.h"
42 #import "Regress141809.h"
43
44 #import <pthread.h>
45 #import <vector>
46 #import <wtf/MemoryFootprint.h>
47 #import <wtf/Optional.h>
48 #import <wtf/DataLog.h>
49
50 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
51 extern "C" void JSSynchronousEdenCollectForDebugging(JSContextRef);
52
53 extern "C" bool _Block_has_signature(id);
54 extern "C" const char * _Block_signature(id);
55
56 extern int failed;
57 extern "C" void testObjectiveCAPI(void);
58 extern "C" void checkResult(NSString *, bool);
59
60 #if JSC_OBJC_API_ENABLED
61
62 @interface UnexportedObject : NSObject
63 @end
64
65 @implementation UnexportedObject
66 @end
67
68 @protocol ParentObject <JSExport>
69 @end
70
71 @interface ParentObject : NSObject<ParentObject>
72 + (NSString *)parentTest;
73 @end
74
75 @implementation ParentObject
76 + (NSString *)parentTest
77 {
78     return [self description];
79 }
80 @end
81
82 @protocol TestObject <JSExport>
83 - (id)init;
84 @property int variable;
85 @property (readonly) int six;
86 @property CGPoint point;
87 + (NSString *)classTest;
88 + (NSString *)parentTest;
89 - (NSString *)getString;
90 JSExportAs(testArgumentTypes,
91 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
92 );
93 - (void)callback:(JSValue *)function;
94 - (void)bogusCallback:(void(^)(int))function;
95 @end
96
97 @interface TestObject : ParentObject <TestObject>
98 @property int six;
99 + (id)testObject;
100 @end
101
102 @implementation TestObject
103 @synthesize variable;
104 @synthesize six;
105 @synthesize point;
106 + (id)testObject
107 {
108     return [[TestObject alloc] init];
109 }
110 + (NSString *)classTest
111 {
112     return @"classTest - okay";
113 }
114 - (NSString *)getString
115 {
116     return @"42";
117 }
118 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
119 {
120     return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
121 }
122 - (void)callback:(JSValue *)function
123 {
124     [function callWithArguments:[NSArray arrayWithObject:[NSNumber numberWithInt:42]]];
125 }
126 - (void)bogusCallback:(void(^)(int))function
127 {
128     function(42);
129 }
130 @end
131
132 bool testXYZTested = false;
133
134 @protocol TextXYZ <JSExport>
135 - (id)initWithString:(NSString*)string;
136 @property int x;
137 @property (readonly) int y;
138 @property (assign) JSValue *onclick;
139 @property (assign) JSValue *weakOnclick;
140 - (void)test:(NSString *)message;
141 @end
142
143 @interface TextXYZ : NSObject <TextXYZ>
144 @property int x;
145 @property int y;
146 @property int z;
147 - (void)click;
148 @end
149
150 @implementation TextXYZ {
151     JSManagedValue *m_weakOnclickHandler;
152     JSManagedValue *m_onclickHandler;
153 }
154 @synthesize x;
155 @synthesize y;
156 @synthesize z;
157 - (id)initWithString:(NSString*)string
158 {
159     self = [super init];
160     if (!self)
161         return nil;
162
163     NSLog(@"%@", string);
164
165     return self;
166 }
167 - (void)test:(NSString *)message
168 {
169     testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
170 }
171 - (void)setWeakOnclick:(JSValue *)value
172 {
173     m_weakOnclickHandler = [JSManagedValue managedValueWithValue:value];
174 }
175
176 - (void)setOnclick:(JSValue *)value
177 {
178     m_onclickHandler = [JSManagedValue managedValueWithValue:value];
179     [value.context.virtualMachine addManagedReference:m_onclickHandler withOwner:self];
180 }
181 - (JSValue *)weakOnclick
182 {
183     return [m_weakOnclickHandler value];
184 }
185 - (JSValue *)onclick
186 {
187     return [m_onclickHandler value];
188 }
189 - (void)click
190 {
191     if (!m_onclickHandler)
192         return;
193
194     JSValue *function = [m_onclickHandler value];
195     [function callWithArguments:[NSArray array]];
196 }
197 @end
198
199 @class TinyDOMNode;
200
201 @protocol TinyDOMNode <JSExport>
202 - (void)appendChild:(TinyDOMNode *)child;
203 - (NSUInteger)numberOfChildren;
204 - (TinyDOMNode *)childAtIndex:(NSUInteger)index;
205 - (void)removeChildAtIndex:(NSUInteger)index;
206 @end
207
208 @interface TinyDOMNode : NSObject<TinyDOMNode>
209 @end
210
211 @implementation TinyDOMNode {
212     NSMutableArray *m_children;
213     JSVirtualMachine *m_sharedVirtualMachine;
214 }
215
216 - (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine
217 {
218     self = [super init];
219     if (!self)
220         return nil;
221
222     m_children = [[NSMutableArray alloc] initWithCapacity:0];
223     m_sharedVirtualMachine = virtualMachine;
224 #if !__has_feature(objc_arc)
225     [m_sharedVirtualMachine retain];
226 #endif
227
228     return self;
229 }
230
231 - (void)appendChild:(TinyDOMNode *)child
232 {
233     [m_sharedVirtualMachine addManagedReference:child withOwner:self];
234     [m_children addObject:child];
235 }
236
237 - (NSUInteger)numberOfChildren
238 {
239     return [m_children count];
240 }
241
242 - (TinyDOMNode *)childAtIndex:(NSUInteger)index
243 {
244     if (index >= [m_children count])
245         return nil;
246     return [m_children objectAtIndex:index];
247 }
248
249 - (void)removeChildAtIndex:(NSUInteger)index
250 {
251     if (index >= [m_children count])
252         return;
253     [m_sharedVirtualMachine removeManagedReference:[m_children objectAtIndex:index] withOwner:self];
254     [m_children removeObjectAtIndex:index];
255 }
256
257 @end
258
259 @interface JSCollection : NSObject
260 - (void)setValue:(JSValue *)value forKey:(NSString *)key;
261 - (JSValue *)valueForKey:(NSString *)key;
262 @end
263
264 @implementation JSCollection {
265     NSMutableDictionary *_dict;
266 }
267 - (id)init
268 {
269     self = [super init];
270     if (!self)
271         return nil;
272
273     _dict = [[NSMutableDictionary alloc] init];
274
275     return self;
276 }
277
278 - (void)setValue:(JSValue *)value forKey:(NSString *)key
279 {
280     JSManagedValue *oldManagedValue = [_dict objectForKey:key];
281     if (oldManagedValue) {
282         JSValue* oldValue = [oldManagedValue value];
283         if (oldValue)
284             [oldValue.context.virtualMachine removeManagedReference:oldManagedValue withOwner:self];
285     }
286     JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value];
287     [value.context.virtualMachine addManagedReference:managedValue withOwner:self];
288     [_dict setObject:managedValue forKey:key];
289 }
290
291 - (JSValue *)valueForKey:(NSString *)key
292 {
293     JSManagedValue *managedValue = [_dict objectForKey:key];
294     if (!managedValue)
295         return nil;
296     return [managedValue value];
297 }
298 @end
299
300 @protocol InitA <JSExport>
301 - (id)initWithA:(int)a;
302 - (int)initialize;
303 @end
304
305 @protocol InitB <JSExport>
306 - (id)initWithA:(int)a b:(int)b;
307 @end
308
309 @protocol InitC <JSExport>
310 - (id)_init;
311 @end
312
313 @interface ClassA : NSObject<InitA>
314 @end
315
316 @interface ClassB : ClassA<InitB>
317 @end
318
319 @interface ClassC : ClassB<InitA, InitB>
320 @end
321
322 @interface ClassCPrime : ClassB<InitA, InitC>
323 @end
324
325 @interface ClassD : NSObject<InitA>
326 - (id)initWithA:(int)a;
327 @end
328
329 @interface ClassE : ClassD
330 - (id)initWithA:(int)a;
331 @end
332
333 @implementation ClassA {
334     int _a;
335 }
336 - (id)initWithA:(int)a
337 {
338     self = [super init];
339     if (!self)
340         return nil;
341
342     _a = a;
343
344     return self;
345 }
346 - (int)initialize
347 {
348     return 42;
349 }
350 @end
351
352 @implementation ClassB {
353     int _b;
354 }
355 - (id)initWithA:(int)a b:(int)b
356 {
357     self = [super initWithA:a];
358     if (!self)
359         return nil;
360
361     _b = b;
362
363     return self;
364 }
365 @end
366
367 @implementation ClassC {
368     int _c;
369 }
370 - (id)initWithA:(int)a
371 {
372     return [self initWithA:a b:0];
373 }
374 - (id)initWithA:(int)a b:(int)b
375 {
376     self = [super initWithA:a b:b];
377     if (!self)
378         return nil;
379
380     _c = a + b;
381
382     return self;
383 }
384 @end
385
386 @implementation ClassCPrime
387 - (id)initWithA:(int)a
388 {
389     self = [super initWithA:a b:0];
390     if (!self)
391         return nil;
392     return self;
393 }
394 - (id)_init
395 {
396     return [self initWithA:42];
397 }
398 @end
399
400 @implementation ClassD
401
402 - (id)initWithA:(int)a
403 {
404     self = nil;
405     return [[ClassE alloc] initWithA:a];
406 }
407 - (int)initialize
408 {
409     return 0;
410 }
411 @end
412
413 @implementation ClassE {
414     int _a;
415 }
416
417 - (id)initWithA:(int)a
418 {
419     self = [super init];
420     if (!self)
421         return nil;
422
423     _a = a;
424
425     return self;
426 }
427 @end
428
429 static bool evilAllocationObjectWasDealloced = false;
430
431 @interface EvilAllocationObject : NSObject
432 - (JSValue *)doEvilThingsWithContext:(JSContext *)context;
433 @end
434
435 @implementation EvilAllocationObject {
436     JSContext *m_context;
437 }
438 - (id)initWithContext:(JSContext *)context
439 {
440     self = [super init];
441     if (!self)
442         return nil;
443
444     m_context = context;
445
446     return self;
447 }
448 - (void)dealloc
449 {
450     [self doEvilThingsWithContext:m_context];
451     evilAllocationObjectWasDealloced = true;
452 #if !__has_feature(objc_arc)
453     [super dealloc];
454 #endif
455 }
456
457 - (JSValue *)doEvilThingsWithContext:(JSContext *)context
458 {
459     JSValue *result = [context evaluateScript:@" \
460         (function() { \
461             var a = []; \
462             var sum = 0; \
463             for (var i = 0; i < 10000; ++i) { \
464                 sum += i; \
465                 a[i] = sum; \
466             } \
467             return sum; \
468         })()"];
469
470     JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
471     return result;
472 }
473 @end
474
475 extern "C" void checkResult(NSString *description, bool passed)
476 {
477     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
478     if (!passed)
479         failed = 1;
480 }
481
482 static bool blockSignatureContainsClass()
483 {
484     static bool containsClass = ^{
485         id block = ^(NSString *string){ return string; };
486         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
487     }();
488     return containsClass;
489 }
490
491 static void* threadMain(void* contextPtr)
492 {
493     JSContext *context = (__bridge JSContext*)contextPtr;
494
495     // Do something to enter the VM.
496     TestObject *testObject = [TestObject testObject];
497     context[@"testObject"] = testObject;
498     return nullptr;
499 }
500
501 static void* multiVMThreadMain(void* okPtr)
502 {
503     bool& ok = *static_cast<bool*>(okPtr);
504     JSVirtualMachine *vm = [[JSVirtualMachine alloc] init];
505     JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
506     [context evaluateScript:
507         @"var array = [{}];\n"
508          "for (var i = 0; i < 20; ++i) {\n"
509          "    var newArray = new Array(array.length * 2);\n"
510          "    for (var j = 0; j < newArray.length; ++j)\n"
511          "        newArray[j] = {parent: array[j / 2]};\n"
512          "    array = newArray;\n"
513          "}\n"];
514     if (context.exception) {
515         NSLog(@"Uncaught exception.\n");
516         ok = false;
517     }
518     if (![context.globalObject valueForProperty:@"array"].toObject) {
519         NSLog(@"Did not find \"array\" variable.\n");
520         ok = false;
521     }
522     JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
523     return nullptr;
524 }
525
526 static void runJITThreadLimitTests()
527 {
528 #if ENABLE(DFG_JIT)
529     auto testDFG = [] {
530         unsigned defaultNumberOfThreads = JSC::Options::numberOfDFGCompilerThreads();
531         unsigned targetNumberOfThreads = 1;
532         unsigned initialNumberOfThreads = [JSVirtualMachine setNumberOfDFGCompilerThreads:1];
533         checkResult(@"Initial number of DFG threads should be the value provided through Options", initialNumberOfThreads == defaultNumberOfThreads);
534         unsigned updatedNumberOfThreads = [JSVirtualMachine setNumberOfDFGCompilerThreads:initialNumberOfThreads];
535         checkResult(@"Number of DFG threads should have been updated", updatedNumberOfThreads == targetNumberOfThreads);
536     };
537
538     auto testFTL = [] {
539         unsigned defaultNumberOfThreads = JSC::Options::numberOfFTLCompilerThreads();
540         unsigned targetNumberOfThreads = 3;
541         unsigned initialNumberOfThreads = [JSVirtualMachine setNumberOfFTLCompilerThreads:1];
542         checkResult(@"Initial number of FTL threads should be the value provided through Options", initialNumberOfThreads == defaultNumberOfThreads);
543         unsigned updatedNumberOfThreads = [JSVirtualMachine setNumberOfFTLCompilerThreads:initialNumberOfThreads];
544         checkResult(@"Number of FTL threads should have been updated", updatedNumberOfThreads == targetNumberOfThreads);
545     };
546
547     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());
548
549     testDFG();
550     JSC::DFG::ensureGlobalDFGWorklist();
551     testDFG();
552
553     testFTL();
554     JSC::DFG::ensureGlobalFTLWorklist();
555     testFTL();
556 #endif // ENABLE(DFG_JIT)
557 }
558
559 static void testObjectiveCAPIMain()
560 {
561     @autoreleasepool {
562         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
563         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
564         [context evaluateScript:@"bad"];
565     }
566
567     @autoreleasepool {
568         JSContext *context = [[JSContext alloc] init];
569         JSValue *result = [context evaluateScript:@"2 + 2"];
570         checkResult(@"2 + 2", result.isNumber && [result toInt32] == 4);
571     }
572
573     @autoreleasepool {
574         JSContext *context = [[JSContext alloc] init];
575         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
576         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
577     }
578
579     @autoreleasepool {
580         JSContext *context = [[JSContext alloc] init];
581         context[@"message"] = @"Hello";
582         JSValue *result = [context evaluateScript:@"message + ', World!'"];
583         checkResult(@"Hello, World!", result.isString && [result isEqualToObject:@"Hello, World!"]);
584     }
585
586     @autoreleasepool {
587         JSContext *context = [[JSContext alloc] init];
588         checkResult(@"Promise is exposed", ![context[@"Promise"] isUndefined]);
589         JSValue *result = [context evaluateScript:@"typeof Promise"];
590         checkResult(@"typeof Promise is 'function'", result.isString && [result isEqualToObject:@"function"]);
591     }
592
593     @autoreleasepool {
594         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
595         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
596         [context evaluateScript:@"result = 0; Promise.resolve(42).then(function (value) { result = value; });"];
597         checkResult(@"Microtask is drained", [context[@"result"]  isEqualToObject:@42]);
598     }
599
600     @autoreleasepool {
601         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
602         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
603         TestObject* testObject = [TestObject testObject];
604         context[@"testObject"] = testObject;
605         [context evaluateScript:@"result = 0; callbackResult = 0; Promise.resolve(42).then(function (value) { result = value; }); callbackResult = testObject.getString();"];
606         checkResult(@"Microtask is drained with same VM", [context[@"result"]  isEqualToObject:@42] && [context[@"callbackResult"] isEqualToObject:@"42"]);
607     }
608
609     @autoreleasepool {
610         JSContext *context = [[JSContext alloc] init];
611         JSValue *result = [context evaluateScript:@"({ x:42 })"];
612         checkResult(@"({ x:42 })", result.isObject && [result[@"x"] isEqualToObject:@42]);
613         id obj = [result toObject];
614         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
615         id num = (NSDictionary *)obj[@"x"];
616         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
617     }
618
619     @autoreleasepool {
620         JSContext *context = [[JSContext alloc] init];
621         JSValue *result = [context evaluateScript:@"[ ]"];
622         checkResult(@"[ ]", result.isArray);
623     }
624
625     @autoreleasepool {
626         JSContext *context = [[JSContext alloc] init];
627         JSValue *result = [context evaluateScript:@"new Date"];
628         checkResult(@"new Date", result.isDate);
629     }
630
631     @autoreleasepool {
632         JSContext *context = [[JSContext alloc] init];
633         JSValue *symbol = [context evaluateScript:@"Symbol('dope');"];
634         JSValue *notSymbol = [context evaluateScript:@"'dope'"];
635         checkResult(@"Should be a symbol value", symbol.isSymbol);
636         checkResult(@"Should not be a symbol value", !notSymbol.isSymbol);
637     }
638
639     @autoreleasepool {
640         JSContext *context = [[JSContext alloc] init];
641         JSValue *symbol = [JSValue valueWithNewSymbolFromDescription:@"dope" inContext:context];
642         checkResult(@"Should be a created from Obj-C", symbol.isSymbol);
643     }
644
645     @autoreleasepool {
646         JSContext *context = [[JSContext alloc] init];
647         JSValue *arrayIterator = [context evaluateScript:@"Array.prototype[Symbol.iterator]"];
648         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
649         JSValue *array = [JSValue valueWithNewArrayInContext:context];
650         checkResult(@"Looking up by subscript with symbol should work", [array[iteratorSymbol] isEqual:arrayIterator]);
651         checkResult(@"Looking up by method with symbol should work", [[array valueForProperty:iteratorSymbol] isEqual:arrayIterator]);
652     }
653
654     @autoreleasepool {
655         JSContext *context = [[JSContext alloc] init];
656         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
657         JSValue *object = [JSValue valueWithNewObjectInContext:context];
658         JSValue *theAnswer = [JSValue valueWithUInt32:42 inContext:context];
659         object[iteratorSymbol] = theAnswer;
660         checkResult(@"Setting by subscript with symbol should work", [object[iteratorSymbol] isEqual:theAnswer]);
661     }
662
663     @autoreleasepool {
664         JSContext *context = [[JSContext alloc] init];
665         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
666         JSValue *object = [JSValue valueWithNewObjectInContext:context];
667         JSValue *theAnswer = [JSValue valueWithUInt32:42 inContext:context];
668         [object setValue:theAnswer forProperty:iteratorSymbol];
669         checkResult(@"Setting by method with symbol should work", [object[iteratorSymbol] isEqual:theAnswer]);
670     }
671
672     @autoreleasepool {
673         JSContext *context = [[JSContext alloc] init];
674         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
675         JSValue *object = [JSValue valueWithNewObjectInContext:context];
676         JSValue *theAnswer = [JSValue valueWithUInt32:42 inContext:context];
677         object[iteratorSymbol] = theAnswer;
678         checkResult(@"has property with symbol should work", [object hasProperty:iteratorSymbol]);
679     }
680
681     @autoreleasepool {
682         JSContext *context = [[JSContext alloc] init];
683         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
684         JSValue *object = [JSValue valueWithNewObjectInContext:context];
685         JSValue *theAnswer = [JSValue valueWithUInt32:42 inContext:context];
686         checkResult(@"delete property with symbol should work without property", [object deleteProperty:iteratorSymbol]);
687         object[iteratorSymbol] = theAnswer;
688         checkResult(@"delete property with symbol should work with property", [object deleteProperty:iteratorSymbol]);
689         checkResult(@"delete should be false with non-configurable property", ![context[@"Array"] deleteProperty:@"prototype"]);
690     }
691
692     @autoreleasepool {
693         JSContext *context = [[JSContext alloc] init];
694         JSValue *object = [JSValue valueWithNewObjectInContext:context];
695         NSObject *objCObject = [[NSObject alloc] init];
696         JSValue *result = object[objCObject];
697         checkResult(@"getting a non-convertable object should return undefined", [result isUndefined]);
698         object[objCObject] = @(1);
699         result = object[objCObject];
700         checkResult(@"getting a non-convertable object should return the stored value", [result toUInt32] == 1);
701     }
702
703     @autoreleasepool {
704         JSContext *context = [[JSContext alloc] init];
705         JSValue *object = [JSValue valueWithNewObjectInContext:context];
706         JSValue *iteratorSymbol = context[@"Symbol"][@"iterator"];
707         object[@"value"] = @(1);
708         context[@"object"] = object;
709
710         object[iteratorSymbol] = ^{
711             JSValue *result = [JSValue valueWithNewObjectInContext:context];
712             result[@"object"] = [JSContext currentThis];
713             result[@"next"] = ^{
714                 JSValue *result = [JSValue valueWithNewObjectInContext:context];
715                 JSValue *value = [JSContext currentThis][@"object"][@"value"];
716                 [[JSContext currentThis][@"object"] deleteProperty:@"value"];
717                 result[@"value"] = value;
718                 result[@"done"] = [JSValue valueWithBool:[value isUndefined] inContext:context];
719                 return result;
720             };
721             return result;
722         };
723
724
725         JSValue *count = [context evaluateScript:@"let count = 0; for (let v of object) { if (v !== 1) throw new Error(); count++; } count;"];
726         checkResult(@"iterator should not throw", ![context exception]);
727         checkResult(@"iteration count should be 1", [count toUInt32] == 1);
728     }
729
730     @autoreleasepool {
731         JSCollection* myPrivateProperties = [[JSCollection alloc] init];
732
733         @autoreleasepool {
734             JSContext* context = [[JSContext alloc] init];
735             TestObject* rootObject = [TestObject testObject];
736             context[@"root"] = rootObject;
737             [context.virtualMachine addManagedReference:myPrivateProperties withOwner:rootObject];
738             [myPrivateProperties setValue:[JSValue valueWithBool:true inContext:context] forKey:@"is_ham"];
739             [myPrivateProperties setValue:[JSValue valueWithObject:@"hello!" inContext:context] forKey:@"message"];
740             [myPrivateProperties setValue:[JSValue valueWithInt32:42 inContext:context] forKey:@"my_number"];
741             [myPrivateProperties setValue:[JSValue valueWithNullInContext:context] forKey:@"definitely_null"];
742             [myPrivateProperties setValue:[JSValue valueWithUndefinedInContext:context] forKey:@"not_sure_if_undefined"];
743
744             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
745
746             JSValue *isHam = [myPrivateProperties valueForKey:@"is_ham"];
747             JSValue *message = [myPrivateProperties valueForKey:@"message"];
748             JSValue *myNumber = [myPrivateProperties valueForKey:@"my_number"];
749             JSValue *definitelyNull = [myPrivateProperties valueForKey:@"definitely_null"];
750             JSValue *notSureIfUndefined = [myPrivateProperties valueForKey:@"not_sure_if_undefined"];
751             checkResult(@"is_ham is true", isHam.isBoolean && [isHam toBool]);
752             checkResult(@"message is hello!", message.isString && [@"hello!" isEqualToString:[message toString]]);
753             checkResult(@"my_number is 42", myNumber.isNumber && [myNumber toInt32] == 42);
754             checkResult(@"definitely_null is null", definitelyNull.isNull);
755             checkResult(@"not_sure_if_undefined is undefined", notSureIfUndefined.isUndefined);
756         }
757
758         checkResult(@"is_ham is nil", ![myPrivateProperties valueForKey:@"is_ham"]);
759         checkResult(@"message is nil", ![myPrivateProperties valueForKey:@"message"]);
760         checkResult(@"my_number is 42", ![myPrivateProperties valueForKey:@"my_number"]);
761         checkResult(@"definitely_null is null", ![myPrivateProperties valueForKey:@"definitely_null"]);
762         checkResult(@"not_sure_if_undefined is undefined", ![myPrivateProperties valueForKey:@"not_sure_if_undefined"]);
763     }
764
765     @autoreleasepool {
766         JSContext *context = [[JSContext alloc] init];
767         JSValue *message = [JSValue valueWithObject:@"hello" inContext:context];
768         TestObject *rootObject = [TestObject testObject];
769         JSCollection *collection = [[JSCollection alloc] init];
770         context[@"root"] = rootObject;
771         @autoreleasepool {
772             JSValue *jsCollection = [JSValue valueWithObject:collection inContext:context];
773             JSManagedValue *weakCollection = [JSManagedValue managedValueWithValue:jsCollection andOwner:rootObject];
774             [context.virtualMachine addManagedReference:weakCollection withOwner:message];
775             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
776         }
777     }
778
779     @autoreleasepool {
780         JSContext *context = [[JSContext alloc] init];
781         __block int result;
782         context[@"blockCallback"] = ^(int value){
783             result = value;
784         };
785         [context evaluateScript:@"blockCallback(42)"];
786         checkResult(@"blockCallback", result == 42);
787     }
788
789     if (blockSignatureContainsClass()) {
790         @autoreleasepool {
791             JSContext *context = [[JSContext alloc] init];
792             __block bool result = false;
793             context[@"blockCallback"] = ^(NSString *value){
794                 result = [@"42" isEqualToString:value] == YES;
795             };
796             [context evaluateScript:@"blockCallback(42)"];
797             checkResult(@"blockCallback(NSString *)", result);
798         }
799     } else
800         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
801
802     @autoreleasepool {
803         JSContext *context = [[JSContext alloc] init];
804         checkResult(@"!context.exception", !context.exception);
805         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
806         checkResult(@"context.exception", context.exception);
807     }
808
809     @autoreleasepool {
810         JSContext *context = [[JSContext alloc] init];
811         __block bool caught = false;
812         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
813             (void)context;
814             (void)exception;
815             caught = true;
816         };
817         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
818         checkResult(@"JSContext.exceptionHandler", caught);
819     }
820
821     @autoreleasepool {
822         JSContext *context = [[JSContext alloc] init];
823         __block int expectedExceptionLineNumber = 1;
824         __block bool sawExpectedExceptionLineNumber = false;
825         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
826             sawExpectedExceptionLineNumber = [exception[@"line"] toInt32] == expectedExceptionLineNumber;
827         };
828         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
829         checkResult(@"evaluteScript exception on line 1", sawExpectedExceptionLineNumber);
830
831         expectedExceptionLineNumber = 2;
832         sawExpectedExceptionLineNumber = false;
833         [context evaluateScript:@"// Line 1\n!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
834         checkResult(@"evaluteScript exception on line 2", sawExpectedExceptionLineNumber);
835     }
836
837     @autoreleasepool {
838         JSContext *context = [[JSContext alloc] init];
839         __block bool emptyExceptionSourceURL = false;
840         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
841             emptyExceptionSourceURL = exception[@"sourceURL"].isUndefined;
842         };
843         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
844         checkResult(@"evaluteScript: exception has no sourceURL", emptyExceptionSourceURL);
845
846         __block NSString *exceptionSourceURL = nil;
847         context.exceptionHandler = ^(JSContext *, JSValue *exception) {
848             exceptionSourceURL = [exception[@"sourceURL"] toString];
849         };
850         NSURL *url = [NSURL fileURLWithPath:@"/foo/bar.js" isDirectory:NO];
851         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()" withSourceURL:url];
852         checkResult(@"evaluateScript:withSourceURL: exception has expected sourceURL", [exceptionSourceURL isEqualToString:[url absoluteString]]);
853     }
854
855     @autoreleasepool {
856         JSContext *context = [[JSContext alloc] init];
857         context[@"callback"] = ^{
858             JSContext *context = [JSContext currentContext];
859             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
860         };
861         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
862         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
863         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
864     }
865
866     @autoreleasepool {
867         JSContext *context = [[JSContext alloc] init];
868         context[@"callback"] = ^{
869             JSContext *context = [JSContext currentContext];
870             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
871         };
872         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
873         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
874         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
875     }
876
877     @autoreleasepool {
878         JSContext *context = [[JSContext alloc] init];
879         [context evaluateScript:
880             @"function sum(array) { \
881                 var result = 0; \
882                 for (var i in array) \
883                     result += array[i]; \
884                 return result; \
885             }"];
886         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
887         JSValue *sumFunction = context[@"sum"];
888         JSValue *result = [sumFunction callWithArguments:@[ array ]];
889         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
890     }
891
892     @autoreleasepool {
893         JSContext *context = [[JSContext alloc] init];
894         JSValue *mulAddFunction = [context evaluateScript:
895             @"(function(array, object) { \
896                 var result = []; \
897                 for (var i in array) \
898                     result.push(array[i] * object.x + object.y); \
899                 return result; \
900             })"];
901         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
902         checkResult(@"mulAddFunction", result.isObject && [[result toString] isEqual:@"43,44,46"]);
903     }
904
905     @autoreleasepool {
906         JSContext *context = [[JSContext alloc] init];        
907         JSValue *array = [JSValue valueWithNewArrayInContext:context];
908         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
909         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
910         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
911         NSUInteger lowIndex = 5;
912         NSUInteger maxLength = UINT_MAX;
913
914         [array setValue:value1 atIndex:lowIndex];
915         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
916
917         [array setValue:value1 atIndex:(maxLength - 1)];
918         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
919
920         [array setValue:value2 atIndex:maxLength];
921         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
922
923         [array setValue:value2 atIndex:(maxLength + 1)];
924         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
925
926         if (sizeof(NSUInteger) == 8)
927             checkResult(@"valueAtIndex:0 is undefined", [array valueAtIndex:0].isUndefined);
928         else
929             checkResult(@"valueAtIndex:0", [[array valueAtIndex:0] toInt32] == 24);
930         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
931         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
932         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
933         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
934     }
935
936     @autoreleasepool {
937         JSContext *context = [[JSContext alloc] init];
938         JSValue *object = [JSValue valueWithNewObjectInContext:context];
939
940         object[@"point"] = @{ @"x":@1, @"y":@2 };
941         object[@"point"][@"x"] = @3;
942         CGPoint point = [object[@"point"] toPoint];
943         checkResult(@"toPoint", point.x == 3 && point.y == 2);
944
945         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
946         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
947
948         object[[@"foobar" substringToIndex:3]] = @"bar";
949         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
950     }
951
952     @autoreleasepool {
953         JSContext *context = [[JSContext alloc] init];
954         TextXYZ *testXYZ = [[TextXYZ alloc] init];
955         context[@"testXYZ"] = testXYZ;
956         testXYZ.x = 3;
957         testXYZ.y = 4;
958         testXYZ.z = 5;
959         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
960         [context evaluateScript:@"testXYZ.test('test')"];
961         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
962         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
963         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
964     }
965
966     @autoreleasepool {
967         JSContext *context = [[JSContext alloc] init];
968         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
969             JSPropertyDescriptorGetKey:^{
970                 return [JSContext currentThis][@"x"];
971             }
972         }];
973         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
974         int result = [object [@"getterProperty"] toInt32];
975         checkResult(@"getterProperty", result == 101);
976     }
977
978     @autoreleasepool {
979         JSContext *context = [[JSContext alloc] init];
980         context[@"concatenate"] = ^{
981             NSArray *arguments = [JSContext currentArguments];
982             if (![arguments count])
983                 return @"";
984             NSString *message = [arguments[0] description];
985             for (NSUInteger index = 1; index < [arguments count]; ++index)
986                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
987             return message;
988         };
989         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
990         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
991     }
992
993     @autoreleasepool {
994         JSContext *context = [[JSContext alloc] init];
995         context[@"foo"] = @YES;
996         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
997         JSValue *result = [context evaluateScript:@"typeof foo"];
998         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
999     }
1000
1001     @autoreleasepool {
1002         JSContext *context = [[JSContext alloc] init];
1003         JSValue *result = [context evaluateScript:@"String(console)"];
1004         checkResult(@"String(console)", [result isEqualToObject:@"[object Console]"]);
1005         result = [context evaluateScript:@"typeof console.log"];
1006         checkResult(@"typeof console.log", [result isEqualToObject:@"function"]);
1007     }
1008
1009     @autoreleasepool {
1010         JSContext *context = [[JSContext alloc] init];
1011         TestObject* testObject = [TestObject testObject];
1012         context[@"testObject"] = testObject;
1013         JSValue *result = [context evaluateScript:@"String(testObject)"];
1014         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
1015     }
1016
1017     @autoreleasepool {
1018         JSContext *context = [[JSContext alloc] init];
1019         TestObject* testObject = [TestObject testObject];
1020         context[@"testObject"] = testObject;
1021         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
1022         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
1023     }
1024
1025     @autoreleasepool {
1026         JSContext *context = [[JSContext alloc] init];
1027         context[@"TestObject"] = [TestObject class];
1028         JSValue *result = [context evaluateScript:@"String(TestObject)"];
1029         checkResult(@"String(TestObject)", [result isEqualToObject:@"function TestObject() {\n    [native code]\n}"]);
1030     }
1031
1032     @autoreleasepool {
1033         JSContext *context = [[JSContext alloc] init];
1034         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
1035         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
1036     }
1037
1038     @autoreleasepool {
1039         JSContext *context = [[JSContext alloc] init];
1040         context[@"TestObject"] = [TestObject class];
1041         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
1042         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
1043     }
1044
1045     @autoreleasepool {
1046         JSContext *context = [[JSContext alloc] init];
1047         TestObject* testObject = [TestObject testObject];
1048         context[@"testObjectA"] = testObject;
1049         context[@"testObjectB"] = testObject;
1050         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
1051         checkResult(@"testObjectA == testObjectB", result.isBoolean && [result toBool]);
1052     }
1053
1054     @autoreleasepool {
1055         JSContext *context = [[JSContext alloc] init];
1056         TestObject* testObject = [TestObject testObject];
1057         context[@"testObject"] = testObject;
1058         testObject.point = (CGPoint){3,4};
1059         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
1060         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
1061         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
1062     }
1063
1064     @autoreleasepool {
1065         JSContext *context = [[JSContext alloc] init];
1066         TestObject* testObject = [TestObject testObject];
1067         testObject.six = 6;
1068         context[@"testObject"] = testObject;
1069         context[@"mul"] = ^(int x, int y){ return x * y; };
1070         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
1071         checkResult(@"mul(testObject.six, 7)", result.isNumber && [result toInt32] == 42);
1072     }
1073
1074     @autoreleasepool {
1075         JSContext *context = [[JSContext alloc] init];
1076         TestObject* testObject = [TestObject testObject];
1077         context[@"testObject"] = testObject;
1078         context[@"testObject"][@"variable"] = @4;
1079         [context evaluateScript:@"++testObject.variable"];
1080         checkResult(@"++testObject.variable", testObject.variable == 5);
1081     }
1082
1083     @autoreleasepool {
1084         JSContext *context = [[JSContext alloc] init];
1085         context[@"point"] = @{ @"x":@6, @"y":@7 };
1086         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
1087         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
1088     }
1089
1090     @autoreleasepool {
1091         JSContext *context = [[JSContext alloc] init];
1092         context[@"point"] = @{ @"x":@6, @"y":@7 };
1093         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
1094         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
1095     }
1096
1097     @autoreleasepool {
1098         JSContext *context = [[JSContext alloc] init];
1099         TestObject* testObject = [TestObject testObject];
1100         context[@"testObject"] = testObject;
1101         JSValue *result = [context evaluateScript:@"testObject.getString()"];
1102         checkResult(@"testObject.getString()", result.isString && [result toInt32] == 42);
1103     }
1104
1105     @autoreleasepool {
1106         JSContext *context = [[JSContext alloc] init];
1107         TestObject* testObject = [TestObject testObject];
1108         context[@"testObject"] = testObject;
1109         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
1110         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
1111     }
1112
1113     @autoreleasepool {
1114         JSContext *context = [[JSContext alloc] init];
1115         TestObject* testObject = [TestObject testObject];
1116         context[@"testObject"] = testObject;
1117         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
1118         checkResult(@"testObject.getString.call(testObject)", result.isString && [result toInt32] == 42);
1119     }
1120
1121     @autoreleasepool {
1122         JSContext *context = [[JSContext alloc] init];
1123         TestObject* testObject = [TestObject testObject];
1124         context[@"testObject"] = testObject;
1125         checkResult(@"testObject.getString.call({}) pre", !context.exception);
1126         [context evaluateScript:@"testObject.getString.call({})"];
1127         checkResult(@"testObject.getString.call({}) post", context.exception);
1128     }
1129
1130     @autoreleasepool {
1131         JSContext *context = [[JSContext alloc] init];
1132         TestObject* testObject = [TestObject testObject];
1133         context[@"testObject"] = testObject;
1134         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
1135         checkResult(@"testObject.callback", result.isNumber && [result toInt32] == 42);
1136         result = [context evaluateScript:@"testObject.bogusCallback"];
1137         checkResult(@"testObject.bogusCallback == undefined", result.isUndefined);
1138     }
1139
1140     @autoreleasepool {
1141         JSContext *context = [[JSContext alloc] init];
1142         TestObject *testObject = [TestObject testObject];
1143         context[@"testObject"] = testObject;
1144         JSValue *result = [context evaluateScript:@"Function.prototype.toString.call(testObject.callback)"];
1145         checkResult(@"Function.prototype.toString", !context.exception && !result.isUndefined);
1146     }
1147
1148     @autoreleasepool {
1149         JSContext *context1 = [[JSContext alloc] init];
1150         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
1151         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
1152         context1[@"passValueBetweenContexts"] = value;
1153         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
1154         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
1155     }
1156
1157     @autoreleasepool {
1158         JSContext *context = [[JSContext alloc] init];
1159         context[@"handleTheDictionary"] = ^(NSDictionary *dict) {
1160             NSDictionary *expectedDict = @{
1161                 @"foo" : [NSNumber numberWithInt:1],
1162                 @"bar" : @{
1163                     @"baz": [NSNumber numberWithInt:2]
1164                 }
1165             };
1166             checkResult(@"recursively convert nested dictionaries", [dict isEqualToDictionary:expectedDict]);
1167         };
1168         [context evaluateScript:@"var myDict = { \
1169             'foo': 1, \
1170             'bar': {'baz': 2} \
1171         }; \
1172         handleTheDictionary(myDict);"];
1173
1174         context[@"handleTheArray"] = ^(NSArray *array) {
1175             NSArray *expectedArray = @[@"foo", @"bar", @[@"baz"]];
1176             checkResult(@"recursively convert nested arrays", [array isEqualToArray:expectedArray]);
1177         };
1178         [context evaluateScript:@"var myArray = ['foo', 'bar', ['baz']]; handleTheArray(myArray);"];
1179     }
1180
1181     @autoreleasepool {
1182         JSContext *context = [[JSContext alloc] init];
1183         TestObject *testObject = [TestObject testObject];
1184         @autoreleasepool {
1185             context[@"testObject"] = testObject;
1186             [context evaluateScript:@"var constructor = Object.getPrototypeOf(testObject).constructor; constructor.prototype = undefined;"];
1187             [context evaluateScript:@"testObject = undefined"];
1188         }
1189         
1190         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1191
1192         @autoreleasepool {
1193             context[@"testObject"] = testObject;
1194         }
1195     }
1196
1197     @autoreleasepool {
1198         JSContext *context = [[JSContext alloc] init];
1199         TextXYZ *testXYZ = [[TextXYZ alloc] init];
1200
1201         @autoreleasepool {
1202             context[@"testXYZ"] = testXYZ;
1203
1204             [context evaluateScript:@" \
1205                 didClick = false; \
1206                 testXYZ.onclick = function() { \
1207                     didClick = true; \
1208                 }; \
1209                  \
1210                 testXYZ.weakOnclick = function() { \
1211                     return 'foo'; \
1212                 }; \
1213             "];
1214         }
1215
1216         @autoreleasepool {
1217             [testXYZ click];
1218             JSValue *result = [context evaluateScript:@"didClick"];
1219             checkResult(@"Event handler onclick", [result toBool]);
1220         }
1221
1222         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1223
1224         @autoreleasepool {
1225             JSValue *result = [context evaluateScript:@"testXYZ.onclick"];
1226             checkResult(@"onclick still around after GC", !(result.isNull || result.isUndefined));
1227         }
1228
1229
1230         @autoreleasepool {
1231             JSValue *result = [context evaluateScript:@"testXYZ.weakOnclick"];
1232             checkResult(@"weakOnclick not around after GC", result.isNull || result.isUndefined);
1233         }
1234
1235         @autoreleasepool {
1236             [context evaluateScript:@" \
1237                 didClick = false; \
1238                 testXYZ = null; \
1239             "];
1240         }
1241
1242         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1243
1244         @autoreleasepool {
1245             [testXYZ click];
1246             JSValue *result = [context evaluateScript:@"didClick"];
1247             checkResult(@"Event handler onclick doesn't fire", ![result toBool]);
1248         }
1249     }
1250
1251     @autoreleasepool {
1252         JSContext *context = [[JSContext alloc] init];
1253         TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1254         TinyDOMNode *lastNode = root;
1255         for (NSUInteger i = 0; i < 3; i++) {
1256             TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1257             [lastNode appendChild:newNode];
1258             lastNode = newNode;
1259         }
1260
1261         @autoreleasepool {
1262             context[@"root"] = root;
1263             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
1264                 TinyDOMNode *lastNode = nil;
1265                 while (head) {
1266                     lastNode = head;
1267                     head = [lastNode childAtIndex:0];
1268                 }
1269                 return lastNode;
1270             };
1271             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
1272         }
1273
1274         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1275
1276         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
1277         checkResult(@"My custom property == 42", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
1278     }
1279
1280     @autoreleasepool {
1281         JSContext *context = [[JSContext alloc] init];
1282         TinyDOMNode *root = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1283         TinyDOMNode *lastNode = root;
1284         for (NSUInteger i = 0; i < 3; i++) {
1285             TinyDOMNode *newNode = [[TinyDOMNode alloc] initWithVirtualMachine:context.virtualMachine];
1286             [lastNode appendChild:newNode];
1287             lastNode = newNode;
1288         }
1289
1290         @autoreleasepool {
1291             context[@"root"] = root;
1292             context[@"getLastNodeInChain"] = ^(TinyDOMNode *head){
1293                 TinyDOMNode *lastNode = nil;
1294                 while (head) {
1295                     lastNode = head;
1296                     head = [lastNode childAtIndex:0];
1297                 }
1298                 return lastNode;
1299             };
1300             [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty = 42;"];
1301
1302             [root appendChild:[root childAtIndex:0]];
1303             [root removeChildAtIndex:0];
1304         }
1305
1306         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1307
1308         JSValue *myCustomProperty = [context evaluateScript:@"getLastNodeInChain(root).myCustomProperty"];
1309         checkResult(@"duplicate calls to addManagedReference don't cause things to die", myCustomProperty.isNumber && [myCustomProperty toInt32] == 42);
1310     }
1311
1312     @autoreleasepool {
1313         JSContext *context = [[JSContext alloc] init];
1314         JSValue *o = [JSValue valueWithNewObjectInContext:context];
1315         o[@"foo"] = @"foo";
1316         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1317
1318         checkResult(@"JSValue correctly protected its internal value", [[o[@"foo"] toString] isEqualToString:@"foo"]);
1319     }
1320
1321     @autoreleasepool {
1322         JSContext *context = [[JSContext alloc] init];
1323         TestObject *testObject = [TestObject testObject];
1324         context[@"testObject"] = testObject;
1325         [context evaluateScript:@"testObject.__lookupGetter__('variable').call({})"];
1326         checkResult(@"Make sure we throw an exception when calling getter on incorrect |this|", context.exception);
1327     }
1328
1329     @autoreleasepool {
1330         static const unsigned count = 100;
1331         NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
1332         JSContext *context = [[JSContext alloc] init];
1333         @autoreleasepool {
1334             for (unsigned i = 0; i < count; ++i) {
1335                 JSValue *object = [JSValue valueWithNewObjectInContext:context];
1336                 JSManagedValue *managedObject = [JSManagedValue managedValueWithValue:object];
1337                 [array addObject:managedObject];
1338             }
1339         }
1340         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1341         for (unsigned i = 0; i < count; ++i)
1342             [context.virtualMachine addManagedReference:array[i] withOwner:array];
1343     }
1344
1345     @autoreleasepool {
1346         TestObject *testObject = [TestObject testObject];
1347         JSManagedValue *managedTestObject;
1348         @autoreleasepool {
1349             JSContext *context = [[JSContext alloc] init];
1350             context[@"testObject"] = testObject;
1351             managedTestObject = [JSManagedValue managedValueWithValue:context[@"testObject"]];
1352             [context.virtualMachine addManagedReference:managedTestObject withOwner:testObject];
1353         }
1354     }
1355
1356     @autoreleasepool {
1357         JSContext *context = [[JSContext alloc] init];
1358         TestObject *testObject = [TestObject testObject];
1359         context[@"testObject"] = testObject;
1360         JSManagedValue *managedValue = nil;
1361         @autoreleasepool {
1362             JSValue *object = [JSValue valueWithNewObjectInContext:context];
1363             managedValue = [JSManagedValue managedValueWithValue:object andOwner:testObject];
1364             [context.virtualMachine addManagedReference:managedValue withOwner:testObject];
1365         }
1366         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1367     }
1368
1369     @autoreleasepool {
1370         JSContext *context = [[JSContext alloc] init];
1371         context[@"MyClass"] = ^{
1372             JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1373             JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1374             JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1375             JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyClass"][@"prototype"] JSValueRef]);
1376             return newThis;
1377         };
1378
1379         context[@"MyOtherClass"] = ^{
1380             JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1381             JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1382             JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1383             JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentContext][@"MyOtherClass"][@"prototype"] JSValueRef]);
1384             return newThis;
1385         };
1386
1387         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
1388             NSLog(@"EXCEPTION: %@", [exception toString]);
1389             context.exception = nil;
1390         };
1391
1392         JSValue *constructor1 = context[@"MyClass"];
1393         JSValue *constructor2 = context[@"MyOtherClass"];
1394
1395         JSValue *value1 = [context evaluateScript:@"new MyClass()"];
1396         checkResult(@"value1 instanceof MyClass", [value1 isInstanceOf:constructor1]);
1397         checkResult(@"!(value1 instanceof MyOtherClass)", ![value1 isInstanceOf:constructor2]);
1398         checkResult(@"MyClass.prototype.constructor === MyClass", [[context evaluateScript:@"MyClass.prototype.constructor === MyClass"] toBool]);
1399         checkResult(@"MyClass instanceof Function", [[context evaluateScript:@"MyClass instanceof Function"] toBool]);
1400
1401         JSValue *value2 = [context evaluateScript:@"new MyOtherClass()"];
1402         checkResult(@"value2 instanceof MyOtherClass", [value2 isInstanceOf:constructor2]);
1403         checkResult(@"!(value2 instanceof MyClass)", ![value2 isInstanceOf:constructor1]);
1404         checkResult(@"MyOtherClass.prototype.constructor === MyOtherClass", [[context evaluateScript:@"MyOtherClass.prototype.constructor === MyOtherClass"] toBool]);
1405         checkResult(@"MyOtherClass instanceof Function", [[context evaluateScript:@"MyOtherClass instanceof Function"] toBool]);
1406     }
1407
1408     @autoreleasepool {
1409         JSContext *context = [[JSContext alloc] init];
1410         context[@"MyClass"] = ^{
1411             NSLog(@"I'm intentionally not returning anything.");
1412         };
1413         JSValue *result = [context evaluateScript:@"new MyClass()"];
1414         checkResult(@"result === undefined", result.isUndefined);
1415         checkResult(@"exception.message is correct'", context.exception 
1416             && [@"Objective-C blocks called as constructors must return an object." isEqualToString:[context.exception[@"message"] toString]]);
1417     }
1418
1419     @autoreleasepool {
1420         checkResult(@"[JSContext currentThis] == nil outside of callback", ![JSContext currentThis]);
1421         checkResult(@"[JSContext currentArguments] == nil outside of callback", ![JSContext currentArguments]);
1422         if ([JSContext currentCallee])
1423             checkResult(@"[JSContext currentCallee] == nil outside of callback", ![JSContext currentCallee]);
1424     }
1425
1426     if ([JSContext currentCallee]) {
1427         @autoreleasepool {
1428             JSContext *context = [[JSContext alloc] init];
1429             context[@"testFunction"] = ^{
1430                 checkResult(@"testFunction.foo === 42", [[JSContext currentCallee][@"foo"] toInt32] == 42);
1431             };
1432             context[@"testFunction"][@"foo"] = @42;
1433             [context[@"testFunction"] callWithArguments:nil];
1434
1435             context[@"TestConstructor"] = ^{
1436                 JSValue *newThis = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
1437                 JSGlobalContextRef contextRef = [[JSContext currentContext] JSGlobalContextRef];
1438                 JSObjectRef newThisRef = JSValueToObject(contextRef, [newThis JSValueRef], NULL);
1439                 JSObjectSetPrototype(contextRef, newThisRef, [[JSContext currentCallee][@"prototype"] JSValueRef]);
1440                 return newThis;
1441             };
1442             checkResult(@"(new TestConstructor) instanceof TestConstructor", [context evaluateScript:@"(new TestConstructor) instanceof TestConstructor"]);
1443         }
1444     }
1445
1446     @autoreleasepool {
1447         JSContext *context = [[JSContext alloc] init];
1448         context[@"TestObject"] = [TestObject class];
1449         JSValue *testObject = [context evaluateScript:@"(new TestObject())"];
1450         checkResult(@"testObject instanceof TestObject", [testObject isInstanceOf:context[@"TestObject"]]);
1451
1452         context[@"TextXYZ"] = [TextXYZ class];
1453         JSValue *textObject = [context evaluateScript:@"(new TextXYZ(\"Called TextXYZ constructor!\"))"];
1454         checkResult(@"textObject instanceof TextXYZ", [textObject isInstanceOf:context[@"TextXYZ"]]);
1455     }
1456
1457     @autoreleasepool {
1458         JSContext *context = [[JSContext alloc] init];
1459         context[@"ClassA"] = [ClassA class];
1460         context[@"ClassB"] = [ClassB class];
1461         context[@"ClassC"] = [ClassC class]; // Should print error message about too many inits found.
1462         context[@"ClassCPrime"] = [ClassCPrime class]; // Ditto.
1463
1464         JSValue *a = [context evaluateScript:@"(new ClassA(42))"];
1465         checkResult(@"a instanceof ClassA", [a isInstanceOf:context[@"ClassA"]]);
1466         checkResult(@"a.initialize() is callable", [[a invokeMethod:@"initialize" withArguments:@[]] toInt32] == 42);
1467
1468         JSValue *b = [context evaluateScript:@"(new ClassB(42, 53))"];
1469         checkResult(@"b instanceof ClassB", [b isInstanceOf:context[@"ClassB"]]);
1470
1471         JSValue *canConstructClassC = [context evaluateScript:@"(function() { \
1472             try { \
1473                 (new ClassC(1, 2)); \
1474                 return true; \
1475             } catch(e) { \
1476                 return false; \
1477             } \
1478         })()"];
1479         checkResult(@"shouldn't be able to construct ClassC", ![canConstructClassC toBool]);
1480
1481         JSValue *canConstructClassCPrime = [context evaluateScript:@"(function() { \
1482             try { \
1483                 (new ClassCPrime(1)); \
1484                 return true; \
1485             } catch(e) { \
1486                 return false; \
1487             } \
1488         })()"];
1489         checkResult(@"shouldn't be able to construct ClassCPrime", ![canConstructClassCPrime toBool]);
1490     }
1491
1492     @autoreleasepool {
1493         JSContext *context = [[JSContext alloc] init];
1494         context[@"ClassA"] = [ClassA class];
1495         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
1496             NSLog(@"%@", [exception toString]);
1497             context.exception = exception;
1498         };
1499
1500         checkResult(@"ObjC Constructor without 'new' pre", !context.exception);
1501         [context evaluateScript:@"ClassA(42)"];
1502         checkResult(@"ObjC Constructor without 'new' post", context.exception);
1503     }
1504
1505     @autoreleasepool {
1506         JSContext *context = [[JSContext alloc] init];
1507         context[@"ClassD"] = [ClassD class];
1508         context[@"ClassE"] = [ClassE class];
1509        
1510         JSValue *d = [context evaluateScript:@"(new ClassD())"];
1511         checkResult(@"Returning instance of ClassE from ClassD's init has correct class", [d isInstanceOf:context[@"ClassE"]]);
1512     }
1513
1514     @autoreleasepool {
1515         JSContext *context = [[JSContext alloc] init];
1516         while (!evilAllocationObjectWasDealloced) {
1517             @autoreleasepool {
1518                 EvilAllocationObject *evilObject = [[EvilAllocationObject alloc] initWithContext:context];
1519                 context[@"evilObject"] = evilObject;
1520                 context[@"evilObject"] = nil;
1521             }
1522         }
1523         checkResult(@"EvilAllocationObject was successfully dealloced without crashing", evilAllocationObjectWasDealloced);
1524     }
1525
1526     @autoreleasepool {
1527         JSContext *context = [[JSContext alloc] init];
1528         checkResult(@"default context.name is nil", context.name == nil);
1529         NSString *name1 = @"Name1";
1530         NSString *name2 = @"Name2";
1531         context.name = name1;
1532         NSString *fetchedName1 = context.name;
1533         context.name = name2;
1534         NSString *fetchedName2 = context.name;
1535         context.name = nil;
1536         NSString *fetchedName3 = context.name;
1537         checkResult(@"fetched context.name was expected", [fetchedName1 isEqualToString:name1]);
1538         checkResult(@"fetched context.name was expected", [fetchedName2 isEqualToString:name2]);
1539         checkResult(@"fetched context.name was expected", ![fetchedName1 isEqualToString:fetchedName2]);
1540         checkResult(@"fetched context.name was expected", fetchedName3 == nil);
1541     }
1542
1543     @autoreleasepool {
1544         JSContext *context = [[JSContext alloc] init];
1545         context[@"UnexportedObject"] = [UnexportedObject class];
1546         context[@"makeObject"] = ^{
1547             return [[UnexportedObject alloc] init];
1548         };
1549         JSValue *result = [context evaluateScript:@"(makeObject() instanceof UnexportedObject)"];
1550         checkResult(@"makeObject() instanceof UnexportedObject", result.isBoolean && [result toBool]);
1551     }
1552
1553     @autoreleasepool {
1554         JSContext *context = [[JSContext alloc] init];
1555         [[JSValue valueWithInt32:42 inContext:context] toDictionary];
1556         [[JSValue valueWithInt32:42 inContext:context] toArray];
1557     }
1558
1559     @autoreleasepool {
1560         JSContext *context = [[JSContext alloc] init];
1561
1562         // Create the root, make it reachable from JS, and force an EdenCollection
1563         // so that we scan the external object graph.
1564         TestObject *root = [TestObject testObject];
1565         @autoreleasepool {
1566             context[@"root"] = root;
1567         }
1568         JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
1569
1570         // Create a new Obj-C object only reachable via the external object graph
1571         // through the object we already scanned during the EdenCollection.
1572         TestObject *child = [TestObject testObject];
1573         [context.virtualMachine addManagedReference:child withOwner:root];
1574
1575         // Create a new managed JSValue that will only be kept alive if we properly rescan
1576         // the external object graph.
1577         JSManagedValue *managedJSObject = nil;
1578         @autoreleasepool {
1579             JSValue *jsObject = [JSValue valueWithObject:@"hello" inContext:context];
1580             managedJSObject = [JSManagedValue managedValueWithValue:jsObject];
1581             [context.virtualMachine addManagedReference:managedJSObject withOwner:child];
1582         }
1583
1584         // Force another EdenCollection. It should rescan the new part of the external object graph.
1585         JSSynchronousEdenCollectForDebugging([context JSGlobalContextRef]);
1586         
1587         // Check that the managed JSValue is still alive.
1588         checkResult(@"EdenCollection doesn't reclaim new managed values", [managedJSObject value] != nil);
1589     }
1590
1591     @autoreleasepool {
1592         JSContext *context = [[JSContext alloc] init];
1593         
1594         pthread_t threadID;
1595         pthread_create(&threadID, NULL, &threadMain, (__bridge void*)context);
1596         pthread_join(threadID, nullptr);
1597         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
1598
1599         checkResult(@"Did not crash after entering the VM from another thread", true);
1600     }
1601     
1602     @autoreleasepool {
1603         std::vector<pthread_t> threads;
1604         bool ok = true;
1605         for (unsigned i = 0; i < 5; ++i) {
1606             pthread_t threadID;
1607             pthread_create(&threadID, nullptr, multiVMThreadMain, &ok);
1608             threads.push_back(threadID);
1609         }
1610
1611         for (pthread_t thread : threads)
1612             pthread_join(thread, nullptr);
1613
1614         checkResult(@"Ran code in five concurrent VMs that GC'd", ok);
1615     }
1616
1617     currentThisInsideBlockGetterTest();
1618     runDateTests();
1619     runJSExportTests();
1620     runJSWrapperMapTests();
1621     runRegress141275();
1622     runRegress141809();
1623 }
1624
1625 @protocol NumberProtocol <JSExport>
1626
1627 @property (nonatomic) NSInteger number;
1628
1629 @end
1630
1631 @interface NumberObject : NSObject <NumberProtocol>
1632
1633 @property (nonatomic) NSInteger number;
1634
1635 @end
1636
1637 @implementation NumberObject
1638
1639 @end
1640
1641 // Check that negative NSIntegers retain the correct value when passed into JS code.
1642 static void checkNegativeNSIntegers()
1643 {
1644     NumberObject *container = [[NumberObject alloc] init];
1645     container.number = -1;
1646     JSContext *context = [[JSContext alloc] init];
1647     context[@"container"] = container;
1648     NSString *jsID = @"var getContainerNumber = function() { return container.number }";
1649     [context evaluateScript:jsID];
1650     JSValue *jsFunction = context[@"getContainerNumber"];
1651     JSValue *result = [jsFunction callWithArguments:@[]];
1652     
1653     checkResult(@"Negative number maintained its original value", [[result toString] isEqualToString:@"-1"]);
1654 }
1655
1656 enum class Resolution {
1657     ResolveEager,
1658     RejectEager,
1659     ResolveLate,
1660     RejectLate,
1661 };
1662
1663 static void promiseWithExecutor(Resolution resolution)
1664 {
1665     @autoreleasepool {
1666         JSContext *context = [[JSContext alloc] init];
1667
1668         __block JSValue *resolveCallback;
1669         __block JSValue *rejectCallback;
1670         JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *reject) {
1671             if (resolution == Resolution::ResolveEager)
1672                 [resolve callWithArguments:@[@YES]];
1673             if (resolution == Resolution::RejectEager)
1674                 [reject callWithArguments:@[@YES]];
1675             resolveCallback = resolve;
1676             rejectCallback = reject;
1677         }];
1678
1679         __block bool valueWasResolvedTrue = false;
1680         __block bool valueWasRejectedTrue = false;
1681         [promise invokeMethod:@"then" withArguments:@[
1682             ^(JSValue *value) { valueWasResolvedTrue = [value isBoolean] && [value toBool]; },
1683             ^(JSValue *value) { valueWasRejectedTrue = [value isBoolean] && [value toBool]; },
1684         ]];
1685
1686         switch (resolution) {
1687         case Resolution::ResolveEager:
1688             checkResult(@"ResolveEager should have set resolve early.", valueWasResolvedTrue && !valueWasRejectedTrue);
1689             break;
1690         case Resolution::RejectEager:
1691             checkResult(@"RejectEager should have set reject early.", !valueWasResolvedTrue && valueWasRejectedTrue);
1692             break;
1693         default:
1694             checkResult(@"Resolve/RejectLate should have not have set anything early.", !valueWasResolvedTrue && !valueWasRejectedTrue);
1695             break;
1696         }
1697
1698         valueWasResolvedTrue = false;
1699         valueWasRejectedTrue = false;
1700
1701         // Run script to make sure reactions don't happen again
1702         [context evaluateScript:@"{ };"];
1703
1704         if (resolution == Resolution::ResolveLate)
1705             [resolveCallback callWithArguments:@[@YES]];
1706         if (resolution == Resolution::RejectLate)
1707             [rejectCallback callWithArguments:@[@YES]];
1708
1709         switch (resolution) {
1710         case Resolution::ResolveLate:
1711             checkResult(@"ResolveLate should have set resolve late.", valueWasResolvedTrue && !valueWasRejectedTrue);
1712             break;
1713         case Resolution::RejectLate:
1714             checkResult(@"RejectLate should have set reject late.", !valueWasResolvedTrue && valueWasRejectedTrue);
1715             break;
1716         default:
1717             checkResult(@"Resolve/RejectEarly should have not have set anything late.", !valueWasResolvedTrue && !valueWasRejectedTrue);
1718             break;
1719         }
1720     }
1721 }
1722
1723 static void promiseRejectOnJSException()
1724 {
1725     @autoreleasepool {
1726         JSContext *context = [[JSContext alloc] init];
1727
1728         JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *) {
1729             context.exception = [JSValue valueWithNewErrorFromMessage:@"dope" inContext:context];
1730         }];
1731         checkResult(@"Exception set in callback should not propagate", !context.exception);
1732
1733         __block bool reasonWasObject = false;
1734         [promise invokeMethod:@"catch" withArguments:@[^(JSValue *reason) { reasonWasObject = [reason isObject]; }]];
1735
1736         checkResult(@"Setting an exception in executor causes the promise to be rejected", reasonWasObject);
1737
1738         promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *) {
1739             [context evaluateScript:@"throw new Error('dope');"];
1740         }];
1741         checkResult(@"Exception thrown in callback should not propagate", !context.exception);
1742
1743         reasonWasObject = false;
1744         [promise invokeMethod:@"catch" withArguments:@[^(JSValue *reason) { reasonWasObject = [reason isObject]; }]];
1745
1746         checkResult(@"Running code that throws an exception in the executor causes the promise to be rejected", reasonWasObject);
1747     }
1748 }
1749
1750 static void promiseCreateResolved()
1751 {
1752     @autoreleasepool {
1753         JSContext *context = [[JSContext alloc] init];
1754
1755         JSValue *promise = [JSValue valueWithNewPromiseResolvedWithResult:[NSNull null] inContext:context];
1756         __block bool calledWithNull = false;
1757         [promise invokeMethod:@"then" withArguments:@[
1758             ^(JSValue *result) { calledWithNull = [result isNull]; }
1759         ]];
1760
1761         checkResult(@"ResolvedPromise should actually resolve the promise", calledWithNull);
1762     }
1763 }
1764
1765 static void promiseCreateRejected()
1766 {
1767     @autoreleasepool {
1768         JSContext *context = [[JSContext alloc] init];
1769
1770         JSValue *promise = [JSValue valueWithNewPromiseRejectedWithReason:[NSNull null] inContext:context];
1771         __block bool calledWithNull = false;
1772         [promise invokeMethod:@"then" withArguments:@[
1773             [NSNull null],
1774             ^(JSValue *result) { calledWithNull = [result isNull]; }
1775         ]];
1776
1777         checkResult(@"RejectedPromise should actually reject the promise", calledWithNull);
1778     }
1779 }
1780
1781 static void parallelPromiseResolveTest()
1782 {
1783     @autoreleasepool {
1784         JSContext *context = [[JSContext alloc] init];
1785
1786         __block RefPtr<Thread> thread;
1787
1788         Atomic<bool> shouldResolveSoon { false };
1789         Atomic<bool> startedThread { false };
1790         auto* shouldResolveSoonPtr = &shouldResolveSoon;
1791         auto* startedThreadPtr = &startedThread;
1792
1793         JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *) {
1794             thread = Thread::create("async thread", ^() {
1795                 startedThreadPtr->store(true);
1796                 while (!shouldResolveSoonPtr->load()) { }
1797                 [resolve callWithArguments:@[[NSNull null]]];
1798             });
1799
1800         }];
1801
1802         shouldResolveSoon.store(true);
1803         while (!startedThread.load())
1804             [context evaluateScript:@"for (let i = 0; i < 10000; i++) { }"];
1805
1806         thread->waitForCompletion();
1807
1808         __block bool calledWithNull = false;
1809         [promise invokeMethod:@"then" withArguments:@[
1810             ^(JSValue *result) { calledWithNull = [result isNull]; }
1811         ]];
1812
1813         checkResult(@"Promise should be resolved", calledWithNull);
1814     }
1815 }
1816
1817 typedef JSValue *(^ResolveBlock)(JSContext *, JSValue *, JSScript *);
1818 typedef void (^FetchBlock)(JSContext *, JSValue *, JSValue *, JSValue *);
1819
1820 @interface JSContextFetchDelegate : JSContext <JSModuleLoaderDelegate>
1821
1822 + (instancetype)contextWithBlockForFetch:(FetchBlock)block;
1823
1824 @end
1825
1826 @implementation JSContextFetchDelegate {
1827     FetchBlock m_fetchBlock;
1828 }
1829
1830 + (instancetype)contextWithBlockForFetch:(FetchBlock)block
1831 {
1832     auto *result = [[JSContextFetchDelegate alloc] init];
1833     result->m_fetchBlock = block;
1834     return result;
1835 }
1836
1837 - (void)context:(JSContext *)context fetchModuleForIdentifier:(JSValue *)identifier withResolveHandler:(JSValue *)resolve andRejectHandler:(JSValue *)reject
1838 {
1839     m_fetchBlock(context, identifier, resolve, reject);
1840 }
1841
1842 @end
1843
1844 static void checkModuleCodeRan(JSContext *context, JSValue *promise, JSValue *expected)
1845 {
1846     __block BOOL promiseWasResolved = false;
1847     [promise invokeMethod:@"then" withArguments:@[^(JSValue *exportValue) {
1848         promiseWasResolved = true;
1849         checkResult(@"module exported value 'exp' is null", [exportValue[@"exp"] isEqualToObject:expected]);
1850         checkResult(@"ran is %@", [context[@"ran"] isEqualToObject:expected]);
1851     }, ^(JSValue *error) {
1852         NSLog(@"%@", [error toString]);
1853         checkResult(@"module graph was resolved as expected", NO);
1854     }]];
1855     checkResult(@"Promise was resolved", promiseWasResolved);
1856 }
1857
1858 static void checkModuleWasRejected(JSContext *context, JSValue *promise)
1859 {
1860     __block BOOL promiseWasRejected = false;
1861     [promise invokeMethod:@"then" withArguments:@[^() {
1862         checkResult(@"module was rejected as expected", NO);
1863     }, ^(JSValue *error) {
1864         promiseWasRejected = true;
1865         NSLog(@"%@", [error toString]);
1866         checkResult(@"module graph was rejected with error", ![error isEqualWithTypeCoercionToObject:[JSValue valueWithNullInContext:context]]);
1867     }]];
1868 }
1869
1870 static void testFetch()
1871 {
1872     @autoreleasepool {
1873         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *context, JSValue *identifier, JSValue *resolve, JSValue *reject) {
1874             if ([identifier isEqualToObject:@"file:///directory/bar.js"])
1875                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import \"../foo.js\"; export let exp = null;" inVirtualMachine:[context virtualMachine]]]];
1876             else if ([identifier isEqualToObject:@"file:///foo.js"])
1877                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"globalThis.ran = null;" inVirtualMachine:[context virtualMachine]]]];
1878             else
1879                 [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Weird path" inContext:context]]];
1880         }];
1881         context.moduleLoaderDelegate = context;
1882         JSValue *promise = [context evaluateScript:@"import('./bar.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
1883         JSValue *null = [JSValue valueWithNullInContext:context];
1884         checkModuleCodeRan(context, promise, null);
1885     }
1886 }
1887
1888 static void testFetchWithTwoCycle()
1889 {
1890     @autoreleasepool {
1891         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *context, JSValue *identifier, JSValue *resolve, JSValue *reject) {
1892             if ([identifier isEqualToObject:@"file:///directory/bar.js"])
1893                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import { n } from \"../foo.js\"; export let exp = n;" inVirtualMachine:[context virtualMachine]]]];
1894             else if ([identifier isEqualToObject:@"file:///foo.js"])
1895                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import \"directory/bar.js\"; globalThis.ran = null; export let n = null;" inVirtualMachine:[context virtualMachine]]]];
1896             else
1897                 [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Weird path" inContext:context]]];
1898         }];
1899         context.moduleLoaderDelegate = context;
1900         JSValue *promise = [context evaluateScript:@"import('./bar.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
1901         JSValue *null = [JSValue valueWithNullInContext:context];
1902         checkModuleCodeRan(context, promise, null);
1903     }
1904 }
1905
1906
1907 static void testFetchWithThreeCycle()
1908 {
1909     @autoreleasepool {
1910         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *context, JSValue *identifier, JSValue *resolve, JSValue *reject) {
1911             if ([identifier isEqualToObject:@"file:///directory/bar.js"])
1912                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import { n } from \"../foo.js\"; export let foo = n;" inVirtualMachine:[context virtualMachine]]]];
1913             else if ([identifier isEqualToObject:@"file:///foo.js"])
1914                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import \"otherDirectory/baz.js\"; export let n = null;" inVirtualMachine:[context virtualMachine]]]];
1915             else if ([identifier isEqualToObject:@"file:///otherDirectory/baz.js"])
1916                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"import { foo } from \"../directory/bar.js\"; globalThis.ran = null; export let exp = foo;" inVirtualMachine:[context virtualMachine]]]];
1917             else
1918                 [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Weird path" inContext:context]]];
1919         }];
1920         context.moduleLoaderDelegate = context;
1921         JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
1922         JSValue *null = [JSValue valueWithNullInContext:context];
1923         checkModuleCodeRan(context, promise, null);
1924     }
1925 }
1926
1927 static void testLoaderResolvesAbsoluteScriptURL()
1928 {
1929     @autoreleasepool {
1930         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *context, JSValue *identifier, JSValue *resolve, JSValue *reject) {
1931             if ([identifier isEqualToObject:@"file:///directory/bar.js"])
1932                 [resolve callWithArguments:@[[JSScript scriptWithSource:@"export let exp = null; globalThis.ran = null;" inVirtualMachine:[context virtualMachine]]]];
1933             else
1934                 [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Weird path" inContext:context]]];
1935         }];
1936         context.moduleLoaderDelegate = context;
1937         JSValue *promise = [context evaluateScript:@"import('/directory/bar.js');"];
1938         JSValue *null = [JSValue valueWithNullInContext:context];
1939         checkModuleCodeRan(context, promise, null);
1940     }
1941 }
1942
1943 static void testLoaderRejectsNilScriptURL()
1944 {
1945     @autoreleasepool {
1946         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *, JSValue *, JSValue *, JSValue *) {
1947             checkResult(@"Code is not run", NO);
1948         }];
1949         context.moduleLoaderDelegate = context;
1950         JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');"];
1951         checkModuleWasRejected(context, promise);
1952     }
1953 }
1954
1955 static void testLoaderRejectsFailedFetch()
1956 {
1957     @autoreleasepool {
1958         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext *context, JSValue *, JSValue *, JSValue *reject) {
1959             [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Nope" inContext:context]]];
1960         }];
1961         context.moduleLoaderDelegate = context;
1962         JSValue *promise = [context evaluateScript:@"import('/otherDirectory/baz.js');"];
1963         checkModuleWasRejected(context, promise);
1964     }
1965 }
1966
1967 static void testImportModuleTwice()
1968 {
1969     @autoreleasepool {
1970         auto *context = [JSContextFetchDelegate contextWithBlockForFetch:^(JSContext * context, JSValue *, JSValue *resolve, JSValue *) {
1971             [resolve callWithArguments:@[[JSScript scriptWithSource:@"ran++; export let exp = 1;" inVirtualMachine:[context virtualMachine]]]];
1972         }];
1973         context.moduleLoaderDelegate = context;
1974         context[@"ran"] = @(0);
1975         JSValue *promise = [context evaluateScript:@"import('/baz.js');"];
1976         JSValue *promise2 = [context evaluateScript:@"import('/baz.js');"];
1977         JSValue *one = [JSValue valueWithInt32:1 inContext:context];
1978         checkModuleCodeRan(context, promise, one);
1979         checkModuleCodeRan(context, promise2, one);
1980     }
1981 }
1982
1983 static void testBytecodeCache()
1984 {
1985     @autoreleasepool {
1986         NSURL* tempDirectory = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES];
1987
1988         NSString* fooSource = @"import { n } from \"../foo.js\"; export let foo = n;";
1989         NSString* barSource = @"import \"otherDirectory/baz.js\"; export let n = null;";
1990         NSString* bazSource = @"import { foo } from \"../directory/bar.js\"; globalThis.ran = null; export let exp = foo;";
1991
1992         NSURL* fooPath = [tempDirectory URLByAppendingPathComponent:@"foo.js"];
1993         NSURL* barPath = [tempDirectory URLByAppendingPathComponent:@"bar.js"];
1994         NSURL* bazPath = [tempDirectory URLByAppendingPathComponent:@"baz.js"];
1995
1996         NSURL* fooCachePath = [tempDirectory URLByAppendingPathComponent:@"foo.js.cache"];
1997         NSURL* barCachePath = [tempDirectory URLByAppendingPathComponent:@"bar.js.cache"];
1998         NSURL* bazCachePath = [tempDirectory URLByAppendingPathComponent:@"baz.js.cache"];
1999
2000         [fooSource writeToURL:fooPath atomically:NO encoding:NSASCIIStringEncoding error:nil];
2001         [barSource writeToURL:barPath atomically:NO encoding:NSASCIIStringEncoding error:nil];
2002         [bazSource writeToURL:bazPath atomically:NO encoding:NSASCIIStringEncoding error:nil];
2003
2004         __block bool forceDiskCache = false;
2005         auto block = ^(JSContext *context, JSValue *identifier, JSValue *resolve, JSValue *reject) {
2006             JSC::Options::forceDiskCache() = forceDiskCache;
2007             if ([identifier isEqualToObject:@"file:///directory/bar.js"])
2008                 [resolve callWithArguments:@[[JSScript scriptFromASCIIFile:fooPath inVirtualMachine:context.virtualMachine withCodeSigning:nil andBytecodeCache:fooCachePath]]];
2009             else if ([identifier isEqualToObject:@"file:///foo.js"])
2010                 [resolve callWithArguments:@[[JSScript scriptFromASCIIFile:barPath inVirtualMachine:context.virtualMachine withCodeSigning:nil andBytecodeCache:barCachePath]]];
2011             else if ([identifier isEqualToObject:@"file:///otherDirectory/baz.js"])
2012                 [resolve callWithArguments:@[[JSScript scriptFromASCIIFile:bazPath inVirtualMachine:context.virtualMachine withCodeSigning:nil andBytecodeCache:bazCachePath]]];
2013             else
2014                 [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Weird path" inContext:context]]];
2015         };
2016
2017         @autoreleasepool {
2018             auto *context = [JSContextFetchDelegate contextWithBlockForFetch:block];
2019             context.moduleLoaderDelegate = context;
2020             JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
2021             JSValue *null = [JSValue valueWithNullInContext:context];
2022             checkModuleCodeRan(context, promise, null);
2023         }
2024
2025         @autoreleasepool {
2026             forceDiskCache = true;
2027             auto *context = [JSContextFetchDelegate contextWithBlockForFetch:block];
2028             context.moduleLoaderDelegate = context;
2029             JSValue *promise = [context evaluateScript:@"import('../otherDirectory/baz.js');" withSourceURL:[NSURL fileURLWithPath:@"/directory" isDirectory:YES]];
2030             JSValue *null = [JSValue valueWithNullInContext:context];
2031             checkModuleCodeRan(context, promise, null);
2032             JSC::Options::forceDiskCache() = false;
2033         }
2034
2035         NSFileManager* fileManager = [NSFileManager defaultManager];
2036         [fileManager removeItemAtURL:fooPath error:nil];
2037         [fileManager removeItemAtURL:barPath error:nil];
2038         [fileManager removeItemAtURL:bazPath error:nil];
2039         [fileManager removeItemAtURL:fooCachePath error:nil];
2040         [fileManager removeItemAtURL:barCachePath error:nil];
2041         [fileManager removeItemAtURL:bazCachePath error:nil];
2042     }
2043 }
2044
2045 @interface JSContextFileLoaderDelegate : JSContext <JSModuleLoaderDelegate>
2046
2047 + (instancetype)newContext;
2048
2049 @end
2050
2051 @implementation JSContextFileLoaderDelegate {
2052 }
2053
2054 + (instancetype)newContext
2055 {
2056     auto *result = [[JSContextFileLoaderDelegate alloc] init];
2057     return result;
2058 }
2059
2060 static NSURL *resolvePathToScripts()
2061 {
2062     NSString *arg0 = NSProcessInfo.processInfo.arguments[0];
2063     NSURL *base;
2064     if ([arg0 hasPrefix:@"/"])
2065         base = [NSURL fileURLWithPath:arg0 isDirectory:NO];
2066     else {
2067         const size_t maxLength = 10000;
2068         char cwd[maxLength];
2069         if (!getcwd(cwd, maxLength)) {
2070             NSLog(@"getcwd errored with code: %s", strerror(errno));
2071             exit(1);
2072         }
2073         NSURL *cwdURL = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%s", cwd]];
2074         base = [NSURL fileURLWithPath:arg0 isDirectory:NO relativeToURL:cwdURL];
2075     }
2076     return [NSURL fileURLWithPath:@"./testapiScripts/" isDirectory:YES relativeToURL:base];
2077 }
2078
2079 - (void)context:(JSContext *)context fetchModuleForIdentifier:(JSValue *)identifier withResolveHandler:(JSValue *)resolve andRejectHandler:(JSValue *)reject
2080 {
2081     NSURL *filePath = [NSURL URLWithString:[identifier toString]];
2082     auto *script = [JSScript scriptFromASCIIFile:filePath inVirtualMachine:context.virtualMachine withCodeSigning:nil andBytecodeCache:nil];
2083     if (script)
2084         [resolve callWithArguments:@[script]];
2085     else
2086         [reject callWithArguments:@[[JSValue valueWithNewErrorFromMessage:@"Unable to create Script" inContext:context]]];
2087 }
2088
2089 @end
2090
2091 static void testLoadBasicFile()
2092 {
2093     @autoreleasepool {
2094         auto *context = [JSContextFileLoaderDelegate newContext];
2095         context.moduleLoaderDelegate = context;
2096         JSValue *promise = [context evaluateScript:@"import('./basic.js');" withSourceURL:resolvePathToScripts()];
2097         JSValue *null = [JSValue valueWithNullInContext:context];
2098         checkModuleCodeRan(context, promise, null);
2099     }
2100 }
2101
2102 void testObjectiveCAPI()
2103 {
2104     NSLog(@"Testing Objective-C API");
2105
2106     checkNegativeNSIntegers();
2107     runJITThreadLimitTests();
2108
2109     testLoaderResolvesAbsoluteScriptURL();
2110     testFetch();
2111     testFetchWithTwoCycle();
2112     testFetchWithThreeCycle();
2113     testImportModuleTwice();
2114     testBytecodeCache();
2115
2116     testLoaderRejectsNilScriptURL();
2117     testLoaderRejectsFailedFetch();
2118
2119     // File loading
2120     testLoadBasicFile();
2121
2122     promiseWithExecutor(Resolution::ResolveEager);
2123     promiseWithExecutor(Resolution::RejectEager);
2124     promiseWithExecutor(Resolution::ResolveLate);
2125     promiseWithExecutor(Resolution::RejectLate);
2126     promiseRejectOnJSException();
2127     promiseCreateResolved();
2128     promiseCreateRejected();
2129     parallelPromiseResolveTest();
2130
2131     testObjectiveCAPIMain();
2132 }
2133
2134 #else
2135
2136 void testObjectiveCAPI()
2137 {
2138 }
2139
2140 #endif // JSC_OBJC_API_ENABLED