Title: [180992] trunk/Source/_javascript_Core
Revision
180992
Author
msab...@apple.com
Date
2015-03-03 21:33:37 -0800 (Tue, 03 Mar 2015)

Log Message

DelayedReleaseScope drops locks during GC which can cause a thread switch and code reentry
https://bugs.webkit.org/show_bug.cgi?id=141275

Reviewed by Geoffrey Garen.

The original issue is that the CodeCache uses an unsafe method to add new UnlinkedCodeBlocks.
It basically adds a null UnlinkedCodeBlock if there isn't a cached entry and then later
updates the null entry to the result of the compilation.  If during that compilation and
related processing we need to garbage collect, the DelayedReleaseScope would drop locks
possibly allowing another thread to try to get the same source out of the CodeCache.
This second thread would find the null entry and crash.  The fix is to move the processing of
DelayedReleaseScope to when we drop locks and not drop locks during GC.  That was done in
the original patch with the new function releaseDelayedReleasedObjects().

Updated releaseDelayedReleasedObjects() so that objects are released with all locks
dropped.  Now its processing follows these steps
    Increment recursion counter and do recursion check and exit if recursing
    While there are objects to release
        ASSERT that lock is held by current thread
        Take all items from delayed release Vector and put into temporary Vector
        Release API lock
        Release and clear items from temporary vector
        Reaquire API lock
This meets the requirement that we release while the API lock is released and it is
safer processing of the delayed release Vector.

Added new regression test to testapi.

Also added comment describing how recursion into releaseDelayedReleasedObjects() is
prevented.

* API/tests/Regress141275.h: Added.
* API/tests/Regress141275.mm: Added.
(+[JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:completionHandler:]):
(-[JSTEvaluator init]):
(-[JSTEvaluator initWithScript:]):
(-[JSTEvaluator _accessPendingTasksWithBlock:]):
(-[JSTEvaluator insertSignPostWithCompletion:]):
(-[JSTEvaluator evaluateScript:completion:]):
(-[JSTEvaluator evaluateBlock:completion:]):
(-[JSTEvaluator waitForTasksDoneAndReportResults]):
(__JSTRunLoopSourceScheduleCallBack):
(__JSTRunLoopSourcePerformCallBack):
(__JSTRunLoopSourceCancelCallBack):
(-[JSTEvaluator _jsThreadMain]):
(-[JSTEvaluator _sourceScheduledOnRunLoop:]):
(-[JSTEvaluator _setupEvaluatorThreadContextIfNeeded]):
(-[JSTEvaluator _callCompletionHandler:ifNeededWithError:]):
(-[JSTEvaluator _sourcePerform]):
(-[JSTEvaluator _sourceCanceledOnRunLoop:]):
(runRegress141275):
* API/tests/testapi.mm:
(testObjectiveCAPI):
* _javascript_Core.xcodeproj/project.pbxproj:
* heap/Heap.cpp:
(JSC::Heap::releaseDelayedReleasedObjects):
* runtime/JSLock.cpp:
(JSC::JSLock::unlock):

Modified Paths

Added Paths

Diff

Added: trunk/Source/_javascript_Core/API/tests/Regress141275.h (0 => 180992)


--- trunk/Source/_javascript_Core/API/tests/Regress141275.h	                        (rev 0)
+++ trunk/Source/_javascript_Core/API/tests/Regress141275.h	2015-03-04 05:33:37 UTC (rev 180992)
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import <Foundation/Foundation.h>
+#import <_javascript_Core/_javascript_Core.h>
+
+#if JSC_OBJC_API_ENABLED
+
+void runRegress141275();
+
+#endif // JSC_OBJC_API_ENABLED
+

Added: trunk/Source/_javascript_Core/API/tests/Regress141275.mm (0 => 180992)


