| /* | 
 |  * Copyright (C) 2017-2021 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. ``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 | 
 |  * 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. | 
 |  */ | 
 |  | 
 | #pragma once | 
 |  | 
 | #include <wtf/AutomaticThread.h> | 
 | #include <wtf/Box.h> | 
 | #include <wtf/Expected.h> | 
 | #include <wtf/HashSet.h> | 
 | #include <wtf/Lock.h> | 
 | #include <wtf/Locker.h> | 
 | #include <wtf/RefPtr.h> | 
 | #include <wtf/StackBounds.h> | 
 |  | 
 | namespace JSC { | 
 |  | 
 | class CallFrame; | 
 | class JSGlobalObject; | 
 | class VM; | 
 |  | 
 | class VMTraps { | 
 | public: | 
 |     using BitField = uint32_t; | 
 |     static constexpr size_t bitsInBitField = sizeof(BitField) * CHAR_BIT; | 
 |  | 
 |     // The following are the type of VMTrap events / signals that can be fired. | 
 |     // This list should be sorted in servicing priority order from highest to | 
 |     // lowest. | 
 |     // | 
 |     // The currently imlemented events are (in highest to lowest priority): | 
 |     // | 
 |     //  NeedShellTimeoutCheck | 
 |     //  - Only used by the jsc shell to check if we need to force a hard shutdown. | 
 |     //  - This event may fire more than once before the jsc shell forces the | 
 |     //    shutdown (see NeedWatchdogCheck's discussion of CPU time for why | 
 |     //    this may be). | 
 |     // | 
 |     //  NeedTermination | 
 |     //  - Used to request the termination of execution of the "current" stack. | 
 |     //    Note: "Termination" here simply means we terminate whatever is currently | 
 |     //    executing on the stack. It does not mean termination of the VM, and hence, | 
 |     //    is not permanent. Permanent VM termination mechanisms (like stopping the | 
 |     //    request to stop a woker thread) may use this Event to terminate the | 
 |     //    "current" stack, but it needs to do some additional work to prevent | 
 |     //    re-entry into the VM. | 
 |     // | 
 |     //  - The mechanism for achieving this stack termination is by throwing the | 
 |     //    uncatchable TerminationException that piggy back on the VM's exception | 
 |     //    handling machinery to the unwind stack. The TerminationException is | 
 |     //    uncatchable in the sense that the VM will refuse to let JS code's | 
 |     //    catch handlers catch the exception. C++ code in the VM (that calls into | 
 |     //    JS) needs to do exception checks, and make sure to propagate the | 
 |     //    exception if it is the TerminationException. | 
 |     // | 
 |     //  - Again, the termination request is not permanent. Once the VM unwinds out | 
 |     //    of the "current" execution state on the stack, the client may choose to | 
 |     //    clear the exception, and re-enter the VM to executing JS code again. | 
 |     //    See NeedWatchdogCheck below on why the VM watchdog needs this ability | 
 |     //    to re-enter the VM after terminating the current stack. | 
 |     // | 
 |     //  - Many clients enter the VM via APIs that return an uncaught exception | 
 |     //    in a NakedPointer<Exception>&. Those APIs would automatically clear | 
 |     //    the uncaught TerminationException and return it via the | 
 |     //    NakedPointer<Exception>&. Hence, the VM is ready for re-entry upon | 
 |     //    returning to the client. | 
 |     // | 
 |     //  - In the above notes, "current" (as in "current" stack) is in quotes because | 
 |     //    NeedTermination needs to guarantee that the TerminationException has | 
 |     //    been thrown in response to this event. If the event fires just before | 
 |     //    the VM exits and the TerminationException was not thrown yet, then we'll | 
 |     //    keep the NeedTermination trap bit set for the next VM entry. In this case, | 
 |     //    the termination will actual happen on the next stack of execution. | 
 |     // | 
 |     //    This behavior is needed because some clients rely on seeing an uncaught | 
 |     //    TerminationException to know that a termination has been requested. | 
 |     //    Technically, there are better ways for the client to know about the | 
 |     //    termination request (after all, the termination is initiated by the | 
 |     //    client). However, this is how some current client code works. So, we need | 
 |     //    to retain this behavior until we can change all the clients that rely on | 
 |     //    it. | 
 |     // | 
 |     //  NeedWatchdogCheck | 
 |     //  - Used to request a check as to whether the watchdog timer has expired. | 
 |     //    Note: the watchdog timeout is logically measured in CPU time. However, | 
 |     //    the real timer implementation (that fires this NeedWatchdogCheck event) | 
 |     //    has to operate on wall clock time. Hence, NeedWatchdogCheck firing does not | 
 |     //    necessarily mean that the watchdog timeout has expired, and we can expect | 
 |     //    to see NeedWatchdogCheck firing more than once for a single watchdog | 
 |     //    timeout. | 
 |     // | 
 |     //  - The watchdog mechanism has the option to request termination of the | 
 |     //    the current execution stack on watchdog timeout (see | 
 |     //    Watchdog::shouldTerminate()). If termination is requested, it will | 
 |     //    be executed via the same mechanism as NeedTermination (see how the | 
 |     //    NeedWatchdogCheck case can fall through to the NeedTermination case in | 
 |     //    VMTraps::handleTraps()). | 
 |     // | 
 |     //  - The watchdog timing out is not permanent i.e. after terminating the | 
 |     //    current stack, the client may choose to re-enter the VM to execute more | 
 |     //    JS. For example, a client may use the watchdog to ensure that an untrusted | 
 |     //    3rd party script (that it runs) does not get trapped in an infinite loop. | 
 |     //    If so, the watchdog timeout can terminate that script. After terminating | 
 |     //    that bad script, the client may choose to allow other 3rd party scripts | 
 |     //    to execute, or even allow more tries on the current one that timed out. | 
 |     //    Hence, the timeout and termination must not be permanent. | 
 |     // | 
 |     //    This is why termination via the NeedTermination event is not permanent, | 
 |     //    but only terminates the "current" stack. | 
 |     // | 
 |     //  NeedDebuggerBreak | 
 |     //  - Services asynchronous debugger break requests. | 
 |     // | 
 |     //  NeedExceptionHandling | 
 |     //  - Unlike the other events (which are asynchronous to the mutator thread), | 
 |     //    NeedExceptionHandling is set when the mutator thread throws a JS exception | 
 |     //    and cleared when the exception is handled / caught. | 
 |     // | 
 |     //  - The reason why NeedExceptionHandling is a bit on VMTraps as well is so | 
 |     //    that we can piggy back on all the RETURN_IF_EXCEPTION checks in C++ code | 
 |     //    to service VMTraps as well. Having the NeedExceptionHandling event as | 
 |     //    part of VMTraps allows RETURN_IF_EXCEPTION to optimally only do a single | 
 |     //    check to determine if the VM possibly has a pending exception to handle, | 
 |     //    as well as if there are asynchronous VMTraps events to handle. | 
 |  | 
 | #define FOR_EACH_VMTRAPS_EVENTS(v) \ | 
 |     v(NeedShellTimeoutCheck) \ | 
 |     v(NeedTermination) \ | 
 |     v(NeedWatchdogCheck) \ | 
 |     v(NeedDebuggerBreak) \ | 
 |     v(NeedExceptionHandling) | 
 |  | 
 | #define DECLARE_VMTRAPS_EVENT_BIT_SHIFT(event__)  event__##BitShift, | 
 |     enum EventBitShift { | 
 |         FOR_EACH_VMTRAPS_EVENTS(DECLARE_VMTRAPS_EVENT_BIT_SHIFT) | 
 |         NumberOfEvents, // This entry must be last in this list. | 
 |     }; | 
 | #undef DECLARE_VMTRAPS_EVENT_BIT_SHIFT | 
 |  | 
 |     using Event = BitField; | 
 |  | 
 | #define DECLARE_VMTRAPS_EVENT(event__) \ | 
 |     static_assert(event__##BitShift < bitsInBitField); \ | 
 |     static constexpr Event event__ = (1 << event__##BitShift); | 
 |     FOR_EACH_VMTRAPS_EVENTS(DECLARE_VMTRAPS_EVENT) | 
 | #undef DECLARE_VMTRAPS_EVENT | 
 |  | 
 | #undef FOR_EACH_VMTRAPS_EVENTS | 
 |  | 
 |     static constexpr Event NoEvent = 0; | 
 |  | 
 |     static_assert(NumberOfEvents <= bitsInBitField); | 
 |     static constexpr BitField AllEvents = (1ull << NumberOfEvents) - 1; | 
 |     static constexpr BitField AsyncEvents = AllEvents & ~NeedExceptionHandling; | 
 |     static constexpr BitField NonDebuggerEvents = AllEvents & ~NeedDebuggerBreak; | 
 |     static constexpr BitField NonDebuggerAsyncEvents = AsyncEvents & ~NeedDebuggerBreak; | 
 |  | 
 |     static constexpr bool onlyContainsAsyncEvents(BitField events) | 
 |     { | 
 |         return (AsyncEvents & events) && !(~AsyncEvents & events); | 
 |     } | 
 |  | 
 |     ~VMTraps(); | 
 |     VMTraps(); | 
 |  | 
 |     static void initializeSignals(); | 
 |  | 
 |     void willDestroyVM(); | 
 |  | 
 |     bool needHandling(BitField mask) const { return m_trapBits.loadRelaxed() & mask; } | 
 |     void* trapBitsAddress() { return &m_trapBits; } | 
 |  | 
 |     enum class DeferAction { | 
 |         DeferForAWhile, | 
 |         DeferUntilEndOfScope | 
 |     }; | 
 |  | 
 |     bool isDeferringTermination() const { return m_deferTerminationCount; } | 
 |     void deferTermination(DeferAction); | 
 |     void undoDeferTermination(DeferAction); | 
 |  | 
 |     void notifyGrabAllLocks() | 
 |     { | 
 |         if (needHandling(AsyncEvents)) | 
 |             invalidateCodeBlocksOnStack(); | 
 |     } | 
 |  | 
 |     bool hasTrapBit(Event event, BitField mask) | 
 |     { | 
 |         BitField maskedBits = event & mask; | 
 |         return m_trapBits.loadRelaxed() & maskedBits; | 
 |     } | 
 |     void clearTrapBit(Event event) { m_trapBits.exchangeAnd(~event); } | 
 |     void setTrapBit(Event event) | 
 |     { | 
 |         ASSERT((event & ~AllEvents) == 0); | 
 |         m_trapBits.exchangeOr(event); | 
 |     } | 
 |  | 
 |     JS_EXPORT_PRIVATE void fireTrap(Event); | 
 |     void handleTraps(BitField mask = AsyncEvents); | 
 |  | 
 | #if ENABLE(SIGNAL_BASED_VM_TRAPS) | 
 |     struct SignalContext; | 
 |     void tryInstallTrapBreakpoints(struct VMTraps::SignalContext&, StackBounds); | 
 | #endif | 
 |  | 
 | private: | 
 |     VM& vm() const; | 
 |  | 
 |     JS_EXPORT_PRIVATE void deferTerminationSlow(DeferAction); | 
 |     JS_EXPORT_PRIVATE void undoDeferTerminationSlow(DeferAction); | 
 |     Event takeTopPriorityTrap(BitField mask); | 
 |  | 
 | #if ENABLE(SIGNAL_BASED_VM_TRAPS) | 
 |     class SignalSender; | 
 |     friend class SignalSender; | 
 |  | 
 |     void invalidateCodeBlocksOnStack(); | 
 |     void invalidateCodeBlocksOnStack(CallFrame* topCallFrame); | 
 |     void invalidateCodeBlocksOnStack(Locker<Lock>& codeBlockSetLocker, CallFrame* topCallFrame); | 
 |  | 
 |     void addSignalSender(SignalSender*); | 
 |     void removeSignalSender(SignalSender*); | 
 | #else | 
 |     void invalidateCodeBlocksOnStack() { } | 
 |     void invalidateCodeBlocksOnStack(CallFrame*) { } | 
 | #endif | 
 |  | 
 |     static constexpr BitField NeedExceptionHandlingMask = ~(1 << NeedExceptionHandling); | 
 |  | 
 |     Box<Lock> m_lock; | 
 |     Ref<AutomaticThreadCondition> m_condition; | 
 |     Atomic<BitField> m_trapBits { 0 }; | 
 |     bool m_needToInvalidatedCodeBlocks { false }; | 
 |     bool m_isShuttingDown { false }; | 
 |     bool m_suspendedTerminationException { false }; | 
 |     unsigned m_deferTerminationCount { 0 }; | 
 |  | 
 | #if ENABLE(SIGNAL_BASED_VM_TRAPS) | 
 |     RefPtr<SignalSender> m_signalSender; | 
 | #endif | 
 |  | 
 |     friend class LLIntOffsetsExtractor; | 
 |     friend class SignalSender; | 
 | }; | 
 |  | 
 | } // namespace JSC |