diff options
| author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
|---|---|---|
| committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2015-10-15 09:45:50 +0000 |
| commit | e15dd966d523731101f70ccf768bba12435a0208 (patch) | |
| tree | ae9cb828a24ded2585a41af3f21411523b47897d /Source/JavaScriptCore/debugger | |
| download | WebKitGtk-tarball-e15dd966d523731101f70ccf768bba12435a0208.tar.gz | |
webkitgtk-2.10.2webkitgtk-2.10.2
Diffstat (limited to 'Source/JavaScriptCore/debugger')
| -rw-r--r-- | Source/JavaScriptCore/debugger/Breakpoint.h | 96 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.cpp | 808 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/Debugger.h | 222 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp | 261 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerCallFrame.h | 94 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h | 63 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerPrimitives.h | 41 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerScope.cpp | 208 | ||||
| -rw-r--r-- | Source/JavaScriptCore/debugger/DebuggerScope.h | 120 |
9 files changed, 1913 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/debugger/Breakpoint.h b/Source/JavaScriptCore/debugger/Breakpoint.h new file mode 100644 index 000000000..8518ce469 --- /dev/null +++ b/Source/JavaScriptCore/debugger/Breakpoint.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2013 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 Breakpoint_h +#define Breakpoint_h + +#include "DebuggerPrimitives.h" +#include <wtf/DoublyLinkedList.h> +#include <wtf/RefCounted.h> +#include <wtf/text/WTFString.h> + +namespace JSC { + +struct Breakpoint : public DoublyLinkedListNode<Breakpoint> { + Breakpoint() + : id(noBreakpointID) + , sourceID(noSourceID) + , line(0) + , column(0) + , autoContinue(false) + { + } + + Breakpoint(SourceID sourceID, unsigned line, unsigned column, const String& condition, bool autoContinue) + : id(noBreakpointID) + , sourceID(sourceID) + , line(line) + , column(column) + , condition(condition) + , autoContinue(autoContinue) + { + } + + Breakpoint(const Breakpoint& other) + : id(other.id) + , sourceID(other.sourceID) + , line(other.line) + , column(other.column) + , condition(other.condition) + , autoContinue(other.autoContinue) + { + } + + BreakpointID id; + SourceID sourceID; + unsigned line; + unsigned column; + String condition; + bool autoContinue; + + static const unsigned unspecifiedColumn = UINT_MAX; + +private: + Breakpoint* m_prev; + Breakpoint* m_next; + + friend class WTF::DoublyLinkedListNode<Breakpoint>; +}; + +class BreakpointsList : public DoublyLinkedList<Breakpoint>, + public RefCounted<BreakpointsList> { +public: + ~BreakpointsList() + { + Breakpoint* breakpoint; + while ((breakpoint = removeHead())) + delete breakpoint; + ASSERT(isEmpty()); + } +}; + +} // namespace JSC + +#endif // Breakpoint_h diff --git a/Source/JavaScriptCore/debugger/Debugger.cpp b/Source/JavaScriptCore/debugger/Debugger.cpp new file mode 100644 index 000000000..4d7871ff8 --- /dev/null +++ b/Source/JavaScriptCore/debugger/Debugger.cpp @@ -0,0 +1,808 @@ +/* + * Copyright (C) 2008, 2013, 2014 Apple Inc. All rights reserved. + * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) + * Copyright (C) 2001 Peter Kelly (pmk@post.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "config.h" +#include "Debugger.h" + +#include "CodeBlock.h" +#include "DebuggerCallFrame.h" +#include "Error.h" +#include "HeapIterationScope.h" +#include "Interpreter.h" +#include "JSCJSValueInlines.h" +#include "JSFunction.h" +#include "JSGlobalObject.h" +#include "JSCInlines.h" +#include "Parser.h" +#include "Protect.h" +#include "VMEntryScope.h" + +namespace { + +using namespace JSC; + +class Recompiler : public MarkedBlock::VoidFunctor { +public: + Recompiler(JSC::Debugger*); + ~Recompiler(); + IterationStatus operator()(JSCell*); + +private: + typedef HashSet<FunctionExecutable*> FunctionExecutableSet; + typedef HashMap<SourceProvider*, ExecState*> SourceProviderMap; + + void visit(JSCell*); + + 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::visit(JSCell* cell) +{ + if (!cell->inherits(JSFunction::info())) + return; + + JSFunction* function = jsCast<JSFunction*>(cell); + if (function->executable()->isHostFunction()) + return; + + FunctionExecutable* executable = function->jsExecutable(); + + // 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; + + ExecState* exec = function->scope()->globalObject()->JSGlobalObject::globalExec(); + executable->clearCode(); + executable->clearUnlinkedCodeForRecompilation(); + if (m_debugger == function->scope()->globalObject()->debugger()) + m_sourceProviders.add(executable->source().provider(), exec); +} + +inline IterationStatus Recompiler::operator()(JSCell* cell) +{ + visit(cell); + return IterationStatus::Continue; +} + +} // namespace + +namespace JSC { + +class DebuggerPausedScope { +public: + 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); + } + + ~DebuggerPausedScope() + { + if (m_debugger.m_currentDebuggerCallFrame) { + m_debugger.m_currentDebuggerCallFrame->invalidate(); + m_debugger.m_currentDebuggerCallFrame = nullptr; + } + } + +private: + Debugger& m_debugger; +}; + +// This is very similar to TemporaryChange<bool>, but that cannot be used +// as the m_isPaused field uses only one bit. +class TemporaryPausedState { +public: + TemporaryPausedState(Debugger& debugger) + : m_debugger(debugger) + { + ASSERT(!m_debugger.m_isPaused); + m_debugger.m_isPaused = true; + } + + ~TemporaryPausedState() + { + m_debugger.m_isPaused = false; + } + +private: + Debugger& m_debugger; +}; + +template<typename Functor> +void Debugger::forEachCodeBlock(Functor& functor) +{ + m_vm->prepareToDeleteCode(); + m_vm->heap.forEachCodeBlock(functor); +} + +Debugger::Debugger(bool isInWorkerThread) + : m_vm(nullptr) + , m_pauseOnExceptionsState(DontPauseOnExceptions) + , m_pauseOnNextStatement(false) + , m_isPaused(false) + , m_breakpointsActivated(true) + , m_hasHandlerForExceptionCallback(false) + , m_isInWorkerThread(isInWorkerThread) + , m_steppingMode(SteppingModeDisabled) + , m_reasonForPause(NotPaused) + , m_pauseOnCallFrame(0) + , m_currentCallFrame(0) + , m_lastExecutedLine(UINT_MAX) + , m_lastExecutedSourceID(noSourceID) + , m_topBreakpointID(noBreakpointID) + , m_pausingBreakpointID(noBreakpointID) +{ +} + +Debugger::~Debugger() +{ + HashSet<JSGlobalObject*>::iterator end = m_globalObjects.end(); + for (HashSet<JSGlobalObject*>::iterator it = m_globalObjects.begin(); it != end; ++it) + (*it)->setDebugger(0); +} + +void Debugger::attach(JSGlobalObject* globalObject) +{ + ASSERT(!globalObject->debugger()); + if (!m_vm) + m_vm = &globalObject->vm(); + else + ASSERT(m_vm == &globalObject->vm()); + globalObject->setDebugger(this); + m_globalObjects.add(globalObject); +} + +void Debugger::detach(JSGlobalObject* globalObject, ReasonForDetach reason) +{ + // If we're detaching from the currently executing global object, manually tear down our + // stack, since we won't get further debugger callbacks to do so. Also, resume execution, + // since there's no point in staying paused once a window closes. + if (m_currentCallFrame && m_currentCallFrame->vmEntryGlobalObject() == globalObject) { + m_currentCallFrame = 0; + m_pauseOnCallFrame = 0; + continueProgram(); + } + + ASSERT(m_globalObjects.contains(globalObject)); + m_globalObjects.remove(globalObject); + + // If the globalObject is destructing, then its CodeBlocks will also be + // destructed. There is no need to do the debugger requests clean up, and + // it is not safe to access those CodeBlocks at this time anyway. + if (reason != GlobalObjectIsDestructing) + clearDebuggerRequests(globalObject); + + globalObject->setDebugger(0); + if (!m_globalObjects.size()) + m_vm = nullptr; +} + +bool Debugger::isAttached(JSGlobalObject* globalObject) +{ + return globalObject->debugger() == this; +} + +class Debugger::SetSteppingModeFunctor { +public: + SetSteppingModeFunctor(Debugger* debugger, SteppingMode mode) + : m_debugger(debugger) + , m_mode(mode) + { + } + + bool operator()(CodeBlock* codeBlock) + { + if (m_debugger == codeBlock->globalObject()->debugger()) { + if (m_mode == SteppingModeEnabled) + codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); + else + codeBlock->setSteppingMode(CodeBlock::SteppingModeDisabled); + } + return false; + } + +private: + Debugger* m_debugger; + SteppingMode m_mode; +}; + +void Debugger::setSteppingMode(SteppingMode mode) +{ + if (mode == m_steppingMode || !m_vm) + return; + + m_vm->prepareToDeleteCode(); + + m_steppingMode = mode; + SetSteppingModeFunctor functor(this, mode); + m_vm->heap.forEachCodeBlock(functor); +} + +void Debugger::registerCodeBlock(CodeBlock* codeBlock) +{ + // FIXME: We should never have to jettison a code block (due to pending breakpoints + // or stepping mode) that is being registered. operationOptimize() should have + // prevented the optimizing of such code blocks in the first place. Find a way to + // express this with greater clarity in the code. See <https://webkit.org/b131771>. + applyBreakpoints(codeBlock); + if (isStepping()) + codeBlock->setSteppingMode(CodeBlock::SteppingModeEnabled); +} + +void Debugger::toggleBreakpoint(CodeBlock* codeBlock, Breakpoint& breakpoint, BreakpointState enabledOrNot) +{ + ScriptExecutable* executable = codeBlock->ownerExecutable(); + + SourceID sourceID = static_cast<SourceID>(executable->sourceID()); + if (breakpoint.sourceID != sourceID) + return; + + unsigned line = breakpoint.line; + unsigned column = breakpoint.column; + + unsigned startLine = executable->firstLine(); + unsigned startColumn = executable->startColumn(); + unsigned endLine = executable->lastLine(); + unsigned endColumn = executable->endColumn(); + + // Inspector breakpoint line and column values are zero-based but the executable + // and CodeBlock line and column values are one-based. + line += 1; + column = column ? column + 1 : Breakpoint::unspecifiedColumn; + + if (line < startLine || line > endLine) + return; + if (column != Breakpoint::unspecifiedColumn) { + if (line == startLine && column < startColumn) + return; + if (line == endLine && column > endColumn) + return; + } + if (!codeBlock->hasOpDebugForLineAndColumn(line, column)) + return; + + if (enabledOrNot == BreakpointEnabled) + codeBlock->addBreakpoint(1); + else + codeBlock->removeBreakpoint(1); +} + +void Debugger::applyBreakpoints(CodeBlock* codeBlock) +{ + BreakpointIDToBreakpointMap& breakpoints = m_breakpointIDToBreakpoint; + for (auto it = breakpoints.begin(); it != breakpoints.end(); ++it) { + Breakpoint& breakpoint = *it->value; + toggleBreakpoint(codeBlock, breakpoint, BreakpointEnabled); + } +} + +class Debugger::ToggleBreakpointFunctor { +public: + ToggleBreakpointFunctor(Debugger* debugger, Breakpoint& breakpoint, BreakpointState enabledOrNot) + : m_debugger(debugger) + , m_breakpoint(breakpoint) + , m_enabledOrNot(enabledOrNot) + { + } + + bool operator()(CodeBlock* codeBlock) + { + if (m_debugger == codeBlock->globalObject()->debugger()) + m_debugger->toggleBreakpoint(codeBlock, m_breakpoint, m_enabledOrNot); + return false; + } + +private: + Debugger* m_debugger; + Breakpoint& m_breakpoint; + BreakpointState m_enabledOrNot; +}; + +void Debugger::toggleBreakpoint(Breakpoint& breakpoint, Debugger::BreakpointState enabledOrNot) +{ + if (!m_vm) + return; + ToggleBreakpointFunctor functor(this, breakpoint, enabledOrNot); + forEachCodeBlock(functor); +} + +void Debugger::recompileAllJSFunctions(VM* vm) +{ + // If JavaScript is running, it's not safe to recompile, since we'll end + // up throwing away code that is live on the stack. + if (vm->entryScope) { + auto listener = [] (VM& vm, JSGlobalObject* globalObject) + { + if (Debugger* debugger = globalObject->debugger()) + debugger->recompileAllJSFunctions(&vm); + }; + + vm->entryScope->setEntryScopeDidPopListener(this, listener); + return; + } + + vm->prepareToDeleteCode(); + + Recompiler recompiler(this); + HeapIterationScope iterationScope(vm->heap); + vm->heap.objectSpace().forEachLiveCell(iterationScope, recompiler); +} + +BreakpointID Debugger::setBreakpoint(Breakpoint breakpoint, unsigned& actualLine, unsigned& actualColumn) +{ + SourceID sourceID = breakpoint.sourceID; + unsigned line = breakpoint.line; + unsigned column = breakpoint.column; + + SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); + if (it == m_sourceIDToBreakpoints.end()) + it = m_sourceIDToBreakpoints.set(sourceID, LineToBreakpointsMap()).iterator; + LineToBreakpointsMap::iterator breaksIt = it->value.find(line); + if (breaksIt == it->value.end()) + breaksIt = it->value.set(line, adoptRef(new BreakpointsList)).iterator; + + BreakpointsList& breakpoints = *breaksIt->value; + for (Breakpoint* current = breakpoints.head(); current; current = current->next()) { + if (current->column == column) { + // The breakpoint already exists. We're not allowed to create a new + // breakpoint at this location. Rather than returning the breakpointID + // of the pre-existing breakpoint, we need to return noBreakpointID + // to indicate that we're not creating a new one. + return noBreakpointID; + } + } + + BreakpointID id = ++m_topBreakpointID; + RELEASE_ASSERT(id != noBreakpointID); + + breakpoint.id = id; + actualLine = line; + actualColumn = column; + + Breakpoint* newBreakpoint = new Breakpoint(breakpoint); + breakpoints.append(newBreakpoint); + m_breakpointIDToBreakpoint.set(id, newBreakpoint); + + toggleBreakpoint(breakpoint, BreakpointEnabled); + + return id; +} + +void Debugger::removeBreakpoint(BreakpointID id) +{ + ASSERT(id != noBreakpointID); + + BreakpointIDToBreakpointMap::iterator idIt = m_breakpointIDToBreakpoint.find(id); + ASSERT(idIt != m_breakpointIDToBreakpoint.end()); + Breakpoint* breakpoint = idIt->value; + + SourceID sourceID = breakpoint->sourceID; + ASSERT(sourceID); + SourceIDToBreakpointsMap::iterator it = m_sourceIDToBreakpoints.find(sourceID); + ASSERT(it != m_sourceIDToBreakpoints.end()); + LineToBreakpointsMap::iterator breaksIt = it->value.find(breakpoint->line); + ASSERT(breaksIt != it->value.end()); + + toggleBreakpoint(*breakpoint, BreakpointDisabled); + + BreakpointsList& breakpoints = *breaksIt->value; +#if !ASSERT_DISABLED + bool found = false; + for (Breakpoint* current = breakpoints.head(); current && !found; current = current->next()) { + if (current->id == breakpoint->id) + found = true; + } + ASSERT(found); +#endif + + m_breakpointIDToBreakpoint.remove(idIt); + breakpoints.remove(breakpoint); + delete breakpoint; + + if (breakpoints.isEmpty()) { + it->value.remove(breaksIt); + if (it->value.isEmpty()) + m_sourceIDToBreakpoints.remove(it); + } +} + +bool Debugger::hasBreakpoint(SourceID sourceID, const TextPosition& position, Breakpoint *hitBreakpoint) +{ + if (!m_breakpointsActivated) + return false; + + SourceIDToBreakpointsMap::const_iterator it = m_sourceIDToBreakpoints.find(sourceID); + if (it == m_sourceIDToBreakpoints.end()) + return false; + + unsigned line = position.m_line.zeroBasedInt(); + unsigned column = position.m_column.zeroBasedInt(); + + LineToBreakpointsMap::const_iterator breaksIt = it->value.find(line); + if (breaksIt == it->value.end()) + return false; + + bool hit = false; + const BreakpointsList& breakpoints = *breaksIt->value; + Breakpoint* breakpoint; + for (breakpoint = breakpoints.head(); breakpoint; breakpoint = breakpoint->next()) { + unsigned breakLine = breakpoint->line; + unsigned breakColumn = breakpoint->column; + // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0). + ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger()); + if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) + || (line == breakLine && column == breakColumn)) { + hit = true; + break; + } + } + if (!hit) + return false; + + if (hitBreakpoint) + *hitBreakpoint = *breakpoint; + + if (breakpoint->condition.isEmpty()) + return true; + + // We cannot stop in the debugger while executing condition code, + // so make it looks like the debugger is already paused. + TemporaryPausedState pausedState(*this); + + NakedPtr<Exception> exception; + DebuggerCallFrame* debuggerCallFrame = currentDebuggerCallFrame(); + JSValue result = debuggerCallFrame->evaluate(breakpoint->condition, exception); + + // We can lose the debugger while executing JavaScript. + if (!m_currentCallFrame) + return false; + + if (exception) { + // An erroneous condition counts as "false". + handleExceptionInBreakpointCondition(m_currentCallFrame, exception); + return false; + } + + return result.toBoolean(m_currentCallFrame); +} + +class Debugger::ClearCodeBlockDebuggerRequestsFunctor { +public: + ClearCodeBlockDebuggerRequestsFunctor(Debugger* debugger) + : m_debugger(debugger) + { + } + + bool operator()(CodeBlock* codeBlock) + { + if (codeBlock->hasDebuggerRequests() && m_debugger == codeBlock->globalObject()->debugger()) + codeBlock->clearDebuggerRequests(); + return false; + } + +private: + Debugger* m_debugger; +}; + +void Debugger::clearBreakpoints() +{ + m_topBreakpointID = noBreakpointID; + m_breakpointIDToBreakpoint.clear(); + m_sourceIDToBreakpoints.clear(); + + if (!m_vm) + return; + ClearCodeBlockDebuggerRequestsFunctor functor(this); + forEachCodeBlock(functor); +} + +class Debugger::ClearDebuggerRequestsFunctor { +public: + ClearDebuggerRequestsFunctor(JSGlobalObject* globalObject) + : m_globalObject(globalObject) + { + } + + bool operator()(CodeBlock* codeBlock) + { + if (codeBlock->hasDebuggerRequests() && m_globalObject == codeBlock->globalObject()) + codeBlock->clearDebuggerRequests(); + return false; + } + +private: + JSGlobalObject* m_globalObject; +}; + +void Debugger::clearDebuggerRequests(JSGlobalObject* globalObject) +{ + ASSERT(m_vm); + ClearDebuggerRequestsFunctor functor(globalObject); + forEachCodeBlock(functor); +} + +void Debugger::setBreakpointsActivated(bool activated) +{ + m_breakpointsActivated = activated; +} + +void Debugger::setPauseOnExceptionsState(PauseOnExceptionsState pause) +{ + m_pauseOnExceptionsState = pause; +} + +void Debugger::setPauseOnNextStatement(bool pause) +{ + m_pauseOnNextStatement = pause; + if (pause) + setSteppingMode(SteppingModeEnabled); +} + +void Debugger::breakProgram() +{ + if (m_isPaused) + return; + + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + m_currentCallFrame = m_vm->topCallFrame; + ASSERT(m_currentCallFrame); + pauseIfNeeded(m_currentCallFrame); +} + +void Debugger::continueProgram() +{ + if (!m_isPaused) + return; + + m_pauseOnNextStatement = false; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepIntoStatement() +{ + if (!m_isPaused) + return; + + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepOverStatement() +{ + if (!m_isPaused) + return; + + m_pauseOnCallFrame = m_currentCallFrame; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::stepOutOfFunction() +{ + if (!m_isPaused) + return; + + VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame ? m_currentCallFrame->callerFrame(topVMEntryFrame) : 0; + notifyDoneProcessingDebuggerEvents(); +} + +void Debugger::updateCallFrame(CallFrame* callFrame) +{ + m_currentCallFrame = callFrame; + SourceID sourceID = DebuggerCallFrame::sourceIDForCallFrame(callFrame); + if (m_lastExecutedSourceID != sourceID) { + m_lastExecutedLine = UINT_MAX; + m_lastExecutedSourceID = sourceID; + } +} + +void Debugger::updateCallFrameAndPauseIfNeeded(CallFrame* callFrame) +{ + updateCallFrame(callFrame); + pauseIfNeeded(callFrame); + if (!isStepping()) + m_currentCallFrame = 0; +} + +void Debugger::pauseIfNeeded(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + JSGlobalObject* vmEntryGlobalObject = callFrame->vmEntryGlobalObject(); + if (!needPauseHandling(vmEntryGlobalObject)) + return; + + Breakpoint breakpoint; + bool didHitBreakpoint = false; + bool pauseNow = m_pauseOnNextStatement; + pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame); + + DebuggerPausedScope debuggerPausedScope(*this); + + intptr_t sourceID = DebuggerCallFrame::sourceIDForCallFrame(m_currentCallFrame); + TextPosition position = DebuggerCallFrame::positionForCallFrame(m_currentCallFrame); + pauseNow |= didHitBreakpoint = hasBreakpoint(sourceID, position, &breakpoint); + m_lastExecutedLine = position.m_line.zeroBasedInt(); + if (!pauseNow) + return; + + // Make sure we are not going to pause again on breakpoint actions by + // reseting the pause state before executing any breakpoint actions. + TemporaryPausedState pausedState(*this); + m_pauseOnCallFrame = 0; + m_pauseOnNextStatement = false; + + if (didHitBreakpoint) { + handleBreakpointHit(vmEntryGlobalObject, breakpoint); + // Note that the actions can potentially stop the debugger, so we need to check that + // we still have a current call frame when we get back. + if (breakpoint.autoContinue || !m_currentCallFrame) + return; + m_pausingBreakpointID = breakpoint.id; + } + + { + PauseReasonDeclaration reason(*this, didHitBreakpoint ? PausedForBreakpoint : m_reasonForPause); + handlePause(vmEntryGlobalObject, m_reasonForPause); + } + + m_pausingBreakpointID = noBreakpointID; + + if (!m_pauseOnNextStatement && !m_pauseOnCallFrame) { + setSteppingMode(SteppingModeDisabled); + m_currentCallFrame = nullptr; + } +} + +void Debugger::exception(CallFrame* callFrame, JSValue exception, bool hasCatchHandler) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedForException); + if (m_pauseOnExceptionsState == PauseOnAllExceptions || (m_pauseOnExceptionsState == PauseOnUncaughtExceptions && !hasCatchHandler)) { + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + } + + m_hasHandlerForExceptionCallback = true; + m_currentException = exception; + updateCallFrameAndPauseIfNeeded(callFrame); + m_currentException = JSValue(); + m_hasHandlerForExceptionCallback = false; +} + +void Debugger::atStatement(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAtStatement); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +void Debugger::callEvent(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAfterCall); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +void Debugger::returnEvent(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedBeforeReturn); + updateCallFrameAndPauseIfNeeded(callFrame); + + // detach may have been called during pauseIfNeeded + if (!m_currentCallFrame) + return; + + // Treat stepping over a return statement like stepping out. + if (m_currentCallFrame == m_pauseOnCallFrame) { + VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + } + + VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; + m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); +} + +void Debugger::willExecuteProgram(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAtStartOfProgram); + // FIXME: This check for whether we're debugging a worker thread is a workaround + // for https://bugs.webkit.org/show_bug.cgi?id=102637. Remove it when we rework + // the debugger implementation to not require callbacks. + if (!m_isInWorkerThread) + updateCallFrameAndPauseIfNeeded(callFrame); + else if (isStepping()) + updateCallFrame(callFrame); +} + +void Debugger::didExecuteProgram(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedAtEndOfProgram); + updateCallFrameAndPauseIfNeeded(callFrame); + + // Treat stepping over the end of a program like stepping out. + if (!m_currentCallFrame) + return; + if (m_currentCallFrame == m_pauseOnCallFrame) { + VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; + m_pauseOnCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); + if (!m_currentCallFrame) + return; + } + VMEntryFrame* topVMEntryFrame = m_vm->topVMEntryFrame; + m_currentCallFrame = m_currentCallFrame->callerFrame(topVMEntryFrame); +} + +void Debugger::didReachBreakpoint(CallFrame* callFrame) +{ + if (m_isPaused) + return; + + PauseReasonDeclaration reason(*this, PausedForDebuggerStatement); + m_pauseOnNextStatement = true; + setSteppingMode(SteppingModeEnabled); + updateCallFrameAndPauseIfNeeded(callFrame); +} + +DebuggerCallFrame* Debugger::currentDebuggerCallFrame() const +{ + ASSERT(m_currentDebuggerCallFrame); + return m_currentDebuggerCallFrame.get(); +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/Debugger.h b/Source/JavaScriptCore/debugger/Debugger.h new file mode 100644 index 000000000..d70f3b76f --- /dev/null +++ b/Source/JavaScriptCore/debugger/Debugger.h @@ -0,0 +1,222 @@ +/* + * Copyright (C) 1999-2001 Harri Porten (porten@kde.org) + * Copyright (C) 2001 Peter Kelly (pmk@post.com) + * Copyright (C) 2008, 2009, 2013, 2014 Apple Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef Debugger_h +#define Debugger_h + +#include "Breakpoint.h" +#include "DebuggerCallFrame.h" +#include "DebuggerPrimitives.h" +#include "JSCJSValue.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/RefPtr.h> +#include <wtf/text/TextPosition.h> + +namespace JSC { + +class CodeBlock; +class Exception; +class ExecState; +class JSGlobalObject; +class SourceProvider; +class VM; + +typedef ExecState CallFrame; + +class JS_EXPORT_PRIVATE Debugger { +public: + Debugger(bool isInWorkerThread = false); + virtual ~Debugger(); + + JSC::DebuggerCallFrame* currentDebuggerCallFrame() const; + bool hasHandlerForExceptionCallback() const + { + ASSERT(m_reasonForPause == PausedForException); + return m_hasHandlerForExceptionCallback; + } + JSValue currentException() + { + ASSERT(m_reasonForPause == PausedForException); + return m_currentException; + } + + bool needsExceptionCallbacks() const { return m_pauseOnExceptionsState != DontPauseOnExceptions; } + + enum ReasonForDetach { + TerminatingDebuggingSession, + GlobalObjectIsDestructing + }; + void attach(JSGlobalObject*); + void detach(JSGlobalObject*, ReasonForDetach); + bool isAttached(JSGlobalObject*); + + BreakpointID setBreakpoint(Breakpoint, unsigned& actualLine, unsigned& actualColumn); + void removeBreakpoint(BreakpointID); + void clearBreakpoints(); + void setBreakpointsActivated(bool); + void activateBreakpoints() { setBreakpointsActivated(true); } + void deactivateBreakpoints() { setBreakpointsActivated(false); } + + enum PauseOnExceptionsState { + DontPauseOnExceptions, + PauseOnAllExceptions, + PauseOnUncaughtExceptions + }; + PauseOnExceptionsState pauseOnExceptionsState() const { return m_pauseOnExceptionsState; } + void setPauseOnExceptionsState(PauseOnExceptionsState); + + enum ReasonForPause { + NotPaused, + PausedForException, + PausedAtStatement, + PausedAfterCall, + PausedBeforeReturn, + PausedAtStartOfProgram, + PausedAtEndOfProgram, + PausedForBreakpoint, + PausedForDebuggerStatement, + }; + ReasonForPause reasonForPause() const { return m_reasonForPause; } + BreakpointID pausingBreakpointID() const { return m_pausingBreakpointID; } + + void setPauseOnNextStatement(bool); + void breakProgram(); + void continueProgram(); + void stepIntoStatement(); + void stepOverStatement(); + void stepOutOfFunction(); + + bool isPaused() const { return m_isPaused; } + bool isStepping() const { return m_steppingMode == SteppingModeEnabled; } + + virtual void sourceParsed(ExecState*, SourceProvider*, int errorLineNumber, const WTF::String& errorMessage) = 0; + + void exception(CallFrame*, JSValue exceptionValue, bool hasCatchHandler); + void atStatement(CallFrame*); + void callEvent(CallFrame*); + void returnEvent(CallFrame*); + void willExecuteProgram(CallFrame*); + void didExecuteProgram(CallFrame*); + void didReachBreakpoint(CallFrame*); + + void recompileAllJSFunctions(VM*); + + void registerCodeBlock(CodeBlock*); + +protected: + virtual bool needPauseHandling(JSGlobalObject*) { return false; } + 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 HashMap<unsigned, RefPtr<BreakpointsList>, WTF::IntHash<int>, WTF::UnsignedWithZeroKeyHashTraits<int>> LineToBreakpointsMap; + typedef HashMap<SourceID, LineToBreakpointsMap, WTF::IntHash<SourceID>, WTF::UnsignedWithZeroKeyHashTraits<SourceID>> SourceIDToBreakpointsMap; + + class ClearCodeBlockDebuggerRequestsFunctor; + class ClearDebuggerRequestsFunctor; + class SetSteppingModeFunctor; + class ToggleBreakpointFunctor; + + class PauseReasonDeclaration { + public: + PauseReasonDeclaration(Debugger& debugger, ReasonForPause reason) + : m_debugger(debugger) + { + m_debugger.m_reasonForPause = reason; + } + + ~PauseReasonDeclaration() + { + m_debugger.m_reasonForPause = NotPaused; + } + private: + Debugger& m_debugger; + }; + + bool hasBreakpoint(SourceID, const TextPosition&, Breakpoint* hitBreakpoint); + + void updateNeedForOpDebugCallbacks(); + + // These update functions are only needed because our current breakpoints are + // key'ed off the source position instead of the bytecode PC. This ensures + // 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*); + void pauseIfNeeded(JSC::CallFrame*); + + enum SteppingMode { + SteppingModeDisabled, + SteppingModeEnabled + }; + void setSteppingMode(SteppingMode); + + enum BreakpointState { + BreakpointDisabled, + BreakpointEnabled + }; + void toggleBreakpoint(CodeBlock*, Breakpoint&, BreakpointState); + void applyBreakpoints(CodeBlock*); + void toggleBreakpoint(Breakpoint&, BreakpointState); + + void clearDebuggerRequests(JSGlobalObject*); + + template<typename Functor> inline void forEachCodeBlock(Functor&); + + VM* m_vm; + HashSet<JSGlobalObject*> m_globalObjects; + + PauseOnExceptionsState m_pauseOnExceptionsState; + bool m_pauseOnNextStatement : 1; + bool m_isPaused : 1; + bool m_breakpointsActivated : 1; + bool m_hasHandlerForExceptionCallback : 1; + bool m_isInWorkerThread : 1; + unsigned m_steppingMode : 1; // SteppingMode + + ReasonForPause m_reasonForPause; + JSValue m_currentException; + CallFrame* m_pauseOnCallFrame; + CallFrame* m_currentCallFrame; + 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 DebuggerPausedScope; + friend class TemporaryPausedState; + friend class LLIntOffsetsExtractor; +}; + +} // namespace JSC + +#endif // Debugger_h diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp new file mode 100644 index 000000000..bf093e89b --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.cpp @@ -0,0 +1,261 @@ +/* + * 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 + * 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. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#include "config.h" +#include "DebuggerCallFrame.h" + +#include "CodeBlock.h" +#include "DebuggerEvalEnabler.h" +#include "DebuggerScope.h" +#include "Interpreter.h" +#include "JSFunction.h" +#include "JSLexicalEnvironment.h" +#include "JSCInlines.h" +#include "Parser.h" +#include "StackVisitor.h" +#include "StrongInlines.h" + +namespace JSC { + +class LineAndColumnFunctor { +public: + StackVisitor::Status operator()(StackVisitor& visitor) + { + visitor->computeLineAndColumn(m_line, m_column); + return StackVisitor::Done; + } + + unsigned line() const { return m_line; } + unsigned column() const { return m_column; } + +private: + unsigned m_line; + unsigned m_column; +}; + +class FindCallerMidStackFunctor { +public: + FindCallerMidStackFunctor(CallFrame* callFrame) + : m_callFrame(callFrame) + , m_callerFrame(nullptr) + { } + + StackVisitor::Status operator()(StackVisitor& visitor) + { + if (visitor->callFrame() == m_callFrame) { + m_callerFrame = visitor->callerFrame(); + return StackVisitor::Done; + } + return StackVisitor::Continue; + } + + CallFrame* getCallerFrame() const { return m_callerFrame; } + +private: + CallFrame* m_callFrame; + CallFrame* m_callerFrame; +}; + +DebuggerCallFrame::DebuggerCallFrame(CallFrame* callFrame) + : m_callFrame(callFrame) +{ + m_position = positionForCallFrame(m_callFrame); +} + +RefPtr<DebuggerCallFrame> DebuggerCallFrame::callerFrame() +{ + ASSERT(isValid()); + if (!isValid()) + return 0; + + if (m_caller) + return m_caller; + + FindCallerMidStackFunctor functor(m_callFrame); + m_callFrame->vm().topCallFrame->iterate(functor); + + CallFrame* callerFrame = functor.getCallerFrame(); + if (!callerFrame) + return nullptr; + + m_caller = DebuggerCallFrame::create(callerFrame); + return m_caller; +} + +JSC::JSGlobalObject* DebuggerCallFrame::vmEntryGlobalObject() const +{ + ASSERT(isValid()); + if (!isValid()) + return 0; + return m_callFrame->vmEntryGlobalObject(); +} + +SourceID DebuggerCallFrame::sourceID() const +{ + ASSERT(isValid()); + if (!isValid()) + return noSourceID; + return sourceIDForCallFrame(m_callFrame); +} + +String DebuggerCallFrame::functionName() const +{ + ASSERT(isValid()); + if (!isValid()) + return String(); + JSFunction* function = jsDynamicCast<JSFunction*>(m_callFrame->callee()); + if (!function) + return String(); + + return getCalculatedDisplayName(m_callFrame, function); +} + +DebuggerScope* DebuggerCallFrame::scope() +{ + ASSERT(isValid()); + if (!isValid()) + return 0; + + if (!m_scope) { + VM& vm = m_callFrame->vm(); + JSScope* scope; + CodeBlock* codeBlock = m_callFrame->codeBlock(); + if (codeBlock && codeBlock->scopeRegister().isValid()) + scope = m_callFrame->scope(codeBlock->scopeRegister().offset()); + else if (JSCallee* callee = jsDynamicCast<JSCallee*>(m_callFrame->callee())) + scope = callee->scope(); + else + scope = m_callFrame->lexicalGlobalObject(); + + m_scope.set(vm, DebuggerScope::create(vm, scope)); + } + return m_scope.get(); +} + +DebuggerCallFrame::Type DebuggerCallFrame::type() const +{ + ASSERT(isValid()); + if (!isValid()) + return ProgramType; + + if (jsDynamicCast<JSFunction*>(m_callFrame->callee())) + return FunctionType; + + return ProgramType; +} + +JSValue DebuggerCallFrame::thisValue() const +{ + ASSERT(isValid()); + return thisValueForCallFrame(m_callFrame); +} + +// Evaluate some JavaScript code in the scope of this frame. +JSValue DebuggerCallFrame::evaluate(const String& script, NakedPtr<Exception>& exception) +{ + ASSERT(isValid()); + CallFrame* callFrame = m_callFrame; + if (!callFrame) + return jsNull(); + + JSLockHolder lock(callFrame); + + if (!callFrame->codeBlock()) + return JSValue(); + + DebuggerEvalEnabler evalEnabler(callFrame); + VM& vm = callFrame->vm(); + auto& codeBlock = *callFrame->codeBlock(); + ThisTDZMode thisTDZMode = codeBlock.unlinkedCodeBlock()->constructorKind() == ConstructorKind::Derived ? ThisTDZMode::AlwaysCheck : ThisTDZMode::CheckIfNeeded; + + VariableEnvironment variablesUnderTDZ; + JSScope::collectVariablesUnderTDZ(scope()->jsScope(), variablesUnderTDZ); + + EvalExecutable* eval = EvalExecutable::create(callFrame, makeSource(script), codeBlock.isStrictMode(), thisTDZMode, &variablesUnderTDZ); + if (vm.exception()) { + exception = vm.exception(); + vm.clearException(); + return jsUndefined(); + } + + JSValue thisValue = thisValueForCallFrame(callFrame); + JSValue result = vm.interpreter->execute(eval, callFrame, thisValue, scope()->jsScope()); + if (vm.exception()) { + exception = vm.exception(); + vm.clearException(); + } + ASSERT(result); + return result; +} + +void DebuggerCallFrame::invalidate() +{ + RefPtr<DebuggerCallFrame> frame = this; + while (frame) { + frame->m_callFrame = nullptr; + if (frame->m_scope) { + frame->m_scope->invalidateChain(); + frame->m_scope.clear(); + } + frame = frame->m_caller.release(); + } +} + +TextPosition DebuggerCallFrame::positionForCallFrame(CallFrame* callFrame) +{ + if (!callFrame) + return TextPosition(); + + LineAndColumnFunctor functor; + callFrame->iterate(functor); + return TextPosition(OrdinalNumber::fromOneBasedInt(functor.line()), OrdinalNumber::fromOneBasedInt(functor.column())); +} + +SourceID DebuggerCallFrame::sourceIDForCallFrame(CallFrame* callFrame) +{ + ASSERT(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; +} + +} // namespace JSC diff --git a/Source/JavaScriptCore/debugger/DebuggerCallFrame.h b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h new file mode 100644 index 000000000..aa3cca52b --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerCallFrame.h @@ -0,0 +1,94 @@ +/* + * 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 + * 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. + * 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. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef DebuggerCallFrame_h +#define DebuggerCallFrame_h + +#include "DebuggerPrimitives.h" +#include "Strong.h" +#include <wtf/NakedPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/text/TextPosition.h> + +namespace JSC { + +class DebuggerScope; +class Exception; +class ExecState; +typedef ExecState CallFrame; + +class DebuggerCallFrame : public RefCounted<DebuggerCallFrame> { +public: + enum Type { ProgramType, FunctionType }; + + static Ref<DebuggerCallFrame> create(CallFrame* callFrame) + { + return adoptRef(*new DebuggerCallFrame(callFrame)); + } + + JS_EXPORT_PRIVATE explicit DebuggerCallFrame(CallFrame*); + + JS_EXPORT_PRIVATE RefPtr<DebuggerCallFrame> callerFrame(); + ExecState* exec() const { return m_callFrame; } + JS_EXPORT_PRIVATE SourceID sourceID() const; + + // line and column are in base 0 e.g. the first line is line 0. + int line() const { return m_position.m_line.zeroBasedInt(); } + int column() const { return m_position.m_column.zeroBasedInt(); } + JS_EXPORT_PRIVATE const TextPosition& position() const { return m_position; } + + JS_EXPORT_PRIVATE JSGlobalObject* vmEntryGlobalObject() const; + JS_EXPORT_PRIVATE DebuggerScope* scope(); + JS_EXPORT_PRIVATE String functionName() const; + JS_EXPORT_PRIVATE Type type() const; + JS_EXPORT_PRIVATE JSValue thisValue() const; + JSValue evaluate(const String&, NakedPtr<Exception>&); + + bool isValid() const { return !!m_callFrame; } + 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 TextPosition positionForCallFrame(CallFrame*); + JS_EXPORT_PRIVATE static SourceID sourceIDForCallFrame(CallFrame*); + static JSValue thisValueForCallFrame(CallFrame*); + +private: + CallFrame* m_callFrame; + 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; +}; + +} // 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..bb75b1f15 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerEvalEnabler.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#ifndef DebuggerEvalEnabler_h +#define DebuggerEvalEnabler_h + +#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 + +#endif // DebuggerEvalEnabler_h diff --git a/Source/JavaScriptCore/debugger/DebuggerPrimitives.h b/Source/JavaScriptCore/debugger/DebuggerPrimitives.h new file mode 100644 index 000000000..ab7301b5c --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerPrimitives.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 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 DebuggerPrimitives_h +#define DebuggerPrimitives_h + +#include <stddef.h> + +namespace JSC { + +typedef size_t SourceID; +static const SourceID noSourceID = 0; + +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..c6eba4e93 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerScope.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2008-2009, 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. + */ + +#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(VM& vm, JSScope* scope) + : JSNonFinalObject(vm, scope->globalObject()->debuggerScopeStructure()) +{ + 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); + ASSERT(scope->isValid()); + 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); + ASSERT(scope->isValid()); + 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; +} + +void DebuggerScope::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot) +{ + DebuggerScope* scope = jsCast<DebuggerScope*>(cell); + ASSERT(scope->isValid()); + if (!scope->isValid()) + return; + JSObject* thisObject = JSScope::objectAtScope(scope->jsScope()); + slot.setThisValue(JSValue(thisObject)); + 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::isFunctionOrEvalScope() 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->isActivationObject() && !isCatchScope(); +} + +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()); + 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..e1edb9248 --- /dev/null +++ b/Source/JavaScriptCore/debugger/DebuggerScope.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2008-2009, 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. + */ + +#ifndef DebuggerScope_h +#define DebuggerScope_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; + + static DebuggerScope* create(VM& vm, JSScope* scope) + { + DebuggerScope* debuggerScope = new (NotNull, allocateCell<DebuggerScope>(vm.heap)) DebuggerScope(vm, scope); + debuggerScope->finishCreation(vm); + return debuggerScope; + } + + 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) + { + 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 isFunctionOrEvalScope() const; + + JSValue caughtValue(ExecState*) const; + +private: + JS_EXPORT_PRIVATE DebuggerScope(VM&, JSScope*); + JS_EXPORT_PRIVATE 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 + +#endif // DebuggerScope_h |