--- trunk/Source/_javascript_Core/API/tests/Regress141275.mm	                        (rev 0)
+++ trunk/Source/_javascript_Core/API/tests/Regress141275.mm	2015-03-04 05:33:37 UTC (rev 180992)
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2015 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+#import "Regress141275.h"
+
+#import <Foundation/Foundation.h>
+#import <objc/objc.h>
+#import <objc/runtime.h>
+
+#if JSC_OBJC_API_ENABLED
+
+extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
+
+extern int failed;
+
+static const NSUInteger scriptToEvaluate = 50;
+
+@interface JSTEvaluator : NSObject
+- (instancetype)initWithScript:(NSString*)script;
+
+- (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler;
+
+- (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler;
+- (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler;
+
+- (void)waitForTasksDoneAndReportResults;
+@end
+
+
+static const NSString* JSTEvaluatorThreadContextKey = @"JSTEvaluatorThreadContextKey";
+
+/*
+ * A JSTEvaluatorThreadContext is kept in the thread dictionary of threads used by JSEvaluator.
+ *
+ * This includes the run loop thread, and any threads used by _jsSourcePerformQueue to execute a task.
+ */
+@interface JSTEvaluatorThreadContext : NSObject
+@property (weak) JSTEvaluator* evaluator;
+@property (strong) JSContext* jsContext;
+@end
+
+@implementation JSTEvaluatorThreadContext
+@end
+
+
+/*!
+ * A JSTEvaluatorTask is a single task to be executed.
+ *
+ * JSTEvaluator keeps a list of pending tasks. The run loop thread is repsonsible for feeding pending tasks to the _jsSourcePerformQueue, while respecting sign posts.
+ */
+@interface JSTEvaluatorTask : NSObject
+
+@property (nonatomic, copy) void (^evaluateBlock)(JSContext* jsContext);
+@property (nonatomic, copy) void (^completionHandler)(NSError* error);
+@property (nonatomic, copy) NSError* error;
+
++ (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))block completionHandler:(void (^)(NSError* error))completionBlock;
+
+@end
+
+@implementation JSTEvaluatorTask
+
++ (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))evaluationBlock completionHandler:(void (^)(NSError* error))completionHandler
+{
+    JSTEvaluatorTask* task = [self new];
+    task.evaluateBlock = evaluationBlock;
+    task.completionHandler = completionHandler;
+    return task;
+}
+
+@end
+
+@implementation JSTEvaluator {
+    dispatch_queue_t _jsSourcePerformQueue;
+    dispatch_semaphore_t _allScriptsDone;
+    CFRunLoopRef _jsThreadRunLoop;
+    CFRunLoopSourceRef _jsThreadRunLoopSource;
+    JSContext* _jsContext;
+    NSMutableArray* __pendingTasks;
+}
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        _jsSourcePerformQueue = dispatch_queue_create("JSTEval", DISPATCH_QUEUE_CONCURRENT);
+
+        _allScriptsDone = dispatch_semaphore_create(0);
+
+        _jsContext = [JSContext new];
+        _jsContext.name = @"JSTEval";
+        __pendingTasks = [NSMutableArray new];
+
+        NSThread* jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(_jsThreadMain) object:nil];
+        [jsThread setName:@"JSTEval"];
+        [jsThread start];
+
+    }
+    return self;
+}
+
+- (instancetype)initWithScript:(NSString*)script
+{
+    self = [self init];
+    if (self) {
+        __block NSError* scriptError = nil;
+        dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
+        [self evaluateScript:script
+            completion:^(NSError* error) {
+                scriptError = error;
+                dispatch_semaphore_signal(dsema);
+            }];
+        dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
+    }
+    return self;
+}
+
+- (void)_accessPendingTasksWithBlock:(void(^)(NSMutableArray* pendingTasks))block
+{
+    @synchronized(self) {
+        block(__pendingTasks);
+        if (__pendingTasks.count > 0) {
+            if (_jsThreadRunLoop && _jsThreadRunLoopSource) {
+                CFRunLoopSourceSignal(_jsThreadRunLoopSource);
+                CFRunLoopWakeUp(_jsThreadRunLoop);
+            }
+        }
+    }
+}
+
+- (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler
+{
+    [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
+        JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:nil
+            completionHandler:completionHandler];
+
+        [pendingTasks addObject:task];
+    }];
+}
+
+- (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler
+{
+    [self evaluateBlock:^(JSContext* context) {
+        [context evaluateScript:script];
+    } completion:completionHandler];
+}
+
+- (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler
+{
+    NSParameterAssert(evaluationBlock != nil);
+    [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
+        JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:evaluationBlock
+            completionHandler:completionHandler];
+
+        [pendingTasks addObject:task];
+    }];
+}
+
+- (void)waitForTasksDoneAndReportResults
+{
+    NSString* passFailString = @"PASSED";
+
+    if (!dispatch_semaphore_wait(_allScriptsDone, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC))) {
+        int totalScriptsRun = [_jsContext[@"counter"] toInt32];
+
+        if (totalScriptsRun != scriptToEvaluate) {
+            passFailString = @"FAILED";
+            failed = 1;
+        }
+
+        NSLog(@"  Ran a total of %d scripts: %@", totalScriptsRun, passFailString);
+    } else {
+        passFailString = @"FAILED";
+        failed = 1;
+        NSLog(@"  Error, timeout waiting for all tasks to complete: %@", passFailString);
+    }
+}
+
+static void __JSTRunLoopSourceScheduleCallBack(void* info, CFRunLoopRef rl, CFStringRef)
+{
+    @autoreleasepool {
+        [(__bridge JSTEvaluator*)info _sourceScheduledOnRunLoop:rl];
+    }
+}
+
+static void __JSTRunLoopSourcePerformCallBack(void* info )
+{
+    @autoreleasepool {
+        [(__bridge JSTEvaluator*)info _sourcePerform];
+    }
+}
+
+static void __JSTRunLoopSourceCancelCallBack(void* info, CFRunLoopRef rl, CFStringRef)
+{
+    @autoreleasepool {
+        [(__bridge JSTEvaluator*)info _sourceCanceledOnRunLoop:rl];
+    }
+}
+
+- (void)_jsThreadMain
+{
+    @autoreleasepool {
+        const CFIndex kRunLoopSourceContextVersion = 0;
+        CFRunLoopSourceContext sourceContext = {
+            kRunLoopSourceContextVersion, (__bridge void*)(self),
+            NULL, NULL, NULL, NULL, NULL,
+            __JSTRunLoopSourceScheduleCallBack,
+            __JSTRunLoopSourceCancelCallBack,
+            __JSTRunLoopSourcePerformCallBack
+        };
+
+        @synchronized(self) {
+            _jsThreadRunLoop = CFRunLoopGetCurrent();
+            CFRetain(_jsThreadRunLoop);
+
+            _jsThreadRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
+            CFRunLoopAddSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
+        }
+
+        CFRunLoopRun();
+
+        @synchronized(self) {
+            NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
+            [threadDict removeObjectForKey:threadDict[JSTEvaluatorThreadContextKey]];
+
+            CFRelease(_jsThreadRunLoopSource);
+            _jsThreadRunLoopSource = NULL;
+
+            CFRelease(_jsThreadRunLoop);
+            _jsThreadRunLoop = NULL;
+
+            __pendingTasks = nil;
+        }
+    }
+}
+
+- (void)_sourceScheduledOnRunLoop:(CFRunLoopRef)runLoop
+{
+    UNUSED_PARAM(runLoop);
+    assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
+
+    // Wake up the run loop in case requests were submitted prior to the
+    // run loop & run loop source getting created.
+    CFRunLoopSourceSignal(_jsThreadRunLoopSource);
+    CFRunLoopWakeUp(_jsThreadRunLoop);
+}
+
+- (void)_setupEvaluatorThreadContextIfNeeded
+{
+    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
+    JSTEvaluatorThreadContext* context = threadDict[JSTEvaluatorThreadContextKey];
+    // The evaluator may be other evualuator, or nil if this thread has not been used before. Eaither way take ownership.
+    if (context.evaluator != self) {
+        context = [JSTEvaluatorThreadContext new];
+        context.evaluator = self;
+        threadDict[JSTEvaluatorThreadContextKey] = context;
+    }
+}
+
+- (void)_callCompletionHandler:(void(^)(NSError* error))completionHandler ifNeededWithError:(NSError*)error
+{
+    if (completionHandler) {
+        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+            completionHandler(error);
+        });
+    }
+}
+
+- (void)_sourcePerform
+{
+    assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
+
+    __block NSArray* tasks = nil;
+    [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
+        // No signpost, take all tasks.
+        tasks = [pendingTasks copy];
+        [pendingTasks removeAllObjects];
+    }];
+
+    if (tasks.count > 0) {
+        for (JSTEvaluatorTask* task in tasks) {
+            dispatch_block_t block = ^{
+                NSError* error = nil;
+                if (task.evaluateBlock) {
+                    [self _setupEvaluatorThreadContextIfNeeded];
+                    task.evaluateBlock(_jsContext);
+                    if (_jsContext.exception) {
+                        NSLog(@"Did fail on JSContext: %@", _jsContext.name);
+                        NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : [_jsContext.exception[@"message"] toString] };
+                        error = [NSError errorWithDomain:@"JSTEvaluator" code:1 userInfo:userInfo];
+                        _jsContext.exception = nil;
+                    }
+                }
+                [self _callCompletionHandler:task.completionHandler ifNeededWithError:error];
+            };
+
+            if (task.evaluateBlock)
+                dispatch_async(_jsSourcePerformQueue, block);
+            else
+                dispatch_barrier_async(_jsSourcePerformQueue, block);
+        }
+
+        dispatch_barrier_sync(_jsSourcePerformQueue, ^{
+            if ([_jsContext[@"counter"] toInt32] == scriptToEvaluate)
+                dispatch_semaphore_signal(_allScriptsDone);
+        });
+    }
+}
+
+- (void)_sourceCanceledOnRunLoop:(CFRunLoopRef)runLoop
+{
+    UNUSED_PARAM(runLoop);
+    assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
+
+    @synchronized(self) {
+        assert(_jsThreadRunLoop);
+        assert(_jsThreadRunLoopSource);
+
+        CFRunLoopRemoveSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
+        CFRunLoopStop(_jsThreadRunLoop);
+    }
+}
+
+@end
+
+void runRegress141275()
+{
+    // Test that we can execute the same script from multiple threads with a shared context.
+    // See <https://webkit.org/b/141275>
+    NSLog(@"TEST: Testing multiple threads executing the same script with a shared context");
+
+    @autoreleasepool {
+        JSTEvaluator* evaluator = [[JSTEvaluator alloc] initWithScript:@"this['counter'] = 0;"];
+
+        void (^showErrorIfNeeded)(NSError* error) = ^(NSError* error) {
+            if (error) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    NSLog(@"Error: %@", error);
+                });
+            }
+        };
+
+        [evaluator evaluateBlock:^(JSContext* context) {
+            JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+        } completion:showErrorIfNeeded];
+
+        [evaluator evaluateBlock:^(JSContext* context) {
+            context[@"wait"] = ^{
+                [NSThread sleepForTimeInterval:0.01];
+            };
+        } completion:^(NSError* error) {
+            if (error) {
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    NSLog(@"Error: %@", error);
+                });
+            }
+            for (unsigned i = 0; i < scriptToEvaluate; i++)
+                [evaluator evaluateScript:@"this['counter']++; this['wait']();" completion:showErrorIfNeeded];
+        }];
+
+        [evaluator waitForTasksDoneAndReportResults];
+    }
+}
+
+#endif // JSC_OBJC_API_ENABLED

