2017-08-12 16:48:01 +00:00
|
|
|
/*
|
2022-10-23 02:55:20 +00:00
|
|
|
* Copyright (C) 2008-2019 Apple Inc. All rights reserved.
|
2017-08-12 16:48:01 +00:00
|
|
|
* Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
|
|
|
|
* Copyright (C) 2001 Peter Kelly (pmk@post.com)
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "Debugger.h"
|
|
|
|
|
|
|
|
#include "CodeBlock.h"
|
|
|
|
#include "DebuggerCallFrame.h"
|
2022-10-23 02:55:20 +00:00
|
|
|
#include "DebuggerScope.h"
|
2017-08-12 16:48:01 +00:00
|
|
|
#include "HeapIterationScope.h"
|
|
|
|
#include "JSCInlines.h"
|
|
|
|
#include "MarkedSpaceInlines.h"
|
2018-01-03 05:16:05 +00:00
|
|
|
#include "VMEntryScope.h"
|
2022-10-23 02:55:20 +00:00
|
|
|
#include <wtf/HashMap.h>
|
|
|
|
#include <wtf/HashSet.h>
|
|
|
|
#include <wtf/RefPtr.h>
|
|
|
|
#include <wtf/Vector.h>
|
|
|
|
#include <wtf/text/TextPosition.h>
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
namespace JSC {
|
|
|
|
|
|
|
|
class DebuggerPausedScope {
|
|
|
|
public:
|
|
|
|
DebuggerPausedScope(Debugger& debugger)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
{
|
|
|
|
ASSERT(!m_debugger.m_currentDebuggerCallFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
~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 SetForScope<bool>, but that cannot be used
|
|
|
|
// as the m_isPaused field uses only one bit.
|
|
|
|
class TemporaryPausedState {
|
|
|
|
public:
|
|
|
|
TemporaryPausedState(Debugger& debugger)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
{
|
|
|
|
ASSERT(!m_debugger.m_isPaused);
|
|
|
|
m_debugger.m_isPaused = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
~TemporaryPausedState()
|
|
|
|
{
|
|
|
|
m_debugger.m_isPaused = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Debugger& m_debugger;
|
|
|
|
};
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
Debugger::TemporarilyDisableExceptionBreakpoints::TemporarilyDisableExceptionBreakpoints(Debugger& debugger)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Debugger::TemporarilyDisableExceptionBreakpoints::~TemporarilyDisableExceptionBreakpoints()
|
|
|
|
{
|
|
|
|
restore();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::TemporarilyDisableExceptionBreakpoints::replace()
|
|
|
|
{
|
|
|
|
if (m_debugger.m_pauseOnAllExceptionsBreakpoint)
|
|
|
|
m_pauseOnAllExceptionsBreakpoint = WTFMove(m_debugger.m_pauseOnAllExceptionsBreakpoint);
|
|
|
|
|
|
|
|
if (m_debugger.m_pauseOnUncaughtExceptionsBreakpoint)
|
|
|
|
m_pauseOnUncaughtExceptionsBreakpoint = WTFMove(m_debugger.m_pauseOnUncaughtExceptionsBreakpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::TemporarilyDisableExceptionBreakpoints::restore()
|
|
|
|
{
|
|
|
|
if (m_pauseOnAllExceptionsBreakpoint)
|
|
|
|
m_debugger.m_pauseOnAllExceptionsBreakpoint = WTFMove(m_pauseOnAllExceptionsBreakpoint);
|
|
|
|
|
|
|
|
if (m_pauseOnUncaughtExceptionsBreakpoint)
|
|
|
|
m_debugger.m_pauseOnUncaughtExceptionsBreakpoint = WTFMove(m_pauseOnUncaughtExceptionsBreakpoint);
|
|
|
|
}
|
|
|
|
|
2018-01-03 05:16:05 +00:00
|
|
|
|
|
|
|
Debugger::ProfilingClient::~ProfilingClient()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
Debugger::Debugger(VM& vm)
|
|
|
|
: m_vm(vm)
|
|
|
|
, m_pauseAtNextOpportunity(false)
|
|
|
|
, m_pastFirstExpressionInStatement(false)
|
|
|
|
, m_isPaused(false)
|
|
|
|
, m_breakpointsActivated(false)
|
|
|
|
, m_hasHandlerForExceptionCallback(false)
|
|
|
|
, m_suppressAllPauses(false)
|
|
|
|
, m_steppingMode(SteppingModeDisabled)
|
|
|
|
, m_reasonForPause(NotPaused)
|
|
|
|
, m_lastExecutedLine(UINT_MAX)
|
|
|
|
, m_lastExecutedSourceID(noSourceID)
|
|
|
|
, 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(nullptr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::attach(JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
ASSERT(!globalObject->debugger());
|
|
|
|
globalObject->setDebugger(this);
|
|
|
|
m_globalObjects.add(globalObject);
|
|
|
|
|
|
|
|
m_vm.setShouldBuildPCToCodeOriginMapping();
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
// Call `sourceParsed` after iterating because it will execute JavaScript in Web Inspector.
|
|
|
|
HashSet<RefPtr<SourceProvider>> sourceProviders;
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
HeapIterationScope iterationScope(m_vm.heap);
|
2022-10-23 02:55:20 +00:00
|
|
|
m_vm.heap.objectSpace().forEachLiveCell(iterationScope, [&] (HeapCell* heapCell, HeapCell::Kind kind) {
|
|
|
|
if (isJSCellKind(kind)) {
|
|
|
|
auto* cell = static_cast<JSCell*>(heapCell);
|
|
|
|
if (auto* function = jsDynamicCast<JSFunction*>(cell->vm(), cell)) {
|
|
|
|
if (function->scope()->globalObject() == globalObject && function->executable()->isFunctionExecutable() && !function->isHostOrBuiltinFunction())
|
|
|
|
sourceProviders.add(jsCast<FunctionExecutable*>(function->executable())->source().provider());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return IterationStatus::Continue;
|
|
|
|
});
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
2022-10-23 02:55:20 +00:00
|
|
|
for (auto& sourceProvider : sourceProviders)
|
|
|
|
sourceParsed(globalObject, sourceProvider.get(), -1, nullString());
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2018-01-03 05:16:05 +00:00
|
|
|
// We know there is an entry scope, otherwise, m_currentCallFrame would be null.
|
2020-08-29 13:27:11 +00:00
|
|
|
VM& vm = globalObject->vm();
|
2022-10-23 02:55:20 +00:00
|
|
|
JSLockHolder locker(vm);
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
if (m_isPaused && m_currentCallFrame && vm.entryScope->globalObject() == globalObject) {
|
2017-08-12 16:48:01 +00:00
|
|
|
m_currentCallFrame = nullptr;
|
|
|
|
m_pauseOnCallFrame = nullptr;
|
|
|
|
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(nullptr);
|
|
|
|
|
|
|
|
if (m_globalObjects.isEmpty())
|
|
|
|
clearParsedData();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Debugger::isAttached(JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
return globalObject->debugger() == this;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Debugger::SetSteppingModeFunctor {
|
|
|
|
public:
|
|
|
|
SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
, m_mode(mode)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
void operator()(CodeBlock* codeBlock) const
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (m_debugger == codeBlock->globalObject()->debugger()) {
|
|
|
|
if (m_mode == SteppingModeEnabled)
|
|
|
|
codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
|
|
|
|
else
|
|
|
|
codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Debugger* m_debugger;
|
|
|
|
SteppingMode m_mode;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Debugger::setSteppingMode(SteppingMode mode)
|
|
|
|
{
|
|
|
|
if (mode == m_steppingMode)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_vm.heap.completeAllJITPlans();
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::setClient(Client* client)
|
|
|
|
{
|
|
|
|
ASSERT(!!m_client != !!client);
|
|
|
|
m_client = client;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::addObserver(Observer& observer)
|
|
|
|
{
|
|
|
|
bool wasEmpty = m_observers.isEmpty();
|
|
|
|
|
|
|
|
m_observers.add(&observer);
|
|
|
|
|
|
|
|
if (wasEmpty)
|
|
|
|
attachDebugger();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::removeObserver(Observer& observer, bool isBeingDestroyed)
|
|
|
|
{
|
|
|
|
m_observers.remove(&observer);
|
|
|
|
|
|
|
|
if (m_observers.isEmpty())
|
|
|
|
detachDebugger(isBeingDestroyed);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Debugger::canDispatchFunctionToObservers() const
|
|
|
|
{
|
|
|
|
return !m_dispatchingFunctionToObservers && !m_observers.isEmpty();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::dispatchFunctionToObservers(Function<void(Observer&)> func)
|
|
|
|
{
|
|
|
|
if (!canDispatchFunctionToObservers())
|
|
|
|
return;
|
|
|
|
|
|
|
|
SetForScope<bool> change(m_dispatchingFunctionToObservers, true);
|
|
|
|
|
|
|
|
for (auto* observer : copyToVector(m_observers))
|
|
|
|
func(*observer);
|
|
|
|
}
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
void Debugger::setProfilingClient(ProfilingClient* client)
|
|
|
|
{
|
|
|
|
ASSERT(!!m_profilingClient != !!client);
|
|
|
|
m_profilingClient = client;
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::sourceParsed(JSGlobalObject* globalObject, SourceProvider* sourceProvider, int errorLine, const String& errorMessage)
|
|
|
|
{
|
|
|
|
// Preemptively check whether we can dispatch so that we don't do any unnecessary allocations.
|
|
|
|
if (!canDispatchFunctionToObservers())
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (errorLine != -1) {
|
|
|
|
auto sourceURL = sourceProvider->sourceURL();
|
|
|
|
auto data = sourceProvider->source().toString();
|
|
|
|
auto firstLine = sourceProvider->startPosition().m_line.oneBasedInt();
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.failedToParseSource(sourceURL, data, firstLine, errorLine, errorMessage);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
JSC::SourceID sourceID = sourceProvider->asID();
|
|
|
|
|
|
|
|
// FIXME: <https://webkit.org/b/162773> Web Inspector: Simplify Debugger::Script to use SourceProvider
|
|
|
|
Debugger::Script script;
|
|
|
|
script.sourceProvider = sourceProvider;
|
|
|
|
script.url = sourceProvider->sourceURL();
|
|
|
|
script.source = sourceProvider->source().toString();
|
|
|
|
script.startLine = sourceProvider->startPosition().m_line.zeroBasedInt();
|
|
|
|
script.startColumn = sourceProvider->startPosition().m_column.zeroBasedInt();
|
|
|
|
script.isContentScript = isContentScript(globalObject);
|
|
|
|
script.sourceURL = sourceProvider->sourceURLDirective();
|
|
|
|
script.sourceMappingURL = sourceProvider->sourceMappingURLDirective();
|
|
|
|
|
|
|
|
int sourceLength = script.source.length();
|
|
|
|
int lineCount = 1;
|
|
|
|
int lastLineStart = 0;
|
|
|
|
for (int i = 0; i < sourceLength; ++i) {
|
|
|
|
if (script.source[i] == '\n') {
|
|
|
|
lineCount += 1;
|
|
|
|
lastLineStart = i + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
script.endLine = script.startLine + lineCount - 1;
|
|
|
|
if (lineCount == 1)
|
|
|
|
script.endColumn = script.startColumn + sourceLength;
|
|
|
|
else
|
|
|
|
script.endColumn = sourceLength - lastLineStart;
|
|
|
|
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.didParseSource(sourceID, script);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
Seconds Debugger::willEvaluateScript()
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
return m_profilingClient->willEvaluateScript();
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
void Debugger::didEvaluateScript(Seconds startTime, ProfilingReason reason)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
m_profilingClient->didEvaluateScript(startTime, reason);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
|
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
ASSERT(breakpoint.isResolved());
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
ScriptExecutable* executable = codeBlock->ownerExecutable();
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
SourceID sourceID = static_cast<SourceID>(executable->sourceID());
|
2022-10-23 02:55:20 +00:00
|
|
|
if (breakpoint.sourceID() != sourceID)
|
2017-08-12 16:48:01 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
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.
|
2022-10-23 02:55:20 +00:00
|
|
|
unsigned line = breakpoint.lineNumber() + 1;
|
|
|
|
Optional<unsigned> column;
|
|
|
|
if (breakpoint.columnNumber())
|
|
|
|
column = breakpoint.columnNumber() + 1;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
if (line < startLine || line > endLine)
|
|
|
|
return;
|
2022-10-23 02:55:20 +00:00
|
|
|
if (column) {
|
2017-08-12 16:48:01 +00:00
|
|
|
if (line == startLine && column < startColumn)
|
|
|
|
return;
|
|
|
|
if (line == endLine && column > endColumn)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (enabledOrNot == BreakpointEnabled)
|
|
|
|
codeBlock->addBreakpoint(1);
|
|
|
|
else
|
|
|
|
codeBlock->removeBreakpoint(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::applyBreakpoints(CodeBlock* codeBlock)
|
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
for (auto& breakpoint : m_breakpoints)
|
|
|
|
toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class Debugger::ToggleBreakpointFunctor {
|
|
|
|
public:
|
|
|
|
ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
, m_breakpoint(breakpoint)
|
|
|
|
, m_enabledOrNot(enabledOrNot)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
void operator()(CodeBlock* codeBlock) const
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (m_debugger == codeBlock->globalObject()->debugger())
|
|
|
|
m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Debugger* m_debugger;
|
|
|
|
Breakpoint& m_breakpoint;
|
|
|
|
BreakpointState m_enabledOrNot;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
|
|
|
|
{
|
|
|
|
m_vm.heap.completeAllJITPlans();
|
|
|
|
|
|
|
|
ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
|
|
|
|
m_vm.heap.forEachCodeBlock(functor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::recompileAllJSFunctions()
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool Debugger::resolveBreakpoint(Breakpoint& breakpoint, SourceProvider* sourceProvider)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
RELEASE_ASSERT(!breakpoint.isResolved());
|
|
|
|
ASSERT(breakpoint.isLinked());
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// 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
|
2022-10-23 02:55:20 +00:00
|
|
|
// and CodeBlock line values are one-based while column is zero-based.
|
|
|
|
int line = breakpoint.lineNumber() + 1;
|
|
|
|
int column = breakpoint.columnNumber();
|
|
|
|
|
|
|
|
// Account for a <script>'s start position on the first line only.
|
|
|
|
int providerStartLine = sourceProvider->startPosition().m_line.oneBasedInt(); // One based to match the already adjusted line.
|
|
|
|
int providerStartColumn = sourceProvider->startPosition().m_column.zeroBasedInt(); // Zero based so column zero is zero.
|
|
|
|
if (line == providerStartLine && breakpoint.columnNumber()) {
|
|
|
|
ASSERT(providerStartColumn <= column);
|
|
|
|
if (providerStartColumn)
|
|
|
|
column -= providerStartColumn;
|
|
|
|
}
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
DebuggerParseData& parseData = debuggerParseData(breakpoint.sourceID(), sourceProvider);
|
|
|
|
Optional<JSTextPosition> resolvedPosition = parseData.pausePositions.breakpointLocationForLineColumn(line, column);
|
2017-08-12 16:48:01 +00:00
|
|
|
if (!resolvedPosition)
|
2022-10-23 02:55:20 +00:00
|
|
|
return false;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
int resolvedLine = resolvedPosition->line;
|
|
|
|
int resolvedColumn = resolvedPosition->column();
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
// Re-account for a <script>'s start position on the first line only.
|
|
|
|
if (resolvedLine == providerStartLine && breakpoint.columnNumber()) {
|
|
|
|
if (providerStartColumn)
|
|
|
|
resolvedColumn += providerStartColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
return breakpoint.resolve(resolvedLine - 1, resolvedColumn);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool Debugger::setBreakpoint(Breakpoint& breakpoint)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
ASSERT(breakpoint.isResolved());
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto& breakpointsForLine = m_breakpointsForSourceID.ensure(breakpoint.sourceID(), [] {
|
|
|
|
return LineToBreakpointsMap();
|
|
|
|
}).iterator->value;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto& breakpoints = breakpointsForLine.ensure(breakpoint.lineNumber(), [] {
|
|
|
|
return BreakpointsVector();
|
|
|
|
}).iterator->value;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
for (auto& existingBreakpoint : breakpoints) {
|
|
|
|
if (existingBreakpoint->columnNumber() == breakpoint.columnNumber()) {
|
|
|
|
ASSERT(existingBreakpoint->id() != breakpoint.id());
|
2017-08-12 16:48:01 +00:00
|
|
|
// Found existing breakpoint. Do not create a duplicate at this location.
|
2022-10-23 02:55:20 +00:00
|
|
|
return false;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
breakpoints.append(makeRef(breakpoint));
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
m_breakpoints.add(makeRef(breakpoint));
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
toggleBreakpoint(breakpoint, BreakpointEnabled);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
return true;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool Debugger::removeBreakpoint(Breakpoint& breakpoint)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
ASSERT(breakpoint.isResolved());
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto breakpointsForLineIterator = m_breakpointsForSourceID.find(breakpoint.sourceID());
|
|
|
|
if (breakpointsForLineIterator == m_breakpointsForSourceID.end())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
auto breakpointsIterator = breakpointsForLineIterator->value.find(breakpoint.lineNumber());
|
|
|
|
if (breakpointsIterator == breakpointsForLineIterator->value.end())
|
|
|
|
return false;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
toggleBreakpoint(breakpoint, BreakpointDisabled);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto& breakpoints = breakpointsIterator->value;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
#if ASSERT_ENABLED
|
2017-08-12 16:48:01 +00:00
|
|
|
bool found = false;
|
2022-10-23 02:55:20 +00:00
|
|
|
for (auto& existingBreakpoint : breakpoints) {
|
|
|
|
if (existingBreakpoint->columnNumber() == breakpoint.columnNumber()) {
|
|
|
|
ASSERT(existingBreakpoint->id() == breakpoint.id());
|
|
|
|
ASSERT(!found);
|
2017-08-12 16:48:01 +00:00
|
|
|
found = true;
|
2022-10-23 02:55:20 +00:00
|
|
|
}
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
2022-10-23 02:55:20 +00:00
|
|
|
#endif // ASSERT_ENABLED
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto removed = m_breakpoints.remove(breakpoint);
|
|
|
|
removed |= !breakpoints.removeAllMatching([&] (const Ref<Breakpoint>& existingBreakpoint) {
|
|
|
|
return &breakpoint == existingBreakpoint.ptr();
|
|
|
|
});
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
if (breakpoints.isEmpty()) {
|
2022-10-23 02:55:20 +00:00
|
|
|
breakpointsForLineIterator->value.remove(breakpointsIterator);
|
|
|
|
if (breakpointsForLineIterator->value.isEmpty())
|
|
|
|
m_breakpointsForSourceID.remove(breakpointsForLineIterator);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
2022-10-23 02:55:20 +00:00
|
|
|
|
|
|
|
return removed;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
RefPtr<Breakpoint> Debugger::didHitBreakpoint(JSGlobalObject* globalObject, SourceID sourceID, const TextPosition& position)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (!m_breakpointsActivated)
|
2022-10-23 02:55:20 +00:00
|
|
|
return nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto breakpointsForLineIterator = m_breakpointsForSourceID.find(sourceID);
|
|
|
|
if (breakpointsForLineIterator == m_breakpointsForSourceID.end())
|
|
|
|
return nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
unsigned line = position.m_line.zeroBasedInt();
|
|
|
|
unsigned column = position.m_column.zeroBasedInt();
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
auto breakpointsIterator = breakpointsForLineIterator->value.find(line);
|
|
|
|
if (breakpointsIterator == breakpointsForLineIterator->value.end())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
for (auto& breakpoint : breakpointsIterator->value) {
|
|
|
|
unsigned breakLine = breakpoint->lineNumber();
|
|
|
|
unsigned breakColumn = breakpoint->columnNumber();
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
|
|
|
|
ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
|
2022-10-23 02:55:20 +00:00
|
|
|
if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) || (line == breakLine && column == breakColumn)) {
|
|
|
|
if (breakpoint->shouldPause(*this, globalObject))
|
|
|
|
return breakpoint.copyRef();
|
2017-08-12 16:48:01 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
return nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class Debugger::ClearCodeBlockDebuggerRequestsFunctor {
|
|
|
|
public:
|
|
|
|
ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger)
|
|
|
|
: m_debugger(debugger)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
void operator()(CodeBlock* codeBlock) const
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
|
|
|
|
codeBlock->clearDebuggerRequests();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Debugger* m_debugger;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Debugger::clearBreakpoints()
|
|
|
|
{
|
|
|
|
m_vm.heap.completeAllJITPlans();
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
m_breakpointsForSourceID.clear();
|
|
|
|
m_breakpoints.clear();
|
|
|
|
m_specialBreakpoint = nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
ClearCodeBlockDebuggerRequestsFunctor functor(this);
|
|
|
|
m_vm.heap.forEachCodeBlock(functor);
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool Debugger::evaluateBreakpointCondition(Breakpoint& breakpoint, JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
const String& condition = breakpoint.condition();
|
|
|
|
if (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();
|
|
|
|
JSObject* scopeExtensionObject = m_client ? m_client->scopeExtensionObject(*this, globalObject, debuggerCallFrame) : nullptr;
|
|
|
|
JSValue result = debuggerCallFrame.evaluateWithScopeExtension(condition, scopeExtensionObject, exception);
|
|
|
|
|
|
|
|
// We can lose the debugger while executing JavaScript.
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (exception) {
|
|
|
|
reportException(globalObject, exception);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result.toBoolean(globalObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::evaluateBreakpointActions(Breakpoint& breakpoint, JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
ASSERT(m_isPaused);
|
|
|
|
ASSERT(isAttached(globalObject));
|
|
|
|
|
|
|
|
m_currentProbeBatchId++;
|
|
|
|
|
|
|
|
for (auto& action : breakpoint.actions()) {
|
|
|
|
auto& debuggerCallFrame = currentDebuggerCallFrame();
|
|
|
|
|
|
|
|
switch (action.type) {
|
|
|
|
case Breakpoint::Action::Type::Log:
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.breakpointActionLog(debuggerCallFrame.globalObject(), action.data);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Breakpoint::Action::Type::Evaluate: {
|
|
|
|
NakedPtr<Exception> exception;
|
|
|
|
JSObject* scopeExtensionObject = m_client ? m_client->scopeExtensionObject(*this, globalObject, debuggerCallFrame) : nullptr;
|
|
|
|
debuggerCallFrame.evaluateWithScopeExtension(action.data, scopeExtensionObject, exception);
|
|
|
|
if (exception)
|
|
|
|
reportException(debuggerCallFrame.globalObject(), exception);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Breakpoint::Action::Type::Sound:
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.breakpointActionSound(action.id);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Breakpoint::Action::Type::Probe: {
|
|
|
|
NakedPtr<Exception> exception;
|
|
|
|
JSObject* scopeExtensionObject = m_client ? m_client->scopeExtensionObject(*this, globalObject, debuggerCallFrame) : nullptr;
|
|
|
|
JSValue result = debuggerCallFrame.evaluateWithScopeExtension(action.data, scopeExtensionObject, exception);
|
|
|
|
JSC::JSGlobalObject* debuggerGlobalObject = debuggerCallFrame.globalObject();
|
|
|
|
if (exception)
|
|
|
|
reportException(debuggerGlobalObject, exception);
|
|
|
|
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.breakpointActionProbe(debuggerGlobalObject, action.id, m_currentProbeBatchId, m_nextProbeSampleId++, exception ? exception->value() : result);
|
|
|
|
});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isAttached(globalObject))
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
class Debugger::ClearDebuggerRequestsFunctor {
|
|
|
|
public:
|
|
|
|
ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject)
|
|
|
|
: m_globalObject(globalObject)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
void operator()(CodeBlock* codeBlock) const
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
|
|
|
|
codeBlock->clearDebuggerRequests();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
JSGlobalObject* m_globalObject;
|
|
|
|
};
|
|
|
|
|
|
|
|
void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
m_vm.heap.completeAllJITPlans();
|
|
|
|
|
|
|
|
ClearDebuggerRequestsFunctor functor(globalObject);
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::schedulePauseAtNextOpportunity()
|
|
|
|
{
|
|
|
|
m_pauseAtNextOpportunity = true;
|
|
|
|
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::cancelPauseAtNextOpportunity()
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
m_pauseAtNextOpportunity = false;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool Debugger::schedulePauseForSpecialBreakpoint(Breakpoint& breakpoint)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
if (m_specialBreakpoint)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_specialBreakpoint = makeRef(breakpoint);
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Debugger::cancelPauseForSpecialBreakpoint(Breakpoint& breakpoint)
|
|
|
|
{
|
|
|
|
if (&breakpoint != m_specialBreakpoint)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_specialBreakpoint = nullptr;
|
|
|
|
return true;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::breakProgram(RefPtr<Breakpoint>&& specialBreakpoint)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!m_vm.topCallFrame)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
if (specialBreakpoint) {
|
|
|
|
ASSERT(!m_specialBreakpoint);
|
|
|
|
m_specialBreakpoint = WTFMove(specialBreakpoint);
|
|
|
|
} else
|
|
|
|
m_pauseAtNextOpportunity = true;
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
setSteppingMode(SteppingModeEnabled);
|
|
|
|
m_currentCallFrame = m_vm.topCallFrame;
|
2022-10-23 02:55:20 +00:00
|
|
|
pauseIfNeeded(m_currentCallFrame->lexicalGlobalObject(m_vm));
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::continueProgram()
|
|
|
|
{
|
2018-01-03 05:16:05 +00:00
|
|
|
clearNextPauseState();
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
if (!m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
m_doneProcessingDebuggerEvents = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::stepNextExpression()
|
|
|
|
{
|
|
|
|
if (!m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pauseOnCallFrame = m_currentCallFrame;
|
|
|
|
m_pauseOnStepNext = true;
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
|
|
|
m_doneProcessingDebuggerEvents = true;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::stepIntoStatement()
|
|
|
|
{
|
|
|
|
if (!m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pauseAtNextOpportunity = true;
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
2022-10-23 02:55:20 +00:00
|
|
|
m_doneProcessingDebuggerEvents = true;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::stepOverStatement()
|
|
|
|
{
|
|
|
|
if (!m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pauseOnCallFrame = m_currentCallFrame;
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
2022-10-23 02:55:20 +00:00
|
|
|
m_doneProcessingDebuggerEvents = true;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::stepOutOfFunction()
|
|
|
|
{
|
|
|
|
if (!m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
EntryFrame* topEntryFrame = m_vm.topEntryFrame;
|
|
|
|
m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topEntryFrame) : nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
m_pauseOnStepOut = true;
|
|
|
|
setSteppingMode(SteppingModeEnabled);
|
2022-10-23 02:55:20 +00:00
|
|
|
m_doneProcessingDebuggerEvents = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline JSGlobalObject* lexicalGlobalObjectForCallFrame(VM& vm, CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (!callFrame)
|
|
|
|
return nullptr;
|
|
|
|
return callFrame->lexicalGlobalObject(vm);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::updateCallFrame(JSGlobalObject* globalObject, CallFrame* callFrame, CallFrameUpdateAction action)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (!callFrame) {
|
|
|
|
m_currentCallFrame = nullptr;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
updateCallFrameInternal(callFrame);
|
|
|
|
|
|
|
|
if (action == AttemptPause)
|
2022-10-23 02:55:20 +00:00
|
|
|
pauseIfNeeded(globalObject);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
if (!isStepping())
|
|
|
|
m_currentCallFrame = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::updateCallFrameInternal(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
m_currentCallFrame = callFrame;
|
|
|
|
SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame);
|
|
|
|
if (m_lastExecutedSourceID != sourceID) {
|
|
|
|
m_lastExecutedLine = UINT_MAX;
|
|
|
|
m_lastExecutedSourceID = sourceID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::pauseIfNeeded(JSGlobalObject* globalObject)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2018-01-03 05:16:05 +00:00
|
|
|
VM& vm = m_vm;
|
2017-08-12 16:48:01 +00:00
|
|
|
auto scope = DECLARE_THROW_SCOPE(vm);
|
|
|
|
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_suppressAllPauses)
|
|
|
|
return;
|
|
|
|
|
|
|
|
intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame);
|
2022-10-23 02:55:20 +00:00
|
|
|
|
|
|
|
auto blackboxTypeIterator = m_blackboxedScripts.find(sourceID);
|
|
|
|
if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Ignored)
|
2017-08-12 16:48:01 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
DebuggerPausedScope debuggerPausedScope(*this);
|
|
|
|
|
|
|
|
bool pauseNow = m_pauseAtNextOpportunity;
|
|
|
|
pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
|
|
|
|
|
|
|
|
bool didPauseForStep = pauseNow;
|
|
|
|
|
2018-01-03 05:16:05 +00:00
|
|
|
TextPosition position = DebuggerCallFrame::positionForCallFrame(vm, m_currentCallFrame);
|
2022-10-23 02:55:20 +00:00
|
|
|
|
|
|
|
auto breakpoint = didHitBreakpoint(globalObject, sourceID, position);
|
|
|
|
if (breakpoint)
|
|
|
|
pauseNow = true;
|
|
|
|
|
|
|
|
// Special breakpoints are only given one opportunity to pause.
|
|
|
|
auto specialBreakpoint = WTFMove(m_specialBreakpoint);
|
|
|
|
if (specialBreakpoint && specialBreakpoint->shouldPause(*this, globalObject))
|
|
|
|
pauseNow = true;
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
m_lastExecutedLine = position.m_line.zeroBasedInt();
|
|
|
|
if (!pauseNow)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool afterBlackboxedScript = m_afterBlackboxedScript;
|
2017-08-12 16:48:01 +00:00
|
|
|
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);
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
if (breakpoint || specialBreakpoint) {
|
2017-08-12 16:48:01 +00:00
|
|
|
// 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.
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
bool autoContinue = false;
|
|
|
|
|
|
|
|
if (breakpoint) {
|
|
|
|
evaluateBreakpointActions(*breakpoint, globalObject);
|
|
|
|
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (breakpoint->isAutoContinue())
|
|
|
|
autoContinue = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (specialBreakpoint) {
|
|
|
|
evaluateBreakpointActions(*specialBreakpoint, globalObject);
|
|
|
|
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (specialBreakpoint->isAutoContinue())
|
|
|
|
autoContinue = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (autoContinue) {
|
2017-08-12 16:48:01 +00:00
|
|
|
if (!didPauseForStep)
|
|
|
|
return;
|
2022-10-23 02:55:20 +00:00
|
|
|
|
|
|
|
breakpoint = nullptr;
|
|
|
|
specialBreakpoint = nullptr;
|
|
|
|
} else if (breakpoint)
|
|
|
|
m_pausingBreakpointID = breakpoint->id();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Deferred) {
|
|
|
|
m_afterBlackboxedScript = true;
|
|
|
|
schedulePauseAtNextOpportunity();
|
|
|
|
return;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
auto reason = m_reasonForPause;
|
|
|
|
if (afterBlackboxedScript)
|
|
|
|
reason = PausedAfterBlackboxedScript;
|
|
|
|
else if (breakpoint)
|
|
|
|
reason = PausedForBreakpoint;
|
|
|
|
PauseReasonDeclaration rauseReasonDeclaration(*this, reason);
|
|
|
|
|
|
|
|
handlePause(globalObject, m_reasonForPause);
|
2018-01-03 05:16:05 +00:00
|
|
|
scope.releaseAssertNoException();
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m_pausingBreakpointID = noBreakpointID;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
if (!m_pauseAtNextOpportunity && !m_pauseOnCallFrame && !m_specialBreakpoint) {
|
2017-08-12 16:48:01 +00:00
|
|
|
setSteppingMode(SteppingModeDisabled);
|
|
|
|
m_currentCallFrame = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::handlePause(JSGlobalObject* globalObject, ReasonForPause)
|
|
|
|
{
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
ASSERT(isPaused());
|
|
|
|
observer.didPause(globalObject, currentDebuggerCallFrame(), exceptionOrCaughtValue(globalObject));
|
|
|
|
});
|
|
|
|
|
|
|
|
didPause(globalObject);
|
|
|
|
|
|
|
|
m_doneProcessingDebuggerEvents = false;
|
|
|
|
runEventLoopWhilePaused();
|
|
|
|
|
|
|
|
didContinue(globalObject);
|
|
|
|
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.didContinue();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
JSC::JSValue Debugger::exceptionOrCaughtValue(JSC::JSGlobalObject* globalObject)
|
|
|
|
{
|
|
|
|
if (reasonForPause() == PausedForException)
|
|
|
|
return currentException();
|
|
|
|
|
|
|
|
for (RefPtr<DebuggerCallFrame> frame = ¤tDebuggerCallFrame(); frame; frame = frame->callerFrame()) {
|
|
|
|
DebuggerScope& scope = *frame->scope();
|
|
|
|
if (scope.isCatchScope())
|
|
|
|
return scope.caughtValue(globalObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
return { };
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::exception(JSGlobalObject* globalObject, CallFrame* callFrame, JSValue exception, bool hasCatchHandler)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2018-01-03 05:16:05 +00:00
|
|
|
if (JSObject* object = jsDynamicCast<JSObject*>(m_vm, exception)) {
|
|
|
|
if (object->isErrorInstance()) {
|
|
|
|
ErrorInstance* error = static_cast<ErrorInstance*>(object);
|
|
|
|
// FIXME: <https://webkit.org/b/173625> Web Inspector: Should be able to pause and debug a StackOverflow Exception
|
|
|
|
// FIXME: <https://webkit.org/b/173627> Web Inspector: Should be able to pause and debug an OutOfMemory Exception
|
|
|
|
if (error->isStackOverflowError() || error->isOutOfMemoryError())
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
PauseReasonDeclaration reason(*this, PausedForException);
|
2022-10-23 02:55:20 +00:00
|
|
|
if (m_pauseOnAllExceptionsBreakpoint || (m_pauseOnUncaughtExceptionsBreakpoint && !hasCatchHandler)) {
|
|
|
|
m_specialBreakpoint = m_pauseOnAllExceptionsBreakpoint ? m_pauseOnAllExceptionsBreakpoint.copyRef() : m_pauseOnUncaughtExceptionsBreakpoint.copyRef();
|
2017-08-12 16:48:01 +00:00
|
|
|
setSteppingMode(SteppingModeEnabled);
|
|
|
|
}
|
|
|
|
|
|
|
|
m_hasHandlerForExceptionCallback = true;
|
|
|
|
m_currentException = exception;
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(globalObject, callFrame, AttemptPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
m_currentException = JSValue();
|
|
|
|
m_hasHandlerForExceptionCallback = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::atStatement(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_pastFirstExpressionInStatement = false;
|
|
|
|
|
|
|
|
PauseReasonDeclaration reason(*this, PausedAtStatement);
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
// Only pause at the next expression for step-in, step-next, step-out, or special breakpoints.
|
|
|
|
bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepNext || m_pauseOnStepOut || m_specialBreakpoint;
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
PauseReasonDeclaration reason(*this, PausedAtExpression);
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, shouldAttemptPause ? AttemptPause : NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::callEvent(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::returnEvent(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
{
|
|
|
|
PauseReasonDeclaration reason(*this, PausedBeforeReturn);
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Detach may have been called during pauseIfNeeded.
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return;
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
EntryFrame* topEntryFrame = m_vm.topEntryFrame;
|
|
|
|
CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::unwindEvent(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return;
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
EntryFrame* topEntryFrame = m_vm.topEntryFrame;
|
|
|
|
CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// Treat stepping over an exception location like a step-out.
|
|
|
|
if (m_currentCallFrame == m_pauseOnCallFrame)
|
|
|
|
m_pauseOnCallFrame = callerFrame;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::willExecuteProgram(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::didExecuteProgram(CallFrame* callFrame)
|
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
|
|
|
PauseReasonDeclaration reason(*this, PausedAtEndOfProgram);
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// Detach may have been called during pauseIfNeeded.
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
return;
|
|
|
|
|
2020-08-29 13:27:11 +00:00
|
|
|
EntryFrame* topEntryFrame = m_vm.topEntryFrame;
|
|
|
|
CallFrame* callerFrame = m_currentCallFrame->callerFrame(topEntryFrame);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// 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 = callerFrame;
|
|
|
|
m_pauseAtNextOpportunity = true;
|
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callerFrame), callerFrame, NoPause);
|
2017-08-12 16:48:01 +00:00
|
|
|
|
|
|
|
// Do not continue stepping into an unknown future program.
|
|
|
|
if (!m_currentCallFrame)
|
|
|
|
clearNextPauseState();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::clearNextPauseState()
|
|
|
|
{
|
|
|
|
m_pauseOnCallFrame = nullptr;
|
|
|
|
m_pauseAtNextOpportunity = false;
|
2022-10-23 02:55:20 +00:00
|
|
|
m_pauseOnStepNext = false;
|
2017-08-12 16:48:01 +00:00
|
|
|
m_pauseOnStepOut = false;
|
2022-10-23 02:55:20 +00:00
|
|
|
m_afterBlackboxedScript = false;
|
|
|
|
m_specialBreakpoint = nullptr;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::didReachDebuggerStatement(CallFrame* callFrame)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (m_isPaused)
|
|
|
|
return;
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
if (!m_pauseOnDebuggerStatementsBreakpoint)
|
|
|
|
return;
|
|
|
|
|
2017-08-12 16:48:01 +00:00
|
|
|
PauseReasonDeclaration reason(*this, PausedForDebuggerStatement);
|
2022-10-23 02:55:20 +00:00
|
|
|
m_specialBreakpoint = m_pauseOnDebuggerStatementsBreakpoint.copyRef();
|
2017-08-12 16:48:01 +00:00
|
|
|
setSteppingMode(SteppingModeEnabled);
|
2022-10-23 02:55:20 +00:00
|
|
|
updateCallFrame(lexicalGlobalObjectForCallFrame(m_vm, callFrame), callFrame, AttemptPause);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::willRunMicrotask()
|
|
|
|
{
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.willRunMicrotask();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Debugger::didRunMicrotask()
|
|
|
|
{
|
|
|
|
dispatchFunctionToObservers([&] (Observer& observer) {
|
|
|
|
observer.didRunMicrotask();
|
|
|
|
});
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2018-01-03 05:16:05 +00:00
|
|
|
DebuggerCallFrame& Debugger::currentDebuggerCallFrame()
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
|
|
|
if (!m_currentDebuggerCallFrame)
|
2018-01-03 05:16:05 +00:00
|
|
|
m_currentDebuggerCallFrame = DebuggerCallFrame::create(m_vm, m_currentCallFrame);
|
|
|
|
return *m_currentDebuggerCallFrame;
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::setBlackboxType(SourceID sourceID, Optional<BlackboxType> type)
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
if (type)
|
|
|
|
m_blackboxedScripts.set(sourceID, type.value());
|
|
|
|
else
|
|
|
|
m_blackboxedScripts.remove(sourceID);
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
2022-10-23 02:55:20 +00:00
|
|
|
void Debugger::clearBlackbox()
|
2017-08-12 16:48:01 +00:00
|
|
|
{
|
2022-10-23 02:55:20 +00:00
|
|
|
m_blackboxedScripts.clear();
|
2017-08-12 16:48:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace JSC
|