bmalloc, WTF and JavaScriptCore parts of [Xcode] Update some build settings as recomm...
[WebKit-https.git] / Source / JavaScriptCore / API / tests / Regress141275.mm
1 /*
2  * Copyright (C) 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "Regress141275.h"
28
29 #import <Foundation/Foundation.h>
30 #import <objc/objc.h>
31 #import <objc/runtime.h>
32
33 #if JSC_OBJC_API_ENABLED
34
35 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
36
37 extern int failed;
38
39 static const NSUInteger scriptToEvaluate = 50;
40
41 @interface JSTEvaluator : NSObject
42 - (instancetype)initWithScript:(NSString*)script;
43
44 - (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler;
45
46 - (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler;
47 - (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler;
48
49 - (void)waitForTasksDoneAndReportResults;
50 @end
51
52
53 static const NSString* JSTEvaluatorThreadContextKey = @"JSTEvaluatorThreadContextKey";
54
55 /*
56  * A JSTEvaluatorThreadContext is kept in the thread dictionary of threads used by JSEvaluator.
57  *
58  * This includes the run loop thread, and any threads used by _jsSourcePerformQueue to execute a task.
59  */
60 @interface JSTEvaluatorThreadContext : NSObject
61 @property (weak) JSTEvaluator* evaluator;
62 @property (strong) JSContext* jsContext;
63 @end
64
65 @implementation JSTEvaluatorThreadContext
66 @end
67
68
69 /*!
70  * A JSTEvaluatorTask is a single task to be executed.
71  *
72  * JSTEvaluator keeps a list of pending tasks. The run loop thread is repsonsible for feeding pending tasks to the _jsSourcePerformQueue, while respecting sign posts.
73  */
74 @interface JSTEvaluatorTask : NSObject
75
76 @property (nonatomic, copy) void (^evaluateBlock)(JSContext* jsContext);
77 @property (nonatomic, copy) void (^completionHandler)(NSError* error);
78 @property (nonatomic, copy) NSError* error;
79
80 + (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))block completionHandler:(void (^)(NSError* error))completionBlock;
81
82 @end
83
84 @implementation JSTEvaluatorTask
85
86 + (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))evaluationBlock completionHandler:(void (^)(NSError* error))completionHandler
87 {
88     JSTEvaluatorTask* task = [self new];
89     task.evaluateBlock = evaluationBlock;
90     task.completionHandler = completionHandler;
91     return task;
92 }
93
94 @end
95
96 @implementation JSTEvaluator {
97     dispatch_queue_t _jsSourcePerformQueue;
98     dispatch_semaphore_t _allScriptsDone;
99     CFRunLoopRef _jsThreadRunLoop;
100     CFRunLoopSourceRef _jsThreadRunLoopSource;
101     JSContext* _jsContext;
102     NSMutableArray* __pendingTasks;
103 }
104
105 - (instancetype)init
106 {
107     self = [super init];
108     if (self) {
109         _jsSourcePerformQueue = dispatch_queue_create("JSTEval", DISPATCH_QUEUE_CONCURRENT);
110
111         _allScriptsDone = dispatch_semaphore_create(0);
112
113         _jsContext = [JSContext new];
114         _jsContext.name = @"JSTEval";
115         __pendingTasks = [NSMutableArray new];
116
117         NSThread* jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(_jsThreadMain) object:nil];
118         [jsThread setName:@"JSTEval"];
119         [jsThread start];
120
121     }
122     return self;
123 }
124
125 - (instancetype)initWithScript:(NSString*)script
126 {
127     self = [self init];
128     if (self) {
129         __block NSError* scriptError = nil;
130         dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
131         [self evaluateScript:script
132             completion:^(NSError* error) {
133                 scriptError = error;
134                 dispatch_semaphore_signal(dsema);
135             }];
136         dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
137     }
138     return self;
139 }
140
141 - (void)_accessPendingTasksWithBlock:(void(^)(NSMutableArray* pendingTasks))block
142 {
143     @synchronized(self) {
144         block(__pendingTasks);
145         if (__pendingTasks.count > 0) {
146             if (_jsThreadRunLoop && _jsThreadRunLoopSource) {
147                 CFRunLoopSourceSignal(_jsThreadRunLoopSource);
148                 CFRunLoopWakeUp(_jsThreadRunLoop);
149             }
150         }
151     }
152 }
153
154 - (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler
155 {
156     [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
157         JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:nil
158             completionHandler:completionHandler];
159
160         [pendingTasks addObject:task];
161     }];
162 }
163
164 - (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler
165 {
166     [self evaluateBlock:^(JSContext* context) {
167         [context evaluateScript:script];
168     } completion:completionHandler];
169 }
170
171 - (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler
172 {
173     NSParameterAssert(evaluationBlock != nil);
174     [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
175         JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:evaluationBlock
176             completionHandler:completionHandler];
177
178         [pendingTasks addObject:task];
179     }];
180 }
181
182 - (void)waitForTasksDoneAndReportResults
183 {
184     NSString* passFailString = @"PASSED";
185
186     if (!dispatch_semaphore_wait(_allScriptsDone, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC))) {
187         int totalScriptsRun = [_jsContext[@"counter"] toInt32];
188
189         if (totalScriptsRun != scriptToEvaluate) {
190             passFailString = @"FAILED";
191             failed = 1;
192         }
193
194         NSLog(@"  Ran a total of %d scripts: %@", totalScriptsRun, passFailString);
195     } else {
196         passFailString = @"FAILED";
197         failed = 1;
198         NSLog(@"  Error, timeout waiting for all tasks to complete: %@", passFailString);
199     }
200 }
201
202 static void __JSTRunLoopSourceScheduleCallBack(void* info, CFRunLoopRef rl, CFStringRef)
203 {
204     @autoreleasepool {
205         [(__bridge JSTEvaluator*)info _sourceScheduledOnRunLoop:rl];
206     }
207 }
208
209 static void __JSTRunLoopSourcePerformCallBack(void* info )
210 {
211     @autoreleasepool {
212         [(__bridge JSTEvaluator*)info _sourcePerform];
213     }
214 }
215
216 static void __JSTRunLoopSourceCancelCallBack(void* info, CFRunLoopRef rl, CFStringRef)
217 {
218     @autoreleasepool {
219         [(__bridge JSTEvaluator*)info _sourceCanceledOnRunLoop:rl];
220     }
221 }
222
223 - (void)_jsThreadMain
224 {
225     @autoreleasepool {
226         const CFIndex kRunLoopSourceContextVersion = 0;
227         CFRunLoopSourceContext sourceContext = {
228             kRunLoopSourceContextVersion, (__bridge void*)(self),
229             NULL, NULL, NULL, NULL, NULL,
230             __JSTRunLoopSourceScheduleCallBack,
231             __JSTRunLoopSourceCancelCallBack,
232             __JSTRunLoopSourcePerformCallBack
233         };
234
235         @synchronized(self) {
236             _jsThreadRunLoop = CFRunLoopGetCurrent();
237             CFRetain(_jsThreadRunLoop);
238
239             _jsThreadRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
240             CFRunLoopAddSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
241         }
242
243         CFRunLoopRun();
244
245         @synchronized(self) {
246             NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
247             [threadDict removeObjectForKey:threadDict[JSTEvaluatorThreadContextKey]];
248
249             CFRelease(_jsThreadRunLoopSource);
250             _jsThreadRunLoopSource = NULL;
251
252             CFRelease(_jsThreadRunLoop);
253             _jsThreadRunLoop = NULL;
254
255             __pendingTasks = nil;
256         }
257     }
258 }
259
260 - (void)_sourceScheduledOnRunLoop:(CFRunLoopRef)runLoop
261 {
262     UNUSED_PARAM(runLoop);
263     assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
264
265     // Wake up the run loop in case requests were submitted prior to the
266     // run loop & run loop source getting created.
267     CFRunLoopSourceSignal(_jsThreadRunLoopSource);
268     CFRunLoopWakeUp(_jsThreadRunLoop);
269 }
270
271 - (void)_setupEvaluatorThreadContextIfNeeded
272 {
273     NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
274     JSTEvaluatorThreadContext* context = threadDict[JSTEvaluatorThreadContextKey];
275     // The evaluator may be other evualuator, or nil if this thread has not been used before. Eaither way take ownership.
276     if (context.evaluator != self) {
277         context = [JSTEvaluatorThreadContext new];
278         context.evaluator = self;
279         threadDict[JSTEvaluatorThreadContextKey] = context;
280     }
281 }
282
283 - (void)_callCompletionHandler:(void(^)(NSError* error))completionHandler ifNeededWithError:(NSError*)error
284 {
285     if (completionHandler) {
286         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
287             completionHandler(error);
288         });
289     }
290 }
291
292 - (void)_sourcePerform
293 {
294     assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
295
296     __block NSArray* tasks = nil;
297     [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
298         // No signpost, take all tasks.
299         tasks = [pendingTasks copy];
300         [pendingTasks removeAllObjects];
301     }];
302
303     if (tasks.count > 0) {
304         for (JSTEvaluatorTask* task in tasks) {
305             dispatch_block_t block = ^{
306                 NSError* error = nil;
307                 if (task.evaluateBlock) {
308                     [self _setupEvaluatorThreadContextIfNeeded];
309                     task.evaluateBlock(self->_jsContext);
310                     if (self->_jsContext.exception) {
311                         NSLog(@"Did fail on JSContext: %@", self->_jsContext.name);
312                         NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : [self->_jsContext.exception[@"message"] toString] };
313                         error = [NSError errorWithDomain:@"JSTEvaluator" code:1 userInfo:userInfo];
314                         self->_jsContext.exception = nil;
315                     }
316                 }
317                 [self _callCompletionHandler:task.completionHandler ifNeededWithError:error];
318             };
319
320             if (task.evaluateBlock)
321                 dispatch_async(_jsSourcePerformQueue, block);
322             else
323                 dispatch_barrier_async(_jsSourcePerformQueue, block);
324         }
325
326         dispatch_barrier_sync(_jsSourcePerformQueue, ^{
327             if ([self->_jsContext[@"counter"] toInt32] == scriptToEvaluate)
328                 dispatch_semaphore_signal(self->_allScriptsDone);
329         });
330     }
331 }
332
333 - (void)_sourceCanceledOnRunLoop:(CFRunLoopRef)runLoop
334 {
335     UNUSED_PARAM(runLoop);
336     assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
337
338     @synchronized(self) {
339         assert(_jsThreadRunLoop);
340         assert(_jsThreadRunLoopSource);
341
342         CFRunLoopRemoveSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
343         CFRunLoopStop(_jsThreadRunLoop);
344     }
345 }
346
347 @end
348
349 void runRegress141275()
350 {
351     // Test that we can execute the same script from multiple threads with a shared context.
352     // See <https://webkit.org/b/141275>
353     NSLog(@"TEST: Testing multiple threads executing the same script with a shared context");
354
355     @autoreleasepool {
356         JSTEvaluator* evaluator = [[JSTEvaluator alloc] initWithScript:@"this['counter'] = 0;"];
357
358         void (^showErrorIfNeeded)(NSError* error) = ^(NSError* error) {
359             if (error) {
360                 dispatch_async(dispatch_get_main_queue(), ^{
361                     NSLog(@"Error: %@", error);
362                 });
363             }
364         };
365
366         [evaluator evaluateBlock:^(JSContext* context) {
367             JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
368         } completion:showErrorIfNeeded];
369
370         [evaluator evaluateBlock:^(JSContext* context) {
371             context[@"wait"] = ^{
372                 [NSThread sleepForTimeInterval:0.01];
373             };
374         } completion:^(NSError* error) {
375             if (error) {
376                 dispatch_async(dispatch_get_main_queue(), ^{
377                     NSLog(@"Error: %@", error);
378                 });
379             }
380             for (unsigned i = 0; i < scriptToEvaluate; i++)
381                 [evaluator evaluateScript:@"this['counter']++; this['wait']();" completion:showErrorIfNeeded];
382         }];
383
384         [evaluator waitForTasksDoneAndReportResults];
385     }
386 }
387
388 #endif // JSC_OBJC_API_ENABLED