]> git.saurik.com Git - apple/javascriptcore.git/blobdiff - debugger/Debugger.cpp
JavaScriptCore-7600.1.4.13.1.tar.gz
[apple/javascriptcore.git] / debugger / Debugger.cpp
index 64f60029d9b6d3a55e6f1047a9f1813520042731..168d7a3f43793a247fa98fbe3da21947013ca7f0 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright (C) 2008 Apple Inc. All rights reserved.
+ *  Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved.
  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
  *  Copyright (C) 2001 Peter Kelly (pmk@post.com)
  *
 #include "config.h"
 #include "Debugger.h"
 
+#include "CodeBlock.h"
+#include "DebuggerCallFrame.h"
 #include "Error.h"
+
+#include "HeapIterationScope.h"
 #include "Interpreter.h"
+#include "JSCJSValueInlines.h"
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
+#include "JSCInlines.h"
 #include "Parser.h"
 #include "Protect.h"
+#include "VMEntryScope.h"
 
 namespace {
 
 using namespace JSC;
 
-class Recompiler {
+class Recompiler : public MarkedBlock::VoidFunctor {
 public:
-    Recompiler(Debugger*);
+    Recompiler(JSC::Debugger*);
     ~Recompiler();
     void operator()(JSCell*);
 
@@ -43,12 +50,12 @@ private:
     typedef HashSet<FunctionExecutable*> FunctionExecutableSet;
     typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap;
     
-    Debugger* m_debugger;
+    JSC::Debugger* m_debugger;
     FunctionExecutableSet m_functionExecutables;
     SourceProviderMap m_sourceProviders;
 };
 
-inline Recompiler::Recompiler(Debugger* debugger)
+inline Recompiler::Recompiler(JSC::Debugger* debugger)
     : m_debugger(debugger)
 {
 }
@@ -59,15 +66,15 @@ inline Recompiler::~Recompiler()
     // JavaScript in the inspector.
     SourceProviderMap::const_iterator end = m_sourceProviders.end();
     for (SourceProviderMap::const_iterator iter = m_sourceProviders.begin(); iter != end; ++iter)
-        m_debugger->sourceParsed(iter->second, iter->first, -1, UString());
+        m_debugger->sourceParsed(iter->value, iter->key, -1, String());
 }
 
 inline void Recompiler::operator()(JSCell* cell)
 {
-    if (!cell->inherits(&JSFunction::s_info))
+    if (!cell->inherits(JSFunction::info()))
         return;
 
-    JSFunction* function = asFunction(cell);
+    JSFunction* function = jsCast<JSFunction*>(cell);
     if (function->executable()->isHostFunction())
         return;
 
@@ -75,12 +82,13 @@ inline void Recompiler::operator()(JSCell* cell)
 
     // Check if the function is already in the set - if so,
     // we've already retranslated it, nothing to do here.
-    if (!m_functionExecutables.add(executable).second)
+    if (!m_functionExecutables.add(executable).isNewEntry)
         return;
 
-    ExecState* exec = function->scope()->globalObject->JSGlobalObject::globalExec();
-    executable->discardCode();
-    if (m_debugger == function->scope()->globalObject->debugger())
+    ExecState* exec = function->scope()->globalObject()->JSGlobalObject::globalExec();
+    executable->clearCodeIfNotCompiling();
+    executable->clearUnlinkedCodeForRecompilationIfNotCompiling();
+    if (m_debugger == function->scope()->globalObject()->debugger())
         m_sourceProviders.add(executable->source().provider(), exec);
 }
 
@@ -88,6 +96,73 @@ inline void Recompiler::operator()(JSCell* cell)
 
 namespace JSC {
 
+class DebuggerCallFrameScope {
+public:
+    DebuggerCallFrameScope(Debugger& debugger)
+        : m_debugger(debugger)
+    {
+        ASSERT(!m_debugger.m_currentDebuggerCallFrame);
+        if (m_debugger.m_currentCallFrame)
+            m_debugger.m_currentDebuggerCallFrame = DebuggerCallFrame::create(debugger.m_currentCallFrame);
+    }
+
+    ~DebuggerCallFrameScope()
+    {
+        if (m_debugger.m_currentDebuggerCallFrame) {
+            m_debugger.m_currentDebuggerCallFrame->invalidate();
+            m_debugger.m_currentDebuggerCallFrame = 0;
+        }
+    }
+
+private:
+    Debugger& m_debugger;
+};
+
+// This is very similar to TemporaryChange<bool>, but that cannot be used
+// as the m_isPaused field uses only one bit.
+class TemporaryPausedState {
+public:
+    TemporaryPausedState(Debugger& debugger)
+        : m_debugger(debugger)
+    {
+        ASSERT(!m_debugger.m_isPaused);
+        m_debugger.m_isPaused = true;
+    }
+
+    ~TemporaryPausedState()
+    {
+        m_debugger.m_isPaused = false;
+    }
+
+private:
+    Debugger& m_debugger;
+};
+
+template<typename Functor>
+void Debugger::forEachCodeBlock(Functor& functor)
+{
+    m_vm->waitForCompilationsToComplete();
+    m_vm->heap.forEachCodeBlock(functor);
+}
+
+Debugger::Debugger(bool isInWorkerThread)
+    : m_vm(nullptr)
+    , m_pauseOnExceptionsState(DontPauseOnExceptions)
+    , m_pauseOnNextStatement(false)
+    , m_isPaused(false)
+    , m_breakpointsActivated(true)
+    , m_hasHandlerForExceptionCallback(false)
+    , m_isInWorkerThread(isInWorkerThread)
+    , m_steppingMode(SteppingModeDisabled)
+    , m_reasonForPause(NotPaused)
+    , m_pauseOnCallFrame(0)
+    , m_currentCallFrame(0)
+    , m_lastExecutedLine(UINT_MAX)
+    , m_lastExecutedSourceID(noSourceID)
+    , m_topBreakpointID(noBreakpointID)
+{
+}
+
 Debugger::~Debugger()
 {
     HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end();
@@ -98,48 +173,605 @@ Debugger::~Debugger()
 void Debugger::attach(JSGlobalObject* globalObject)
 {
     ASSERT(!globalObject->debugger());
+    if (!m_vm)
+        m_vm = &globalObject->vm();
+    else
+        ASSERT(m_vm == &globalObject->vm());
     globalObject->setDebugger(this);
     m_globalObjects.add(globalObject);
 }
 
-void Debugger::detach(JSGlobalObject* globalObject)
+void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason)
 {
+    // If we're detaching from the currently executing global object, manually tear down our
+    // stack, since we won't get further debugger callbacks to do so. Also, resume execution,
+    // since there's no point in staying paused once a window closes.
+    if (m_currentCallFrame && m_currentCallFrame->vmEntryGlobalObject() == globalObject) {
+        m_currentCallFrame = 0;
+        m_pauseOnCallFrame = 0;
+        continueProgram();
+    }
+
     ASSERT(m_globalObjects.contains(globalObject));
     m_globalObjects.remove(globalObject);
+
+    // If the globalObject is destructing, then its CodeBlocks will also be
+    // destructed. There is no need to do the debugger requests clean up, and
+    // it is not safe to access those CodeBlocks at this time anyway.
+    if (reason != GlobalObjectIsDestructing)
+        clearDebuggerRequests(globalObject);
+
     globalObject->setDebugger(0);
+    if (!m_globalObjects.size())
+        m_vm = nullptr;
+}
+
+class Debugger::SetSteppingModeFunctor {
+public:
+    SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode)
+        : m_debugger(debugger)
+        , m_mode(mode)
+    {
+    }
+
+    bool operator()(CodeBlock* codeBlock)
+    {
+        if (m_debugger == codeBlock->globalObject()->debugger()) {
+            if (m_mode == SteppingModeEnabled)
+                codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
+            else
+                codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled);
+        }
+        return false;
+    }
+
+private:
+    Debugger* m_debugger;
+    SteppingMode m_mode;
+};
+
+void Debugger::setSteppingMode(SteppingMode mode)
+{
+    if (mode == m_steppingMode || !m_vm)
+        return;
+
+    m_vm->waitForCompilationsToComplete();
+
+    m_steppingMode = mode;
+    SetSteppingModeFunctor functor(this, mode);
+    m_vm->heap.forEachCodeBlock(functor);
+}
+
+void Debugger::registerCodeBlock(CodeBlock* codeBlock)
+{
+    // FIXME: We should never have to jettison a code block (due to pending breakpoints
+    // or stepping mode) that is being registered. operationOptimize() should have
+    // prevented the optimizing of such code blocks in the first place. Find a way to
+    // express this with greater clarity in the code. See <https://webkit.org/b131771>.
+    applyBreakpoints(codeBlock);
+    if (isStepping())
+        codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
 }
 