Modified: trunk/Source/_javascript_Core/API/tests/testapi.mm (180991 => 180992)


--- trunk/Source/_javascript_Core/API/tests/testapi.mm	2015-03-04 05:31:23 UTC (rev 180991)
+++ trunk/Source/_javascript_Core/API/tests/testapi.mm	2015-03-04 05:33:37 UTC (rev 180992)
@@ -28,6 +28,7 @@
 #import "CurrentThisInsideBlockGetterTest.h"
 #import "DateTests.h"
 #import "JSExportTests.h"
+#import "Regress141275.h"
 #import "Regress141809.h"
 
 #import <pthread.h>
@@ -486,7 +487,6 @@
 void testObjectiveCAPI()
 {
     NSLog(@"Testing Objective-C API");
-
     @autoreleasepool {
         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
@@ -1386,6 +1386,7 @@
     currentThisInsideBlockGetterTest();
     runDateTests();
     runJSExportTests();
+    runRegress141275();
     runRegress141809();
 }
 

Modified: trunk/Source/_javascript_Core/ChangeLog (180991 => 180992)


--- trunk/Source/_javascript_Core/ChangeLog	2015-03-04 05:31:23 UTC (rev 180991)
+++ trunk/Source/_javascript_Core/ChangeLog	2015-03-04 05:33:37 UTC (rev 180992)
@@ -1,3 +1,64 @@
+2015-03-03  Michael Saboff  <msab...@apple.com>
+
+        DelayedReleaseScope drops locks during GC which can cause a thread switch and code reentry
+        https://bugs.webkit.org/show_bug.cgi?id=141275
+
+        Reviewed by Geoffrey Garen.
+
+        The original issue is that the CodeCache uses an unsafe method to add new UnlinkedCodeBlocks.
+        It basically adds a null UnlinkedCodeBlock if there isn't a cached entry and then later
+        updates the null entry to the result of the compilation.  If during that compilation and
+        related processing we need to garbage collect, the DelayedReleaseScope would drop locks
+        possibly allowing another thread to try to get the same source out of the CodeCache.
+        This second thread would find the null entry and crash.  The fix is to move the processing of
+        DelayedReleaseScope to when we drop locks and not drop locks during GC.  That was done in
+        the original patch with the new function releaseDelayedReleasedObjects().
+
+        Updated releaseDelayedReleasedObjects() so that objects are released with all locks
+        dropped.  Now its processing follows these steps
+            Increment recursion counter and do recursion check and exit if recursing
+            While there are objects to release
+                ASSERT that lock is held by current thread
+                Take all items from delayed release Vector and put into temporary Vector
+                Release API lock
+                Release and clear items from temporary vector
+                Reaquire API lock
+        This meets the requirement that we release while the API lock is released and it is
+        safer processing of the delayed release Vector.
+
+        Added new regression test to testapi.
+
+        Also added comment describing how recursion into releaseDelayedReleasedObjects() is
+        prevented.
+
+        * API/tests/Regress141275.h: Added.
+        * API/tests/Regress141275.mm: Added.
+        (+[JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:completionHandler:]):
+        (-[JSTEvaluator init]):
+        (-[JSTEvaluator initWithScript:]):
+        (-[JSTEvaluator _accessPendingTasksWithBlock:]):
+        (-[JSTEvaluator insertSignPostWithCompletion:]):
+        (-[JSTEvaluator evaluateScript:completion:]):
+        (-[JSTEvaluator evaluateBlock:completion:]):
+        (-[JSTEvaluator waitForTasksDoneAndReportResults]):
+        (__JSTRunLoopSourceScheduleCallBack):
+        (__JSTRunLoopSourcePerformCallBack):
+        (__JSTRunLoopSourceCancelCallBack):
+        (-[JSTEvaluator _jsThreadMain]):
+        (-[JSTEvaluator _sourceScheduledOnRunLoop:]):
+        (-[JSTEvaluator _setupEvaluatorThreadContextIfNeeded]):
+        (-[JSTEvaluator _callCompletionHandler:ifNeededWithError:]):
+        (-[JSTEvaluator _sourcePerform]):
+        (-[JSTEvaluator _sourceCanceledOnRunLoop:]):
+        (runRegress141275):
+        * API/tests/testapi.mm:
+        (testObjectiveCAPI):
+        * _javascript_Core.xcodeproj/project.pbxproj:
+        * heap/Heap.cpp:
+        (JSC::Heap::releaseDelayedReleasedObjects):
+        * runtime/JSLock.cpp:
+        (JSC::JSLock::unlock):
+
 2015-03-03  Filip Pizlo  <fpi...@apple.com>
 
         DFG should constant fold GetScope, and accesses to the scope register in the ByteCodeParser should not pretend that it's a constant as that breaks OSR exit liveness tracking

Modified: trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj (180991 => 180992)


--- trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj	2015-03-04 05:31:23 UTC (rev 180991)
+++ trunk/Source/_javascript_Core/_javascript_Core.xcodeproj/project.pbxproj	2015-03-04 05:33:37 UTC (rev 180992)
@@ -934,6 +934,7 @@
 		65525FC51A6DD801007B5495 /* NullSetterFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 65525FC31A6DD3B3007B5495 /* NullSetterFunction.cpp */; };
 		6553A33117A1F1EE008CF6F3 /* CommonSlowPathsExceptions.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6553A32F17A1F1EE008CF6F3 /* CommonSlowPathsExceptions.cpp */; };
 		6553A33217A1F1EE008CF6F3 /* CommonSlowPathsExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 6553A33017A1F1EE008CF6F3 /* CommonSlowPathsExceptions.h */; };
