Unreviewed, rolling out r141809.
[WebKit-https.git] / Source / JavaScriptCore / API / tests / testapi.mm
1 /*
2  * Copyright (C) 2013 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 "JavaScriptCore.h"
27
28 extern "C" bool _Block_has_signature(void *);
29 extern "C" const char * _Block_signature(void *);
30
31 extern int failed;
32 extern "C" void testObjectiveCAPI(void);
33
34 #if JS_OBJC_API_ENABLED
35
36 @protocol ParentObject <JSExport>
37 @end
38
39 @interface ParentObject : NSObject<ParentObject>
40 + (NSString *)parentTest;
41 @end
42
43 @implementation ParentObject
44 + (NSString *)parentTest
45 {
46     return [self description];
47 }
48 @end
49
50 @protocol TestObject <JSExport>
51 @property int variable;
52 @property (readonly) int six;
53 @property CGPoint point;
54 + (NSString *)classTest;
55 + (NSString *)parentTest;
56 - (NSString *)getString;
57 JSExportAs(testArgumentTypes,
58 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
59 );
60 - (void)callback:(void(^)(int))block;
61 @end
62
63 @interface TestObject : ParentObject <TestObject>
64 @property int six;
65 + (id)testObject;
66 @end
67
68 @implementation TestObject
69 @synthesize variable;
70 @synthesize six;
71 @synthesize point;
72 + (id)testObject
73 {
74     return [[[TestObject alloc] init] autorelease];
75 }
76 + (NSString *)classTest
77 {
78     return @"classTest - okay";
79 }
80 - (NSString *)getString
81 {
82     return @"42";
83 }
84 - (NSString *)testArgumentTypesWithInt:(int)i double:(double)d boolean:(BOOL)b string:(NSString *)s number:(NSNumber *)n array:(NSArray *)a dictionary:(NSDictionary *)o
85 {
86     return [NSString stringWithFormat:@"%d,%g,%d,%@,%d,%@,%@", i, d, b==YES?true:false,s,[n intValue],a[1],o[@"x"]];
87 }
88 - (void)callback:(void(^)(int))block
89 {
90     block(42);
91 }
92 @end
93
94 bool testXYZTested = false;
95
96 @protocol TextXYZ <JSExport>
97 @property int x;
98 @property (readonly) int y;
99 - (void)test:(NSString *)message;
100 @end
101
102 @interface TextXYZ : NSObject <TextXYZ>
103 @property int x;
104 @property int y;
105 @property int z;
106 @end
107
108 @implementation TextXYZ
109 @synthesize x;
110 @synthesize y;
111 @synthesize z;
112 - (void)test:(NSString *)message
113 {
114     testXYZTested = [message isEqual:@"test"] && x == 13 & y == 4 && z == 5;
115 }
116 @end
117
118 static void checkResult(NSString *description, bool passed)
119 {
120     NSLog(@"TEST: \"%@\": %@", description, passed ? @"PASSED" : @"FAILED");
121     if (!passed)
122         failed = 1;
123 }
124
125 static bool blockSignatureContainsClass()
126 {
127     static bool containsClass = ^{
128         id block = ^(NSString *string){ return string; };
129         return _Block_has_signature(block) && strstr(_Block_signature(block), "NSString");
130     }();
131     return containsClass;
132 }
133
134 void testObjectiveCAPI()
135 {
136     NSLog(@"Testing Objective-C API");
137
138     @autoreleasepool {
139         JSContext *context = [[[JSContext alloc] init] autorelease];
140         JSValue *result = [context evaluateScript:@"2 + 2"];
141         checkResult(@"2 + 2", [result isNumber] && [result toInt32] == 4);
142     }
143
144     @autoreleasepool {
145         JSContext *context = [[[JSContext alloc] init] autorelease];
146         NSString *result = [NSString stringWithFormat:@"Two plus two is %@", [context evaluateScript:@"2 + 2"]];
147         checkResult(@"stringWithFormat", [result isEqual:@"Two plus two is 4"]);
148     }
149
150     @autoreleasepool {
151         JSContext *context = [[[JSContext alloc] init] autorelease];
152         context[@"message"] = @"Hello";
153         JSValue *result = [context evaluateScript:@"message + ', World!'"];
154         checkResult(@"Hello, World!", [result isString] && [result isEqualToObject:@"Hello, World!"]);
155     }
156
157     @autoreleasepool {
158         JSContext *context = [[[JSContext alloc] init] autorelease];
159         JSValue *result = [context evaluateScript:@"({ x:42 })"];
160         checkResult(@"({ x:42 })", [result isObject] && [result[@"x"] isEqualToObject:@42]);
161         id obj = [result toObject];
162         checkResult(@"Check dictionary literal", [obj isKindOfClass:[NSDictionary class]]);
163         id num = (NSDictionary *)obj[@"x"];
164         checkResult(@"Check numeric literal", [num isKindOfClass:[NSNumber class]]);
165     }
166
167     @autoreleasepool {
168         JSContext *context = [[[JSContext alloc] init] autorelease];
169         __block int result;
170         context[@"blockCallback"] = ^(int value){
171             result = value;
172         };
173         [context evaluateScript:@"blockCallback(42)"];
174         checkResult(@"blockCallback", result == 42);
175     }
176
177     if (blockSignatureContainsClass()) {
178         @autoreleasepool {
179             JSContext *context = [[[JSContext alloc] init] autorelease];
180             __block bool result = false;
181             context[@"blockCallback"] = ^(NSString *value){
182                 result = [@"42" isEqualToString:value] == YES;
183             };
184             [context evaluateScript:@"blockCallback(42)"];
185             checkResult(@"blockCallback(NSString *)", result);
186         }
187     } else
188         NSLog(@"Skipping 'blockCallback(NSString *)' test case");
189
190     @autoreleasepool {
191         JSContext *context = [[[JSContext alloc] init] autorelease];
192         checkResult(@"!context.exception", !context.exception);
193         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
194         checkResult(@"context.exception", context.exception);
195     }
196
197     @autoreleasepool {
198         JSContext *context = [[[JSContext alloc] init] autorelease];
199         __block bool caught = false;
200         context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
201             (void)context;
202             (void)exception;
203             caught = true;
204         };
205         [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
206         checkResult(@"JSContext.exceptionHandler", caught);
207     }
208
209     @autoreleasepool {
210         JSContext *context = [[[JSContext alloc] init] autorelease];
211         context[@"callback"] = ^{
212             JSContext *context = [JSContext currentContext];
213             context.exception = [JSValue valueWithNewErrorFromMessage:@"Something went wrong." inContext:context];
214         };
215         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
216         checkResult(@"Explicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
217         checkResult(@"Explicit throw in callback - not thrown to Objective-C", !context.exception);
218     }
219
220     @autoreleasepool {
221         JSContext *context = [[[JSContext alloc] init] autorelease];
222         context[@"callback"] = ^{
223             JSContext *context = [JSContext currentContext];
224             [context evaluateScript:@"!@#$%^&*() THIS IS NOT VALID JAVASCRIPT SYNTAX !@#$%^&*()"];
225         };
226         JSValue *result = [context evaluateScript:@"var result; try { callback(); } catch (e) { result = 'Caught exception'; }"];
227         checkResult(@"Implicit throw in callback - was caught by JavaScript", [result isEqualToObject:@"Caught exception"]);
228         checkResult(@"Implicit throw in callback - not thrown to Objective-C", !context.exception);
229     }
230
231     @autoreleasepool {
232         JSContext *context = [[[JSContext alloc] init] autorelease];
233         [context evaluateScript:
234             @"function sum(array) { \
235                 var result = 0; \
236                 for (var i in array) \
237                     result += array[i]; \
238                 return result; \
239             }"];
240         JSValue *array = [JSValue valueWithObject:@[@13, @2, @7] inContext:context];
241         JSValue *sumFunction = context[@"sum"];
242         JSValue *result = [sumFunction callWithArguments:@[ array ]];
243         checkResult(@"sum([13, 2, 7])", [result toInt32] == 22);
244     }
245
246     @autoreleasepool {
247         JSContext *context = [[[JSContext alloc] init] autorelease];
248         JSValue *mulAddFunction = [context evaluateScript:
249             @"(function(array, object) { \
250                 var result = []; \
251                 for (var i in array) \
252                     result.push(array[i] * object.x + object.y); \
253                 return result; \
254             })"];
255         JSValue *result = [mulAddFunction callWithArguments:@[ @[ @2, @4, @8 ], @{ @"x":@0.5, @"y":@42 } ]];
256         checkResult(@"mulAddFunction", [result isObject] && [[result toString] isEqual:@"43,44,46"]);
257     }
258
259     @autoreleasepool {
260         JSContext *context = [[[JSContext alloc] init] autorelease];        
261         JSValue *array = [JSValue valueWithNewArrayInContext:context];
262         checkResult(@"arrayLengthEmpty", [[array[@"length"] toNumber] unsignedIntegerValue] == 0);
263         JSValue *value1 = [JSValue valueWithInt32:42 inContext:context];
264         JSValue *value2 = [JSValue valueWithInt32:24 inContext:context];
265         NSUInteger lowIndex = 5;
266         NSUInteger maxLength = UINT_MAX;
267
268         [array setValue:value1 atIndex:lowIndex];
269         checkResult(@"array.length after put to low index", [[array[@"length"] toNumber] unsignedIntegerValue] == (lowIndex + 1));
270
271         [array setValue:value1 atIndex:(maxLength - 1)];
272         checkResult(@"array.length after put to maxLength - 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
273
274         [array setValue:value2 atIndex:maxLength];
275         checkResult(@"array.length after put to maxLength", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
276
277         [array setValue:value2 atIndex:(maxLength + 1)];
278         checkResult(@"array.length after put to maxLength + 1", [[array[@"length"] toNumber] unsignedIntegerValue] == maxLength);
279
280         checkResult(@"valueAtIndex:0 is undefined", [[array valueAtIndex:0] isUndefined]);
281         checkResult(@"valueAtIndex:lowIndex", [[array valueAtIndex:lowIndex] toInt32] == 42);
282         checkResult(@"valueAtIndex:maxLength - 1", [[array valueAtIndex:(maxLength - 1)] toInt32] == 42);
283         checkResult(@"valueAtIndex:maxLength", [[array valueAtIndex:maxLength] toInt32] == 24);
284         checkResult(@"valueAtIndex:maxLength + 1", [[array valueAtIndex:(maxLength + 1)] toInt32] == 24);
285     }
286
287     @autoreleasepool {
288         JSContext *context = [[[JSContext alloc] init] autorelease];
289         JSValue *object = [JSValue valueWithNewObjectInContext:context];
290
291         object[@"point"] = @{ @"x":@1, @"y":@2 };
292         object[@"point"][@"x"] = @3;
293         CGPoint point = [object[@"point"] toPoint];
294         checkResult(@"toPoint", point.x == 3 && point.y == 2);
295
296         object[@{ @"toString":^{ return @"foo"; } }] = @"bar";
297         checkResult(@"toString in object literal used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
298
299         object[[@"foobar" substringToIndex:3]] = @"bar";
300         checkResult(@"substring used as subscript", [[object[@"foo"] toString] isEqual:@"bar"]);
301     }
302
303     @autoreleasepool {
304         JSContext *context = [[[JSContext alloc] init] autorelease];
305         TextXYZ *testXYZ = [[TextXYZ alloc] init];
306         context[@"testXYZ"] = testXYZ;
307         testXYZ.x = 3;
308         testXYZ.y = 4;
309         testXYZ.z = 5;
310         [context evaluateScript:@"testXYZ.x = 13; testXYZ.y = 14;"];
311         [context evaluateScript:@"testXYZ.test('test')"];
312         checkResult(@"TextXYZ - testXYZTested", testXYZTested);
313         JSValue *result = [context evaluateScript:@"testXYZ.x + ',' + testXYZ.y + ',' + testXYZ.z"];
314         checkResult(@"TextXYZ - result", [result isEqualToObject:@"13,4,undefined"]);
315     }
316
317     @autoreleasepool {
318         JSContext *context = [[[JSContext alloc] init] autorelease];
319         [context[@"Object"][@"prototype"] defineProperty:@"getterProperty" descriptor:@{
320             JSPropertyDescriptorGetKey:^{
321                 return [JSContext currentThis][@"x"];
322             }
323         }];
324         JSValue *object = [JSValue valueWithObject:@{ @"x":@101 } inContext:context];
325         int result = [object [@"getterProperty"] toInt32];
326         checkResult(@"getterProperty", result == 101);
327     }
328
329     @autoreleasepool {
330         JSContext *context = [[[JSContext alloc] init] autorelease];
331         context[@"concatenate"] = ^{
332             NSArray *arguments = [JSContext currentArguments];
333             if (![arguments count])
334                 return @"";
335             NSString *message = [arguments[0] description];
336             for (NSUInteger index = 1; index < [arguments count]; ++index)
337                 message = [NSString stringWithFormat:@"%@ %@", message, arguments[index]];
338             return message;
339         };
340         JSValue *result = [context evaluateScript:@"concatenate('Hello,', 'World!')"];
341         checkResult(@"concatenate", [result isEqualToObject:@"Hello, World!"]);
342     }
343
344     @autoreleasepool {
345         JSContext *context = [[[JSContext alloc] init] autorelease];
346         context[@"foo"] = @YES;
347         checkResult(@"@YES is boolean", [context[@"foo"] isBoolean]);
348         JSValue *result = [context evaluateScript:@"typeof foo"];
349         checkResult(@"@YES is boolean", [result isEqualToObject:@"boolean"]);
350     }
351
352     @autoreleasepool {
353         JSContext *context = [[[JSContext alloc] init] autorelease];
354         TestObject* testObject = [TestObject testObject];
355         context[@"testObject"] = testObject;
356         JSValue *result = [context evaluateScript:@"String(testObject)"];
357         checkResult(@"String(testObject)", [result isEqualToObject:@"[object TestObject]"]);
358     }
359
360     @autoreleasepool {
361         JSContext *context = [[[JSContext alloc] init] autorelease];
362         TestObject* testObject = [TestObject testObject];
363         context[@"testObject"] = testObject;
364         JSValue *result = [context evaluateScript:@"String(testObject.__proto__)"];
365         checkResult(@"String(testObject.__proto__)", [result isEqualToObject:@"[object TestObjectPrototype]"]);
366     }
367
368     @autoreleasepool {
369         JSContext *context = [[[JSContext alloc] init] autorelease];
370         context[@"TestObject"] = [TestObject class];
371         JSValue *result = [context evaluateScript:@"String(TestObject)"];
372         checkResult(@"String(TestObject)", [result isEqualToObject:@"[object TestObjectConstructor]"]);
373     }
374
375     @autoreleasepool {
376         JSContext *context = [[[JSContext alloc] init] autorelease];
377         JSValue* value = [JSValue valueWithObject:[TestObject class] inContext:context];
378         checkResult(@"[value toObject] == [TestObject class]", [value toObject] == [TestObject class]);
379     }
380
381     @autoreleasepool {
382         JSContext *context = [[[JSContext alloc] init] autorelease];
383         context[@"TestObject"] = [TestObject class];
384         JSValue *result = [context evaluateScript:@"TestObject.parentTest()"];
385         checkResult(@"TestObject.parentTest()", [result isEqualToObject:@"TestObject"]);
386     }
387
388     @autoreleasepool {
389         JSContext *context = [[[JSContext alloc] init] autorelease];
390         TestObject* testObject = [TestObject testObject];
391         context[@"testObjectA"] = testObject;
392         context[@"testObjectB"] = testObject;
393         JSValue *result = [context evaluateScript:@"testObjectA == testObjectB"];
394         checkResult(@"testObjectA == testObjectB", [result isBoolean] && [result toBool]);
395     }
396
397     @autoreleasepool {
398         JSContext *context = [[[JSContext alloc] init] autorelease];
399         TestObject* testObject = [TestObject testObject];
400         context[@"testObject"] = testObject;
401         testObject.point = (CGPoint){3,4};
402         JSValue *result = [context evaluateScript:@"var result = JSON.stringify(testObject.point); testObject.point = {x:12,y:14}; result"];
403         checkResult(@"testObject.point - result", [result isEqualToObject:@"{\"x\":3,\"y\":4}"]);
404         checkResult(@"testObject.point - {x:12,y:14}", testObject.point.x == 12 && testObject.point.y == 14);
405     }
406
407     @autoreleasepool {
408         JSContext *context = [[[JSContext alloc] init] autorelease];
409         TestObject* testObject = [TestObject testObject];
410         testObject.six = 6;
411         context[@"testObject"] = testObject;
412         context[@"mul"] = ^(int x, int y){ return x * y; };
413         JSValue *result = [context evaluateScript:@"mul(testObject.six, 7)"];
414         checkResult(@"mul(testObject.six, 7)", [result isNumber] && [result toInt32] == 42);
415     }
416
417     @autoreleasepool {
418         JSContext *context = [[[JSContext alloc] init] autorelease];
419         TestObject* testObject = [TestObject testObject];
420         context[@"testObject"] = testObject;
421         context[@"testObject"][@"variable"] = @4;
422         [context evaluateScript:@"++testObject.variable"];
423         checkResult(@"++testObject.variable", testObject.variable == 5);
424     }
425
426     @autoreleasepool {
427         JSContext *context = [[[JSContext alloc] init] autorelease];
428         context[@"point"] = @{ @"x":@6, @"y":@7 };
429         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
430         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
431     }
432
433     @autoreleasepool {
434         JSContext *context = [[[JSContext alloc] init] autorelease];
435         context[@"point"] = @{ @"x":@6, @"y":@7 };
436         JSValue *result = [context evaluateScript:@"point.x + ',' + point.y"];
437         checkResult(@"point.x + ',' + point.y", [result isEqualToObject:@"6,7"]);
438     }
439
440     @autoreleasepool {
441         JSContext *context = [[[JSContext alloc] init] autorelease];
442         TestObject* testObject = [TestObject testObject];
443         context[@"testObject"] = testObject;
444         JSValue *result = [context evaluateScript:@"testObject.getString()"];
445         checkResult(@"testObject.getString()", [result isString] && [result toInt32] == 42);
446     }
447
448     @autoreleasepool {
449         JSContext *context = [[[JSContext alloc] init] autorelease];
450         TestObject* testObject = [TestObject testObject];
451         context[@"testObject"] = testObject;
452         JSValue *result = [context evaluateScript:@"testObject.testArgumentTypes(101,0.5,true,'foo',666,[false,'bar',false],{x:'baz'})"];
453         checkResult(@"testObject.testArgumentTypes", [result isEqualToObject:@"101,0.5,1,foo,666,bar,baz"]);
454     }
455
456     @autoreleasepool {
457         JSContext *context = [[[JSContext alloc] init] autorelease];
458         TestObject* testObject = [TestObject testObject];
459         context[@"testObject"] = testObject;
460         JSValue *result = [context evaluateScript:@"testObject.getString.call(testObject)"];
461         checkResult(@"testObject.getString.call(testObject)", [result isString] && [result toInt32] == 42);
462     }
463
464     @autoreleasepool {
465         JSContext *context = [[[JSContext alloc] init] autorelease];
466         TestObject* testObject = [TestObject testObject];
467         context[@"testObject"] = testObject;
468         checkResult(@"testObject.getString.call({}) pre", !context.exception);
469         [context evaluateScript:@"testObject.getString.call({})"];
470         checkResult(@"testObject.getString.call({}) post", context.exception);
471     }
472
473     @autoreleasepool {
474         JSContext *context = [[[JSContext alloc] init] autorelease];
475         TestObject* testObject = [TestObject testObject];
476         context[@"testObject"] = testObject;
477         JSValue *result = [context evaluateScript:@"var result = 0; testObject.callback(function(x){ result = x; }); result"];
478         checkResult(@"testObject.callback", [result isNumber] && [result toInt32] == 42);
479     }
480
481     @autoreleasepool {
482         JSContext *context1 = [[JSContext alloc] init];
483         JSContext *context2 = [[JSContext alloc] initWithVirtualMachine:context1.virtualMachine];
484         JSValue *value = [JSValue valueWithDouble:42 inContext:context2];
485         checkResult(@"value.context == context2", value.context == context2);
486         context1[@"passValueBetweenContexts"] = value;
487         JSValue *result = [context1 evaluateScript:@"passValueBetweenContexts"];
488         checkResult(@"result.context == context1", result.context == context1);
489         checkResult(@"[value isEqualToObject:result]", [value isEqualToObject:result]);
490         [context1 release];
491         [context2 release];
492     }
493 }
494
495 #else
496
497 void testObjectiveCAPI()
498 {
499 }
500
501 #endif