/* * 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