-void Debugger::recompileAllJSFunctions(JSGlobalData* globalData)
+void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
+{
+    ScriptExecutable* executable = codeBlock->ownerExecutable();
+
+    SourceID sourceID = static_cast<SourceID>(executable->sourceID());
+    if (breakpoint.sourceID != sourceID)
+        return;
+
+    unsigned line = breakpoint.line;
+    unsigned column = breakpoint.column;
+
+    unsigned startLine = executable->lineNo();
+    unsigned startColumn = executable->startColumn();
+    unsigned endLine = executable->lastLine();
+    unsigned endColumn = executable->endColumn();
+
+    // Inspector breakpoint line and column values are zero-based but the executable
+    // and CodeBlock line and column values are one-based.
+    line += 1;
+    column = column ? column + 1 : Breakpoint::unspecifiedColumn;
+
+    if (line < startLine || line > endLine)
+        return;
+    if (column != Breakpoint::unspecifiedColumn) {
+        if (line == startLine && column < startColumn)
+            return;
+        if (line == endLine && column > endColumn)
+            return;
+    }
+    if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
+        return;
+
+    if (enabledOrNot == BreakpointEnabled)
+        codeBlock->addBreakpoint(1);
+    else
+        codeBlock->removeBreakpoint(1);
+}
+
+void Debugger::applyBreakpoints(CodeBlock* codeBlock)
+{
+    BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint;
+    for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) {
+        Breakpoint& breakpoint = *it->value;
+        toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled);
+    }
+}
+
+class Debugger::ToggleBreakpointFunctor {
+public:
+    ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot)
+        : m_debugger(debugger)
+        , m_breakpoint(breakpoint)
+        , m_enabledOrNot(enabledOrNot)
+    {
+    }
+
+    bool operator()(CodeBlock* codeBlock)
+    {
+        if (m_debugger == codeBlock->globalObject()->debugger())
+            m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
+        return false;
+    }
+
+private:
+    Debugger* m_debugger;
+    Breakpoint& m_breakpoint;
+    BreakpointState m_enabledOrNot;
+};
+
+void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
+{
+    if (!m_vm)
+        return;
+    ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
+    forEachCodeBlock(functor);
+}
+
+void Debugger::recompileAllJSFunctions(VM* vm)
 {
     // If JavaScript is running, it's not safe to recompile, since we'll end
     // up throwing away code that is live on the stack.
-    ASSERT(!globalData->dynamicGlobalObject);
-    if (globalData->dynamicGlobalObject)
+    if (vm->entryScope) {
+        vm->entryScope->setRecompilationNeeded(true);
         return;
+    }
+
+    vm->waitForCompilationsToComplete();
 
     Recompiler recompiler(this);
-    globalData->heap.forEach(recompiler);
+    HeapIterationScope iterationScope(vm->heap);
+    vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler);
+}
+
+BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn)
+{
+    SourceID sourceID = breakpoint.sourceID;
+    unsigned line = breakpoint.line;
+    unsigned column = breakpoint.column;
+
+    SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
+    if (it == m_sourceIDToBreakpoints.end())
+        it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator;
+    LineToBreakpointsMap::iterator breaksIt = it->value.find(line);
+    if (breaksIt == it->value.end())
+        breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator;
+
+    BreakpointsList& breakpoints = *breaksIt->value;
+    for (Breakpoint* current = breakpoints.head(); current; current = current->next()) {
+        if (current->column == column) {
+            // The breakpoint already exists. We're not allowed to create a new
+            // breakpoint at this location. Rather than returning the breakpointID
+            // of the pre-existing breakpoint, we need to return noBreakpointID
+            // to indicate that we're not creating a new one.
+            return noBreakpointID;
+        }
+    }
+
+    BreakpointID id = ++m_topBreakpointID;
+    RELEASE_ASSERT(id != noBreakpointID);
+
+    breakpoint.id = id;
+    actualLine = line;
+    actualColumn = column;
+
+    Breakpoint* newBreakpoint = new Breakpoint(breakpoint);
+    breakpoints.append(newBreakpoint);
+    m_breakpointIDToBreakpoint.set(id, newBreakpoint);
+
+    toggleBreakpoint(breakpoint, BreakpointEnabled);
+
+    return id;
+}
+
+void Debugger::removeBreakpoint(BreakpointID id)
+{
+    ASSERT(id != noBreakpointID);
+
+    BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id);
+    ASSERT(idIt != m_breakpointIDToBreakpoint.end());
+    Breakpoint* breakpoint = idIt->value;
+
+    SourceID sourceID = breakpoint->sourceID;
+    ASSERT(sourceID);
+    SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
+    ASSERT(it != m_sourceIDToBreakpoints.end());
+    LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line);
+    ASSERT(breaksIt != it->value.end());
+
+    toggleBreakpoint(*breakpoint, BreakpointDisabled);
+
+    BreakpointsList& breakpoints = *breaksIt->value;
+#if !ASSERT_DISABLED
+    bool found = false;
+    for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) {
+        if (current->id == breakpoint->id)
+            found = true;
+    }
+    ASSERT(found);
+#endif
+
+    m_breakpointIDToBreakpoint.remove(idIt);
+    breakpoints.remove(breakpoint);
+    delete breakpoint;
+
+    if (breakpoints.isEmpty()) {
+        it->value.remove(breaksIt);
+        if (it->value.isEmpty())
+            m_sourceIDToBreakpoints.remove(it);
+    }
+}
+
+bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint)
+{
+    if (!m_breakpointsActivated)
+        return false;
+
+    SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID);
+    if (it == m_sourceIDToBreakpoints.end())
+        return false;
+
+    unsigned line = position.m_line.zeroBasedInt();
+    unsigned column = position.m_column.zeroBasedInt();
+
+    LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line);
+    if (breaksIt == it->value.end())
+        return false;
+
+    bool hit = false;
+    const BreakpointsList& breakpoints = *breaksIt->value;
+    Breakpoint* breakpoint;
+    for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) {
+        unsigned breakLine = breakpoint->line;
+        unsigned breakColumn = breakpoint->column;
+        // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
+        ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
+        if ((line != m_lastExecutedLine && line == breakLine && !breakColumn)
+            || (line == breakLine && column == breakColumn)) {
+            hit = true;
+            break;
+        }
+    }
+    if (!hit)
+        return false;
+
+    if (hitBreakpoint)
+        *hitBreakpoint = *breakpoint;
+
+    if (breakpoint->condition.isEmpty())
+        return true;
+
+    // We cannot stop in the debugger while executing condition code,
+    // so make it looks like the debugger is already paused.
+    TemporaryPausedState pausedState(*this);
+
+    JSValue exception;
+    DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
+    JSValue result = debuggerCallFrame->evaluate(breakpoint->condition, exception);
+
+    // We can lose the debugger while executing JavaScript.
+    if (!m_currentCallFrame)
+        return false;
+
+    if (exception) {
+        // An erroneous condition counts as "false".
+        handleExceptionInBreakpointCondition(m_currentCallFrame, exception);
+        return false;
+    }
+
+    return result.toBoolean(m_currentCallFrame);
+}
+
+class Debugger::ClearCodeBlockDebuggerRequestsFunctor {
+public:
+    ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger)
+        : m_debugger(debugger)
+    {
+    }
+
+    bool operator()(CodeBlock* codeBlock)
+    {
+        if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
+            codeBlock->clearDebuggerRequests();
+        return false;
+    }
+
+private:
+    Debugger* m_debugger;
+};
+
+void Debugger::clearBreakpoints()
+{
+    m_topBreakpointID = noBreakpointID;
+    m_breakpointIDToBreakpoint.clear();
+    m_sourceIDToBreakpoints.clear();
+
+    if (!m_vm)
+        return;
+    ClearCodeBlockDebuggerRequestsFunctor functor(this);
+    forEachCodeBlock(functor);
+}
+
+class Debugger::ClearDebuggerRequestsFunctor {
+public:
+    ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject)
+        : m_globalObject(globalObject)
+    {
+    }
+
+    bool operator()(CodeBlock* codeBlock)
+    {
+        if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
+            codeBlock->clearDebuggerRequests();
+        return false;
+    }
+
+private:
+    JSGlobalObject* m_globalObject;
+};
+
+void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
+{
+    ASSERT(m_vm);
+    ClearDebuggerRequestsFunctor functor(globalObject);
+    forEachCodeBlock(functor);
+}
+
+void Debugger::setBreakpointsActivated(bool activated)
+{
+    m_breakpointsActivated = activated;
+}
+
+void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
+{
+    m_pauseOnExceptionsState = pause;
+}
+
+void Debugger::setPauseOnNextStatement(bool pause)
+{
+    m_pauseOnNextStatement = pause;
+    if (pause)
+        setSteppingMode(SteppingModeEnabled);
+}
+
+void Debugger::breakProgram()
+{
+    if (m_isPaused)
+        return;
+
+    m_pauseOnNextStatement = true;
+    setSteppingMode(SteppingModeEnabled);
+    m_currentCallFrame = m_vm->topCallFrame;
+    ASSERT(m_currentCallFrame);
+    pauseIfNeeded(m_currentCallFrame);
+}
+
+void Debugger::continueProgram()
+{
+    if (!m_isPaused)
+        return;
+
+    m_pauseOnNextStatement = false;
+    notifyDoneProcessingDebuggerEvents();
+}
+
+void Debugger::stepIntoStatement()
+{
+    if (!m_isPaused)
+        return;
+
+    m_pauseOnNextStatement = true;
+    setSteppingMode(SteppingModeEnabled);
+    notifyDoneProcessingDebuggerEvents();
+}
+
+void Debugger::stepOverStatement()
+{
+    if (!m_isPaused)
+        return;
+
+    m_pauseOnCallFrame = m_currentCallFrame;
+    notifyDoneProcessingDebuggerEvents();
 }
 
