Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: 9ea05fa06f20960616a47235f58d08bb1fbdea55
      
https://github.com/WebKit/WebKit/commit/9ea05fa06f20960616a47235f58d08bb1fbdea55
  Author: Mark Lam <[email protected]>
  Date:   2025-09-16 (Tue, 16 Sep 2025)

  Changed paths:
    M Source/JavaScriptCore/runtime/StackManager.cpp
    M Source/JavaScriptCore/runtime/StackManager.h
    M Source/JavaScriptCore/runtime/VM.h
    M Source/JavaScriptCore/runtime/VMTraps.cpp
    M Source/JavaScriptCore/runtime/VMTraps.h
    M Source/WTF/wtf/Compiler.h
    M Source/WTF/wtf/Forward.h
    M Source/WTF/wtf/OptionSet.h
    M Source/WTF/wtf/OptionSetHash.h
    M Tools/TestWebKitAPI/Tests/WTF/OptionSet.cpp

  Log Message:
  -----------
  Make VM::setHasTerminationRequest() thread-safe.
https://bugs.webkit.org/show_bug.cgi?id=298798
rdar://160494627

Reviewed by Yijia Huang.

VM::setHasTerminationRequest() should be thread-safe but is not because it sets 
a bit on the
VM::m_entryScopeServices OptionSet, and the bit set operation is not atomic..

For the record, this is how the concurrent access works:
1. VM::m_entryScopeServices is normally set by the mutator thread running that 
VM.
2. However, the Worker.terminate() API can be used by the main thread to 
terminate worker threads.
    Worker.terminate() calls ...
        WorkerMessagingProxy::terminateWorkerGlobalScope(), which calls ...
            WorkerOrWorkletThread::stop(), which calls ...
                
WorkerOrWorkletScriptController::scheduleExecutionTermination(), which calls ...
                    VM::notifyNeedTermination(), which calls ...
                        VM::setHasTerminationRequest(), which calls ...
                            VM::requestEntryScopeService(), and ...

VM::requestEntryScopeService() does bitwise OR of the 
EntryScopeService::ResetTerminationRequest bit
into the worker thread's VM::m_entryScopeServices.  This is not thread safe 
because it may collide with
the worker thread writing values into its VM::m_entryScopeServices.

We can fix this by moving the EntryScopeService::ResetTerminationRequest bit 
into a separate byte that
is safe to write to concurrently.  However, will go one step further and make a 
concurrent-safe
OptionSet and use that for storing the 
EntryScopeService::ResetTerminationRequest bit.  This way, we
can add more concurrent bits if needed.

In practice, the bug never manifests because it is extremely rare for 
VM::m_entryScopeServices to ever
be written to, and the impact is benign.  Regardless, we are fixing this.

Changes:
1. #define a CONCURRENT_SAFE macro in Compiler.h, and use it to annotate 
functions that we intend to
   be safe to call concurrently from other threads.  Currently, CONCURRENT_SAFE 
is just a documentation
   mechanism, and does not correspond to any compiler artifact.  In the future, 
if desired, we can
   add a verifier pass that ensures that CONCURRENT_SAFE functions can only 
call other CONCURRENT_SAFE
   functions, and such a verifier would have caught the bug in this commit.

2. Move EntryScopeService::ResetTerminationRequest into a separate enum class 
ConcurrentEntryScopeService.
   While EntryScopeService bits continue to be stored in 
VM::m_entryScopeServices,
   ConcurrentEntryScopeService bits will now be stored in a new 
VM::m_concurrentEntryScopeServices.

   Both VM::m_entryScopeServices and VM::m_concurrentEntryScopeServices are 
backed by storage in
   VM::m_entryScopeServicesRawBits.  This allows us to do a single null check on
   VM::m_entryScopeServicesRawBits to quickly determine if we have any 
"services" that we need to
   service (concurrent or otherwise).

3. Enhance WTF::OptionSet to be able to accept a ConcurrencyTag template 
parameter.  By default,
   if not explicitly specified, ConcurrencyTag::None will be used.  This 
results in OptionSet
   behaving exactly as it is today.

   However, we can now use OptionSet with ConcurrencyTag::Atomic, which will 
switch its operations
   to operate on the its m_storage in an atomic way.  This is done for add() 
and remove() where we
   add / remove bits to / from it m_storage word.

   We also enhance hasExactlyOneBitSet() and toSingleValue() so that they are 
not subject to TOCTOU
   issues which may cause their result to be internally inconsistent.  These 
functions need to read
   m_storage more than once.  So, the ConcurrencyTag::Atomic version needs to 
make a copy of
   m_storage and operate on that single copy that is not subject to concurrent 
edits.

   VM::m_concurrentEntryScopeServices is implemented using the 
ConcurrencyTag::Atomic variant of
   OptionSet.

