summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/debugger/Debugger.cpp
diff options
context:
space:
mode:
authorKonstantin Tokarev <annulen@yandex.ru>2016-08-25 19:20:41 +0300
committerKonstantin Tokarev <annulen@yandex.ru>2017-02-02 12:30:55 +0000
commit6882a04fb36642862b11efe514251d32070c3d65 (patch)
treeb7959826000b061fd5ccc7512035c7478742f7b0 /Source/JavaScriptCore/debugger/Debugger.cpp
parentab6df191029eeeb0b0f16f127d553265659f739e (diff)
downloadqtwebkit-6882a04fb36642862b11efe514251d32070c3d65.tar.gz
Imported QtWebKit TP3 (git b57bc6801f1876c3220d5a4bfea33d620d477443)
Change-Id: I3b1d8a2808782c9f34d50240000e20cb38d3680f Reviewed-by: Konstantin Tokarev <annulen@yandex.ru>
Diffstat (limited to 'Source/JavaScriptCore/debugger/Debugger.cpp')
-rw-r--r--Source/JavaScriptCore/debugger/Debugger.cpp763
1 files changed, 696 insertions, 67 deletions
diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp
index 2c5a1261a..f50d54be5 100644
--- a/Source/JavaScriptCore/debugger/Debugger.cpp
+++ b/Source/JavaScriptCore/debugger/Debugger.cpp
@@ -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)
*
@@ -22,126 +22,755 @@
#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 "Operations.h"
+#include "JSCInlines.h"
#include "Parser.h"
#include "Protect.h"
+#include "VMEntryScope.h"
namespace {
using namespace JSC;
-class Recompiler : public MarkedBlock::VoidFunctor {
+struct GatherSourceProviders : public MarkedBlock::VoidFunctor {
+ HashSet<SourceProvider*> sourceProviders;
+ JSGlobalObject* m_globalObject;
+
+ GatherSourceProviders(JSGlobalObject* globalObject)
+ : m_globalObject(globalObject) { }
+
+ IterationStatus operator()(JSCell* cell)
+ {
+ JSFunction* function = jsDynamicCast<JSFunction*>(cell);
+ if (!function)
+ return IterationStatus::Continue;
+
+ if (function->scope()->globalObject() != m_globalObject)
+ return IterationStatus::Continue;
+
+ if (!function->executable()->isFunctionExecutable())
+ return IterationStatus::Continue;
+
+ if (function->isHostOrBuiltinFunction())
+ return IterationStatus::Continue;
+
+ sourceProviders.add(
+ jsCast<FunctionExecutable*>(function->executable())->source().provider());
+ return IterationStatus::Continue;
+ }
+};
+
+} // namespace
+
+namespace JSC {
+
+class DebuggerPausedScope {
+public:
+ DebuggerPausedScope(Debugger& debugger)
+ : m_debugger(debugger)
+ {
+ ASSERT(!m_debugger.m_currentDebuggerCallFrame);
+ if (m_debugger.m_currentCallFrame)
+ m_debugger.m_currentDebuggerCallFrame = DebuggerCallFrame::create(debugger.m_currentCallFrame);
+ }
+
+ ~DebuggerPausedScope()
+ {
+ if (m_debugger.m_currentDebuggerCallFrame) {
+ m_debugger.m_currentDebuggerCallFrame->invalidate();
+ m_debugger.m_currentDebuggerCallFrame = nullptr;
+ }
+ }
+
+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:
- Recompiler(Debugger*);
- ~Recompiler();
- void operator()(JSCell*);
+ 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;
+};
+
+Debugger::Debugger(VM& vm)
+ : m_vm(vm)
+ , m_pauseOnExceptionsState(DontPauseOnExceptions)
+ , m_pauseOnNextStatement(false)
+ , m_isPaused(false)
+ , m_breakpointsActivated(true)
+ , m_hasHandlerForExceptionCallback(false)
+ , m_suppressAllPauses(false)
+ , m_steppingMode(SteppingModeDisabled)
+ , m_reasonForPause(NotPaused)
+ , m_pauseOnCallFrame(0)
+ , m_currentCallFrame(0)
+ , m_lastExecutedLine(UINT_MAX)
+ , m_lastExecutedSourceID(noSourceID)
+ , m_topBreakpointID(noBreakpointID)
+ , m_pausingBreakpointID(noBreakpointID)
+{
+}
+
+Debugger::~Debugger()
+{
+ HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end();
+ for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it)
+ (*it)->setDebugger(0);
+}
+
+void Debugger::attach(JSGlobalObject* globalObject)
+{
+ ASSERT(!globalObject->debugger());
+ globalObject->setDebugger(this);
+ m_globalObjects.add(globalObject);
+
+ m_vm.setShouldBuildPCToCodeOriginMapping();
+
+ // Call sourceParsed because it will execute JavaScript in the inspector.
+ GatherSourceProviders gatherSourceProviders(globalObject);
+ {
+ HeapIterationScope iterationScope(m_vm.heap);
+ m_vm.heap.objectSpace().forEachLiveCell(iterationScope, gatherSourceProviders);
+ }
+ for (auto* sourceProvider : gatherSourceProviders.sourceProviders)
+ sourceParsed(globalObject->globalExec(), sourceProvider, -1, String());
+}
+
+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);
+}
+
+bool Debugger::isAttached(JSGlobalObject* globalObject)
+{
+ return globalObject->debugger() == this;
+}
+
+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:
- typedef HashSet<FunctionExecutable*> FunctionExecutableSet;
- typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap;
-
Debugger* m_debugger;
- FunctionExecutableSet m_functionExecutables;
- SourceProviderMap m_sourceProviders;
+ SteppingMode m_mode;
};
-inline Recompiler::Recompiler(Debugger* debugger)
- : m_debugger(debugger)
+void Debugger::setSteppingMode(SteppingMode mode)
+{
+ if (mode == m_steppingMode)
+ return;
+
+ m_vm.heap.completeAllDFGPlans();
+
+ m_steppingMode = mode;
+ SetSteppingModeFunctor functor(this, mode);
+ m_vm.heap.forEachCodeBlock(functor);
+}
+
+void Debugger::registerCodeBlock(CodeBlock* codeBlock)
{
+ applyBreakpoints(codeBlock);
+ if (isStepping())
+ codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
}
-inline Recompiler::~Recompiler()
+void Debugger::setProfilingClient(ProfilingClient* client)
{
- // Call sourceParsed() after reparsing all functions because it will execute
- // 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->value, iter->key, -1, String());
+ ASSERT(!!m_profilingClient != !!client);
+ m_profilingClient = client;
+
+ recompileAllJSFunctions();
}
-inline void Recompiler::operator()(JSCell* cell)
+double Debugger::willEvaluateScript()
{
- if (!cell->inherits(&JSFunction::s_info))
+ return m_profilingClient->willEvaluateScript();
+}
+
+void Debugger::didEvaluateScript(double startTime, ProfilingReason reason)
+{
+ m_profilingClient->didEvaluateScript(startTime, reason);
+}
+
+void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
+{
+ ScriptExecutable* executable = codeBlock->ownerScriptExecutable();
+
+ SourceID sourceID = static_cast<SourceID>(executable->sourceID());
+ if (breakpoint.sourceID != sourceID)
return;
- JSFunction* function = jsCast<JSFunction*>(cell);
- if (function->executable()->isHostFunction())
+ unsigned line = breakpoint.line;
+ unsigned column = breakpoint.column;
+
+ unsigned startLine = executable->firstLine();
+ 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;
- FunctionExecutable* executable = function->jsExecutable();
+ 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)
+{
+ m_vm.heap.completeAllDFGPlans();
+
+ ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
+ m_vm.heap.forEachCodeBlock(functor);
+}
+
+void Debugger::recompileAllJSFunctions()
+{
+ m_vm.deleteAllCode();
+}
+
+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;
- // 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).isNewEntry)
+ if (hitBreakpoint)
+ *hitBreakpoint = *breakpoint;
+
+ breakpoint->hitCount++;
+ if (breakpoint->ignoreCount >= breakpoint->hitCount)
+ return false;
+
+ 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);
+
+ NakedPtr<Exception> 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_vm.heap.completeAllDFGPlans();
+
+ m_topBreakpointID = noBreakpointID;
+ m_breakpointIDToBreakpoint.clear();
+ m_sourceIDToBreakpoints.clear();
+
+ ClearCodeBlockDebuggerRequestsFunctor functor(this);
+ m_vm.heap.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)
+{
+ m_vm.heap.completeAllDFGPlans();
+
+ ClearDebuggerRequestsFunctor functor(globalObject);
+ m_vm.heap.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;
+
+ if (!m_vm.topCallFrame)
return;
- 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);
+ m_pauseOnNextStatement = true;
+ setSteppingMode(SteppingModeEnabled);
+ m_currentCallFrame = m_vm.topCallFrame;
+ pauseIfNeeded(m_currentCallFrame);
}
-} // namespace
+void Debugger::continueProgram()
+{
+ if (!m_isPaused)
+ return;
-namespace JSC {
+ m_pauseOnNextStatement = false;
+ notifyDoneProcessingDebuggerEvents();
+}
-Debugger::~Debugger()
+void Debugger::stepIntoStatement()
{
- HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end();
- for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it)
- (*it)->setDebugger(0);
+ if (!m_isPaused)
+ return;
+
+ m_pauseOnNextStatement = true;
+ setSteppingMode(SteppingModeEnabled);
+ notifyDoneProcessingDebuggerEvents();
}
-void Debugger::attach(JSGlobalObject* globalObject)
+void Debugger::stepOverStatement()
{
- ASSERT(!globalObject->debugger());
- globalObject->setDebugger(this);
- m_globalObjects.add(globalObject);
+ if (!m_isPaused)
+ return;
+
+ m_pauseOnCallFrame = m_currentCallFrame;
+ notifyDoneProcessingDebuggerEvents();
}
-void Debugger::detach(JSGlobalObject* globalObject)
+void Debugger::stepOutOfFunction()
{
- ASSERT(m_globalObjects.contains(globalObject));
- m_globalObjects.remove(globalObject);
- globalObject->setDebugger(0);
+ if (!m_isPaused)
+ return;
+
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topVMEntryFrame) : 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;
+ }
+}
+
+void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame)
+{
+ updateCallFrame(callFrame);
+ pauseIfNeeded(callFrame);
+ if (!isStepping())
+ m_currentCallFrame = 0;
+}
+
+void Debugger::pauseIfNeeded(CallFrame* callFrame)
+{
+ if (m_isPaused)
+ return;
+
+ if (m_suppressAllPauses)
+ return;
+
+ JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject();
+ if (!needPauseHandling(vmEntryGlobalObject))
+ return;
+
+ Breakpoint breakpoint;
+ bool didHitBreakpoint = false;
+ bool pauseNow = m_pauseOnNextStatement;
+ pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
+
+ DebuggerPausedScope debuggerPausedScope(*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(vmEntryGlobalObject, 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;
+ m_pausingBreakpointID = breakpoint.id;
+ }
+
+ {
+ PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause);
+ handlePause(vmEntryGlobalObject, m_reasonForPause);
+ RELEASE_ASSERT(!callFrame->hadException());
+ }
+
+ m_pausingBreakpointID = noBreakpointID;
+
+ if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) {
+ setSteppingMode(SteppingModeDisabled);
+ m_currentCallFrame = nullptr;
+ }
+}
+
+void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler)
+{
+ if (m_isPaused)
+ return;
+
+ PauseReasonDeclaration reason(*this, PausedForException);
+ if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) {
+ m_pauseOnNextStatement = true;
+ setSteppingMode(SteppingModeEnabled);
+ }
+
+ m_hasHandlerForExceptionCallback = true;
+ m_currentException = exception;
+ updateCallFrameAndPauseIfNeeded(callFrame);
+ m_currentException = JSValue();
+ m_hasHandlerForExceptionCallback = false;
}
-void Debugger::recompileAllJSFunctions(VM* vm)
+void Debugger::atStatement(CallFrame* callFrame)
{
- // 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(!vm->dynamicGlobalObject);
- if (vm->dynamicGlobalObject)
+ if (m_isPaused)
return;
- Recompiler recompiler(this);
- vm->heap.objectSpace().forEachLiveCell(recompiler);
+ PauseReasonDeclaration reason(*this, PausedAtStatement);
+ updateCallFrameAndPauseIfNeeded(callFrame);
}
-JSValue evaluateInGlobalCallFrame(const String& script, JSValue& exception, JSGlobalObject* globalObject)
+void Debugger::callEvent(CallFrame* callFrame)
{
- CallFrame* globalCallFrame = globalObject->globalExec();
- VM& vm = globalObject->vm();
+ if (m_isPaused)
+ return;
- EvalExecutable* eval = EvalExecutable::create(globalCallFrame, vm.codeCache(), makeSource(script), false);
- if (!eval) {
- exception = vm.exception;
- vm.exception = JSValue();
- return exception;
+ 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) {
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
}
- JSValue result = vm.interpreter->execute(eval, globalCallFrame, globalObject, globalCallFrame->scope());
- if (vm.exception) {
- exception = vm.exception;
- vm.exception = JSValue();
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+}
+
+void Debugger::willExecuteProgram(CallFrame* callFrame)
+{
+ if (m_isPaused)
+ return;
+
+ PauseReasonDeclaration reason(*this, PausedAtStartOfProgram);
+ updateCallFrameAndPauseIfNeeded(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) {
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+ if (!m_currentCallFrame)
+ return;
}
- ASSERT(result);
- return result;
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+}
+
+void Debugger::didReachBreakpoint(CallFrame* callFrame)
+{
+ if (m_isPaused)
+ return;
+
+ PauseReasonDeclaration reason(*this, PausedForDebuggerStatement);
+ m_pauseOnNextStatement = true;
+ setSteppingMode(SteppingModeEnabled);
+ updateCallFrameAndPauseIfNeeded(callFrame);
+}
+
+DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const
+{
+ ASSERT(m_currentDebuggerCallFrame);
+ return m_currentDebuggerCallFrame.get();
}
} // namespace JSC