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