TestWebKitAPI OptionSet tests has been updated to test the 
ConcurrencyTag::Atomic variant as well.
The VM entryScopeServices mechanism is covered by existing tests e.g. JSC's 
testapi.

* Source/JavaScriptCore/runtime/StackManager.cpp:
(JSC::StackManager::requestStop):
(JSC::StackManager::cancelStop):
* Source/JavaScriptCore/runtime/StackManager.h:
* Source/JavaScriptCore/runtime/VM.h:
(JSC::VM::clearHasTerminationRequest):
(JSC::VM::setHasTerminationRequest):
(JSC::VM::hasAnyEntryScopeServiceRequest):
(JSC::VM::requestEntryScopeService):
(JSC::VM::entryScopeServices):
(JSC::VM::concurrentEntryScopeServices):
(JSC::VM::hasEntryScopeServiceRequest):
(JSC::VM::clearEntryScopeService):
(JSC::VM::notifyNeedDebuggerBreak):
(JSC::VM::notifyNeedShellTimeoutCheck):
(JSC::VM::notifyNeedTermination):
(JSC::VM::notifyNeedWatchdogCheck):
* Source/JavaScriptCore/runtime/VMTraps.cpp:
(JSC::VMTraps::cancelThreadStopIfNeeded):
(JSC::VMTraps::requestThreadStopIfNeeded):
* Source/JavaScriptCore/runtime/VMTraps.h:
(JSC::VMTraps::clearTrap):
(JSC::VMTraps::fireTrap):
* Source/WTF/wtf/Compiler.h:
* Source/WTF/wtf/Forward.h:
* Source/WTF/wtf/OptionSet.h:
(WTF::OptionSet::add):
(WTF::OptionSet::remove):
(WTF::OptionSet::hasExactlyOneBitSet const):
(WTF::OptionSet::toSingleValue const):
(WTF::isValidOptionSet):
(WTF::ConstexprOptionSet::ConstexprOptionSet):
(WTF::ConstexprOptionSet::operator* const):
* Source/WTF/wtf/OptionSetHash.h:
(WTF::DefaultHash<OptionSet<T>>::hash): Deleted.
(WTF::DefaultHash<OptionSet<T>>::equal): Deleted.
(WTF::HashTraits<OptionSet<T>>::emptyValue): Deleted.
(WTF::HashTraits<OptionSet<T>>::constructDeletedValue): Deleted.
(WTF::HashTraits<OptionSet<T>>::isDeletedValue): Deleted.
* Tools/TestWebKitAPI/Tests/WTF/OptionSet.cpp:
(TestWebKitAPI::TEST(WTF_OptionSet, EmptySet)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsOneFlag)):
(TestWebKitAPI::TEST(WTF_OptionSet, Equal)):
(TestWebKitAPI::TEST(WTF_OptionSet, NotEqual)):
(TestWebKitAPI::TEST(WTF_OptionSet, Or)):
(TestWebKitAPI::TEST(WTF_OptionSet, OrAssignment)):
(TestWebKitAPI::TEST(WTF_OptionSet, Minus)):
(TestWebKitAPI::TEST(WTF_OptionSet, AddAndRemove)):
(TestWebKitAPI::TEST(WTF_OptionSet, Set)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsTwoFlags)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsTwoFlags2)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsTwoFlags3)):
(TestWebKitAPI::TEST(WTF_OptionSet, EmptyOptionSetToRawValueToOptionSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, 
OptionSetThatContainsOneFlagToRawValueToOptionSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, 
OptionSetThatContainsOneFlagToRawValueToOptionSet2)):
(TestWebKitAPI::TEST(WTF_OptionSet, 
OptionSetThatContainsTwoFlagsToRawValueToOptionSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, 
OptionSetThatContainsTwoFlagsToRawValueToOptionSet2)):
(TestWebKitAPI::TEST(WTF_OptionSet, TwoIteratorsIntoSameOptionSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, IterateOverOptionSetThatContainsTwoFlags)):
(TestWebKitAPI::TEST(WTF_OptionSet, IterateOverOptionSetThatContainsFlags2)):
(TestWebKitAPI::TEST(WTF_OptionSet, NextItemAfterLargestIn32BitFlagSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, NextItemAfterLargestIn64BitFlagSet)):
(TestWebKitAPI::TEST(WTF_OptionSet, 
IterationOrderTheSameRegardlessOfInsertionOrder)):
(TestWebKitAPI::TEST(WTF_OptionSet, OperatorAnd)):
(TestWebKitAPI::TEST(WTF_OptionSet, OperatorXor)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsAny)):
(TestWebKitAPI::TEST(WTF_OptionSet, ContainsAll)):
(TestWebKitAPI::TEST(WTF_OptionSet, HashSet)):

Canonical link: https://commits.webkit.org/300049@main



To unsubscribe from these emails, change your notification settings at 
https://github.com/WebKit/WebKit/settings/notifications
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to