-JSValue evaluateInGlobalCallFrame(const UString& script, JSValue& exception, JSGlobalObject* globalObject)
+void Debugger::stepOutOfFunction()
 {
-    CallFrame* globalCallFrame = globalObject->globalExec();
-    JSGlobalData& globalData = globalObject->globalData();
+    if (!m_isPaused)
+        return;
 
-    EvalExecutable* eval = EvalExecutable::create(globalCallFrame, makeSource(script), false);
-    if (!eval) {
-        exception = globalData.exception;
-        globalData.exception = JSValue();
-        return exception;
+    m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrameSkippingVMEntrySentinel() : 0;
+    notifyDoneProcessingDebuggerEvents();
+}
+
+void Debugger::updateCallFrame(CallFrame* callFrame)
+{
+    m_currentCallFrame = callFrame;
+    SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame);
+    if (m_lastExecutedSourceID != sourceID) {
+        m_lastExecutedLine = UINT_MAX;
+        m_lastExecutedSourceID = sourceID;
     }
+}
 
-    JSValue result = globalData.interpreter->execute(eval, globalCallFrame, globalObject, globalCallFrame->scopeChain());
-    if (globalData.exception) {
-        exception = globalData.exception;
-        globalData.exception = JSValue();
+void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame)
+{
+    updateCallFrame(callFrame);
+    pauseIfNeeded(callFrame);
+    if (!isStepping())
+        m_currentCallFrame = 0;
+}
+
+void Debugger::pauseIfNeeded(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject();
+    if (!needPauseHandling(vmEntryGlobalObject))
+        return;
+
+    Breakpoint breakpoint;
+    bool didHitBreakpoint = false;
+    bool pauseNow = m_pauseOnNextStatement;
+    pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
+
+    DebuggerCallFrameScope debuggerCallFrameScope(*this);
+
+    intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
+    TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame);
+    pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint);
+    m_lastExecutedLine = position.m_line.zeroBasedInt();
+    if (!pauseNow)
+        return;
+
+    // Make sure we are not going to pause again on breakpoint actions by
+    // reseting the pause state before executing any breakpoint actions.
+    TemporaryPausedState pausedState(*this);
+    m_pauseOnCallFrame = 0;
+    m_pauseOnNextStatement = false;
+
+    if (didHitBreakpoint) {
+        handleBreakpointHit(breakpoint);
+        // Note that the actions can potentially stop the debugger, so we need to check that
+        // we still have a current call frame when we get back.
+        if (breakpoint.autoContinue || !m_currentCallFrame)
+            return;
+    }
+
+    handlePause(m_reasonForPause, vmEntryGlobalObject);
+
+    if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) {
+        setSteppingMode(SteppingModeDisabled);
+        m_currentCallFrame = nullptr;
+    }
+}
+
+void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasHandler)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedForException);
+    if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler)) {
+        m_pauseOnNextStatement = true;
+        setSteppingMode(SteppingModeEnabled);
     }
