+ if (!m_globalObjects.size())
+ m_vm = nullptr;
+}
+
+class Debugger::SetSteppingModeFunctor {
+public:
+ SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode)
+ : m_debugger(debugger)
+ , m_mode(mode)
+ {
+ }
+
+ bool operator()(CodeBlock* codeBlock)
+ {
+ if (m_debugger == codeBlock->globalObject()->debugger()) {
+ if (m_mode == SteppingModeEnabled)
+ codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
+ else
+ codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled);
+ }
+ return false;
+ }
+
+private:
+ Debugger* m_debugger;
+ SteppingMode m_mode;
+};
+
+void Debugger::setSteppingMode(SteppingMode mode)
+{
+ if (mode == m_steppingMode || !m_vm)
+ return;
+
+ m_vm->waitForCompilationsToComplete();
+
+ m_steppingMode = mode;
+ SetSteppingModeFunctor functor(this, mode);
+ m_vm->heap.forEachCodeBlock(functor);
+}
+
+void Debugger::registerCodeBlock(CodeBlock* codeBlock)
+{
+ // FIXME: We should never have to jettison a code block (due to pending breakpoints
+ // or stepping mode) that is being registered. operationOptimize() should have
+ // prevented the optimizing of such code blocks in the first place. Find a way to
+ // express this with greater clarity in the code. See <https://webkit.org/b131771>.
+ applyBreakpoints(codeBlock);
+ if (isStepping())
+ codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled);
+}
+
+void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot)
+{
+ ScriptExecutable* executable = codeBlock->ownerExecutable();
+
+ SourceID sourceID = static_cast<SourceID>(executable->sourceID());
+ if (breakpoint.sourceID != sourceID)
+ return;
+
+ unsigned line = breakpoint.line;
+ unsigned column = breakpoint.column;
+
+ unsigned startLine = executable->lineNo();
+ unsigned startColumn = executable->startColumn();
+ unsigned endLine = executable->lastLine();
+ unsigned endColumn = executable->endColumn();
+
+ // Inspector breakpoint line and column values are zero-based but the executable
+ // and CodeBlock line and column values are one-based.
+ line += 1;
+ column = column ? column + 1 : Breakpoint::unspecifiedColumn;
+
+ if (line < startLine || line > endLine)
+ return;
+ if (column != Breakpoint::unspecifiedColumn) {
+ if (line == startLine && column < startColumn)
+ return;
+ if (line == endLine && column > endColumn)
+ return;
+ }
+ if (!codeBlock->hasOpDebugForLineAndColumn(line, column))
+ return;
+
+ if (enabledOrNot == BreakpointEnabled)
+ codeBlock->addBreakpoint(1);
+ else
+ codeBlock->removeBreakpoint(1);
+}
+
+void Debugger::applyBreakpoints(CodeBlock* codeBlock)
+{
+ BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint;
+ for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) {
+ Breakpoint& breakpoint = *it->value;
+ toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled);
+ }
+}
+
+class Debugger::ToggleBreakpointFunctor {
+public:
+ ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot)
+ : m_debugger(debugger)
+ , m_breakpoint(breakpoint)
+ , m_enabledOrNot(enabledOrNot)
+ {
+ }
+
+ bool operator()(CodeBlock* codeBlock)
+ {
+ if (m_debugger == codeBlock->globalObject()->debugger())
+ m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot);
+ return false;
+ }
+
+private:
+ Debugger* m_debugger;
+ Breakpoint& m_breakpoint;
+ BreakpointState m_enabledOrNot;
+};
+
+void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot)
+{
+ if (!m_vm)
+ return;
+ ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot);
+ forEachCodeBlock(functor);
+}
+
+void Debugger::recompileAllJSFunctions(VM* vm)
+{
+ // 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->waitForCompilationsToComplete();
+
+ Recompiler recompiler(this);
+ HeapIterationScope iterationScope(vm->heap);
+ vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler);
+}
+
+BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn)
+{
+ SourceID sourceID = breakpoint.sourceID;
+ unsigned line = breakpoint.line;
+ unsigned column = breakpoint.column;
+
+ SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
+ if (it == m_sourceIDToBreakpoints.end())
+ it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator;
+ LineToBreakpointsMap::iterator breaksIt = it->value.find(line);
+ if (breaksIt == it->value.end())
+ breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator;
+
+ BreakpointsList& breakpoints = *breaksIt->value;
+ for (Breakpoint* current = breakpoints.head(); current; current = current->next()) {
+ if (current->column == column) {
+ // The breakpoint already exists. We're not allowed to create a new
+ // breakpoint at this location. Rather than returning the breakpointID
+ // of the pre-existing breakpoint, we need to return noBreakpointID
+ // to indicate that we're not creating a new one.
+ return noBreakpointID;
+ }
+ }
+
+ BreakpointID id = ++m_topBreakpointID;
+ RELEASE_ASSERT(id != noBreakpointID);
+
+ breakpoint.id = id;
+ actualLine = line;
+ actualColumn = column;
+
+ Breakpoint* newBreakpoint = new Breakpoint(breakpoint);
+ breakpoints.append(newBreakpoint);
+ m_breakpointIDToBreakpoint.set(id, newBreakpoint);
+
+ toggleBreakpoint(breakpoint, BreakpointEnabled);
+
+ return id;
+}
+
+void Debugger::removeBreakpoint(BreakpointID id)
+{
+ ASSERT(id != noBreakpointID);
+
+ BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id);
+ ASSERT(idIt != m_breakpointIDToBreakpoint.end());
+ Breakpoint* breakpoint = idIt->value;
+
+ SourceID sourceID = breakpoint->sourceID;
+ ASSERT(sourceID);
+ SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID);
+ ASSERT(it != m_sourceIDToBreakpoints.end());
+ LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line);
+ ASSERT(breaksIt != it->value.end());
+
+ toggleBreakpoint(*breakpoint, BreakpointDisabled);
+
+ BreakpointsList& breakpoints = *breaksIt->value;
+#if !ASSERT_DISABLED
+ bool found = false;
+ for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) {
+ if (current->id == breakpoint->id)
+ found = true;
+ }
+ ASSERT(found);
+#endif
+
+ m_breakpointIDToBreakpoint.remove(idIt);
+ breakpoints.remove(breakpoint);
+ delete breakpoint;
+
+ if (breakpoints.isEmpty()) {
+ it->value.remove(breaksIt);
+ if (it->value.isEmpty())
+ m_sourceIDToBreakpoints.remove(it);
+ }
+}
+
+bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint)
+{
+ if (!m_breakpointsActivated)
+ return false;
+
+ SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID);
+ if (it == m_sourceIDToBreakpoints.end())
+ return false;
+
+ unsigned line = position.m_line.zeroBasedInt();
+ unsigned column = position.m_column.zeroBasedInt();
+
+ LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line);
+ if (breaksIt == it->value.end())
+ return false;
+
+ bool hit = false;
+ const BreakpointsList& breakpoints = *breaksIt->value;
+ Breakpoint* breakpoint;
+ for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) {
+ unsigned breakLine = breakpoint->line;
+ unsigned breakColumn = breakpoint->column;
+ // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
+ ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
+ if ((line != m_lastExecutedLine && line == breakLine && !breakColumn)
+ || (line == breakLine && column == breakColumn)) {
+ hit = true;
+ break;
+ }
+ }
+ if (!hit)
+ return false;
+
+ if (hitBreakpoint)
+ *hitBreakpoint = *breakpoint;
+
+ 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;
+ DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame();
+ JSValue result = debuggerCallFrame->evaluate(breakpoint->condition, exception);
+
+ // We can lose the debugger while executing JavaScript.
+ if (!m_currentCallFrame)
+ return false;
+
+ if (exception) {
+ // An erroneous condition counts as "false".
+ handleExceptionInBreakpointCondition(m_currentCallFrame, exception);
+ return false;
+ }
+
+ return result.toBoolean(m_currentCallFrame);
+}
+
+class Debugger::ClearCodeBlockDebuggerRequestsFunctor {
+public:
+ ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger)
+ : m_debugger(debugger)
+ {
+ }
+
+ bool operator()(CodeBlock* codeBlock)
+ {
+ if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger())
+ codeBlock->clearDebuggerRequests();
+ return false;
+ }
+
+private:
+ Debugger* m_debugger;
+};
+
+void Debugger::clearBreakpoints()
+{
+ m_topBreakpointID = noBreakpointID;
+ m_breakpointIDToBreakpoint.clear();
+ m_sourceIDToBreakpoints.clear();
+
+ if (!m_vm)
+ return;
+ ClearCodeBlockDebuggerRequestsFunctor functor(this);
+ forEachCodeBlock(functor);
+}
+
+class Debugger::ClearDebuggerRequestsFunctor {
+public:
+ ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject)
+ : m_globalObject(globalObject)
+ {
+ }
+
+ bool operator()(CodeBlock* codeBlock)
+ {
+ if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject())
+ codeBlock->clearDebuggerRequests();
+ return false;
+ }
+
+private:
+ JSGlobalObject* m_globalObject;
+};
+
+void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject)
+{
+ ASSERT(m_vm);
+ ClearDebuggerRequestsFunctor functor(globalObject);
+ forEachCodeBlock(functor);
+}
+
+void Debugger::setBreakpointsActivated(bool activated)
+{
+ m_breakpointsActivated = activated;
+}
+
+void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause)
+{
+ m_pauseOnExceptionsState = pause;