blob: aa7a8778e237bf7a09b0fdcdffe7aaadadfb56d9 [file] [log] [blame]
/*
* Copyright (C) 2013-2019 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 "ClonedArguments.h"
#include "DebuggerPrimitives.h"
#include "InlineCallFrame.h"
#include "Interpreter.h"
#include "JSCInlines.h"
#include "RegisterAtOffsetList.h"
#include "WasmCallee.h"
#include "WasmIndexOrName.h"
#include "WebAssemblyFunction.h"
#include <wtf/text/StringBuilder.h>
namespace JSC {
StackVisitor::StackVisitor(CallFrame* startFrame, VM& vm)
{
m_frame.m_index = 0;
m_frame.m_isWasmFrame = false;
CallFrame* topFrame;
if (startFrame) {
ASSERT(!vm.topCallFrame || reinterpret_cast<void*>(vm.topCallFrame) != vm.topEntryFrame);
m_frame.m_entryFrame = vm.topEntryFrame;
topFrame = vm.topCallFrame;
if (topFrame && topFrame->isStackOverflowFrame()) {
topFrame = topFrame->callerFrame(m_frame.m_entryFrame);
m_topEntryFrameIsEmpty = (m_frame.m_entryFrame != vm.topEntryFrame);
if (startFrame == vm.topCallFrame)
startFrame = topFrame;
}
} else {
m_frame.m_entryFrame = 0;
topFrame = 0;
}
m_frame.m_callerIsEntryFrame = 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()
{
m_frame.m_index++;
#if ENABLE(DFG_JIT)
if (m_frame.isInlinedFrame()) {
InlineCallFrame* inlineCallFrame = m_frame.inlineCallFrame();
CodeOrigin* callerCodeOrigin = inlineCallFrame->getCallerSkippingTailCalls();
if (!callerCodeOrigin) {
while (inlineCallFrame) {
readInlinedFrame(m_frame.callFrame(), &inlineCallFrame->directCaller);
inlineCallFrame = m_frame.inlineCallFrame();
}
m_frame.m_entryFrame = m_frame.m_callerEntryFrame;
readFrame(m_frame.callerFrame());
} else
readInlinedFrame(m_frame.callFrame(), callerCodeOrigin);
return;
}
#endif // ENABLE(DFG_JIT)
m_frame.m_entryFrame = m_frame.m_callerEntryFrame;
readFrame(m_frame.callerFrame());
}
void StackVisitor::unwindToMachineCodeBlockFrame()
{
#if ENABLE(DFG_JIT)
if (m_frame.isInlinedFrame()) {
CodeOrigin codeOrigin = m_frame.inlineCallFrame()->directCaller;
while (codeOrigin.inlineCallFrame())
codeOrigin = codeOrigin.inlineCallFrame()->directCaller;
readNonInlinedFrame(m_frame.callFrame(), &codeOrigin);
}
#endif
}
void StackVisitor::readFrame(CallFrame* callFrame)
{
if (!callFrame) {
m_frame.setToEnd();
return;
}
if (callFrame->isAnyWasmCallee()) {
readNonInlinedFrame(callFrame);
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;
}
CallSiteIndex index = callFrame->callSiteIndex();
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_callerEntryFrame = m_frame.m_entryFrame;
m_frame.m_callerFrame = callFrame->callerFrame(m_frame.m_callerEntryFrame);
m_frame.m_callerIsEntryFrame = m_frame.m_callerEntryFrame != m_frame.m_entryFrame;
m_frame.m_isWasmFrame = false;
CalleeBits callee = callFrame->callee();
m_frame.m_callee = callee;
if (callFrame->isAnyWasmCallee()) {
m_frame.m_isWasmFrame = true;
m_frame.m_codeBlock = nullptr;
m_frame.m_bytecodeIndex = BytecodeIndex();
#if ENABLE(WEBASSEMBLY)
CalleeBits bits = callFrame->callee();
if (bits.isWasm())
m_frame.m_wasmFunctionIndexOrName = bits.asWasmCallee()->indexOrName();
#endif
} else {
m_frame.m_codeBlock = callFrame->codeBlock();
m_frame.m_bytecodeIndex = !m_frame.codeBlock() ? BytecodeIndex(0)
: codeOrigin ? codeOrigin->bytecodeIndex()
: callFrame->bytecodeIndex();
}
#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);
m_frame.m_isWasmFrame = false;
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->argumentCountIncludingThis;
m_frame.m_codeBlock = inlineCallFrame->baselineCodeBlock.get();
m_frame.m_bytecodeIndex = codeOrigin->bytecodeIndex();
JSFunction* callee = inlineCallFrame->calleeForCallFrame(callFrame);
m_frame.m_callee = callee;
ASSERT(!!m_frame.callee().rawPtr());
// 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 (isWasmFrame())
return CodeType::Wasm;
if (!codeBlock())
return CodeType::Native;
switch (codeBlock()->codeType()) {
case EvalCode:
return CodeType::Eval;
case ModuleCode:
return CodeType::Module;
case FunctionCode:
return CodeType::Function;
case GlobalCode:
return CodeType::Global;
}
RELEASE_ASSERT_NOT_REACHED();
return CodeType::Global;
}
#if ENABLE(ASSEMBLER)
Optional<RegisterAtOffsetList> StackVisitor::Frame::calleeSaveRegistersForUnwinding()
{
if (!NUMBER_OF_CALLEE_SAVES_REGISTERS)
return WTF::nullopt;
if (isInlinedFrame())
return WTF::nullopt;
#if ENABLE(WEBASSEMBLY)
if (isWasmFrame()) {
if (callee().isCell()) {
RELEASE_ASSERT(isWebAssemblyModule(callee().asCell()));
return WTF::nullopt;
}
Wasm::Callee* wasmCallee = callee().asWasmCallee();
return *wasmCallee->calleeSaveRegisters();
}
if (callee().isCell()) {
if (auto* jsToWasmICCallee = jsDynamicCast<JSToWasmICCallee*>(callee().asCell()->vm(), callee().asCell()))
return jsToWasmICCallee->function()->usedCalleeSaveRegisters();
}
#endif // ENABLE(WEBASSEMBLY)
if (CodeBlock* codeBlock = this->codeBlock())
return *codeBlock->calleeSaveRegisters();
return WTF::nullopt;
}
#endif // ENABLE(ASSEMBLER)
String StackVisitor::Frame::functionName() const
{
String traceLine;
switch (codeType()) {
case CodeType::Wasm:
traceLine = makeString(m_wasmFunctionIndexOrName);
break;
case CodeType::Eval:
traceLine = "eval code"_s;
break;
case CodeType::Module:
traceLine = "module code"_s;
break;
case CodeType::Native: {
JSCell* callee = this->callee().asCell();
if (callee)
traceLine = getCalculatedDisplayName(callFrame()->deprecatedVM(), jsCast<JSObject*>(callee)).impl();
break;
}
case CodeType::Function:
traceLine = getCalculatedDisplayName(callFrame()->deprecatedVM(), jsCast<JSObject*>(this->callee().asCell())).impl();
break;
case CodeType::Global:
traceLine = "global code"_s;
break;
}
return traceLine.isNull() ? emptyString() : traceLine;
}
String StackVisitor::Frame::sourceURL() const
{
String traceLine;
switch (codeType()) {
case CodeType::Eval:
case CodeType::Module:
case CodeType::Function:
case CodeType::Global: {
String sourceURL = codeBlock()->ownerExecutable()->sourceURL();
if (!sourceURL.isEmpty())
traceLine = sourceURL.impl();
break;
}
case CodeType::Native:
traceLine = "[native code]"_s;
break;
case CodeType::Wasm:
traceLine = "[wasm code]"_s;
break;
}
return traceLine.isNull() ? emptyString() : traceLine;
}
String StackVisitor::Frame::toString() const
{
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 (hasLineAndColumnInfo()) {
unsigned line = 0;
unsigned column = 0;
computeLineAndColumn(line, column);
traceBuild.append(':');
traceBuild.appendNumber(line);
traceBuild.append(':');
traceBuild.appendNumber(column);
}
}
return traceBuild.toString().impl();
}
intptr_t StackVisitor::Frame::sourceID()
{
if (CodeBlock* codeBlock = this->codeBlock())
return codeBlock->ownerExecutable()->sourceID();
return noSourceID;
}
ClonedArguments* StackVisitor::Frame::createArguments(VM& vm)
{
ASSERT(m_callFrame);
CallFrame* physicalFrame = m_callFrame;
// FIXME: Revisit JSGlobalObject.
// https://bugs.webkit.org/show_bug.cgi?id=203204
JSGlobalObject* globalObject = physicalFrame->lexicalGlobalObject(vm);
ClonedArguments* arguments;
ArgumentsMode mode;
if (Options::useFunctionDotArguments())
mode = ArgumentsMode::Cloned;
else
mode = ArgumentsMode::FakeValues;
#if ENABLE(DFG_JIT)
if (isInlinedFrame()) {
ASSERT(m_inlineCallFrame);
arguments = ClonedArguments::createWithInlineFrame(globalObject, physicalFrame, m_inlineCallFrame, mode);
} else
#endif
arguments = ClonedArguments::createWithMachineFrame(globalObject, physicalFrame, mode);
return arguments;
}
bool StackVisitor::Frame::hasLineAndColumnInfo() const
{
return !!codeBlock();
}
void StackVisitor::Frame::computeLineAndColumn(unsigned& line, unsigned& column) const
{
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()->firstLine();
column = divotColumn + (divotLine ? 1 : codeBlock->firstLineColumnOffset());
if (Optional<int> overrideLineNumber = codeBlock->ownerExecutable()->overrideLineNumber(codeBlock->vm()))
line = overrideLineNumber.value();
}
void StackVisitor::Frame::retrieveExpressionInfo(int& divot, int& startOffset, int& endOffset, unsigned& line, unsigned& column) const
{
CodeBlock* codeBlock = this->codeBlock();
codeBlock->unlinkedCodeBlock()->expressionRangeForBytecodeIndex(bytecodeIndex(), divot, startOffset, endOffset, line, column);
divot += codeBlock->sourceOffset();
}
void StackVisitor::Frame::setToEnd()
{
m_callFrame = 0;
#if ENABLE(DFG_JIT)
m_inlineCallFrame = 0;
#endif
m_isWasmFrame = false;
}
void StackVisitor::Frame::dump(PrintStream& out, Indenter indent) const
{
dump(out, indent, [] (PrintStream&) { });
}
void StackVisitor::Frame::dump(PrintStream& out, Indenter indent, WTF::Function<void(PrintStream&)> prefix) const
{
if (!this->callFrame()) {
out.print(indent, "frame 0x0\n");
return;
}
CodeBlock* codeBlock = this->codeBlock();
out.print(indent);
prefix(out);
out.print("frame ", RawPointer(this->callFrame()), " {\n");
{
indent++;
CallFrame* callFrame = m_callFrame;
CallFrame* callerFrame = this->callerFrame();
const void* returnPC = callFrame->hasReturnPC() ? callFrame->returnPC().value() : nullptr;
out.print(indent, "name: ", functionName(), "\n");
out.print(indent, "sourceURL: ", sourceURL(), "\n");
bool isInlined = false;
#if ENABLE(DFG_JIT)
isInlined = isInlinedFrame();
out.print(indent, "isInlinedFrame: ", isInlinedFrame(), "\n");
if (isInlinedFrame())
out.print(indent, "InlineCallFrame: ", RawPointer(m_inlineCallFrame), "\n");
#endif
out.print(indent, "callee: ", RawPointer(callee().rawPtr()), "\n");
out.print(indent, "returnPC: ", RawPointer(returnPC), "\n");
out.print(indent, "callerFrame: ", RawPointer(callerFrame), "\n");
uintptr_t locationRawBits = callFrame->callSiteAsRawBits();
out.print(indent, "rawLocationBits: ", locationRawBits,
" ", RawPointer(reinterpret_cast<void*>(locationRawBits)), "\n");
out.print(indent, "codeBlock: ", RawPointer(codeBlock));
if (codeBlock)
out.print(" ", *codeBlock);
out.print("\n");
if (codeBlock && !isInlined) {
indent++;
if (callFrame->callSiteBitsAreBytecodeOffset()) {
BytecodeIndex bytecodeIndex = callFrame->bytecodeIndex();
out.print(indent, bytecodeIndex, " of ", codeBlock->instructions().size(), "\n");
#if ENABLE(DFG_JIT)
} else {
out.print(indent, "hasCodeOrigins: ", codeBlock->hasCodeOrigins(), "\n");
if (codeBlock->hasCodeOrigins()) {
CallSiteIndex callSiteIndex = callFrame->callSiteIndex();
out.print(indent, "callSiteIndex: ", callSiteIndex.bits(), " of ", codeBlock->codeOrigins().size(), "\n");
JITType jitType = codeBlock->jitType();
if (jitType != JITType::FTLJIT) {
JITCode* jitCode = codeBlock->jitCode().get();
out.print(indent, "jitCode: ", RawPointer(jitCode),
" start ", RawPointer(jitCode->start()),
" end ", RawPointer(jitCode->end()), "\n");
}
}
#endif
}
unsigned line = 0;
unsigned column = 0;
computeLineAndColumn(line, column);
out.print(indent, "line: ", line, "\n");
out.print(indent, "column: ", column, "\n");
indent--;
}
out.print(indent, "EntryFrame: ", RawPointer(m_entryFrame), "\n");
indent--;
}
out.print(indent, "}\n");
}
} // namespace JSC