X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/6fe7ccc865dc7d7541b93c5bcaf6368d2c98a174..ef99ff287df9046eb88937225e0554eabb00e33c:/debugger/Debugger.cpp diff --git a/debugger/Debugger.cpp b/debugger/Debugger.cpp index 49c28a3..168d7a3 100644 --- a/debugger/Debugger.cpp +++ b/debugger/Debugger.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved. * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) * @@ -22,12 +22,19 @@ #include "config.h" #include "Debugger.h" +#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 "JSCInlines.h" #include "Parser.h" #include "Protect.h" +#include "VMEntryScope.h" namespace { @@ -59,12 +66,12 @@ inline Recompiler::~Recompiler() // 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->second, iter->first, -1, UString()); + m_debugger->sourceParsed(iter->value, iter->key, -1, String()); } inline void Recompiler::operator()(JSCell* cell) { - if (!cell->inherits(&JSFunction::s_info)) + if (!cell->inherits(JSFunction::info())) return; JSFunction* function = jsCast(cell); @@ -78,9 +85,10 @@ inline void Recompiler::operator()(JSCell* cell) if (!m_functionExecutables.add(executable).isNewEntry) return; - ExecState* exec = function->scope()->globalObject->JSGlobalObject::globalExec(); - executable->discardCode(); - if (m_debugger == function->scope()->globalObject->debugger()) + 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); } @@ -88,6 +96,73 @@ inline void Recompiler::operator()(JSCell* cell) namespace JSC { +class DebuggerCallFrameScope { +public: + DebuggerCallFrameScope(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() + { + if (m_debugger.m_currentDebuggerCallFrame) { + m_debugger.m_currentDebuggerCallFrame->invalidate(); + m_debugger.m_currentDebuggerCallFrame = 0; + } + } + +private: + Debugger& m_debugger; +}; + +// This is very similar to TemporaryChange, 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; +}; + +template +void Debugger::forEachCodeBlock(Functor& functor) +{ + m_vm->waitForCompilationsToComplete(); + m_vm->heap.forEachCodeBlock(functor); +} + +Debugger::Debugger(bool isInWorkerThread) + : m_vm(nullptr) + , m_pauseOnExceptionsState(DontPauseOnExceptions) + , m_pauseOnNextStatement(false) + , m_isPaused(false) + , m_breakpointsActivated(true) + , m_hasHandlerForExceptionCallback(false) + , m_isInWorkerThread(isInWorkerThread) + , m_steppingMode(SteppingModeDisabled) + , m_reasonForPause(NotPaused) + , m_pauseOnCallFrame(0) + , m_currentCallFrame(0) + , m_lastExecutedLine(UINT_MAX) + , m_lastExecutedSourceID(noSourceID) + , m_topBreakpointID(noBreakpointID) +{ +} + Debugger::~Debugger() { HashSet::iterator end = m_globalObjects.end(); @@ -98,48 +173,605 @@ 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); } -void Debugger::detach(JSGlobalObject* globalObject) +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; + 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(0); + if (!m_globalObjects.size()) + m_vm = nullptr; } -void Debugger::recompileAllJSFunctions(JSGlobalData* globalData) +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 . + 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(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. - ASSERT(!globalData->dynamicGlobalObject); - if (globalData->dynamicGlobalObject) + if (vm->entryScope) { + vm->entryScope->setRecompilationNeeded(true); return; + } + + vm->waitForCompilationsToComplete(); Recompiler recompiler(this); - globalData->heap.objectSpace().forEachCell(recompiler); + 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; } -JSValue evaluateInGlobalCallFrame(const UString& script, JSValue& exception, JSGlobalObject* globalObject) +void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause) { - CallFrame* globalCallFrame = globalObject->globalExec(); - JSGlobalData& globalData = globalObject->globalData(); + m_pauseOnExceptionsState = pause; +} + +void Debugger::setPauseOnNextStatement(bool pause) +{ + m_pauseOnNextStatement = pause; + if (pause) + setSteppingMode(SteppingModeEnabled); +} + +void Debugger::breakProgram() +{ + if (m_isPaused) + return; - EvalExecutable* eval = EvalExecutable::create(globalCallFrame, makeSource(script), false); - if (!eval) { - exception = globalData.exception; - globalData.exception = JSValue(); - return exception; + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + m_currentCallFrame = m_vm->topCallFrame; + ASSERT(m_currentCallFrame); + pauseIfNeeded(m_currentCallFrame); +} + +void Debugger::continueProgram() +{ + if (!m_isPaused) + return; + + m_pauseOnNextStatement = false; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepIntoStatement() +{ + if (!m_isPaused) + return; + + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepOverStatement() +{ + if (!m_isPaused) + return; + + m_pauseOnCallFrame = m_currentCallFrame; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepOutOfFunction() +{ + if (!m_isPaused) + return; + + m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrameSkippingVMEntrySentinel() : 0; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::updateCallFrame(CallFrame* callFrame) +{ + m_currentCallFrame = callFrame; + SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame); + if (m_lastExecutedSourceID != sourceID) { + m_lastExecutedLine = UINT_MAX; + m_lastExecutedSourceID = sourceID; } +} - JSValue result = globalData.interpreter->execute(eval, globalCallFrame, globalObject, globalCallFrame->scopeChain()); - if (globalData.exception) { - exception = globalData.exception; - globalData.exception = JSValue(); +void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame) +{ + updateCallFrame(callFrame); + pauseIfNeeded(callFrame); + if (!isStepping()) + m_currentCallFrame = 0; +} + +void Debugger::pauseIfNeeded(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); + if (!needPauseHandling(vmEntryGlobalObject)) + return; + + Breakpoint breakpoint; + bool didHitBreakpoint = false; + bool pauseNow = m_pauseOnNextStatement; + pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); + + DebuggerCallFrameScope debuggerCallFrameScope(*this); + + intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame); + TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame); + pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint); + m_lastExecutedLine = position.m_line.zeroBasedInt(); + if (!pauseNow) + return; + + // 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; + + if (didHitBreakpoint) { + handleBreakpointHit(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; + } + + handlePause(m_reasonForPause, vmEntryGlobalObject); + + if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) { + setSteppingMode(SteppingModeDisabled); + m_currentCallFrame = nullptr; + } +} + +void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasHandler) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedForException); + if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler)) { + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + } + + m_hasHandlerForExceptionCallback = true; + m_currentException = exception; + updateCallFrameAndPauseIfNeeded(callFrame); + m_currentException = JSValue(); + m_hasHandlerForExceptionCallback = false; +} + +void Debugger::atStatement(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAtStatement); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +void Debugger::callEvent(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAfterCall); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +void Debugger::returnEvent(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedBeforeReturn); + updateCallFrameAndPauseIfNeeded(callFrame); + + // detach may have been called during pauseIfNeeded + if (!m_currentCallFrame) + return; + + // Treat stepping over a return statement like stepping out. + if (m_currentCallFrame == m_pauseOnCallFrame) + m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + + m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); +} + +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); +} + +void Debugger::didExecuteProgram(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAtEndOfProgram); + updateCallFrameAndPauseIfNeeded(callFrame); + + // Treat stepping over the end of a program like stepping out. + if (!m_currentCallFrame) + return; + if (m_currentCallFrame == m_pauseOnCallFrame) { + m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + if (!m_currentCallFrame) + return; } - ASSERT(result); - return result; + m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); +} + +void Debugger::didReachBreakpoint(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedForBreakpoint); + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const +{ + ASSERT(m_currentDebuggerCallFrame); + return m_currentDebuggerCallFrame.get(); } } // namespace JSC