diff options
Diffstat (limited to 'Source/JavaScriptCore/debugger/Debugger.cpp')
-rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.cpp | 301 |
1 files changed, 158 insertions, 143 deletions
diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index afa7546c8..f50d54be5 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -25,13 +25,12 @@ #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" @@ -40,65 +39,41 @@ 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) -{ -} +struct GatherSourceProviders : public MarkedBlock::VoidFunctor { + HashSet<SourceProvider*> sourceProviders; + JSGlobalObject* m_globalObject; -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()); -} + GatherSourceProviders(JSGlobalObject* globalObject) + : m_globalObject(globalObject) { } -inline void Recompiler::operator()(JSCell* cell) -{ - if (!cell->inherits(JSFunction::info())) - return; + IterationStatus operator()(JSCell* cell) + { + JSFunction* function = jsDynamicCast<JSFunction*>(cell); + if (!function) + return IterationStatus::Continue; - JSFunction* function = jsCast<JSFunction*>(cell); - if (function->executable()->isHostFunction()) - return; + if (function->scope()->globalObject() != m_globalObject) + return IterationStatus::Continue; - FunctionExecutable* executable = function->jsExecutable(); + if (!function->executable()->isFunctionExecutable()) + return IterationStatus::Continue; - // 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; + if (function->isHostOrBuiltinFunction()) + return IterationStatus::Continue; - 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); -} + 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); @@ -106,11 +81,11 @@ public: 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; } } @@ -138,14 +113,14 @@ private: Debugger& m_debugger; }; -Debugger::Debugger(bool isInWorkerThread) - : m_vm(nullptr) +Debugger::Debugger(VM& vm) + : m_vm(vm) , m_pauseOnExceptionsState(DontPauseOnExceptions) , m_pauseOnNextStatement(false) , m_isPaused(false) , m_breakpointsActivated(true) , m_hasHandlerForExceptionCallback(false) - , m_isInWorkerThread(isInWorkerThread) + , m_suppressAllPauses(false) , m_steppingMode(SteppingModeDisabled) , m_reasonForPause(NotPaused) , m_pauseOnCallFrame(0) @@ -153,6 +128,7 @@ Debugger::Debugger(bool isInWorkerThread) , m_lastExecutedLine(UINT_MAX) , m_lastExecutedSourceID(noSourceID) , m_topBreakpointID(noBreakpointID) + , m_pausingBreakpointID(noBreakpointID) { } @@ -166,12 +142,19 @@ 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); + + 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) @@ -195,8 +178,11 @@ void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) clearDebuggerRequests(globalObject); globalObject->setDebugger(0); - if (!m_globalObjects.size()) - m_vm = nullptr; +} + +bool Debugger::isAttached(JSGlobalObject* globalObject) +{ + return globalObject->debugger() == this; } class Debugger::SetSteppingModeFunctor { @@ -227,12 +213,12 @@ void Debugger::setSteppingMode(SteppingMode mode) { if (mode == m_steppingMode) return; - m_steppingMode = mode; - if (!m_vm) - return; + m_vm.heap.completeAllDFGPlans(); + + m_steppingMode = mode; SetSteppingModeFunctor functor(this, mode); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); } void Debugger::registerCodeBlock(CodeBlock* codeBlock) @@ -242,9 +228,27 @@ void Debugger::registerCodeBlock(CodeBlock* codeBlock) codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); } +void Debugger::setProfilingClient(ProfilingClient* client) +{ + ASSERT(!!m_profilingClient != !!client); + m_profilingClient = client; + + recompileAllJSFunctions(); +} + +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(); + ScriptExecutable* executable = codeBlock->ownerScriptExecutable(); SourceID sourceID = static_cast<SourceID>(executable->sourceID()); if (breakpoint.sourceID != sourceID) @@ -253,7 +257,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(); @@ -313,26 +317,15 @@ private: void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot) { - if (!m_vm) - return; + m_vm.heap.completeAllDFGPlans(); + 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); - return; - } - - vm->prepareToDiscardCode(); - - Recompiler recompiler(this); - HeapIterationScope iterationScope(vm->heap); - vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler); + m_vm.deleteAllCode(); } BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn) @@ -346,18 +339,18 @@ BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine 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; + breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator; - BreakpointsInLine& breakpoints = breaksIt->value; - unsigned breakpointsCount = breakpoints.size(); - for (unsigned i = 0; i < breakpointsCount; i++) - if (breakpoints[i].column == column) { + 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); @@ -366,8 +359,9 @@ BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine 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); @@ -380,31 +374,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); } } @@ -425,12 +423,11 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br 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 +440,22 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br return false; if (hitBreakpoint) - *hitBreakpoint = breakpoints[i]; + *hitBreakpoint = *breakpoint; - if (breakpoints[i].condition.isEmpty()) + 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); - JSValue exception; - JSValue result = DebuggerCallFrame::evaluateWithCallFrame(m_currentCallFrame, breakpoints[i].condition, exception); + NakedPtr<Exception> exception; + DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame(); + JSValue result = debuggerCallFrame->evaluate(breakpoint->condition, exception); // We can lose the debugger while executing JavaScript. if (!m_currentCallFrame) @@ -488,14 +490,14 @@ private: void Debugger::clearBreakpoints() { + m_vm.heap.completeAllDFGPlans(); + 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 { @@ -518,9 +520,10 @@ private: void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject) { - ASSERT(m_vm); + m_vm.heap.completeAllDFGPlans(); + ClearDebuggerRequestsFunctor functor(globalObject); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); } void Debugger::setBreakpointsActivated(bool activated) @@ -545,10 +548,12 @@ void Debugger::breakProgram() if (m_isPaused) return; + if (!m_vm.topCallFrame) + return; + m_pauseOnNextStatement = true; setSteppingMode(SteppingModeEnabled); - m_currentCallFrame = m_vm->topCallFrame; - ASSERT(m_currentCallFrame); + m_currentCallFrame = m_vm.topCallFrame; pauseIfNeeded(m_currentCallFrame); } @@ -585,7 +590,8 @@ 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) : 0; notifyDoneProcessingDebuggerEvents(); } @@ -612,6 +618,9 @@ void Debugger::pauseIfNeeded(CallFrame* callFrame) if (m_isPaused) return; + if (m_suppressAllPauses) + return; + JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); if (!needPauseHandling(vmEntryGlobalObject)) return; @@ -621,6 +630,8 @@ void Debugger::pauseIfNeeded(CallFrame* callFrame) 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); @@ -628,8 +639,6 @@ void Debugger::pauseIfNeeded(CallFrame* callFrame) if (!pauseNow) return; - DebuggerCallFrameScope debuggerCallFrameScope(*this); - // 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); @@ -637,14 +646,21 @@ void Debugger::pauseIfNeeded(CallFrame* callFrame) m_pauseOnNextStatement = false; 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) return; + m_pausingBreakpointID = breakpoint.id; + } + + { + PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause); + handlePause(vmEntryGlobalObject, m_reasonForPause); + RELEASE_ASSERT(!callFrame->hadException()); } - handlePause(m_reasonForPause, vmEntryGlobalObject); + m_pausingBreakpointID = noBreakpointID; if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) { setSteppingMode(SteppingModeDisabled); @@ -652,13 +668,13 @@ void Debugger::pauseIfNeeded(CallFrame* callFrame) } } -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)) { + if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) { m_pauseOnNextStatement = true; setSteppingMode(SteppingModeEnabled); } @@ -701,10 +717,13 @@ void Debugger::returnEvent(CallFrame* callFrame) return; // Treat stepping over a return statement like stepping out. - if (m_currentCallFrame == m_pauseOnCallFrame) - m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + if (m_currentCallFrame == m_pauseOnCallFrame) { + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + } - m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); } void Debugger::willExecuteProgram(CallFrame* callFrame) @@ -713,13 +732,7 @@ void Debugger::willExecuteProgram(CallFrame* callFrame) 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); + updateCallFrameAndPauseIfNeeded(callFrame); } void Debugger::didExecuteProgram(CallFrame* callFrame) @@ -734,11 +747,13 @@ void Debugger::didExecuteProgram(CallFrame* callFrame) if (!m_currentCallFrame) return; if (m_currentCallFrame == m_pauseOnCallFrame) { - m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); if (!m_currentCallFrame) return; } - m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); } void Debugger::didReachBreakpoint(CallFrame* callFrame) @@ -746,7 +761,7 @@ void Debugger::didReachBreakpoint(CallFrame* callFrame) if (m_isPaused) return; - PauseReasonDeclaration reason(*this, PausedForBreakpoint); + PauseReasonDeclaration reason(*this, PausedForDebuggerStatement); m_pauseOnNextStatement = true; setSteppingMode(SteppingModeEnabled); updateCallFrameAndPauseIfNeeded(callFrame); |