+		65570F5A1AA4C3EA009B3C23 /* Regress141275.mm in Sources */ = {isa = PBXBuildFile; fileRef = 65570F591AA4C00A009B3C23 /* Regress141275.mm */; };
 		655EB29B10CE2581001A990E /* NodesCodegen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 655EB29A10CE2581001A990E /* NodesCodegen.cpp */; };
 		657CF45819BF6662004ACBF2 /* JSCallee.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 657CF45619BF6662004ACBF2 /* JSCallee.cpp */; };
 		657CF45919BF6662004ACBF2 /* JSCallee.h in Headers */ = {isa = PBXBuildFile; fileRef = 657CF45719BF6662004ACBF2 /* JSCallee.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -2597,6 +2598,8 @@
 		65525FC41A6DD3B3007B5495 /* NullSetterFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NullSetterFunction.h; sourceTree = "<group>"; };
 		6553A32F17A1F1EE008CF6F3 /* CommonSlowPathsExceptions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommonSlowPathsExceptions.cpp; sourceTree = "<group>"; };
 		6553A33017A1F1EE008CF6F3 /* CommonSlowPathsExceptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CommonSlowPathsExceptions.h; sourceTree = "<group>"; };
+		65570F581AA4C00A009B3C23 /* Regress141275.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Regress141275.h; path = API/tests/Regress141275.h; sourceTree = "<group>"; };
+		65570F591AA4C00A009B3C23 /* Regress141275.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = Regress141275.mm; path = API/tests/Regress141275.mm; sourceTree = "<group>"; };
 		655EB29A10CE2581001A990E /* NodesCodegen.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NodesCodegen.cpp; sourceTree = "<group>"; };
 		6560A4CF04B3B3E7008AE952 /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = /System/Library/Frameworks/CoreFoundation.framework; sourceTree = "<absolute>"; };
 		65621E6B089E859700760F35 /* PropertySlot.cpp */ = {isa = PBXFileReference; fileEncoding = 30; indentWidth = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PropertySlot.cpp; sourceTree = "<group>"; tabWidth = 8; };
@@ -3728,6 +3731,8 @@
 				C288B2DD18A54D3E007BE40B /* DateTests.mm */,
 				C2181FC018A948FB0025A235 /* JSExportTests.h */,
 				C2181FC118A948FB0025A235 /* JSExportTests.mm */,
+				65570F581AA4C00A009B3C23 /* Regress141275.h */,
+				65570F591AA4C00A009B3C23 /* Regress141275.mm */,
 				FEB51F6A1A97B688001F921C /* Regress141809.h */,
 				FEB51F6B1A97B688001F921C /* Regress141809.mm */,
 				144005170A531CB50005F061 /* minidom */,
@@ -6757,6 +6762,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				65570F5A1AA4C3EA009B3C23 /* Regress141275.mm in Sources */,
 				C29ECB031804D0ED00D2CBB4 /* CurrentThisInsideBlockGetterTest.mm in Sources */,
 				FEB51F6C1A97B688001F921C /* Regress141809.mm in Sources */,
 				C20328201981979D0088B499 /* CustomGlobalObjectClassTest.c in Sources */,

Modified: trunk/Source/_javascript_Core/heap/Heap.cpp (180991 => 180992)


--- trunk/Source/_javascript_Core/heap/Heap.cpp	2015-03-04 05:31:23 UTC (rev 180991)
+++ trunk/Source/_javascript_Core/heap/Heap.cpp	2015-03-04 05:33:37 UTC (rev 180992)
@@ -368,10 +368,25 @@
 void Heap::releaseDelayedReleasedObjects()
 {
 #if USE(CF)
+    // We need to guard against the case that releasing an object can create more objects due to the
+    // release calling into JS. When those JS call(s) exit and all locks are being dropped we end up
+    // back here and could try to recursively release objects. We guard that with a recursive entry
+    // count. Only the initial call will release objects, recursive calls simple return and let the
+    // the initial call to the function take care of any objects created during release time.
+    // This also means that we need to loop until there are no objects in m_delayedReleaseObjects
+    // and use a temp Vector for the actual releasing.
     if (!m_delayedReleaseRecursionCount++) {
         while (!m_delayedReleaseObjects.isEmpty()) {
-            RetainPtr<CFTypeRef> objectToRelease = m_delayedReleaseObjects.takeLast();
-            objectToRelease.clear();
+            ASSERT(m_vm->currentThreadIsHoldingAPILock());
+
+            Vector<RetainPtr<CFTypeRef>> objectsToRelease = WTF::move(m_delayedReleaseObjects);
+
+            {
+                // We need to drop locks before calling out to arbitrary code.
+                JSLock::DropAllLocks dropAllLocks(m_vm);
+
+                objectsToRelease.clear();
+            }
         }
     }
     m_delayedReleaseRecursionCount--;

Modified: trunk/Source/_javascript_Core/runtime/JSLock.cpp (180991 => 180992)


--- trunk/Source/_javascript_Core/runtime/JSLock.cpp	2015-03-04 05:31:23 UTC (rev 180991)
+++ trunk/Source/_javascript_Core/runtime/JSLock.cpp	2015-03-04 05:33:37 UTC (rev 180992)
@@ -157,10 +157,14 @@
     RELEASE_ASSERT(currentThreadIsHoldingLock());
     ASSERT(m_lockCount >= unlockCount);
 
+    // Maintain m_lockCount while calling willReleaseLock() so that its callees know that
+    // they still have the lock.
+    if (unlockCount == m_lockCount)
+        willReleaseLock();
+
     m_lockCount -= unlockCount;
 
     if (!m_lockCount) {
-        willReleaseLock();
 
         if (!m_hasExclusiveThread) {
             m_ownerThreadID = std::thread::id();
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to