diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/JavaScriptCore/debugger | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/JavaScriptCore/debugger')
-rw-r--r-- | Source/JavaScriptCore/debugger/Breakpoint.h | 66 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.cpp | 611 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.h | 123 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerActivation.cpp | 98 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerActivation.h | 71 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp | 253 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerCallFrame.h | 46 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h | 60 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerLocation.cpp | 46 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerLocation.h | 53 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerParseData.cpp | 185 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerParseData.h | 81 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerPrimitives.h | 7 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerScope.cpp | 253 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerScope.h | 118 | ||||
-rw-r--r-- | Source/JavaScriptCore/debugger/ScriptProfilingScope.h | 90 |
16 files changed, 1617 insertions, 544 deletions
diff --git a/Source/JavaScriptCore/debugger/Breakpoint.h b/Source/JavaScriptCore/debugger/Breakpoint.h index 95b92881d..c1504a150 100644 --- a/Source/JavaScriptCore/debugger/Breakpoint.h +++ b/Source/JavaScriptCore/debugger/Breakpoint.h @@ -20,47 +20,75 @@ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef Breakpoint_h -#define Breakpoint_h +#pragma once #include "DebuggerPrimitives.h" +#include <wtf/DoublyLinkedList.h> +#include <wtf/RefCounted.h> #include <wtf/text/WTFString.h> namespace JSC { -struct Breakpoint { +struct Breakpoint : public DoublyLinkedListNode<Breakpoint> { Breakpoint() - : id(noBreakpointID) - , sourceID(noSourceID) - , line(0) - , column(0) - , autoContinue(false) { } - Breakpoint(SourceID sourceID, unsigned line, unsigned column, String condition, bool autoContinue) - : id(noBreakpointID) - , sourceID(sourceID) + Breakpoint(SourceID sourceID, unsigned line, unsigned column, const String& condition, bool autoContinue, unsigned ignoreCount) + : sourceID(sourceID) , line(line) , column(column) , condition(condition) , autoContinue(autoContinue) + , ignoreCount(ignoreCount) { } - BreakpointID id; - SourceID sourceID; - unsigned line; - unsigned column; + Breakpoint(const Breakpoint& other) + : id(other.id) + , sourceID(other.sourceID) + , line(other.line) + , column(other.column) + , condition(other.condition) + , autoContinue(other.autoContinue) + , ignoreCount(other.ignoreCount) + , hitCount(other.hitCount) + , resolved(other.resolved) + { + } + + BreakpointID id { noBreakpointID }; + SourceID sourceID { noSourceID }; + unsigned line { 0 }; + unsigned column { 0 }; String condition; - bool autoContinue; + bool autoContinue { false }; + unsigned ignoreCount { 0 }; + unsigned hitCount { 0 }; + bool resolved { false }; static const unsigned unspecifiedColumn = UINT_MAX; + +private: + Breakpoint* m_prev; + Breakpoint* m_next; + + friend class WTF::DoublyLinkedListNode<Breakpoint>; }; -} // namespace JSC +class BreakpointsList : public DoublyLinkedList<Breakpoint>, + public RefCounted<BreakpointsList> { +public: + ~BreakpointsList() + { + Breakpoint* breakpoint; + while ((breakpoint = removeHead())) + delete breakpoint; + ASSERT(isEmpty()); + } +}; -#endif // Breakpoint_h +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp index afa7546c8..3eefde28b 100644 --- a/Source/JavaScriptCore/debugger/Debugger.cpp +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2013, 2014, 2016 Apple Inc. All rights reserved. * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) * Copyright (C) 2001 Peter Kelly (pmk@post.com) * @@ -25,13 +25,13 @@ #include "CodeBlock.h" #include "DebuggerCallFrame.h" #include "Error.h" - #include "HeapIterationScope.h" #include "Interpreter.h" +#include "JSCInlines.h" #include "JSCJSValueInlines.h" #include "JSFunction.h" #include "JSGlobalObject.h" -#include "Operations.h" +#include "MarkedSpaceInlines.h" #include "Parser.h" #include "Protect.h" #include "VMEntryScope.h" @@ -40,77 +40,58 @@ namespace { using namespace JSC; -class Recompiler : public MarkedBlock::VoidFunctor { -public: - Recompiler(JSC::Debugger*); - ~Recompiler(); - void operator()(JSCell*); - -private: - typedef HashSet<FunctionExecutable*> FunctionExecutableSet; - typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap; - - JSC::Debugger* m_debugger; - FunctionExecutableSet m_functionExecutables; - SourceProviderMap m_sourceProviders; -}; - -inline Recompiler::Recompiler(JSC::Debugger* debugger) - : m_debugger(debugger) -{ -} - -inline Recompiler::~Recompiler() -{ - // Call sourceParsed() after reparsing all functions because it will execute - // JavaScript in the inspector. - SourceProviderMap::const_iterator end = m_sourceProviders.end(); - for (SourceProviderMap::const_iterator iter = m_sourceProviders.begin(); iter != end; ++iter) - m_debugger->sourceParsed(iter->value, iter->key, -1, String()); -} - -inline void Recompiler::operator()(JSCell* cell) -{ - if (!cell->inherits(JSFunction::info())) - return; - - JSFunction* function = jsCast<JSFunction*>(cell); - if (function->executable()->isHostFunction()) - return; - - FunctionExecutable* executable = function->jsExecutable(); +struct GatherSourceProviders : public MarkedBlock::VoidFunctor { + // FIXME: This is a mutable field because this isn't a C++ lambda. + // https://bugs.webkit.org/show_bug.cgi?id=159644 + mutable HashSet<SourceProvider*> sourceProviders; + JSGlobalObject* m_globalObject; - // Check if the function is already in the set - if so, - // we've already retranslated it, nothing to do here. - if (!m_functionExecutables.add(executable).isNewEntry) - return; + GatherSourceProviders(JSGlobalObject* globalObject) + : m_globalObject(globalObject) { } - ExecState* exec = function->scope()->globalObject()->JSGlobalObject::globalExec(); - executable->clearCodeIfNotCompiling(); - executable->clearUnlinkedCodeForRecompilationIfNotCompiling(); - if (m_debugger == function->scope()->globalObject()->debugger()) - m_sourceProviders.add(executable->source().provider(), exec); -} + IterationStatus operator()(HeapCell* heapCell, HeapCell::Kind kind) const + { + if (kind != HeapCell::JSCell) + return IterationStatus::Continue; + + JSCell* cell = static_cast<JSCell*>(heapCell); + + JSFunction* function = jsDynamicCast<JSFunction*>(*cell->vm(), cell); + if (!function) + return IterationStatus::Continue; + + if (function->scope()->globalObject() != m_globalObject) + return IterationStatus::Continue; + + if (!function->executable()->isFunctionExecutable()) + return IterationStatus::Continue; + + if (function->isHostOrBuiltinFunction()) + return IterationStatus::Continue; + + sourceProviders.add( + jsCast<FunctionExecutable*>(function->executable())->source().provider()); + return IterationStatus::Continue; + } +}; } // namespace namespace JSC { -class DebuggerCallFrameScope { +class DebuggerPausedScope { public: - DebuggerCallFrameScope(Debugger& debugger) + DebuggerPausedScope(Debugger& debugger) : m_debugger(debugger) { ASSERT(!m_debugger.m_currentDebuggerCallFrame); - if (m_debugger.m_currentCallFrame) - m_debugger.m_currentDebuggerCallFrame = DebuggerCallFrame::create(debugger.m_currentCallFrame); } - ~DebuggerCallFrameScope() + ~DebuggerPausedScope() { if (m_debugger.m_currentDebuggerCallFrame) { m_debugger.m_currentDebuggerCallFrame->invalidate(); - m_debugger.m_currentDebuggerCallFrame = 0; + m_debugger.m_currentDebuggerCallFrame = nullptr; } } @@ -118,7 +99,7 @@ private: Debugger& m_debugger; }; -// This is very similar to TemporaryChange<bool>, but that cannot be used +// This is very similar to SetForScope<bool>, but that cannot be used // as the m_isPaused field uses only one bit. class TemporaryPausedState { public: @@ -138,21 +119,26 @@ private: Debugger& m_debugger; }; -Debugger::Debugger(bool isInWorkerThread) - : m_vm(nullptr) + +Debugger::ProfilingClient::~ProfilingClient() +{ +} + +Debugger::Debugger(VM& vm) + : m_vm(vm) , m_pauseOnExceptionsState(DontPauseOnExceptions) - , m_pauseOnNextStatement(false) + , m_pauseAtNextOpportunity(false) + , m_pastFirstExpressionInStatement(false) , m_isPaused(false) - , m_breakpointsActivated(true) + , m_breakpointsActivated(false) , m_hasHandlerForExceptionCallback(false) - , m_isInWorkerThread(isInWorkerThread) + , m_suppressAllPauses(false) , m_steppingMode(SteppingModeDisabled) , m_reasonForPause(NotPaused) - , m_pauseOnCallFrame(0) - , m_currentCallFrame(0) , m_lastExecutedLine(UINT_MAX) , m_lastExecutedSourceID(noSourceID) , m_topBreakpointID(noBreakpointID) + , m_pausingBreakpointID(noBreakpointID) { } @@ -160,18 +146,25 @@ Debugger::~Debugger() { HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end(); for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it) - (*it)->setDebugger(0); + (*it)->setDebugger(nullptr); } void Debugger::attach(JSGlobalObject* globalObject) { ASSERT(!globalObject->debugger()); - if (!m_vm) - m_vm = &globalObject->vm(); - else - ASSERT(m_vm == &globalObject->vm()); globalObject->setDebugger(this); m_globalObjects.add(globalObject); + + m_vm.setShouldBuildPCToCodeOriginMapping(); + + // Call sourceParsed because it will execute JavaScript in the inspector. + GatherSourceProviders gatherSourceProviders(globalObject); + { + HeapIterationScope iterationScope(m_vm.heap); + m_vm.heap.objectSpace().forEachLiveCell(iterationScope, gatherSourceProviders); + } + for (auto* sourceProvider : gatherSourceProviders.sourceProviders) + sourceParsed(globalObject->globalExec(), sourceProvider, -1, String()); } void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) @@ -179,9 +172,10 @@ void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) // If we're detaching from the currently executing global object, manually tear down our // stack, since we won't get further debugger callbacks to do so. Also, resume execution, // since there's no point in staying paused once a window closes. - if (m_currentCallFrame && m_currentCallFrame->vmEntryGlobalObject() == globalObject) { - m_currentCallFrame = 0; - m_pauseOnCallFrame = 0; + // We know there is an entry scope, otherwise, m_currentCallFrame would be null. + if (m_isPaused && m_currentCallFrame && globalObject->vm().entryScope->globalObject() == globalObject) { + m_currentCallFrame = nullptr; + m_pauseOnCallFrame = nullptr; continueProgram(); } @@ -194,9 +188,15 @@ void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) if (reason != GlobalObjectIsDestructing) clearDebuggerRequests(globalObject); - globalObject->setDebugger(0); - if (!m_globalObjects.size()) - m_vm = nullptr; + globalObject->setDebugger(nullptr); + + if (m_globalObjects.isEmpty()) + clearParsedData(); +} + +bool Debugger::isAttached(JSGlobalObject* globalObject) +{ + return globalObject->debugger() == this; } class Debugger::SetSteppingModeFunctor { @@ -207,7 +207,7 @@ public: { } - bool operator()(CodeBlock* codeBlock) + bool operator()(CodeBlock* codeBlock) const { if (m_debugger == codeBlock->globalObject()->debugger()) { if (m_mode == SteppingModeEnabled) @@ -227,12 +227,12 @@ void Debugger::setSteppingMode(SteppingMode mode) { if (mode == m_steppingMode) return; - m_steppingMode = mode; - if (!m_vm) - return; + m_vm.heap.completeAllJITPlans(); + + m_steppingMode = mode; SetSteppingModeFunctor functor(this, mode); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); } void Debugger::registerCodeBlock(CodeBlock* codeBlock) @@ -242,9 +242,27 @@ void Debugger::registerCodeBlock(CodeBlock* codeBlock) codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); } +void Debugger::setProfilingClient(ProfilingClient* client) +{ + ASSERT(!!m_profilingClient != !!client); + m_profilingClient = client; +} + +double Debugger::willEvaluateScript() +{ + return m_profilingClient->willEvaluateScript(); +} + +void Debugger::didEvaluateScript(double startTime, ProfilingReason reason) +{ + m_profilingClient->didEvaluateScript(startTime, reason); +} + void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot) { - ScriptExecutable* executable = codeBlock->ownerExecutable(); + ASSERT(breakpoint.resolved); + + ScriptExecutable* executable = codeBlock->ownerScriptExecutable(); SourceID sourceID = static_cast<SourceID>(executable->sourceID()); if (breakpoint.sourceID != sourceID) @@ -253,7 +271,7 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br unsigned line = breakpoint.line; unsigned column = breakpoint.column; - unsigned startLine = executable->lineNo(); + unsigned startLine = executable->firstLine(); unsigned startColumn = executable->startColumn(); unsigned endLine = executable->lastLine(); unsigned endColumn = executable->endColumn(); @@ -271,6 +289,7 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br if (line == endLine && column > endColumn) return; } + if (!codeBlock->hasOpDebugForLineAndColumn(line, column)) return; @@ -283,10 +302,8 @@ void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, Br void Debugger::applyBreakpoints(CodeBlock* codeBlock) { BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint; - for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) { - Breakpoint& breakpoint = *it->value; - toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled); - } + for (auto* breakpoint : breakpoints.values()) + toggleBreakpoint(codeBlock, *breakpoint, BreakpointEnabled); } class Debugger::ToggleBreakpointFunctor { @@ -298,7 +315,7 @@ public: { } - bool operator()(CodeBlock* codeBlock) + bool operator()(CodeBlock* codeBlock) const { if (m_debugger == codeBlock->globalObject()->debugger()) m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot); @@ -313,63 +330,90 @@ private: void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot) { - if (!m_vm) - return; + m_vm.heap.completeAllJITPlans(); + ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); } -void Debugger::recompileAllJSFunctions(VM* vm) +void Debugger::recompileAllJSFunctions() { - // If JavaScript is running, it's not safe to recompile, since we'll end - // up throwing away code that is live on the stack. - if (vm->entryScope) { - vm->entryScope->setRecompilationNeeded(true); + m_vm.deleteAllCode(PreventCollectionAndDeleteAllCode); +} + +DebuggerParseData& Debugger::debuggerParseData(SourceID sourceID, SourceProvider* provider) +{ + auto iter = m_parseDataMap.find(sourceID); + if (iter != m_parseDataMap.end()) + return iter->value; + + DebuggerParseData parseData; + gatherDebuggerParseDataForSource(m_vm, provider, parseData); + auto result = m_parseDataMap.add(sourceID, parseData); + return result.iterator->value; +} + +void Debugger::resolveBreakpoint(Breakpoint& breakpoint, SourceProvider* sourceProvider) +{ + RELEASE_ASSERT(!breakpoint.resolved); + ASSERT(breakpoint.sourceID != noSourceID); + + // FIXME: <https://webkit.org/b/162771> Web Inspector: Adopt TextPosition in Inspector to avoid oneBasedInt/zeroBasedInt ambiguity + // Inspector breakpoint line and column values are zero-based but the executable + // and CodeBlock line and column values are one-based. + unsigned line = breakpoint.line + 1; + unsigned column = breakpoint.column ? breakpoint.column : Breakpoint::unspecifiedColumn; + + DebuggerParseData& parseData = debuggerParseData(breakpoint.sourceID, sourceProvider); + std::optional<JSTextPosition> resolvedPosition = parseData.pausePositions.breakpointLocationForLineColumn((int)line, (int)column); + if (!resolvedPosition) return; - } - vm->prepareToDiscardCode(); + unsigned resolvedLine = resolvedPosition->line; + unsigned resolvedColumn = resolvedPosition->offset - resolvedPosition->lineStartOffset + 1; - Recompiler recompiler(this); - HeapIterationScope iterationScope(vm->heap); - vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler); + breakpoint.line = resolvedLine - 1; + breakpoint.column = resolvedColumn - 1; + breakpoint.resolved = true; } -BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn) +BreakpointID Debugger::setBreakpoint(Breakpoint& breakpoint, bool& existing) { + ASSERT(breakpoint.resolved); + ASSERT(breakpoint.sourceID != noSourceID); + SourceID sourceID = breakpoint.sourceID; unsigned line = breakpoint.line; unsigned column = breakpoint.column; - SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); + SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(breakpoint.sourceID); if (it == m_sourceIDToBreakpoints.end()) it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator; + LineToBreakpointsMap::iterator breaksIt = it->value.find(line); if (breaksIt == it->value.end()) - breaksIt = it->value.set(line, BreakpointsInLine()).iterator; - - BreakpointsInLine& breakpoints = breaksIt->value; - unsigned breakpointsCount = breakpoints.size(); - for (unsigned i = 0; i < breakpointsCount; i++) - if (breakpoints[i].column == column) { - // The breakpoint already exists. We're not allowed to create a new - // breakpoint at this location. Rather than returning the breakpointID - // of the pre-existing breakpoint, we need to return noBreakpointID - // to indicate that we're not creating a new one. - return noBreakpointID; + breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator; + + BreakpointsList& breakpoints = *breaksIt->value; + for (Breakpoint* current = breakpoints.head(); current; current = current->next()) { + if (current->column == column) { + // Found existing breakpoint. Do not create a duplicate at this location. + existing = true; + return current->id; } + } + existing = false; BreakpointID id = ++m_topBreakpointID; RELEASE_ASSERT(id != noBreakpointID); breakpoint.id = id; - actualLine = line; - actualColumn = column; - breakpoints.append(breakpoint); - m_breakpointIDToBreakpoint.set(id, &breakpoints.last()); + Breakpoint* newBreakpoint = new Breakpoint(breakpoint); + breakpoints.append(newBreakpoint); + m_breakpointIDToBreakpoint.set(id, newBreakpoint); - toggleBreakpoint(breakpoint, BreakpointEnabled); + toggleBreakpoint(*newBreakpoint, BreakpointEnabled); return id; } @@ -380,31 +424,35 @@ void Debugger::removeBreakpoint(BreakpointID id) BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id); ASSERT(idIt != m_breakpointIDToBreakpoint.end()); - Breakpoint& breakpoint = *idIt->value; + Breakpoint* breakpoint = idIt->value; - SourceID sourceID = breakpoint.sourceID; + SourceID sourceID = breakpoint->sourceID; ASSERT(sourceID); SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); ASSERT(it != m_sourceIDToBreakpoints.end()); - LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint.line); + LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line); ASSERT(breaksIt != it->value.end()); - toggleBreakpoint(breakpoint, BreakpointDisabled); + toggleBreakpoint(*breakpoint, BreakpointDisabled); - BreakpointsInLine& breakpoints = breaksIt->value; - unsigned breakpointsCount = breakpoints.size(); - for (unsigned i = 0; i < breakpointsCount; i++) { - if (breakpoints[i].id == breakpoint.id) { - breakpoints.remove(i); - m_breakpointIDToBreakpoint.remove(idIt); + BreakpointsList& breakpoints = *breaksIt->value; +#if !ASSERT_DISABLED + bool found = false; + for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) { + if (current->id == breakpoint->id) + found = true; + } + ASSERT(found); +#endif - if (breakpoints.isEmpty()) { - it->value.remove(breaksIt); - if (it->value.isEmpty()) - m_sourceIDToBreakpoints.remove(it); - } - break; - } + m_breakpointIDToBreakpoint.remove(idIt); + breakpoints.remove(breakpoint); + delete breakpoint; + + if (breakpoints.isEmpty()) { + it->value.remove(breaksIt); + if (it->value.isEmpty()) + m_sourceIDToBreakpoints.remove(it); } } @@ -419,18 +467,17 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br unsigned line = position.m_line.zeroBasedInt(); unsigned column = position.m_column.zeroBasedInt(); - + LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line); if (breaksIt == it->value.end()) return false; bool hit = false; - const BreakpointsInLine& breakpoints = breaksIt->value; - unsigned breakpointsCount = breakpoints.size(); - unsigned i; - for (i = 0; i < breakpointsCount; i++) { - unsigned breakLine = breakpoints[i].line; - unsigned breakColumn = breakpoints[i].column; + const BreakpointsList& breakpoints = *breaksIt->value; + Breakpoint* breakpoint; + for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) { + unsigned breakLine = breakpoint->line; + unsigned breakColumn = breakpoint->column; // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0). ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger()); if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) @@ -443,17 +490,23 @@ bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Br return false; if (hitBreakpoint) - *hitBreakpoint = breakpoints[i]; + *hitBreakpoint = *breakpoint; + + breakpoint->hitCount++; + if (breakpoint->ignoreCount >= breakpoint->hitCount) + return false; - if (breakpoints[i].condition.isEmpty()) + if (breakpoint->condition.isEmpty()) return true; // We cannot stop in the debugger while executing condition code, // so make it looks like the debugger is already paused. TemporaryPausedState pausedState(*this); - JSValue exception; - JSValue result = DebuggerCallFrame::evaluateWithCallFrame(m_currentCallFrame, breakpoints[i].condition, exception); + NakedPtr<Exception> exception; + DebuggerCallFrame& debuggerCallFrame = currentDebuggerCallFrame(); + JSObject* scopeExtensionObject = nullptr; + JSValue result = debuggerCallFrame.evaluateWithScopeExtension(breakpoint->condition, scopeExtensionObject, exception); // We can lose the debugger while executing JavaScript. if (!m_currentCallFrame) @@ -475,7 +528,7 @@ public: { } - bool operator()(CodeBlock* codeBlock) + bool operator()(CodeBlock* codeBlock) const { if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger()) codeBlock->clearDebuggerRequests(); @@ -488,14 +541,14 @@ private: void Debugger::clearBreakpoints() { + m_vm.heap.completeAllJITPlans(); + m_topBreakpointID = noBreakpointID; m_breakpointIDToBreakpoint.clear(); m_sourceIDToBreakpoints.clear(); - if (!m_vm) - return; ClearCodeBlockDebuggerRequestsFunctor functor(this); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); } class Debugger::ClearDebuggerRequestsFunctor { @@ -505,7 +558,7 @@ public: { } - bool operator()(CodeBlock* codeBlock) + bool operator()(CodeBlock* codeBlock) const { if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject()) codeBlock->clearDebuggerRequests(); @@ -518,14 +571,24 @@ private: void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject) { - ASSERT(m_vm); + m_vm.heap.completeAllJITPlans(); + ClearDebuggerRequestsFunctor functor(globalObject); - m_vm->heap.forEachCodeBlock(functor); + m_vm.heap.forEachCodeBlock(functor); +} + +void Debugger::clearParsedData() +{ + m_parseDataMap.clear(); } void Debugger::setBreakpointsActivated(bool activated) { + if (activated == m_breakpointsActivated) + return; + m_breakpointsActivated = activated; + recompileAllJSFunctions(); } void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause) @@ -535,7 +598,7 @@ void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause) void Debugger::setPauseOnNextStatement(bool pause) { - m_pauseOnNextStatement = pause; + m_pauseAtNextOpportunity = pause; if (pause) setSteppingMode(SteppingModeEnabled); } @@ -545,19 +608,22 @@ void Debugger::breakProgram() if (m_isPaused) return; - m_pauseOnNextStatement = true; + if (!m_vm.topCallFrame) + return; + + m_pauseAtNextOpportunity = true; setSteppingMode(SteppingModeEnabled); - m_currentCallFrame = m_vm->topCallFrame; - ASSERT(m_currentCallFrame); + m_currentCallFrame = m_vm.topCallFrame; pauseIfNeeded(m_currentCallFrame); } void Debugger::continueProgram() { + clearNextPauseState(); + if (!m_isPaused) return; - m_pauseOnNextStatement = false; notifyDoneProcessingDebuggerEvents(); } @@ -566,7 +632,7 @@ void Debugger::stepIntoStatement() if (!m_isPaused) return; - m_pauseOnNextStatement = true; + m_pauseAtNextOpportunity = true; setSteppingMode(SteppingModeEnabled); notifyDoneProcessingDebuggerEvents(); } @@ -577,6 +643,7 @@ void Debugger::stepOverStatement() return; m_pauseOnCallFrame = m_currentCallFrame; + setSteppingMode(SteppingModeEnabled); notifyDoneProcessingDebuggerEvents(); } @@ -585,11 +652,30 @@ void Debugger::stepOutOfFunction() if (!m_isPaused) return; - m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrameSkippingVMEntrySentinel() : 0; + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topVMEntryFrame) : nullptr; + m_pauseOnStepOut = true; + setSteppingMode(SteppingModeEnabled); notifyDoneProcessingDebuggerEvents(); } -void Debugger::updateCallFrame(CallFrame* callFrame) +void Debugger::updateCallFrame(CallFrame* callFrame, CallFrameUpdateAction action) +{ + if (!callFrame) { + m_currentCallFrame = nullptr; + return; + } + + updateCallFrameInternal(callFrame); + + if (action == AttemptPause) + pauseIfNeeded(callFrame); + + if (!isStepping()) + m_currentCallFrame = nullptr; +} + +void Debugger::updateCallFrameInternal(CallFrame* callFrame) { m_currentCallFrame = callFrame; SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame); @@ -599,73 +685,87 @@ void Debugger::updateCallFrame(CallFrame* callFrame) } } -void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame) -{ - updateCallFrame(callFrame); - pauseIfNeeded(callFrame); - if (!isStepping()) - m_currentCallFrame = 0; -} - void Debugger::pauseIfNeeded(CallFrame* callFrame) { + VM& vm = callFrame->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + if (m_isPaused) return; - JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); - if (!needPauseHandling(vmEntryGlobalObject)) + if (m_suppressAllPauses) return; - Breakpoint breakpoint; - bool didHitBreakpoint = false; - bool pauseNow = m_pauseOnNextStatement; + intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame); + if (isBlacklisted(sourceID)) + return; + + DebuggerPausedScope debuggerPausedScope(*this); + + bool pauseNow = m_pauseAtNextOpportunity; pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); - intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame); + bool didPauseForStep = pauseNow; + bool didHitBreakpoint = false; + + Breakpoint breakpoint; TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame); pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint); m_lastExecutedLine = position.m_line.zeroBasedInt(); if (!pauseNow) return; - DebuggerCallFrameScope debuggerCallFrameScope(*this); + clearNextPauseState(); // Make sure we are not going to pause again on breakpoint actions by // reseting the pause state before executing any breakpoint actions. TemporaryPausedState pausedState(*this); - m_pauseOnCallFrame = 0; - m_pauseOnNextStatement = false; + + JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); if (didHitBreakpoint) { - handleBreakpointHit(breakpoint); + handleBreakpointHit(vmEntryGlobalObject, breakpoint); // Note that the actions can potentially stop the debugger, so we need to check that // we still have a current call frame when we get back. - if (breakpoint.autoContinue || !m_currentCallFrame) + if (!m_currentCallFrame) return; + + if (breakpoint.autoContinue) { + if (!didPauseForStep) + return; + didHitBreakpoint = false; + } else + m_pausingBreakpointID = breakpoint.id; + } + + { + PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause); + handlePause(vmEntryGlobalObject, m_reasonForPause); + RELEASE_ASSERT(!scope.exception()); } - handlePause(m_reasonForPause, vmEntryGlobalObject); + m_pausingBreakpointID = noBreakpointID; - if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) { + if (!m_pauseAtNextOpportunity && !m_pauseOnCallFrame) { setSteppingMode(SteppingModeDisabled); m_currentCallFrame = nullptr; } } -void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasHandler) +void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler) { if (m_isPaused) return; PauseReasonDeclaration reason(*this, PausedForException); - if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasHandler)) { - m_pauseOnNextStatement = true; + if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) { + m_pauseAtNextOpportunity = true; setSteppingMode(SteppingModeEnabled); } m_hasHandlerForExceptionCallback = true; m_currentException = exception; - updateCallFrameAndPauseIfNeeded(callFrame); + updateCallFrame(callFrame, AttemptPause); m_currentException = JSValue(); m_hasHandlerForExceptionCallback = false; } @@ -675,8 +775,28 @@ void Debugger::atStatement(CallFrame* callFrame) if (m_isPaused) return; + m_pastFirstExpressionInStatement = false; + PauseReasonDeclaration reason(*this, PausedAtStatement); - updateCallFrameAndPauseIfNeeded(callFrame); + updateCallFrame(callFrame, AttemptPause); +} + +void Debugger::atExpression(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + // If this is the first call in a statement, then we would have paused at the statement. + if (!m_pastFirstExpressionInStatement) { + m_pastFirstExpressionInStatement = true; + return; + } + + // Only pause at the next expression with step-in and step-out, not step-over. + bool shouldAttemptPause = m_pauseAtNextOpportunity || m_pauseOnStepOut; + + PauseReasonDeclaration reason(*this, PausedAtExpression); + updateCallFrame(callFrame, shouldAttemptPause ? AttemptPause : NoPause); } void Debugger::callEvent(CallFrame* callFrame) @@ -684,8 +804,7 @@ void Debugger::callEvent(CallFrame* callFrame) if (m_isPaused) return; - PauseReasonDeclaration reason(*this, PausedAfterCall); - updateCallFrameAndPauseIfNeeded(callFrame); + updateCallFrame(callFrame, NoPause); } void Debugger::returnEvent(CallFrame* callFrame) @@ -693,18 +812,48 @@ void Debugger::returnEvent(CallFrame* callFrame) if (m_isPaused) return; - PauseReasonDeclaration reason(*this, PausedBeforeReturn); - updateCallFrameAndPauseIfNeeded(callFrame); + { + PauseReasonDeclaration reason(*this, PausedBeforeReturn); + updateCallFrame(callFrame, AttemptPause); + } + + // Detach may have been called during pauseIfNeeded. + if (!m_currentCallFrame) + return; + + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + + // Returning from a call, there was at least one expression on the statement we are returning to. + m_pastFirstExpressionInStatement = true; + + // Treat stepping over a return statement like a step-out. + if (m_currentCallFrame == m_pauseOnCallFrame) { + m_pauseOnCallFrame = callerFrame; + m_pauseOnStepOut = true; + } + + updateCallFrame(callerFrame, NoPause); +} + +void Debugger::unwindEvent(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + updateCallFrame(callFrame, NoPause); - // detach may have been called during pauseIfNeeded if (!m_currentCallFrame) return; - // Treat stepping over a return statement like stepping out. + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + + // Treat stepping over an exception location like a step-out. if (m_currentCallFrame == m_pauseOnCallFrame) - m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + m_pauseOnCallFrame = callerFrame; - m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + updateCallFrame(callerFrame, NoPause); } void Debugger::willExecuteProgram(CallFrame* callFrame) @@ -712,14 +861,7 @@ void Debugger::willExecuteProgram(CallFrame* callFrame) if (m_isPaused) return; - PauseReasonDeclaration reason(*this, PausedAtStartOfProgram); - // FIXME: This check for whether we're debugging a worker thread is a workaround - // for https://bugs.webkit.org/show_bug.cgi?id=102637. Remove it when we rework - // the debugger implementation to not require callbacks. - if (!m_isInWorkerThread) - updateCallFrameAndPauseIfNeeded(callFrame); - else if (isStepping()) - updateCallFrame(callFrame); + updateCallFrame(callFrame, NoPause); } void Debugger::didExecuteProgram(CallFrame* callFrame) @@ -728,17 +870,36 @@ void Debugger::didExecuteProgram(CallFrame* callFrame) return; PauseReasonDeclaration reason(*this, PausedAtEndOfProgram); - updateCallFrameAndPauseIfNeeded(callFrame); + updateCallFrame(callFrame, AttemptPause); - // Treat stepping over the end of a program like stepping out. + // Detach may have been called during pauseIfNeeded. if (!m_currentCallFrame) return; + + VMEntryFrame* topVMEntryFrame = m_vm.topVMEntryFrame; + CallFrame* callerFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + + // Returning from a program, could be eval(), there was at least one expression on the statement we are returning to. + m_pastFirstExpressionInStatement = true; + + // Treat stepping over the end of a program like a step-out. if (m_currentCallFrame == m_pauseOnCallFrame) { - m_pauseOnCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); - if (!m_currentCallFrame) - return; + m_pauseOnCallFrame = callerFrame; + m_pauseAtNextOpportunity = true; } - m_currentCallFrame = m_currentCallFrame->callerFrameSkippingVMEntrySentinel(); + + updateCallFrame(callerFrame, NoPause); + + // Do not continue stepping into an unknown future program. + if (!m_currentCallFrame) + clearNextPauseState(); +} + +void Debugger::clearNextPauseState() +{ + m_pauseOnCallFrame = nullptr; + m_pauseAtNextOpportunity = false; + m_pauseOnStepOut = false; } void Debugger::didReachBreakpoint(CallFrame* callFrame) @@ -746,16 +907,32 @@ void Debugger::didReachBreakpoint(CallFrame* callFrame) if (m_isPaused) return; - PauseReasonDeclaration reason(*this, PausedForBreakpoint); - m_pauseOnNextStatement = true; + PauseReasonDeclaration reason(*this, PausedForDebuggerStatement); + m_pauseAtNextOpportunity = true; setSteppingMode(SteppingModeEnabled); - updateCallFrameAndPauseIfNeeded(callFrame); + updateCallFrame(callFrame, AttemptPause); +} + +DebuggerCallFrame& Debugger::currentDebuggerCallFrame() +{ + if (!m_currentDebuggerCallFrame) + m_currentDebuggerCallFrame = DebuggerCallFrame::create(m_currentCallFrame); + return *m_currentDebuggerCallFrame; +} + +bool Debugger::isBlacklisted(SourceID sourceID) const +{ + return m_blacklistedScripts.contains(sourceID); +} + +void Debugger::addToBlacklist(SourceID sourceID) +{ + m_blacklistedScripts.add(sourceID); } -DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const +void Debugger::clearBlacklist() { - ASSERT(m_currentDebuggerCallFrame); - return m_currentDebuggerCallFrame.get(); + m_blacklistedScripts.clear(); } } // namespace JSC diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h index f7b734f37..3f51bb42d 100644 --- a/Source/JavaScriptCore/debugger/Debugger.h +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -19,21 +19,23 @@ * */ -#ifndef Debugger_h -#define Debugger_h +#pragma once #include "Breakpoint.h" +#include "CallData.h" #include "DebuggerCallFrame.h" +#include "DebuggerParseData.h" #include "DebuggerPrimitives.h" #include "JSCJSValue.h" #include <wtf/HashMap.h> #include <wtf/HashSet.h> #include <wtf/RefPtr.h> -#include <wtf/Vector.h> #include <wtf/text/TextPosition.h> namespace JSC { +class CodeBlock; +class Exception; class ExecState; class JSGlobalObject; class SourceProvider; @@ -43,10 +45,12 @@ typedef ExecState CallFrame; class JS_EXPORT_PRIVATE Debugger { public: - Debugger(bool isInWorkerThread = false); + Debugger(VM&); virtual ~Debugger(); - JSC::DebuggerCallFrame* currentDebuggerCallFrame() const; + VM& vm() { return m_vm; } + + JSC::DebuggerCallFrame& currentDebuggerCallFrame(); bool hasHandlerForExceptionCallback() const { ASSERT(m_reasonForPause == PausedForException); @@ -58,21 +62,25 @@ public: return m_currentException; } - bool needsExceptionCallbacks() const { return m_pauseOnExceptionsState != DontPauseOnExceptions; } + bool needsExceptionCallbacks() const { return m_breakpointsActivated && m_pauseOnExceptionsState != DontPauseOnExceptions; } + bool isInteractivelyDebugging() const { return m_breakpointsActivated; } - void attach(JSGlobalObject*); enum ReasonForDetach { TerminatingDebuggingSession, GlobalObjectIsDestructing }; - virtual void detach(JSGlobalObject*, ReasonForDetach); + void attach(JSGlobalObject*); + void detach(JSGlobalObject*, ReasonForDetach); + bool isAttached(JSGlobalObject*); - BreakpointID setBreakpoint(Breakpoint, unsigned& actualLine, unsigned& actualColumn); + void resolveBreakpoint(Breakpoint&, SourceProvider*); + BreakpointID setBreakpoint(Breakpoint&, bool& existing); void removeBreakpoint(BreakpointID); void clearBreakpoints(); - void setBreakpointsActivated(bool); + void activateBreakpoints() { setBreakpointsActivated(true); } void deactivateBreakpoints() { setBreakpointsActivated(false); } + bool breakpointsActive() const { return m_breakpointsActivated; } enum PauseOnExceptionsState { DontPauseOnExceptions, @@ -82,6 +90,19 @@ public: PauseOnExceptionsState pauseOnExceptionsState() const { return m_pauseOnExceptionsState; } void setPauseOnExceptionsState(PauseOnExceptionsState); + enum ReasonForPause { + NotPaused, + PausedForException, + PausedAtStatement, + PausedAtExpression, + PausedBeforeReturn, + PausedAtEndOfProgram, + PausedForBreakpoint, + PausedForDebuggerStatement, + }; + ReasonForPause reasonForPause() const { return m_reasonForPause; } + BreakpointID pausingBreakpointID() const { return m_pausingBreakpointID; } + void setPauseOnNextStatement(bool); void breakProgram(); void continueProgram(); @@ -89,47 +110,56 @@ public: void stepOverStatement(); void stepOutOfFunction(); - bool isPaused() { return m_isPaused; } + bool isBlacklisted(SourceID) const; + void addToBlacklist(SourceID); + void clearBlacklist(); + + bool isPaused() const { return m_isPaused; } bool isStepping() const { return m_steppingMode == SteppingModeEnabled; } + bool suppressAllPauses() const { return m_suppressAllPauses; } + void setSuppressAllPauses(bool suppress) { m_suppressAllPauses = suppress; } + virtual void sourceParsed(ExecState*, SourceProvider*, int errorLineNumber, const WTF::String& errorMessage) = 0; - void exception(CallFrame*, JSValue exceptionValue, bool hasHandler); + void exception(CallFrame*, JSValue exceptionValue, bool hasCatchHandler); void atStatement(CallFrame*); + void atExpression(CallFrame*); void callEvent(CallFrame*); void returnEvent(CallFrame*); + void unwindEvent(CallFrame*); void willExecuteProgram(CallFrame*); void didExecuteProgram(CallFrame*); void didReachBreakpoint(CallFrame*); - void recompileAllJSFunctions(VM*); + virtual void recompileAllJSFunctions(); void registerCodeBlock(CodeBlock*); -protected: - virtual bool needPauseHandling(JSGlobalObject*) { return false; } - virtual void handleBreakpointHit(const Breakpoint&) { } - virtual void handleExceptionInBreakpointCondition(ExecState*, JSValue exception) const { UNUSED_PARAM(exception); } - - enum ReasonForPause { - NotPaused, - PausedForException, - PausedAtStatement, - PausedAfterCall, - PausedBeforeReturn, - PausedAtStartOfProgram, - PausedAtEndOfProgram, - PausedForBreakpoint + class ProfilingClient { + public: + virtual ~ProfilingClient(); + virtual bool isAlreadyProfiling() const = 0; + virtual double willEvaluateScript() = 0; + virtual void didEvaluateScript(double startTime, ProfilingReason) = 0; }; - virtual void handlePause(ReasonForPause, JSGlobalObject*) { } + void setProfilingClient(ProfilingClient*); + bool hasProfilingClient() const { return m_profilingClient != nullptr; } + bool isAlreadyProfiling() const { return m_profilingClient && m_profilingClient->isAlreadyProfiling(); } + double willEvaluateScript(); + void didEvaluateScript(double startTime, ProfilingReason); + +protected: + virtual void handleBreakpointHit(JSGlobalObject*, const Breakpoint&) { } + virtual void handleExceptionInBreakpointCondition(ExecState*, Exception*) const { } + virtual void handlePause(JSGlobalObject*, ReasonForPause) { } virtual void notifyDoneProcessingDebuggerEvents() { } private: typedef HashMap<BreakpointID, Breakpoint*> BreakpointIDToBreakpointMap; - typedef Vector<Breakpoint> BreakpointsInLine; - typedef HashMap<unsigned, BreakpointsInLine, WTF::IntHash<int>, WTF::UnsignedWithZeroKeyHashTraits<int>> LineToBreakpointsMap; + typedef HashMap<unsigned, RefPtr<BreakpointsList>, WTF::IntHash<int>, WTF::UnsignedWithZeroKeyHashTraits<int>> LineToBreakpointsMap; typedef HashMap<SourceID, LineToBreakpointsMap, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> SourceIDToBreakpointsMap; class ClearCodeBlockDebuggerRequestsFunctor; @@ -155,6 +185,8 @@ private: bool hasBreakpoint(SourceID, const TextPosition&, Breakpoint* hitBreakpoint); + DebuggerParseData& debuggerParseData(SourceID, SourceProvider*); + void updateNeedForOpDebugCallbacks(); // These update functions are only needed because our current breakpoints are @@ -162,9 +194,11 @@ private: // that we don't break on the same line more than once. Once we switch to a // bytecode PC key'ed breakpoint, we will not need these anymore and should // be able to remove them. - void updateCallFrame(JSC::CallFrame*); - void updateCallFrameAndPauseIfNeeded(JSC::CallFrame*); + enum CallFrameUpdateAction { AttemptPause, NoPause }; + void updateCallFrame(JSC::CallFrame*, CallFrameUpdateAction); + void updateCallFrameInternal(JSC::CallFrame*); void pauseIfNeeded(JSC::CallFrame*); + void clearNextPauseState(); enum SteppingMode { SteppingModeDisabled, @@ -176,41 +210,48 @@ private: BreakpointDisabled, BreakpointEnabled }; + void setBreakpointsActivated(bool); void toggleBreakpoint(CodeBlock*, Breakpoint&, BreakpointState); void applyBreakpoints(CodeBlock*); void toggleBreakpoint(Breakpoint&, BreakpointState); void clearDebuggerRequests(JSGlobalObject*); + void clearParsedData(); - VM* m_vm; + VM& m_vm; HashSet<JSGlobalObject*> m_globalObjects; + HashMap<SourceID, DebuggerParseData, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> m_parseDataMap; + HashSet<SourceID, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> m_blacklistedScripts; PauseOnExceptionsState m_pauseOnExceptionsState; - bool m_pauseOnNextStatement : 1; + bool m_pauseAtNextOpportunity : 1; + bool m_pauseOnStepOut : 1; + bool m_pastFirstExpressionInStatement : 1; bool m_isPaused : 1; bool m_breakpointsActivated : 1; bool m_hasHandlerForExceptionCallback : 1; - bool m_isInWorkerThread : 1; - SteppingMode m_steppingMode : 1; + bool m_suppressAllPauses : 1; + unsigned m_steppingMode : 1; // SteppingMode ReasonForPause m_reasonForPause; JSValue m_currentException; - CallFrame* m_pauseOnCallFrame; - CallFrame* m_currentCallFrame; + CallFrame* m_pauseOnCallFrame { nullptr }; + CallFrame* m_currentCallFrame { nullptr }; unsigned m_lastExecutedLine; SourceID m_lastExecutedSourceID; BreakpointID m_topBreakpointID; + BreakpointID m_pausingBreakpointID; BreakpointIDToBreakpointMap m_breakpointIDToBreakpoint; SourceIDToBreakpointsMap m_sourceIDToBreakpoints; RefPtr<JSC::DebuggerCallFrame> m_currentDebuggerCallFrame; - friend class DebuggerCallFrameScope; + ProfilingClient* m_profilingClient { nullptr }; + + friend class DebuggerPausedScope; friend class TemporaryPausedState; friend class LLIntOffsetsExtractor; }; } // namespace JSC - -#endif // Debugger_h diff --git a/Source/JavaScriptCore/debugger/DebuggerActivation.cpp b/Source/JavaScriptCore/debugger/DebuggerActivation.cpp deleted file mode 100644 index 75b08ea81..000000000 --- a/Source/JavaScriptCore/debugger/DebuggerActivation.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "config.h" -#include "DebuggerActivation.h" - -#include "JSActivation.h" -#include "Operations.h" - -namespace JSC { - -STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DebuggerActivation); - -const ClassInfo DebuggerActivation::s_info = { "DebuggerActivation", &Base::s_info, 0, 0, CREATE_METHOD_TABLE(DebuggerActivation) }; - -DebuggerActivation::DebuggerActivation(VM& vm) - : JSNonFinalObject(vm, vm.debuggerActivationStructure.get()) -{ -} - -void DebuggerActivation::finishCreation(VM& vm, JSObject* activation) -{ - Base::finishCreation(vm); - ASSERT(activation); - ASSERT(activation->isActivationObject()); - m_activation.set(vm, this, jsCast<JSActivation*>(activation)); -} - -void DebuggerActivation::visitChildren(JSCell* cell, SlotVisitor& visitor) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(cell); - ASSERT_GC_OBJECT_INHERITS(thisObject, info()); - COMPILE_ASSERT(StructureFlags & OverridesVisitChildren, OverridesVisitChildrenWithoutSettingFlag); - ASSERT(thisObject->structure()->typeInfo().overridesVisitChildren()); - - JSObject::visitChildren(thisObject, visitor); - visitor.append(&thisObject->m_activation); -} - -String DebuggerActivation::className(const JSObject* object) -{ - const DebuggerActivation* thisObject = jsCast<const DebuggerActivation*>(object); - return thisObject->m_activation->methodTable()->className(thisObject->m_activation.get()); -} - -bool DebuggerActivation::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(object); - return thisObject->m_activation->methodTable()->getOwnPropertySlot(thisObject->m_activation.get(), exec, propertyName, slot); -} - -void DebuggerActivation::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(cell); - thisObject->m_activation->methodTable()->put(thisObject->m_activation.get(), exec, propertyName, value, slot); -} - -bool DebuggerActivation::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(cell); - return thisObject->m_activation->methodTable()->deleteProperty(thisObject->m_activation.get(), exec, propertyName); -} - -void DebuggerActivation::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(object); - thisObject->m_activation->methodTable()->getPropertyNames(thisObject->m_activation.get(), exec, propertyNames, mode); -} - -bool DebuggerActivation::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) -{ - DebuggerActivation* thisObject = jsCast<DebuggerActivation*>(object); - return thisObject->m_activation->methodTable()->defineOwnProperty(thisObject->m_activation.get(), exec, propertyName, descriptor, shouldThrow); -} - -} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerActivation.h b/Source/JavaScriptCore/debugger/DebuggerActivation.h deleted file mode 100644 index e90383eaa..000000000 --- a/Source/JavaScriptCore/debugger/DebuggerActivation.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef DebuggerActivation_h -#define DebuggerActivation_h - -#include "JSObject.h" - -namespace JSC { - -class DebuggerActivation : public JSNonFinalObject { -public: - typedef JSNonFinalObject Base; - - static DebuggerActivation* create(VM& vm, JSObject* object) - { - DebuggerActivation* activation = new (NotNull, allocateCell<DebuggerActivation>(vm.heap)) DebuggerActivation(vm); - activation->finishCreation(vm, object); - return activation; - } - - static void visitChildren(JSCell*, SlotVisitor&); - static String className(const JSObject*); - static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&); - static void put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&); - static bool deleteProperty(JSCell*, ExecState*, PropertyName); - static void getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode); - static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, const PropertyDescriptor&, bool shouldThrow); - - DECLARE_EXPORT_INFO; - - static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) - { - return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info()); - } - -protected: - static const unsigned StructureFlags = OverridesGetOwnPropertySlot | OverridesVisitChildren | JSObject::StructureFlags; - - JS_EXPORT_PRIVATE void finishCreation(VM&, JSObject* activation); - -private: - JS_EXPORT_PRIVATE DebuggerActivation(VM&); - WriteBarrier<JSActivation> m_activation; -}; - -} // namespace JSC - -#endif // DebuggerActivation_h diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp index 90cee8c23..47294b47e 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2013-2014, 2016 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -29,19 +29,24 @@ #include "config.h" #include "DebuggerCallFrame.h" -#include "JSFunction.h" #include "CodeBlock.h" +#include "DebuggerEvalEnabler.h" +#include "DebuggerScope.h" #include "Interpreter.h" -#include "Operations.h" +#include "JSCInlines.h" +#include "JSFunction.h" +#include "JSLexicalEnvironment.h" +#include "JSWithScope.h" #include "Parser.h" +#include "ShadowChickenInlines.h" #include "StackVisitor.h" -#include "VMEntryScope.h" +#include "StrongInlines.h" namespace JSC { class LineAndColumnFunctor { public: - StackVisitor::Status operator()(StackVisitor& visitor) + StackVisitor::Status operator()(StackVisitor& visitor) const { visitor->computeLineAndColumn(m_line, m_column); return StackVisitor::Done; @@ -51,39 +56,68 @@ public: unsigned column() const { return m_column; } private: - unsigned m_line; - unsigned m_column; + mutable unsigned m_line; + mutable unsigned m_column; }; -DebuggerCallFrame::DebuggerCallFrame(CallFrame* callFrame) - : m_callFrame(callFrame) +Ref<DebuggerCallFrame> DebuggerCallFrame::create(CallFrame* callFrame) +{ + if (UNLIKELY(callFrame == callFrame->lexicalGlobalObject()->globalExec())) { + ShadowChicken::Frame emptyFrame; + RELEASE_ASSERT(!emptyFrame.isTailDeleted); + return adoptRef(*new DebuggerCallFrame(callFrame, emptyFrame)); + } + + Vector<ShadowChicken::Frame> frames; + callFrame->vm().shadowChicken().iterate(callFrame->vm(), callFrame, [&] (const ShadowChicken::Frame& frame) -> bool { + frames.append(frame); + return true; + }); + + RELEASE_ASSERT(frames.size()); + ASSERT(!frames[0].isTailDeleted); // The top frame should never be tail deleted. + + RefPtr<DebuggerCallFrame> currentParent = nullptr; + ExecState* exec = callFrame->lexicalGlobalObject()->globalExec(); + // This walks the stack from the entry stack frame to the top of the stack. + for (unsigned i = frames.size(); i--; ) { + const ShadowChicken::Frame& frame = frames[i]; + if (!frame.isTailDeleted) + exec = frame.frame; + Ref<DebuggerCallFrame> currentFrame = adoptRef(*new DebuggerCallFrame(exec, frame)); + currentFrame->m_caller = currentParent; + currentParent = WTFMove(currentFrame); + } + return *currentParent; +} + +DebuggerCallFrame::DebuggerCallFrame(CallFrame* callFrame, const ShadowChicken::Frame& frame) + : m_validMachineFrame(callFrame) + , m_shadowChickenFrame(frame) { - m_position = positionForCallFrame(m_callFrame); + m_position = currentPosition(); } -PassRefPtr<DebuggerCallFrame> DebuggerCallFrame::callerFrame() +RefPtr<DebuggerCallFrame> DebuggerCallFrame::callerFrame() { ASSERT(isValid()); if (!isValid()) - return 0; + return nullptr; - if (m_caller) - return m_caller; - - CallFrame* callerFrame = m_callFrame->callerFrameSkippingVMEntrySentinel(); - if (!callerFrame) - return 0; - - m_caller = DebuggerCallFrame::create(callerFrame); return m_caller; } +ExecState* DebuggerCallFrame::globalExec() +{ + return scope()->globalObject()->globalExec(); +} + JSC::JSGlobalObject* DebuggerCallFrame::vmEntryGlobalObject() const { ASSERT(isValid()); if (!isValid()) - return 0; - return m_callFrame->vmEntryGlobalObject(); + return nullptr; + return m_validMachineFrame->vmEntryGlobalObject(); } SourceID DebuggerCallFrame::sourceID() const @@ -91,7 +125,9 @@ SourceID DebuggerCallFrame::sourceID() const ASSERT(isValid()); if (!isValid()) return noSourceID; - return sourceIDForCallFrame(m_callFrame); + if (isTailDeleted()) + return m_shadowChickenFrame.codeBlock->ownerScriptExecutable()->sourceID(); + return sourceIDForCallFrame(m_validMachineFrame); } String DebuggerCallFrame::functionName() const @@ -99,19 +135,39 @@ String DebuggerCallFrame::functionName() const ASSERT(isValid()); if (!isValid()) return String(); - JSObject* function = m_callFrame->callee(); - if (!function) - return String(); - return getCalculatedDisplayName(m_callFrame, function); + VM& vm = m_validMachineFrame->vm(); + if (isTailDeleted()) { + if (JSFunction* func = jsDynamicCast<JSFunction*>(vm, m_shadowChickenFrame.callee)) + return func->calculatedDisplayName(vm); + return m_shadowChickenFrame.codeBlock->inferredName().data(); + } + + return m_validMachineFrame->friendlyFunctionName(); } -JSScope* DebuggerCallFrame::scope() const +DebuggerScope* DebuggerCallFrame::scope() { ASSERT(isValid()); if (!isValid()) - return 0; - return m_callFrame->scope(); + return nullptr; + + if (!m_scope) { + VM& vm = m_validMachineFrame->vm(); + JSScope* scope; + CodeBlock* codeBlock = m_validMachineFrame->codeBlock(); + if (isTailDeleted()) + scope = m_shadowChickenFrame.scope; + else if (codeBlock && codeBlock->scopeRegister().isValid()) + scope = m_validMachineFrame->scope(codeBlock->scopeRegister().offset()); + else if (JSCallee* callee = jsDynamicCast<JSCallee*>(vm, m_validMachineFrame->jsCallee())) + scope = callee->scope(); + else + scope = m_validMachineFrame->lexicalGlobalObject()->globalLexicalEnvironment(); + + m_scope.set(vm, DebuggerScope::create(vm, scope)); + } + return m_scope.get(); } DebuggerCallFrame::Type DebuggerCallFrame::type() const @@ -120,7 +176,10 @@ DebuggerCallFrame::Type DebuggerCallFrame::type() const if (!isValid()) return ProgramType; - if (m_callFrame->callee()) + if (isTailDeleted()) + return FunctionType; + + if (jsDynamicCast<JSFunction*>(m_validMachineFrame->vm(), m_validMachineFrame->jsCallee())) return FunctionType; return ProgramType; @@ -129,59 +188,120 @@ DebuggerCallFrame::Type DebuggerCallFrame::type() const JSValue DebuggerCallFrame::thisValue() const { ASSERT(isValid()); - return thisValueForCallFrame(m_callFrame); + if (!isValid()) + return jsUndefined(); + + CodeBlock* codeBlock = nullptr; + JSValue thisValue; + if (isTailDeleted()) { + thisValue = m_shadowChickenFrame.thisValue; + codeBlock = m_shadowChickenFrame.codeBlock; + } else { + thisValue = m_validMachineFrame->thisValue(); + codeBlock = m_validMachineFrame->codeBlock(); + } + + if (!thisValue) + return jsUndefined(); + + ECMAMode ecmaMode = NotStrictMode; + if (codeBlock && codeBlock->isStrictMode()) + ecmaMode = StrictMode; + return thisValue.toThis(m_validMachineFrame, ecmaMode); } // Evaluate some JavaScript code in the scope of this frame. -JSValue DebuggerCallFrame::evaluate(const String& script, JSValue& exception) const +JSValue DebuggerCallFrame::evaluateWithScopeExtension(const String& script, JSObject* scopeExtensionObject, NakedPtr<Exception>& exception) { ASSERT(isValid()); - return evaluateWithCallFrame(m_callFrame, script, exception); -} - -JSValue DebuggerCallFrame::evaluateWithCallFrame(CallFrame* callFrame, const String& script, JSValue& exception) -{ + CallFrame* callFrame = m_validMachineFrame; if (!callFrame) - return jsNull(); + return jsUndefined(); - JSLockHolder lock(callFrame); + VM& vm = callFrame->vm(); + JSLockHolder lock(vm); + auto catchScope = DECLARE_CATCH_SCOPE(vm); + + CodeBlock* codeBlock = nullptr; + if (isTailDeleted()) + codeBlock = m_shadowChickenFrame.codeBlock; + else + codeBlock = callFrame->codeBlock(); + if (!codeBlock) + return jsUndefined(); + + DebuggerEvalEnabler evalEnabler(callFrame); - if (!callFrame->codeBlock()) - return JSValue(); + EvalContextType evalContextType; - VM& vm = callFrame->vm(); - EvalExecutable* eval = EvalExecutable::create(callFrame, makeSource(script), callFrame->codeBlock()->isStrictMode()); - if (vm.exception()) { - exception = vm.exception(); - vm.clearException(); + if (isFunctionParseMode(codeBlock->unlinkedCodeBlock()->parseMode())) + evalContextType = EvalContextType::FunctionEvalContext; + else if (codeBlock->unlinkedCodeBlock()->codeType() == EvalCode) + evalContextType = codeBlock->unlinkedCodeBlock()->evalContextType(); + else + evalContextType = EvalContextType::None; + + VariableEnvironment variablesUnderTDZ; + JSScope::collectClosureVariablesUnderTDZ(scope()->jsScope(), variablesUnderTDZ); + + auto* eval = DirectEvalExecutable::create(callFrame, makeSource(script, callFrame->callerSourceOrigin()), codeBlock->isStrictMode(), codeBlock->unlinkedCodeBlock()->derivedContextType(), codeBlock->unlinkedCodeBlock()->isArrowFunction(), evalContextType, &variablesUnderTDZ); + if (UNLIKELY(catchScope.exception())) { + exception = catchScope.exception(); + catchScope.clearException(); return jsUndefined(); } - JSValue thisValue = thisValueForCallFrame(callFrame); - JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, callFrame->scope()); - if (vm.exception()) { - exception = vm.exception(); - vm.clearException(); + JSGlobalObject* globalObject = callFrame->vmEntryGlobalObject(); + if (scopeExtensionObject) { + JSScope* ignoredPreviousScope = globalObject->globalScope(); + globalObject->setGlobalScopeExtension(JSWithScope::create(vm, globalObject, scopeExtensionObject, ignoredPreviousScope)); } + + JSValue thisValue = this->thisValue(); + JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, scope()->jsScope()); + if (UNLIKELY(catchScope.exception())) { + exception = catchScope.exception(); + catchScope.clearException(); + } + + if (scopeExtensionObject) + globalObject->clearGlobalScopeExtension(); + ASSERT(result); return result; } void DebuggerCallFrame::invalidate() { - m_callFrame = nullptr; - RefPtr<DebuggerCallFrame> frame = m_caller.release(); + RefPtr<DebuggerCallFrame> frame = this; while (frame) { - frame->m_callFrame = nullptr; - frame = frame->m_caller.release(); + frame->m_validMachineFrame = nullptr; + if (frame->m_scope) { + frame->m_scope->invalidateChain(); + frame->m_scope.clear(); + } + frame = WTFMove(frame->m_caller); } } -TextPosition DebuggerCallFrame::positionForCallFrame(CallFrame* callFrame) +TextPosition DebuggerCallFrame::currentPosition() { - if (!callFrame) + if (!m_validMachineFrame) return TextPosition(); + if (isTailDeleted()) { + CodeBlock* codeBlock = m_shadowChickenFrame.codeBlock; + if (std::optional<unsigned> bytecodeOffset = codeBlock->bytecodeOffsetFromCallSiteIndex(m_shadowChickenFrame.callSiteIndex)) { + return TextPosition(OrdinalNumber::fromOneBasedInt(codeBlock->lineNumberForBytecodeOffset(*bytecodeOffset)), + OrdinalNumber::fromOneBasedInt(codeBlock->columnNumberForBytecodeOffset(*bytecodeOffset))); + } + } + + return positionForCallFrame(m_validMachineFrame); +} + +TextPosition DebuggerCallFrame::positionForCallFrame(CallFrame* callFrame) +{ LineAndColumnFunctor functor; callFrame->iterate(functor); return TextPosition(OrdinalNumber::fromOneBasedInt(functor.line()), OrdinalNumber::fromOneBasedInt(functor.column())); @@ -193,20 +313,7 @@ SourceID DebuggerCallFrame::sourceIDForCallFrame(CallFrame* callFrame) CodeBlock* codeBlock = callFrame->codeBlock(); if (!codeBlock) return noSourceID; - return codeBlock->ownerExecutable()->sourceID(); -} - -JSValue DebuggerCallFrame::thisValueForCallFrame(CallFrame* callFrame) -{ - if (!callFrame) - return jsNull(); - - ECMAMode ecmaMode = NotStrictMode; - CodeBlock* codeBlock = callFrame->codeBlock(); - if (codeBlock && codeBlock->isStrictMode()) - ecmaMode = StrictMode; - JSValue thisValue = callFrame->thisValue().toThis(callFrame, ecmaMode); - return thisValue; + return codeBlock->ownerScriptExecutable()->sourceID(); } } // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.h b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h index 09c3fb9d8..462465854 100644 --- a/Source/JavaScriptCore/debugger/DebuggerCallFrame.h +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -26,30 +26,29 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DebuggerCallFrame_h -#define DebuggerCallFrame_h +#pragma once #include "CallFrame.h" #include "DebuggerPrimitives.h" -#include <wtf/PassRefPtr.h> +#include "ShadowChicken.h" +#include "Strong.h" +#include <wtf/NakedPtr.h> #include <wtf/RefCounted.h> #include <wtf/text/TextPosition.h> namespace JSC { +class DebuggerScope; +class Exception; + class DebuggerCallFrame : public RefCounted<DebuggerCallFrame> { public: enum Type { ProgramType, FunctionType }; - static PassRefPtr<DebuggerCallFrame> create(CallFrame* callFrame) - { - return adoptRef(new DebuggerCallFrame(callFrame)); - } - - JS_EXPORT_PRIVATE explicit DebuggerCallFrame(CallFrame*); + static Ref<DebuggerCallFrame> create(CallFrame*); - JS_EXPORT_PRIVATE PassRefPtr<DebuggerCallFrame> callerFrame(); - ExecState* exec() const { return m_callFrame; } + JS_EXPORT_PRIVATE RefPtr<DebuggerCallFrame> callerFrame(); + ExecState* globalExec(); JS_EXPORT_PRIVATE SourceID sourceID() const; // line and column are in base 0 e.g. the first line is line 0. @@ -58,29 +57,34 @@ public: JS_EXPORT_PRIVATE const TextPosition& position() const { return m_position; } JS_EXPORT_PRIVATE JSGlobalObject* vmEntryGlobalObject() const; - JS_EXPORT_PRIVATE JSScope* scope() const; + JS_EXPORT_PRIVATE DebuggerScope* scope(); JS_EXPORT_PRIVATE String functionName() const; JS_EXPORT_PRIVATE Type type() const; JS_EXPORT_PRIVATE JSValue thisValue() const; - JS_EXPORT_PRIVATE JSValue evaluate(const String&, JSValue& exception) const; + JSValue evaluateWithScopeExtension(const String&, JSObject* scopeExtensionObject, NakedPtr<Exception>&); - bool isValid() const { return !!m_callFrame; } + bool isValid() const { return !!m_validMachineFrame || isTailDeleted(); } JS_EXPORT_PRIVATE void invalidate(); // The following are only public for the Debugger's use only. They will be // made private soon. Other clients should not use these. - JS_EXPORT_PRIVATE static JSValue evaluateWithCallFrame(CallFrame*, const String& script, JSValue& exception); + JS_EXPORT_PRIVATE TextPosition currentPosition(); JS_EXPORT_PRIVATE static TextPosition positionForCallFrame(CallFrame*); JS_EXPORT_PRIVATE static SourceID sourceIDForCallFrame(CallFrame*); - static JSValue thisValueForCallFrame(CallFrame*); + + bool isTailDeleted() const { return m_shadowChickenFrame.isTailDeleted; } private: - CallFrame* m_callFrame; + DebuggerCallFrame(CallFrame*, const ShadowChicken::Frame&); + + CallFrame* m_validMachineFrame; RefPtr<DebuggerCallFrame> m_caller; TextPosition m_position; + // The DebuggerPausedScope is responsible for calling invalidate() which, + // in turn, will clear this strong ref. + Strong<DebuggerScope> m_scope; + ShadowChicken::Frame m_shadowChickenFrame; }; } // namespace JSC - -#endif // DebuggerCallFrame_h diff --git a/Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h b/Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h new file mode 100644 index 000000000..0e4c4cfac --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "CallFrame.h" +#include "JSGlobalObject.h" + +namespace JSC { + +class DebuggerEvalEnabler { +public: + explicit DebuggerEvalEnabler(const ExecState* exec) + : m_exec(exec) + , m_evalWasDisabled(false) + { + if (exec) { + JSGlobalObject* globalObject = exec->lexicalGlobalObject(); + m_evalWasDisabled = !globalObject->evalEnabled(); + if (m_evalWasDisabled) + globalObject->setEvalEnabled(true, globalObject->evalDisabledErrorMessage()); + } + } + + ~DebuggerEvalEnabler() + { + if (m_evalWasDisabled) { + JSGlobalObject* globalObject = m_exec->lexicalGlobalObject(); + globalObject->setEvalEnabled(false, globalObject->evalDisabledErrorMessage()); + } + } + +private: + const ExecState* m_exec; + bool m_evalWasDisabled; +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerLocation.cpp b/Source/JavaScriptCore/debugger/DebuggerLocation.cpp new file mode 100644 index 000000000..b139720b4 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerLocation.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DebuggerLocation.h" + +#include "ScriptExecutable.h" + +namespace JSC { + +DebuggerLocation::DebuggerLocation(ScriptExecutable* executable) +{ + if (executable->isHostFunction()) + return; + + sourceID = executable->sourceID(); + line = executable->firstLine(); + column = executable->startColumn(); + url = executable->sourceURL(); + if (url.isEmpty()) + url = executable->source().provider()->sourceURL(); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerLocation.h b/Source/JavaScriptCore/debugger/DebuggerLocation.h new file mode 100644 index 000000000..3e1c4166b --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerLocation.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "DebuggerPrimitives.h" +#include <wtf/text/WTFString.h> + +namespace JSC { + +class ScriptExecutable; + +struct DebuggerLocation { + + DebuggerLocation() { } + DebuggerLocation(const String& url, intptr_t sourceID, unsigned line, unsigned column) + : url(url) + , sourceID(sourceID) + , line(line) + , column(column) + { } + + DebuggerLocation(ScriptExecutable*); + + String url; + intptr_t sourceID { noSourceID }; + unsigned line { 0 }; + unsigned column { 0 }; +}; + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerParseData.cpp b/Source/JavaScriptCore/debugger/DebuggerParseData.cpp new file mode 100644 index 000000000..48f939f10 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerParseData.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DebuggerParseData.h" + +#include "Parser.h" + +namespace JSC { + +std::optional<JSTextPosition> DebuggerPausePositions::breakpointLocationForLineColumn(int line, int column) +{ + unsigned start = 0; + unsigned end = m_positions.size(); + while (start != end) { + unsigned middle = start + ((end - start) / 2); + DebuggerPausePosition& pausePosition = m_positions[middle]; + int pauseLine = pausePosition.position.line; + int pauseColumn = pausePosition.position.offset - pausePosition.position.lineStartOffset; + + if (line < pauseLine) { + end = middle; + continue; + } + if (line > pauseLine) { + start = middle + 1; + continue; + } + + if (column == pauseColumn) { + // Found an exact position match. Roll forward if this was a function Entry. + // We are guarenteed to have a Leave for an Entry so we don't need to bounds check. + while (true) { + if (pausePosition.type != DebuggerPausePositionType::Enter) + return std::optional<JSTextPosition>(pausePosition.position); + pausePosition = m_positions[middle++]; + } + } + + if (column < pauseColumn) + end = middle; + else + start = middle + 1; + } + + // Past the end, no possible pause locations. + if (start >= m_positions.size()) + return std::nullopt; + + // If the next location is a function Entry we will need to decide if we should go into + // the function or go past the function. We decide to go into the function if the + // input is on the same line as the function entry. For example: + // + // 1. x; + // 2. + // 3. function foo() { + // 4. x; + // 5. } + // 6. + // 7. x; + // + // If the input was line 2, skip past functions to pause on line 7. + // If the input was line 3, go into the function to pause on line 4. + + // Valid pause location. Use it. + DebuggerPausePosition& firstSlidePosition = m_positions[start]; + if (firstSlidePosition.type != DebuggerPausePositionType::Enter) + return std::optional<JSTextPosition>(firstSlidePosition.position); + + // Determine if we should enter this function or skip past it. + // If entryStackSize is > 0 we are skipping functions. + bool shouldEnterFunction = firstSlidePosition.position.line == line; + int entryStackSize = shouldEnterFunction ? 0 : 1; + for (unsigned i = start + 1; i < m_positions.size(); ++i) { + DebuggerPausePosition& slidePosition = m_positions[i]; + ASSERT(entryStackSize >= 0); + + // Already skipping functions. + if (entryStackSize) { + if (slidePosition.type == DebuggerPausePositionType::Enter) + entryStackSize++; + else if (slidePosition.type == DebuggerPausePositionType::Leave) + entryStackSize--; + continue; + } + + // Start skipping functions. + if (slidePosition.type == DebuggerPausePositionType::Enter) { + entryStackSize++; + continue; + } + + // Found pause position. + return std::optional<JSTextPosition>(slidePosition.position); + } + + // No pause positions found. + return std::nullopt; +} + +void DebuggerPausePositions::sort() +{ + std::sort(m_positions.begin(), m_positions.end(), [] (const DebuggerPausePosition& a, const DebuggerPausePosition& b) { + return a.position.offset < b.position.offset; + }); +} + +typedef enum { Program, Module } DebuggerParseInfoTag; +template <DebuggerParseInfoTag T> struct DebuggerParseInfo { }; + +template <> struct DebuggerParseInfo<Program> { + typedef JSC::ProgramNode RootNode; + static const SourceParseMode parseMode = SourceParseMode::ProgramMode; + static const JSParserStrictMode strictMode = JSParserStrictMode::NotStrict; + static const JSParserScriptMode scriptMode = JSParserScriptMode::Classic; +}; + +template <> struct DebuggerParseInfo<Module> { + typedef JSC::ModuleProgramNode RootNode; + static const SourceParseMode parseMode = SourceParseMode::ModuleEvaluateMode; + static const JSParserStrictMode strictMode = JSParserStrictMode::Strict; + static const JSParserScriptMode scriptMode = JSParserScriptMode::Module; +}; + +template <DebuggerParseInfoTag T> +bool gatherDebuggerParseData(VM& vm, const SourceCode& source, DebuggerParseData& debuggerParseData) +{ + typedef typename DebuggerParseInfo<T>::RootNode RootNode; + SourceParseMode parseMode = DebuggerParseInfo<T>::parseMode; + JSParserStrictMode strictMode = DebuggerParseInfo<T>::strictMode; + JSParserScriptMode scriptMode = DebuggerParseInfo<T>::scriptMode; + + ParserError error; + std::unique_ptr<RootNode> rootNode = parse<RootNode>(&vm, source, Identifier(), + JSParserBuiltinMode::NotBuiltin, strictMode, scriptMode, parseMode, SuperBinding::NotNeeded, + error, nullptr, ConstructorKind::None, DerivedContextType::None, EvalContextType::None, + &debuggerParseData); + if (!rootNode) + return false; + + debuggerParseData.pausePositions.sort(); + + return true; +} + +bool gatherDebuggerParseDataForSource(VM& vm, SourceProvider* provider, DebuggerParseData& debuggerParseData) +{ + ASSERT(provider); + int startLine = provider->startPosition().m_line.oneBasedInt(); + int startColumn = provider->startPosition().m_column.oneBasedInt(); + SourceCode completeSource(*provider, startLine, startColumn); + + switch (provider->sourceType()) { + case SourceProviderSourceType::Program: + return gatherDebuggerParseData<Program>(vm, completeSource, debuggerParseData); + case SourceProviderSourceType::Module: + return gatherDebuggerParseData<Module>(vm, completeSource, debuggerParseData); + default: + return false; + } +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerParseData.h b/Source/JavaScriptCore/debugger/DebuggerParseData.h new file mode 100644 index 000000000..ecc78d0d4 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerParseData.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "ParserTokens.h" +#include <wtf/Optional.h> +#include <wtf/Vector.h> + +namespace JSC { + +class SourceProvider; +class VM; + +enum class DebuggerPausePositionType { Enter, Leave, Pause }; +struct DebuggerPausePosition { + DebuggerPausePositionType type; + JSTextPosition position; +}; + +class DebuggerPausePositions { +public: + DebuggerPausePositions() { } + ~DebuggerPausePositions() { } + + void appendPause(const JSTextPosition& position) + { + m_positions.append({ DebuggerPausePositionType::Pause, position }); + } + + void appendEntry(const JSTextPosition& position) + { + m_positions.append({ DebuggerPausePositionType::Enter, position }); + } + + void appendLeave(const JSTextPosition& position) + { + m_positions.append({ DebuggerPausePositionType::Leave, position }); + } + + std::optional<JSTextPosition> breakpointLocationForLineColumn(int line, int column); + + void sort(); + +private: + Vector<DebuggerPausePosition> m_positions; +}; + + +struct DebuggerParseData { + DebuggerParseData() { } + ~DebuggerParseData() { } + + DebuggerPausePositions pausePositions; +}; + +bool gatherDebuggerParseDataForSource(VM&, SourceProvider*, DebuggerParseData&); + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerPrimitives.h b/Source/JavaScriptCore/debugger/DebuggerPrimitives.h index 635a5bfe8..959f8bd14 100644 --- a/Source/JavaScriptCore/debugger/DebuggerPrimitives.h +++ b/Source/JavaScriptCore/debugger/DebuggerPrimitives.h @@ -23,8 +23,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef DebuggerPrimitives_h -#define DebuggerPrimitives_h +#pragma once + +#include <stddef.h> namespace JSC { @@ -35,5 +36,3 @@ typedef size_t BreakpointID; static const BreakpointID noBreakpointID = 0; } // namespace JSC - -#endif // DebuggerPrimitives_h diff --git a/Source/JavaScriptCore/debugger/DebuggerScope.cpp b/Source/JavaScriptCore/debugger/DebuggerScope.cpp new file mode 100644 index 000000000..bec6f9f9c --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerScope.cpp @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2008-2009, 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DebuggerScope.h" + +#include "JSLexicalEnvironment.h" +#include "JSCInlines.h" +#include "JSWithScope.h" + +namespace JSC { + +STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DebuggerScope); + +const ClassInfo DebuggerScope::s_info = { "DebuggerScope", &Base::s_info, 0, CREATE_METHOD_TABLE(DebuggerScope) }; + +DebuggerScope* DebuggerScope::create(VM& vm, JSScope* scope) +{ + Structure* structure = scope->globalObject()->debuggerScopeStructure(); + DebuggerScope* debuggerScope = new (NotNull, allocateCell<DebuggerScope>(vm.heap)) DebuggerScope(vm, structure, scope); + debuggerScope->finishCreation(vm); + return debuggerScope; +} + +DebuggerScope::DebuggerScope(VM& vm, Structure* structure, JSScope* scope) + : JSNonFinalObject(vm, structure) +{ + ASSERT(scope); + m_scope.set(vm, this, scope); +} + +void DebuggerScope::finishCreation(VM& vm) +{ + Base::finishCreation(vm); +} + +void DebuggerScope::visitChildren(JSCell* cell, SlotVisitor& visitor) +{ + DebuggerScope* thisObject = jsCast<DebuggerScope*>(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + JSObject::visitChildren(thisObject, visitor); + visitor.append(thisObject->m_scope); + visitor.append(thisObject->m_next); +} + +String DebuggerScope::className(const JSObject* object) +{ + const DebuggerScope* scope = jsCast<const DebuggerScope*>(object); + // We cannot assert that scope->isValid() because the TypeProfiler may encounter an invalidated + // DebuggerScope in its log entries. We just need to handle it appropriately as below. + if (!scope->isValid()) + return String(); + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + return thisObject->methodTable()->className(thisObject); +} + +bool DebuggerScope::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(object); + if (!scope->isValid()) + return false; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + slot.setThisValue(JSValue(thisObject)); + + // By default, JSObject::getPropertySlot() will look in the DebuggerScope's prototype + // chain and not the wrapped scope, and JSObject::getPropertySlot() cannot be overridden + // to behave differently for the DebuggerScope. + // + // Instead, we'll treat all properties in the wrapped scope and its prototype chain as + // the own properties of the DebuggerScope. This is fine because the WebInspector + // does not presently need to distinguish between what's owned at each level in the + // prototype chain. Hence, we'll invoke getPropertySlot() on the wrapped scope here + // instead of getOwnPropertySlot(). + bool result = thisObject->getPropertySlot(exec, propertyName, slot); + if (result && slot.isValue() && slot.getValue(exec, propertyName) == jsTDZValue()) { + // FIXME: + // We hit a scope property that has the TDZ empty value. + // Currently, we just lie to the inspector and claim that this property is undefined. + // This is not ideal and we should fix it. + // https://bugs.webkit.org/show_bug.cgi?id=144977 + slot.setValue(slot.slotBase(), DontEnum, jsUndefined()); + return true; + } + return result; +} + +bool DebuggerScope::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(cell); + ASSERT(scope->isValid()); + if (!scope->isValid()) + return false; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + slot.setThisValue(JSValue(thisObject)); + return thisObject->methodTable()->put(thisObject, exec, propertyName, value, slot); +} + +bool DebuggerScope::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(cell); + ASSERT(scope->isValid()); + if (!scope->isValid()) + return false; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + return thisObject->methodTable()->deleteProperty(thisObject, exec, propertyName); +} + +void DebuggerScope::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(object); + ASSERT(scope->isValid()); + if (!scope->isValid()) + return; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + thisObject->methodTable()->getPropertyNames(thisObject, exec, propertyNames, mode); +} + +bool DebuggerScope::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(object); + ASSERT(scope->isValid()); + if (!scope->isValid()) + return false; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + return thisObject->methodTable()->defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow); +} + +DebuggerScope* DebuggerScope::next() +{ + ASSERT(isValid()); + if (!m_next && m_scope->next()) { + VM& vm = *m_scope->vm(); + DebuggerScope* nextScope = create(vm, m_scope->next()); + m_next.set(vm, this, nextScope); + } + return m_next.get(); +} + +void DebuggerScope::invalidateChain() +{ + if (!isValid()) + return; + + DebuggerScope* scope = this; + while (scope) { + DebuggerScope* nextScope = scope->m_next.get(); + scope->m_next.clear(); + scope->m_scope.clear(); // This also marks this scope as invalid. + scope = nextScope; + } +} + +bool DebuggerScope::isCatchScope() const +{ + return m_scope->isCatchScope(); +} + +bool DebuggerScope::isFunctionNameScope() const +{ + return m_scope->isFunctionNameScopeObject(); +} + +bool DebuggerScope::isWithScope() const +{ + return m_scope->isWithScope(); +} + +bool DebuggerScope::isGlobalScope() const +{ + return m_scope->isGlobalObject(); +} + +bool DebuggerScope::isGlobalLexicalEnvironment() const +{ + return m_scope->isGlobalLexicalEnvironment(); +} + +bool DebuggerScope::isClosureScope() const +{ + // In the current debugger implementation, every function or eval will create an + // lexical environment object. Hence, a lexical environment object implies a + // function or eval scope. + return m_scope->isVarScope() || m_scope->isLexicalScope(); +} + +bool DebuggerScope::isNestedLexicalScope() const +{ + return m_scope->isNestedLexicalScope(); +} + +String DebuggerScope::name() const +{ + SymbolTable* symbolTable = m_scope->symbolTable(*vm()); + if (!symbolTable) + return String(); + + CodeBlock* codeBlock = symbolTable->rareDataCodeBlock(); + if (!codeBlock) + return String(); + + return String::fromUTF8(codeBlock->inferredName()); +} + +DebuggerLocation DebuggerScope::location() const +{ + SymbolTable* symbolTable = m_scope->symbolTable(*vm()); + if (!symbolTable) + return DebuggerLocation(); + + CodeBlock* codeBlock = symbolTable->rareDataCodeBlock(); + if (!codeBlock) + return DebuggerLocation(); + + ScriptExecutable* executable = codeBlock->ownerScriptExecutable(); + return DebuggerLocation(executable); +} + +JSValue DebuggerScope::caughtValue(ExecState* exec) const +{ + ASSERT(isCatchScope()); + JSLexicalEnvironment* catchEnvironment = jsCast<JSLexicalEnvironment*>(m_scope.get()); + SymbolTable* catchSymbolTable = catchEnvironment->symbolTable(); + RELEASE_ASSERT(catchSymbolTable->size() == 1); + PropertyName errorName(catchSymbolTable->begin(catchSymbolTable->m_lock)->key.get()); + PropertySlot slot(m_scope.get(), PropertySlot::InternalMethodType::Get); + bool success = catchEnvironment->getOwnPropertySlot(catchEnvironment, exec, errorName, slot); + RELEASE_ASSERT(success && slot.isValue()); + return slot.getValue(exec, errorName); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerScope.h b/Source/JavaScriptCore/debugger/DebuggerScope.h new file mode 100644 index 000000000..479b18651 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerScope.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2008-2009, 2014, 2016 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "DebuggerLocation.h" +#include "JSObject.h" + +namespace JSC { + +class DebuggerCallFrame; +class JSScope; + +class DebuggerScope : public JSNonFinalObject { +public: + typedef JSNonFinalObject Base; + static const unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | OverridesGetPropertyNames; + + JS_EXPORT_PRIVATE static DebuggerScope* create(VM& vm, JSScope* scope); + + static void visitChildren(JSCell*, SlotVisitor&); + static String className(const JSObject*); + static bool getOwnPropertySlot(JSObject*, ExecState*, PropertyName, PropertySlot&); + static bool put(JSCell*, ExecState*, PropertyName, JSValue, PutPropertySlot&); + static bool deleteProperty(JSCell*, ExecState*, PropertyName); + static void getOwnPropertyNames(JSObject*, ExecState*, PropertyNameArray&, EnumerationMode); + static bool defineOwnProperty(JSObject*, ExecState*, PropertyName, const PropertyDescriptor&, bool shouldThrow); + + DECLARE_EXPORT_INFO; + + static Structure* createStructure(VM& vm, JSGlobalObject* globalObject) + { + return Structure::create(vm, globalObject, jsNull(), TypeInfo(ObjectType, StructureFlags), info()); + } + + class iterator { + public: + iterator(DebuggerScope* node) + : m_node(node) + { + } + + DebuggerScope* get() { return m_node; } + iterator& operator++() { m_node = m_node->next(); return *this; } + // postfix ++ intentionally omitted + + bool operator==(const iterator& other) const { return m_node == other.m_node; } + bool operator!=(const iterator& other) const { return m_node != other.m_node; } + + private: + DebuggerScope* m_node; + }; + + iterator begin(); + iterator end(); + DebuggerScope* next(); + + void invalidateChain(); + bool isValid() const { return !!m_scope; } + + bool isCatchScope() const; + bool isFunctionNameScope() const; + bool isWithScope() const; + bool isGlobalScope() const; + bool isClosureScope() const; + bool isGlobalLexicalEnvironment() const; + bool isNestedLexicalScope() const; + + String name() const; + DebuggerLocation location() const; + + JSValue caughtValue(ExecState*) const; + +private: + DebuggerScope(VM&, Structure*, JSScope*); + void finishCreation(VM&); + + JSScope* jsScope() const { return m_scope.get(); } + + WriteBarrier<JSScope> m_scope; + WriteBarrier<DebuggerScope> m_next; + + friend class DebuggerCallFrame; +}; + +inline DebuggerScope::iterator DebuggerScope::begin() +{ + return iterator(this); +} + +inline DebuggerScope::iterator DebuggerScope::end() +{ + return iterator(0); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/ScriptProfilingScope.h b/Source/JavaScriptCore/debugger/ScriptProfilingScope.h new file mode 100644 index 000000000..e40337fe9 --- /dev/null +++ b/Source/JavaScriptCore/debugger/ScriptProfilingScope.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include "Debugger.h" +#include "JSGlobalObject.h" +#include <wtf/Optional.h> + +namespace JSC { + +class ScriptProfilingScope { +public: + ScriptProfilingScope(JSGlobalObject* globalObject, ProfilingReason reason) + : m_globalObject(globalObject) + , m_reason(reason) + { + if (shouldStartProfile()) + m_startTime = m_globalObject->debugger()->willEvaluateScript(); + } + + ~ScriptProfilingScope() + { + if (shouldEndProfile()) + m_globalObject->debugger()->didEvaluateScript(m_startTime.value(), m_reason); + } + +private: + bool shouldStartProfile() const + { + if (!m_globalObject) + return false; + + if (!m_globalObject->hasDebugger()) + return false; + + if (!m_globalObject->debugger()->hasProfilingClient()) + return false; + + if (m_globalObject->debugger()->isAlreadyProfiling()) + return false; + + return true; + } + + bool shouldEndProfile() const + { + // Did not start a profile. + if (!m_startTime) + return false; + + // Debugger may have been removed. + if (!m_globalObject->hasDebugger()) + return false; + + // Profiling Client may have been removed. + if (!m_globalObject->debugger()->hasProfilingClient()) + return false; + + return true; + } + + JSGlobalObject* m_globalObject { nullptr }; + std::optional<double> m_startTime; + ProfilingReason m_reason; +}; + +} // namespace JSC |