blob: 8a7f28023c7d1b3767f0e791006b8d020a6576ed [file] [log] [blame]
/*
* 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