diff options
Diffstat (limited to 'Source/JavaScriptCore/debugger/Debugger.cpp')
-rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.cpp | 611 |
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 |