summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/debugger/Debugger.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/debugger/Debugger.cpp')
-rw-r--r--Source/JavaScriptCore/debugger/Debugger.cpp611
1 files changed, 394 insertions, 217 deletions
diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp
index afa7546c8..3eefde28b 100644
--- a/Source/JavaScriptCore/debugger/Debugger.cpp
+++ b/Source/JavaScriptCore/debugger/Debugger.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2008, 2013, 2014, 2016 Apple Inc. All rights reserved.
* Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
* Copyright (C) 2001 Peter Kelly (pmk@post.com)
*
@@ -25,13 +25,13 @@
#include "CodeBlock.h"
#include "DebuggerCallFrame.h"
#include "Error.h"
-
#include "HeapIterationScope.h"
#include "Interpreter.h"
+#include "JSCInlines.h"
#include "JSCJSValueInlines.h"
#include "JSFunction.h"
#include "JSGlobalObject.h"
-#include "Operations.h"
+#include "MarkedSpaceInlines.h"
#include "Parser.h"
#include "Protect.h"
#include "VMEntryScope.h"
@@ -40,77 +40,58 @@ namespace {
using namespace JSC;
-class Recompiler : public MarkedBlock::VoidFunctor {
-public:
- Recompiler(JSC::Debugger*);
- ~Recompiler();
- void operator()(JSCell*);
-
-private:
- typedef HashSet<FunctionExecutable*> FunctionExecutableSet;
- typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap;
-
- JSC::Debugger* m_debugger;
- FunctionExecutableSet m_functionExecutables;
- SourceProviderMap m_sourceProviders;
-};
-
-inline Recompiler::Recompiler(JSC::Debugger* debugger)
- : m_debugger(debugger)
-{
-}
-
-inline Recompiler::~Recompiler()
-{
- // 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());
-}
-
-inline void Recompiler::operator()(JSCell* cell)
-{
- if (!cell->inherits(JSFunction::info()))
- return;
-
- JSFunction* function = jsCast<JSFunction*>(cell);
- if (function->executable()->isHostFunction())
- return;
-
- FunctionExecutable* executable = function->jsExecutable();
+struct GatherSourceProviders : public MarkedBlock::VoidFunctor {
+ // FIXME: This is a mutable field because this isn't a C++ lambda.
+ // https://bugs.webkit.org/show_bug.cgi?id=159644
+ mutable HashSet<SourceProvider*> sourceProviders;
+ JSGlobalObject* m_globalObject;
- // 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)
- return;
+ GatherSourceProviders(JSGlobalObject* globalObject)
+ : m_globalObject(globalObject) { }
- 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);
-}
+ IterationStatus operator()(HeapCell* heapCell, HeapCell::Kind kind) const
+ {
+ if (kind != HeapCell::JSCell)
+ return IterationStatus::Continue;
+
+ JSCell* cell = static_cast<JSCell*>(heapCell);
+
+ JSFunction* function = jsDynamicCast<JSFunction*>(*cell->vm(), 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 DebuggerCallFrameScope {
+class DebuggerPausedScope {
public:
- DebuggerCallFrameScope(Debugger& debugger)
+ 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);
}
- ~DebuggerCallFrameScope()
+ ~DebuggerPausedScope()
{
if (m_debugger.m_currentDebuggerCallFrame) {
m_debugger.m_currentDebuggerCallFrame->invalidate();
- m_debugger.m_currentDebuggerCallFrame = 0;
+ m_debugger.m_currentDebuggerCallFrame = nullptr;
}
}
@@ -118,7 +99,7 @@ private:
Debugger& m_debugger;
};
-// This is very similar to TemporaryChange<bool>, but that cannot be used
+// This is very similar to SetForScope<bool>, but that cannot be used
// as the m_isPaused field uses only one bit.
class TemporaryPausedState {
public:
@@ -138,21 +119,26 @@ private:
Debugger& m_debugger;
};
-Debugger::Debugger(bool isInWorkerThread)
- : m_vm(nullptr)
+
+Debugger::ProfilingClient::~ProfilingClient()
+{
+}
+
+Debugger::Debugger(VM& vm)
+ : m_vm(vm)
, m_pauseOnExceptionsState(DontPauseOnExceptions)
- , m_pauseOnNextStatement(false)
+ , m_pauseAtNextOpportunity(false)
+ , m_pastFirstExpressionInStatement(false)
, m_isPaused(false)
- , m_breakpointsActivated(true)
+ , m_breakpointsActivated(false)
, m_hasHandlerForExceptionCallback(false)
- , m_isInWorkerThread(isInWorkerThread)
+ , 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)
{
}
@@ -160,18 +146,25 @@ Debugger::~Debugger()
{
HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end();
for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it)
- (*it)->setDebugger(0);
+ (*it)->setDebugger(nullptr);
}
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);
+
+ 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)
@@ -179,9 +172,10 @@ 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;
+ // We know there is an entry scope, otherwise, m_currentCallFrame would be null.
+ if (m_isPaused && m_currentCallFrame && globalObject->vm().entryScope->globalObject() == globalObject) {
+ m_currentCallFrame = nullptr;
+ m_pauseOnCallFrame = nullptr;
continueProgram();
}
@@ -194,9 +188,15 @@ void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason)
if (reason != GlobalObjectIsDestructing)
clearDebuggerRequests(globalObject);
- globalObject->setDebugger(0);
- if (!m_globalObjects.size())
- m_vm = nullptr;
+ globalObject->setDebugger(nullptr);
+
+ if (m_globalObjects.isEmpty())
+ clearParsedData();
+}
+
+bool Debugger::isAttached(JSGlobalObject* globalObject)
+{
+ return globalObject->debugger() == this;
}
class Debugger::SetSteppingModeFunctor {
@@ -207,7 +207,7 @@ public:
{
}
- bool operator()(CodeBlock* codeBlock)
+ bool operator()(CodeBlock* codeBlock) const
{
if (m_debugger == codeBlock->globalObject()->debugger()) {
if (m_mode == SteppingModeEnabled)
@@ -227,12 +227,12 @@ void Debugger::setSteppingMode(SteppingMode mode)
{
if (mode == m_steppingMode)
return;
- m_steppingMode = mode;
- if (!m_vm)
- return;
+ m_vm.heap.completeAllJITPlans();
+
+ m_steppingMode = mode;
SetSteppingModeFunctor functor(this, mode);
- m_vm->heap.forEachCodeBlock(functor);
+ m_vm.heap.forEachCodeBlock(functor);
}
void Debugger::registerCodeBlock(CodeBlock* codeBlock)
@@ -242,9 +242,27 @@ void Debugger::registerCodeBlock(CodeBlock* codeBlock)
codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
}
+void Debugger::setProfilingClient(ProfilingClient* client)
+{
+ ASSERT(!!m_profilingClient != !!client);
+ m_profilingClient = client;
+}
+
+double Debugger::willEvaluateScript()
+{
+ 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->ownerExecutable();
+ ASSERT(breakpoint.resolved);
+
+ ScriptExecutable* executable = codeBlock->ownerScriptExecutable();
SourceID sourceID = static_cast<SourceID>(executable->sourceID());
if (breakpoint.sourceID != sourceID)
@@ -253,7 +271,7 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br
unsigned line = breakpoint.line;
unsigned column = breakpoint.column;
- unsigned startLine = executable->lineNo();
+ unsigned startLine = executable->firstLine();
unsigned startColumn = executable->startColumn();
unsigned endLine = executable->lastLine();
unsigned endColumn = executable->endColumn();
@@ -271,6 +289,7 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br
if (line == endLine && column > endColumn)
return;
}
+
if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
return;
@@ -283,10 +302,8 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br
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);
- }
+ for (auto* breakpoint : breakpoints.values())
+ toggleBreakpoint(codeBlock, *breakpoint, BreakpointEnabled);
}
class Debugger::ToggleBreakpointFunctor {
@@ -298,7 +315,7 @@ public:
{
}
- bool operator()(CodeBlock* codeBlock)
+ bool operator()(CodeBlock* codeBlock) const
{
if (m_debugger == codeBlock->globalObject()->debugger())
m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
@@ -313,63 +330,90 @@ private:
void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
{
- if (!m_vm)
- return;
+ m_vm.heap.completeAllJITPlans();
+
ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
- m_vm->heap.forEachCodeBlock(functor);
+ m_vm.heap.forEachCodeBlock(functor);
}
-void Debugger::recompileAllJSFunctions(VM* vm)
+void Debugger::recompileAllJSFunctions()
{
- // If JavaScript is running, it's not safe to recompile, since we'll end
- // up throwing away code that is live on the stack.
- if (vm->entryScope) {
- vm->entryScope->setRecompilationNeeded(true);
+ m_vm.deleteAllCode(PreventCollectionAndDeleteAllCode);
+}
+
+DebuggerParseData& Debugger::debuggerParseData(SourceID sourceID, SourceProvider* provider)
+{
+ auto iter = m_parseDataMap.find(sourceID);
+ if (iter != m_parseDataMap.end())
+ return iter->value;
+
+ DebuggerParseData parseData;
+ gatherDebuggerParseDataForSource(m_vm, provider, parseData);
+ auto result = m_parseDataMap.add(sourceID, parseData);
+ return result.iterator->value;
+}
+
+void Debugger::resolveBreakpoint(Breakpoint& breakpoint, SourceProvider* sourceProvider)
+{
+ RELEASE_ASSERT(!breakpoint.resolved);
+ ASSERT(breakpoint.sourceID != noSourceID);
+
+ // FIXME: <https://webkit.org/b/162771> Web Inspector: Adopt TextPosition in Inspector to avoid oneBasedInt/zeroBasedInt ambiguity
+ // Inspector breakpoint line and column values are zero-based but the executable
+ // and CodeBlock line and column values are one-based.
+ unsigned line = breakpoint.line + 1;
+ unsigned column = breakpoint.column ? breakpoint.column : Breakpoint::unspecifiedColumn;
+
+ DebuggerParseData& parseData = debuggerParseData(breakpoint.sourceID, sourceProvider);
+ std::optional<JSTextPosition> resolvedPosition = parseData.pausePositions.breakpointLocationForLineColumn((int)line, (int)column);
+ if (!resolvedPosition)
return;
- }
- vm->prepareToDiscardCode();
+ unsigned resolvedLine = resolvedPosition->line;
+ unsigned resolvedColumn = resolvedPosition->offset - resolvedPosition->lineStartOffset + 1;
- Recompiler recompiler(this);
- HeapIterationScope iterationScope(vm->heap);
- vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler);
+ breakpoint.line = resolvedLine - 1;
+ breakpoint.column = resolvedColumn - 1;
+ breakpoint.resolved = true;
}
-BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn)
+BreakpointID Debugger::setBreakpoint(Breakpoint& breakpoint, bool& existing)
{
+ ASSERT(breakpoint.resolved);
+ ASSERT(breakpoint.sourceID != noSourceID);
+
SourceID sourceID = breakpoint.sourceID;
unsigned line = breakpoint.line;
unsigned column = breakpoint.column;
- SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
+ SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(breakpoint.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, BreakpointsInLine()).iterator;
-
- BreakpointsInLine& breakpoints = breaksIt->value;
- unsigned breakpointsCount = breakpoints.size();
- for (unsigned i = 0; i < breakpointsCount; i++)
- if (breakpoints[i].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;
+ 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) {
+ // Found existing breakpoint. Do not create a duplicate at this location.
+ existing = true;
+ return current->id;
}
+ }
+ existing = false;
BreakpointID id = ++m_topBreakpointID;
RELEASE_ASSERT(id != noBreakpointID);
breakpoint.id = id;
- actualLine = line;
- actualColumn = column;
- breakpoints.append(breakpoint);
- m_breakpointIDToBreakpoint.set(id, &breakpoints.last());
+ Breakpoint* newBreakpoint = new Breakpoint(breakpoint);
+ breakpoints.append(newBreakpoint);
+ m_breakpointIDToBreakpoint.set(id, newBreakpoint);
- toggleBreakpoint(breakpoint, BreakpointEnabled);
+ toggleBreakpoint(*newBreakpoint, BreakpointEnabled);
return id;
}
@@ -380,31 +424,35 @@ void Debugger::removeBreakpoint(BreakpointID id)
BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id);
ASSERT(idIt != m_breakpointIDToBreakpoint.end());
- Breakpoint& breakpoint = *idIt->value;
+ Breakpoint* breakpoint = idIt->value;
- SourceID sourceID = breakpoint.sourceID;
+ 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);
+ LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line);
ASSERT(breaksIt != it->value.end());
- toggleBreakpoint(breakpoint, BreakpointDisabled);
+ toggleBreakpoint(*breakpoint, BreakpointDisabled);
- BreakpointsInLine& breakpoints = breaksIt->value;
- unsigned breakpointsCount = breakpoints.size();
- for (unsigned i = 0; i < breakpointsCount; i++) {
- if (breakpoints[i].id == breakpoint.id) {
- breakpoints.remove(i);
- m_breakpointIDToBreakpoint.remove(idIt);
+ 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
- if (breakpoints.isEmpty()) {
- it->value.remove(breaksIt);
- if (it->value.isEmpty())
- m_sourceIDToBreakpoints.remove(it);
- }
- break;
- }
+ m_breakpointIDToBreakpoint.remove(idIt);
+ breakpoints.remove(breakpoint);
+ delete breakpoint;
+
+ if (breakpoints.isEmpty()) {
+ it->value.remove(breaksIt);
+ if (it->value.isEmpty())
+ m_sourceIDToBreakpoints.remove(it);
}
}
@@ -419,18 +467,17 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br
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 BreakpointsInLine& breakpoints = breaksIt->value;
- unsigned breakpointsCount = breakpoints.size();
- unsigned i;
- for (i = 0; i < breakpointsCount; i++) {
- unsigned breakLine = breakpoints[i].line;
- unsigned breakColumn = breakpoints[i].column;
+ 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)
@@ -443,17 +490,23 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br
return false;
if (hitBreakpoint)
- *hitBreakpoint = breakpoints[i];
+ *hitBreakpoint = *breakpoint;
+
+ breakpoint->hitCount++;
+ if (breakpoint->ignoreCount >= breakpoint->hitCount)
+ return false;
- if (breakpoints[i].condition.isEmpty())
+ 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;
- JSValue result = DebuggerCallFrame::evaluateWithCallFrame(m_currentCallFrame, breakpoints[i].condition, exception);
+ NakedPtr<Exception> exception;
+ DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame();
+ JSObject* scopeExtensionObject = nullptr;
+ JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpoint->condition, scopeExtensionObject, exception);
// We can lose the debugger while executing JavaScript.
if (!m_currentCallFrame)
@@ -475,7 +528,7 @@ public:
{
}
- bool operator()(CodeBlock* codeBlock)
+ bool operator()(CodeBlock* codeBlock) const
{
if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
codeBlock->clearDebuggerRequests();
@@ -488,14 +541,14 @@ private:
void Debugger::clearBreakpoints()
{
+ m_vm.heap.completeAllJITPlans();
+
m_topBreakpointID = noBreakpointID;
m_breakpointIDToBreakpoint.clear();
m_sourceIDToBreakpoints.clear();
- if (!m_vm)
- return;
ClearCodeBlockDebuggerRequestsFunctor functor(this);
- m_vm->heap.forEachCodeBlock(functor);
+ m_vm.heap.forEachCodeBlock(functor);
}
class Debugger::ClearDebuggerRequestsFunctor {
@@ -505,7 +558,7 @@ public:
{
}
- bool operator()(CodeBlock* codeBlock)
+ bool operator()(CodeBlock* codeBlock) const
{
if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
codeBlock->clearDebuggerRequests();
@@ -518,14 +571,24 @@ private:
void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
{
- ASSERT(m_vm);
+ m_vm.heap.completeAllJITPlans();
+
ClearDebuggerRequestsFunctor functor(globalObject);
- m_vm->heap.forEachCodeBlock(functor);
+ m_vm.heap.forEachCodeBlock(functor);
+}
+
+void Debugger::clearParsedData()
+{
+ m_parseDataMap.clear();
}
void Debugger::setBreakpointsActivated(bool activated)
{
+ if (activated == m_breakpointsActivated)
+ return;
+
m_breakpointsActivated = activated;
+ recompileAllJSFunctions();
}
void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
@@ -535,7 +598,7 @@ void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
void Debugger::setPauseOnNextStatement(bool pause)
{
- m_pauseOnNextStatement = pause;
+ m_pauseAtNextOpportunity = pause;
if (pause)
setSteppingMode(SteppingModeEnabled);
}
@@ -545,19 +608,22 @@ void Debugger::breakProgram()
if (m_isPaused)
return;
- m_pauseOnNextStatement = true;
+ if (!m_vm.topCallFrame)
+ return;
+
+ m_pauseAtNextOpportunity = true;
setSteppingMode(SteppingModeEnabled);
- m_currentCallFrame = m_vm->topCallFrame;
- ASSERT(m_currentCallFrame);
+ m_currentCallFrame = m_vm.topCallFrame;
pauseIfNeeded(m_currentCallFrame);
}
void Debugger::continueProgram()
{
+ clearNextPauseState();
+
if (!m_isPaused)
return;
- m_pauseOnNextStatement = false;
notifyDoneProcessingDebuggerEvents();
}
@@ -566,7 +632,7 @@ void Debugger::stepIntoStatement()
if (!m_isPaused)
return;
- m_pauseOnNextStatement = true;
+ m_pauseAtNextOpportunity = true;
setSteppingMode(SteppingModeEnabled);
notifyDoneProcessingDebuggerEvents();
}
@@ -577,6 +643,7 @@ void Debugger::stepOverStatement()
return;
m_pauseOnCallFrame = m_currentCallFrame;
+ setSteppingMode(SteppingModeEnabled);
notifyDoneProcessingDebuggerEvents();
}
@@ -585,11 +652,30 @@ void Debugger::stepOutOfFunction()
if (!m_isPaused)
return;
- m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrameSkippingVMEntrySentinel() : 0;
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topVMEntryFrame) : nullptr;
+ m_pauseOnStepOut = true;
+ setSteppingMode(SteppingModeEnabled);
notifyDoneProcessingDebuggerEvents();
}
-void Debugger::updateCallFrame(CallFrame* callFrame)
+void Debugger::updateCallFrame(CallFrame* callFrame, CallFrameUpdateAction action)
+{
+ if (!callFrame) {
+ m_currentCallFrame = nullptr;
+ return;
+ }
+
+ updateCallFrameInternal(callFrame);
+
+ if (action == AttemptPause)
+ pauseIfNeeded(callFrame);
+
+ if (!isStepping())
+ m_currentCallFrame = nullptr;
+}
+
+void Debugger::updateCallFrameInternal(CallFrame* callFrame)
{
m_currentCallFrame = callFrame;
SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame);
@@ -599,73 +685,87 @@ void Debugger::updateCallFrame(CallFrame* callFrame)
}
}
-void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame)
-{
- updateCallFrame(callFrame);
- pauseIfNeeded(callFrame);
- if (!isStepping())
- m_currentCallFrame = 0;
-}
-
void Debugger::pauseIfNeeded(CallFrame* callFrame)
{
+ VM& vm = callFrame->vm();
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
if (m_isPaused)
return;
- JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject();
- if (!needPauseHandling(vmEntryGlobalObject))
+ if (m_suppressAllPauses)
return;
- Breakpoint breakpoint;
- bool didHitBreakpoint = false;
- bool pauseNow = m_pauseOnNextStatement;
+ intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
+ if (isBlacklisted(sourceID))
+ return;
+
+ DebuggerPausedScope debuggerPausedScope(*this);
+
+ bool pauseNow = m_pauseAtNextOpportunity;
pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
- intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
+ bool didPauseForStep = pauseNow;
+ bool didHitBreakpoint = false;
+
+ Breakpoint breakpoint;
TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame);
pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint);
m_lastExecutedLine = position.m_line.zeroBasedInt();
if (!pauseNow)
return;
- DebuggerCallFrameScope debuggerCallFrameScope(*this);
+ clearNextPauseState();
// 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;
+
+ JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject();
if (didHitBreakpoint) {
- handleBreakpointHit(breakpoint);
+ 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)
+ if (!m_currentCallFrame)
return;
+
+ if (breakpoint.autoContinue) {
+ if (!didPauseForStep)
+ return;
+ didHitBreakpoint = false;
+ } else
+ m_pausingBreakpointID = breakpoint.id;
+ }
+
+ {
+ PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause);
+ handlePause(vmEntryGlobalObject, m_reasonForPause);
+ RELEASE_ASSERT(!scope.exception());
}
- handlePause(m_reasonForPause, vmEntryGlobalObject);
+ m_pausingBreakpointID = noBreakpointID;
- if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) {
+ if (!m_pauseAtNextOpportunity && !m_pauseOnCallFrame) {
setSteppingMode(SteppingModeDisabled);
m_currentCallFrame = nullptr;
}
}
-void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasHandler)
+void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler)
{
if (m_isPaused)
return;
PauseReasonDeclaration reason(*this, PausedForException);
- if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler)) {
- m_pauseOnNextStatement = true;
+ if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) {
+ m_pauseAtNextOpportunity = true;
setSteppingMode(SteppingModeEnabled);
}
m_hasHandlerForExceptionCallback = true;
m_currentException = exception;
- updateCallFrameAndPauseIfNeeded(callFrame);
+ updateCallFrame(callFrame, AttemptPause);
m_currentException = JSValue();
m_hasHandlerForExceptionCallback = false;
}
@@ -675,8 +775,28 @@ void Debugger::atStatement(CallFrame* callFrame)
if (m_isPaused)
return;
+ m_pastFirstExpressionInStatement = false;
+
PauseReasonDeclaration reason(*this, PausedAtStatement);
- updateCallFrameAndPauseIfNeeded(callFrame);
+ updateCallFrame(callFrame, AttemptPause);
+}
+
+void Debugger::atExpression(CallFrame* callFrame)
+{
+ if (m_isPaused)
+ return;
+
+ // If this is the first call in a statement, then we would have paused at the statement.
+ if (!m_pastFirstExpressionInStatement) {
+ m_pastFirstExpressionInStatement = true;
+ return;
+ }
+
+ // Only pause at the next expression with step-in and step-out, not step-over.
+ bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepOut;
+
+ PauseReasonDeclaration reason(*this, PausedAtExpression);
+ updateCallFrame(callFrame, shouldAttemptPause ? AttemptPause : NoPause);
}
void Debugger::callEvent(CallFrame* callFrame)
@@ -684,8 +804,7 @@ void Debugger::callEvent(CallFrame* callFrame)
if (m_isPaused)
return;
- PauseReasonDeclaration reason(*this, PausedAfterCall);
- updateCallFrameAndPauseIfNeeded(callFrame);
+ updateCallFrame(callFrame, NoPause);
}
void Debugger::returnEvent(CallFrame* callFrame)
@@ -693,18 +812,48 @@ void Debugger::returnEvent(CallFrame* callFrame)
if (m_isPaused)
return;
- PauseReasonDeclaration reason(*this, PausedBeforeReturn);
- updateCallFrameAndPauseIfNeeded(callFrame);
+ {
+ PauseReasonDeclaration reason(*this, PausedBeforeReturn);
+ updateCallFrame(callFrame, AttemptPause);
+ }
+
+ // Detach may have been called during pauseIfNeeded.
+ if (!m_currentCallFrame)
+ return;
+
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+
+ // Returning from a call, there was at least one expression on the statement we are returning to.
+ m_pastFirstExpressionInStatement = true;
+
+ // Treat stepping over a return statement like a step-out.
+ if (m_currentCallFrame == m_pauseOnCallFrame) {
+ m_pauseOnCallFrame = callerFrame;
+ m_pauseOnStepOut = true;
+ }
+
+ updateCallFrame(callerFrame, NoPause);
+}
+
+void Debugger::unwindEvent(CallFrame* callFrame)
+{
+ if (m_isPaused)
+ return;
+
+ updateCallFrame(callFrame, NoPause);
- // detach may have been called during pauseIfNeeded
if (!m_currentCallFrame)
return;
- // Treat stepping over a return statement like stepping out.
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+
+ // Treat stepping over an exception location like a step-out.
if (m_currentCallFrame == m_pauseOnCallFrame)
- m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+ m_pauseOnCallFrame = callerFrame;
- m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+ updateCallFrame(callerFrame, NoPause);
}
void Debugger::willExecuteProgram(CallFrame* callFrame)
@@ -712,14 +861,7 @@ 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);
+ updateCallFrame(callFrame, NoPause);
}
void Debugger::didExecuteProgram(CallFrame* callFrame)
@@ -728,17 +870,36 @@ void Debugger::didExecuteProgram(CallFrame* callFrame)
return;
PauseReasonDeclaration reason(*this, PausedAtEndOfProgram);
- updateCallFrameAndPauseIfNeeded(callFrame);
+ updateCallFrame(callFrame, AttemptPause);
- // Treat stepping over the end of a program like stepping out.
+ // Detach may have been called during pauseIfNeeded.
if (!m_currentCallFrame)
return;
+
+ VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame;
+ CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame);
+
+ // Returning from a program, could be eval(), there was at least one expression on the statement we are returning to.
+ m_pastFirstExpressionInStatement = true;
+
+ // Treat stepping over the end of a program like a step-out.
if (m_currentCallFrame == m_pauseOnCallFrame) {
- m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
- if (!m_currentCallFrame)
- return;
+ m_pauseOnCallFrame = callerFrame;
+ m_pauseAtNextOpportunity = true;
}
- m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel();
+
+ updateCallFrame(callerFrame, NoPause);
+
+ // Do not continue stepping into an unknown future program.
+ if (!m_currentCallFrame)
+ clearNextPauseState();
+}
+
+void Debugger::clearNextPauseState()
+{
+ m_pauseOnCallFrame = nullptr;
+ m_pauseAtNextOpportunity = false;
+ m_pauseOnStepOut = false;
}
void Debugger::didReachBreakpoint(CallFrame* callFrame)
@@ -746,16 +907,32 @@ void Debugger::didReachBreakpoint(CallFrame* callFrame)
if (m_isPaused)
return;
- PauseReasonDeclaration reason(*this, PausedForBreakpoint);
- m_pauseOnNextStatement = true;
+ PauseReasonDeclaration reason(*this, PausedForDebuggerStatement);
+ m_pauseAtNextOpportunity = true;
setSteppingMode(SteppingModeEnabled);
- updateCallFrameAndPauseIfNeeded(callFrame);
+ updateCallFrame(callFrame, AttemptPause);
+}
+
+DebuggerCallFrame& Debugger::currentDebuggerCallFrame()
+{
+ if (!m_currentDebuggerCallFrame)
+ m_currentDebuggerCallFrame = DebuggerCallFrame::create(m_currentCallFrame);
+ return *m_currentDebuggerCallFrame;
+}
+
+bool Debugger::isBlacklisted(SourceID sourceID) const
+{
+ return m_blacklistedScripts.contains(sourceID);
+}
+
+void Debugger::addToBlacklist(SourceID sourceID)
+{
+ m_blacklistedScripts.add(sourceID);
}
-DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const
+void Debugger::clearBlacklist()
{
- ASSERT(m_currentDebuggerCallFrame);
- return m_currentDebuggerCallFrame.get();
+ m_blacklistedScripts.clear();
}
} // namespace JSC