270 lines
11 KiB
C++
270 lines
11 KiB
C++
/*
|
|
* 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
|