X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/4be4e30906bcb8ee30b4d189205cb70bad6707ce..81345200c95645a1b0d2635520f96ad55dfde63f:/interpreter/StackVisitor.cpp?ds=inline diff --git a/interpreter/StackVisitor.cpp b/interpreter/StackVisitor.cpp new file mode 100644 index 0000000..19f95b9 --- /dev/null +++ b/interpreter/StackVisitor.cpp @@ -0,0 +1,474 @@ +/* + * 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. + */ + +#include "config.h" +#include "StackVisitor.h" + +#include "Arguments.h" +#include "CallFrameInlines.h" +#include "Executable.h" +#include "Interpreter.h" +#include "JSCInlines.h" +#include + +namespace JSC { + +StackVisitor::StackVisitor(CallFrame* startFrame) +{ + m_frame.m_index = 0; + readFrame(startFrame); +} + +void StackVisitor::gotoNextFrame() +{ +#if ENABLE(DFG_JIT) + if (m_frame.isInlinedFrame()) { + InlineCallFrame* inlineCallFrame = m_frame.inlineCallFrame(); + CodeOrigin* callerCodeOrigin = &inlineCallFrame->caller; + readInlinedFrame(m_frame.callFrame(), callerCodeOrigin); + + } else +#endif // ENABLE(DFG_JIT) + readFrame(m_frame.callerFrame()); +} + +void StackVisitor::readFrame(CallFrame* callFrame) +{ + ASSERT(!callFrame->isVMEntrySentinel()); + if (!callFrame) { + m_frame.setToEnd(); + return; + } + +#if !ENABLE(DFG_JIT) + readNonInlinedFrame(callFrame); + +#else // !ENABLE(DFG_JIT) + // If the frame doesn't have a code block, then it's not a DFG frame. + // Hence, we're not at an inlined frame. + CodeBlock* codeBlock = callFrame->codeBlock(); + if (!codeBlock) { + readNonInlinedFrame(callFrame); + return; + } + + // If the code block does not have any code origins, then there's no + // inlining. Hence, we're not at an inlined frame. + if (!codeBlock->hasCodeOrigins()) { + readNonInlinedFrame(callFrame); + return; + } + + unsigned index = callFrame->locationAsCodeOriginIndex(); + ASSERT(codeBlock->canGetCodeOrigin(index)); + if (!codeBlock->canGetCodeOrigin(index)) { + // See assertion above. In release builds, we try to protect ourselves + // from crashing even though stack walking will be goofed up. + m_frame.setToEnd(); + return; + } + + CodeOrigin codeOrigin = codeBlock->codeOrigin(index); + if (!codeOrigin.inlineCallFrame) { + readNonInlinedFrame(callFrame, &codeOrigin); + return; + } + + readInlinedFrame(callFrame, &codeOrigin); +#endif // !ENABLE(DFG_JIT) +} + +void StackVisitor::readNonInlinedFrame(CallFrame* callFrame, CodeOrigin* codeOrigin) +{ + m_frame.m_callFrame = callFrame; + m_frame.m_argumentCountIncludingThis = callFrame->argumentCountIncludingThis(); + m_frame.m_callerFrame = callFrame->callerFrameSkippingVMEntrySentinel(); + m_frame.m_callee = callFrame->callee(); + m_frame.m_scope = callFrame->scope(); + m_frame.m_codeBlock = callFrame->codeBlock(); + m_frame.m_bytecodeOffset = !m_frame.codeBlock() ? 0 + : codeOrigin ? codeOrigin->bytecodeIndex + : callFrame->locationAsBytecodeOffset(); +#if ENABLE(DFG_JIT) + m_frame.m_inlineCallFrame = 0; +#endif +} + +#if ENABLE(DFG_JIT) +static int inlinedFrameOffset(CodeOrigin* codeOrigin) +{ + InlineCallFrame* inlineCallFrame = codeOrigin->inlineCallFrame; + int frameOffset = inlineCallFrame ? inlineCallFrame->stackOffset : 0; + return frameOffset; +} + +void StackVisitor::readInlinedFrame(CallFrame* callFrame, CodeOrigin* codeOrigin) +{ + ASSERT(codeOrigin); + ASSERT(!callFrame->isVMEntrySentinel()); + + int frameOffset = inlinedFrameOffset(codeOrigin); + bool isInlined = !!frameOffset; + if (isInlined) { + InlineCallFrame* inlineCallFrame = codeOrigin->inlineCallFrame; + + m_frame.m_callFrame = callFrame; + m_frame.m_inlineCallFrame = inlineCallFrame; + m_frame.m_argumentCountIncludingThis = inlineCallFrame->arguments.size(); + m_frame.m_codeBlock = inlineCallFrame->baselineCodeBlock(); + m_frame.m_bytecodeOffset = codeOrigin->bytecodeIndex; + + JSFunction* callee = inlineCallFrame->calleeForCallFrame(callFrame); + m_frame.m_scope = callee->scope(); + m_frame.m_callee = callee; + ASSERT(m_frame.scope()); + ASSERT(m_frame.callee()); + + // The callerFrame just needs to be non-null to indicate that we + // haven't reached the last frame yet. Setting it to the root + // frame (i.e. the callFrame that this inlined frame is called from) + // would work just fine. + m_frame.m_callerFrame = callFrame; + return; + } + + readNonInlinedFrame(callFrame, codeOrigin); +} +#endif // ENABLE(DFG_JIT) + +StackVisitor::Frame::CodeType StackVisitor::Frame::codeType() const +{ + if (!isJSFrame()) + return CodeType::Native; + + switch (codeBlock()->codeType()) { + case EvalCode: + return CodeType::Eval; + case FunctionCode: + return CodeType::Function; + case GlobalCode: + return CodeType::Global; + } + RELEASE_ASSERT_NOT_REACHED(); + return CodeType::Global; +} + +String StackVisitor::Frame::functionName() +{ + String traceLine; + JSObject* callee = this->callee(); + + switch (codeType()) { + case CodeType::Eval: + traceLine = "eval code"; + break; + case CodeType::Native: + if (callee) + traceLine = getCalculatedDisplayName(callFrame(), callee).impl(); + break; + case CodeType::Function: + traceLine = getCalculatedDisplayName(callFrame(), callee).impl(); + break; + case CodeType::Global: + traceLine = "global code"; + break; + } + return traceLine.isNull() ? emptyString() : traceLine; +} + +String StackVisitor::Frame::sourceURL() +{ + String traceLine; + + switch (codeType()) { + case CodeType::Eval: + case CodeType::Function: + case CodeType::Global: { + String sourceURL = codeBlock()->ownerExecutable()->sourceURL(); + if (!sourceURL.isEmpty()) + traceLine = sourceURL.impl(); + break; + } + case CodeType::Native: + traceLine = "[native code]"; + break; + } + return traceLine.isNull() ? emptyString() : traceLine; +} + +String StackVisitor::Frame::toString() +{ + StringBuilder traceBuild; + String functionName = this->functionName(); + String sourceURL = this->sourceURL(); + traceBuild.append(functionName); + if (!sourceURL.isEmpty()) { + if (!functionName.isEmpty()) + traceBuild.append('@'); + traceBuild.append(sourceURL); + if (isJSFrame()) { + unsigned line = 0; + unsigned column = 0; + computeLineAndColumn(line, column); + traceBuild.append(':'); + traceBuild.appendNumber(line); + traceBuild.append(':'); + traceBuild.appendNumber(column); + } + } + return traceBuild.toString().impl(); +} + +Arguments* StackVisitor::Frame::createArguments() +{ + ASSERT(m_callFrame); + CallFrame* physicalFrame = m_callFrame; + VM& vm = physicalFrame->vm(); + Arguments* arguments; +#if ENABLE(DFG_JIT) + if (isInlinedFrame()) { + ASSERT(m_inlineCallFrame); + arguments = Arguments::create(vm, physicalFrame, m_inlineCallFrame); + arguments->tearOff(physicalFrame, m_inlineCallFrame); + } else +#endif + { + arguments = Arguments::create(vm, physicalFrame); + arguments->tearOff(physicalFrame); + } + return arguments; +} + +Arguments* StackVisitor::Frame::existingArguments() +{ + if (codeBlock()->codeType() != FunctionCode) + return 0; + if (!codeBlock()->usesArguments()) + return 0; + + VirtualRegister reg; + +#if ENABLE(DFG_JIT) + if (isInlinedFrame()) + reg = inlineCallFrame()->argumentsRegister; + else +#endif // ENABLE(DFG_JIT) + reg = codeBlock()->argumentsRegister(); + + JSValue result = callFrame()->r(unmodifiedArgumentsRegister(reg).offset()).jsValue(); + if (!result || !result.isCell()) // Protect against Undefined in case we throw in op_enter. + return 0; + return jsCast(result); +} + +void StackVisitor::Frame::computeLineAndColumn(unsigned& line, unsigned& column) +{ + CodeBlock* codeBlock = this->codeBlock(); + if (!codeBlock) { + line = 0; + column = 0; + return; + } + + int divot = 0; + int unusedStartOffset = 0; + int unusedEndOffset = 0; + unsigned divotLine = 0; + unsigned divotColumn = 0; + retrieveExpressionInfo(divot, unusedStartOffset, unusedEndOffset, divotLine, divotColumn); + + line = divotLine + codeBlock->ownerExecutable()->lineNo(); + column = divotColumn + (divotLine ? 1 : codeBlock->firstLineColumnOffset()); +} + +void StackVisitor::Frame::retrieveExpressionInfo(int& divot, int& startOffset, int& endOffset, unsigned& line, unsigned& column) +{ + CodeBlock* codeBlock = this->codeBlock(); + codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeOffset(bytecodeOffset(), divot, startOffset, endOffset, line, column); + divot += codeBlock->sourceOffset(); +} + +void StackVisitor::Frame::setToEnd() +{ + m_callFrame = 0; +#if ENABLE(DFG_JIT) + m_inlineCallFrame = 0; +#endif +} + +#ifndef NDEBUG + +static const char* jitTypeName(JITCode::JITType jitType) +{ + switch (jitType) { + case JITCode::None: return "None"; + case JITCode::HostCallThunk: return "HostCallThunk"; + case JITCode::InterpreterThunk: return "InterpreterThunk"; + case JITCode::BaselineJIT: return "BaselineJIT"; + case JITCode::DFGJIT: return "DFGJIT"; + case JITCode::FTLJIT: return "FTLJIT"; + } + return ""; +} + +static void printIndents(int levels) +{ + while (levels--) + dataLogFString(" "); +} + +static void printif(int indentLevels, const char* format, ...) +{ + va_list argList; + va_start(argList, format); + + if (indentLevels) + printIndents(indentLevels); + +#if COMPILER(CLANG) || COMPILER(GCC) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#pragma GCC diagnostic ignored "-Wmissing-format-attribute" +#endif + + WTF::dataLogFV(format, argList); + +#if COMPILER(CLANG) || COMPILER(GCC) +#pragma GCC diagnostic pop +#endif + + va_end(argList); +} + +void StackVisitor::Frame::print(int indentLevel) +{ + int i = indentLevel; + + if (!this->callFrame()) { + printif(i, "frame 0x0\n"); + return; + } + + CodeBlock* codeBlock = this->codeBlock(); + printif(i, "frame %p {\n", this->callFrame()); + + CallFrame* callFrame = m_callFrame; + CallFrame* callerFrame = this->callerFrame(); + void* returnPC = callFrame->hasReturnPC() ? callFrame->returnPC().value() : nullptr; + + printif(i, " name '%s'\n", functionName().utf8().data()); + printif(i, " sourceURL '%s'\n", sourceURL().utf8().data()); + printif(i, " isVMEntrySentinel %d\n", callerFrame->isVMEntrySentinel()); + +#if ENABLE(DFG_JIT) + printif(i, " isInlinedFrame %d\n", isInlinedFrame()); + if (isInlinedFrame()) + printif(i, " InlineCallFrame %p\n", m_inlineCallFrame); +#endif + + printif(i, " callee %p\n", callee()); + printif(i, " returnPC %p\n", returnPC); + printif(i, " callerFrame %p\n", callerFrame); + unsigned locationRawBits = callFrame->locationAsRawBits(); + printif(i, " rawLocationBits %u 0x%x\n", locationRawBits, locationRawBits); + printif(i, " codeBlock %p\n", codeBlock); + if (codeBlock) { + JITCode::JITType jitType = codeBlock->jitType(); + if (callFrame->hasLocationAsBytecodeOffset()) { + unsigned bytecodeOffset = callFrame->locationAsBytecodeOffset(); + printif(i, " bytecodeOffset %u %p / %zu\n", bytecodeOffset, reinterpret_cast(bytecodeOffset), codeBlock->instructions().size()); +#if ENABLE(DFG_JIT) + } else { + unsigned codeOriginIndex = callFrame->locationAsCodeOriginIndex(); + printif(i, " codeOriginIdex %u %p / %zu\n", codeOriginIndex, reinterpret_cast(codeOriginIndex), codeBlock->codeOrigins().size()); +#endif + } + unsigned line = 0; + unsigned column = 0; + computeLineAndColumn(line, column); + printif(i, " line %d\n", line); + printif(i, " column %d\n", column); + printif(i, " jitType %d <%s> isOptimizingJIT %d\n", jitType, jitTypeName(jitType), JITCode::isOptimizingJIT(jitType)); +#if ENABLE(DFG_JIT) + printif(i, " hasCodeOrigins %d\n", codeBlock->hasCodeOrigins()); + if (codeBlock->hasCodeOrigins()) { + JITCode* jitCode = codeBlock->jitCode().get(); + printif(i, " jitCode %p start %p end %p\n", jitCode, jitCode->start(), jitCode->end()); + } +#endif + } + printif(i, "}\n"); +} + +#endif // NDEBUG + +} // namespace JSC + +#ifndef NDEBUG +using JSC::StackVisitor; + +// For debugging use +JS_EXPORT_PRIVATE void debugPrintCallFrame(JSC::CallFrame*); +JS_EXPORT_PRIVATE void debugPrintStack(JSC::CallFrame* topCallFrame); + +class DebugPrintFrameFunctor { +public: + enum Action { + PrintOne, + PrintAll + }; + + DebugPrintFrameFunctor(Action action) + : m_action(action) + { + } + + StackVisitor::Status operator()(StackVisitor& visitor) + { + visitor->print(2); + return m_action == PrintAll ? StackVisitor::Continue : StackVisitor::Done; + } + +private: + Action m_action; +}; + +void debugPrintCallFrame(JSC::CallFrame* callFrame) +{ + if (!callFrame) + return; + DebugPrintFrameFunctor functor(DebugPrintFrameFunctor::PrintOne); + callFrame->iterate(functor); +} + +void debugPrintStack(JSC::CallFrame* topCallFrame) +{ + if (!topCallFrame) + return; + DebugPrintFrameFunctor functor(DebugPrintFrameFunctor::PrintAll); + topCallFrame->iterate(functor); +} + +#endif // !NDEBUG