| /* |
| * Copyright (C) 2013, 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. ``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 <wtf/DataLog.h> |
| |
| namespace JSC { |
| |
| StackVisitor::StackVisitor(CallFrame* startFrame) |
| { |
| m_frame.m_index = 0; |
| CallFrame* topFrame; |
| if (startFrame) { |
| m_frame.m_VMEntryFrame = startFrame->vm().topVMEntryFrame; |
| topFrame = startFrame->vm().topCallFrame; |
| } else { |
| m_frame.m_VMEntryFrame = 0; |
| topFrame = 0; |
| } |
| m_frame.m_callerIsVMEntryFrame = false; |
| readFrame(topFrame); |
| |
| // Find the frame the caller wants to start unwinding from. |
| while (m_frame.callFrame() && m_frame.callFrame() != startFrame) |
| gotoNextFrame(); |
| } |
| |
| 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); |
| return; |
| } |
| #endif // ENABLE(DFG_JIT) |
| m_frame.m_VMEntryFrame = m_frame.m_CallerVMEntryFrame; |
| readFrame(m_frame.callerFrame()); |
| } |
| |
| void StackVisitor::readFrame(CallFrame* callFrame) |
| { |
| 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_CallerVMEntryFrame = m_frame.m_VMEntryFrame; |
| m_frame.m_callerFrame = callFrame->callerFrame(m_frame.m_CallerVMEntryFrame); |
| m_frame.m_callerIsVMEntryFrame = m_frame.m_CallerVMEntryFrame != m_frame.m_VMEntryFrame; |
| m_frame.m_callee = callFrame->callee(); |
| 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); |
| |
| int frameOffset = inlinedFrameOffset(codeOrigin); |
| bool isInlined = !!frameOffset; |
| if (isInlined) { |
| InlineCallFrame* inlineCallFrame = codeOrigin->inlineCallFrame; |
| |
| m_frame.m_callFrame = callFrame; |
| m_frame.m_inlineCallFrame = inlineCallFrame; |
| if (inlineCallFrame->argumentCountRegister.isValid()) |
| m_frame.m_argumentCountIncludingThis = callFrame->r(inlineCallFrame->argumentCountRegister.offset()).unboxedInt32(); |
| else |
| 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_callee = callee; |
| 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 = ASCIILiteral("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 = ASCIILiteral("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 = ASCIILiteral("[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; |
| ArgumentsMode mode; |
| if (Options::enableFunctionDotArguments()) |
| mode = ClonedArgumentsCreationMode; |
| else |
| mode = FakeArgumentValuesCreationMode; |
| #if ENABLE(DFG_JIT) |
| if (isInlinedFrame()) { |
| ASSERT(m_inlineCallFrame); |
| arguments = Arguments::create(vm, physicalFrame, m_inlineCallFrame, mode); |
| arguments->tearOff(physicalFrame, m_inlineCallFrame); |
| jsCast<Arguments*>((JSCell*)arguments); |
| } else |
| #endif |
| { |
| JSLexicalEnvironment* lexicalEnvironment = nullptr; |
| arguments = Arguments::create(vm, physicalFrame, lexicalEnvironment, mode); |
| 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(); |
| |
| // Care should be taken here since exception fuzzing may raise exceptions in |
| // places where they would be otherwise impossible. Therefore, callFrame may |
| // lack activation even if the codeBlock signals need of activation. Also, |
| // even if codeBlock signals the use of arguments, the |
| // unmodifiedArgumentsRegister may not be initialized yet (neither locally |
| // nor in lexicalEnvironment). |
| JSValue result = jsUndefined(); |
| if (codeBlock()->needsActivation() && callFrame()->hasActivation()) |
| result = callFrame()->lexicalEnvironment()->registerAt(unmodifiedArgumentsRegister(reg).offset()).get(); |
| if (!result || !result.isCell()) // Try local unmodifiedArgumentsRegister if lexicalEnvironment is not present (generally possible) or has not set up registers yet (only possible if fuzzing exceptions). |
| result = callFrame()->r(unmodifiedArgumentsRegister(reg).offset()).jsValue(); |
| if (!result || !result.isCell()) // Protect against the case when exception fuzzing throws when unmodifiedArgumentsRegister is not set up yet (e.g., in op_enter). |
| return 0; |
| return jsCast<Arguments*>(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 "<unknown>"; |
| } |
| |
| 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()); |
| |
| #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<void*>(bytecodeOffset), codeBlock->instructions().size()); |
| #if ENABLE(DFG_JIT) |
| } else { |
| unsigned codeOriginIndex = callFrame->locationAsCodeOriginIndex(); |
| printif(i, " codeOriginIdex %u %p / %zu\n", codeOriginIndex, reinterpret_cast<void*>(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 |