-    ASSERT(result);
-    return result;
+
+    m_hasHandlerForExceptionCallback = true;
+    m_currentException = exception;
+    updateCallFrameAndPauseIfNeeded(callFrame);
+    m_currentException = JSValue();
+    m_hasHandlerForExceptionCallback = false;
+}
+
+void Debugger::atStatement(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedAtStatement);
+    updateCallFrameAndPauseIfNeeded(callFrame);
+}
+
+void Debugger::callEvent(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedAfterCall);
+    updateCallFrameAndPauseIfNeeded(callFrame);
+}
+
+void Debugger::returnEvent(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedBeforeReturn);
+    updateCallFrameAndPauseIfNeeded(callFrame);
+
+    // detach may have been called during pauseIfNeeded
+    if (!m_currentCallFrame)
+        return;
+
+    // Treat stepping over a return statement like stepping out.
+    if (m_currentCallFrame == m_pauseOnCallFrame)
+        m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+
+    m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+}
+
+void Debugger::willExecuteProgram(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedAtStartOfProgram);
+    // FIXME: This check for whether we're debugging a worker thread is a workaround
+    // for https://bugs.webkit.org/show_bug.cgi?id=102637. Remove it when we rework
+    // the debugger implementation to not require callbacks.
+    if (!m_isInWorkerThread)
+        updateCallFrameAndPauseIfNeeded(callFrame);
+    else if (isStepping())
+        updateCallFrame(callFrame);
+}
+
+void Debugger::didExecuteProgram(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedAtEndOfProgram);
+    updateCallFrameAndPauseIfNeeded(callFrame);
+
+    // Treat stepping over the end of a program like stepping out.
+    if (!m_currentCallFrame)
+        return;
+    if (m_currentCallFrame == m_pauseOnCallFrame) {
+        m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+        if (!m_currentCallFrame)
+            return;
+    }
+    m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+}
+
+void Debugger::didReachBreakpoint(CallFrame* callFrame)
+{
+    if (m_isPaused)
+        return;
+
+    PauseReasonDeclaration reason(*this, PausedForBreakpoint);
+    m_pauseOnNextStatement = true;
+    setSteppingMode(SteppingModeEnabled);
+    updateCallFrameAndPauseIfNeeded(callFrame);
+}
+
+DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const
+{
+    ASSERT(m_currentDebuggerCallFrame);
+    return m_currentDebuggerCallFrame.get();
 }
 
 } // namespace JSC