fourthTier: Introducing the StackIterator class.
This was a non trivial merge as trunk has changed computation of line and column information
Introducing the StackIterator class.
https://bugs.webkit.org/show_bug.cgi?id=117390.
Reviewed by Geoffrey Garen.
Source/JavaScriptCore:
The StackIterator class is meant to unify the way we iterate the JS
stack. It also makes it so that we don't have to copy the frame data
into the intermediate StackFrame struct before processing it.
Unfortunately we still can't get rid of StackFrame because it is used
to record frame information for the Exception stack that is expected
to persist beyond when the frames have been popped off the JS stack.
The StackIterator will iterate over all "logical" frames (i.e. including
inlined frames). As it iterates the JS stack, if it encounters a DFG
frame that has inlined frames, the iterator will canonicalize the
inlined frames before returning. Once canonicalized, the frame can be
read like any other frame.
The StackIterator implements a Frame class that inherits from CallFrame.
The StackIterator::Frame serves as reader of the CallFrame that makes
it easier to access information about the frame. The StackIterator::Frame
only adds functions, and no additional data fields.
* API/JSContextRef.cpp:
(JSContextCreateBacktrace):
* CMakeLists.txt:
* GNUmakefile.list.am:
* JavaScriptCore.vcproj/JavaScriptCore/JavaScriptCore.vcproj:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj:
* JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters:
* JavaScriptCore.xcodeproj/project.pbxproj:
* Target.pri:
* interpreter/CallFrame.cpp:
(JSC::CallFrame::begin):
(JSC::CallFrame::beginAt):
* interpreter/CallFrame.h:
(JSC::ExecState::setInlineCallFrame):
(ExecState):
(JSC::ExecState::end):
* interpreter/Interpreter.cpp:
(JSC::Interpreter::dumpRegisters):
(JSC::Interpreter::unwindCallFrame):
(JSC::Interpreter::getStackTrace):
(JSC::Interpreter::throwException):
(JSC::Interpreter::debug):
* interpreter/Interpreter.h:
(Interpreter):
* interpreter/StackIterator.cpp: Added.
(JSC::StackIterator::StackIterator):
(JSC::StackIterator::beginAt):
(JSC::StackIterator::gotoNextFrame):
- Based on the deleted Interpreter::findFunctionCallFrameFromVMCode().
(JSC::StackIterator::findFrameForFunction):
- Based on the deleted Interpreter::retrieveCallerFromVMCode().
(JSC::StackIterator::Frame::codeType):
- Based on the deleted getStackFrameCodeType().
(JSC::StackIterator::Frame::functionName):
- Based on StackFrame::friendlyFunctionName().
(JSC::StackIterator::Frame::sourceURL):
- Based on StackFrame::friendlySourceURL().
(JSC::StackIterator::Frame::toString):
- Based on StackFrame::toString().
(JSC::StackIterator::Frame::bytecodeOffset):
(JSC::StackIterator::Frame::line):
- Based on StackFrame::line().
(JSC::StackIterator::Frame::column):
- Based on StackFrame::column().
(JSC::StackIterator::Frame::arguments):
- Based on the deleted Interpreter::retrieveArgumentsFromVMCode().
(JSC::StackIterator::Frame::retrieveExpressionInfo):
- Based on StackFrame::expressionInfo().
(JSC::StackIterator::Frame::logicalFrame):
- Based on the now deleted CallFrame::trueCallFrame().
(JSC::StackIterator::Frame::logicalCallerFrame):
- Based on the now deleted CallFrame::trueCallerFrame().
(JSC::jitTypeName):
(JSC::printIndents):
(JSC::printif):
(JSC::StackIterator::Frame::print):
(debugPrintCallFrame):
- Prints the contents of the frame for debugging purposes.
There are 2 versions that can be used as follows:
1. When you have a valid StackIterator, you can print
the current frame's content using the print instance
method:
iter->print(indentLevel);
2. When you have a CallFrame* that you want to dump from a debugger
console, you can print its content as follows:
(gdb) call debugPrintCallFrame(callFrame)
A sample of the output looks like this:
frame 0x1510c70b0 {
name 'shouldBe'
sourceURL 'testapi.js'
hostFlag 0
isInlinedFrame 0
callee 0x15154efb0
returnPC 0x10ed0786d
callerFrame 0x1510c7058
logicalCallerFrame 0x1510c7058
rawLocationBits 27 0x1b
codeBlock 0x7fe79b037200
bytecodeOffset 27 0x1b / 210
line 46
column 20
jitType 3 <BaselineJIT> isOptimizingJIT 0
hasCodeOrigins 0
}
* interpreter/StackIterator.h: Added.
(StackIterator::Frame):
(JSC::StackIterator::Frame::create):
(JSC::StackIterator::Frame::isJSFrame):
(JSC::StackIterator::Frame::callFrame):
* interpreter/StackIteratorPrivate.h: Added.
(StackIterator):
(JSC::StackIterator::operator*):
(JSC::StackIterator::operator->):
(JSC::StackIterator::operator==):
(JSC::StackIterator::operator!=):
(JSC::StackIterator::operator++):
(JSC::StackIterator::end):
(JSC::StackIterator::empty):
* jsc.cpp:
(functionJSCStack):
* profiler/ProfileGenerator.cpp:
(JSC::ProfileGenerator::addParentForConsoleStart):
* profiler/ProfileNode.h:
(ProfileNode):
* runtime/JSFunction.cpp:
(JSC::retrieveArguments):
(JSC::JSFunction::argumentsGetter):
(JSC::skipOverBoundFunctions):
(JSC::retrieveCallerFunction):
(JSC::JSFunction::callerGetter):
(JSC::JSFunction::getOwnPropertyDescriptor):
(JSC::JSFunction::defineOwnProperty):
* runtime/JSGlobalObjectFunctions.cpp:
(JSC::globalFuncProtoGetter):
(JSC::globalFuncProtoSetter):
* runtime/ObjectConstructor.cpp:
(JSC::objectConstructorGetPrototypeOf):
* runtime/Operations.h:
Source/WebCore:
No new tests.
* ForwardingHeaders/interpreter/StackIterator.h: Added.
* bindings/js/JSXMLHttpRequestCustom.cpp:
(WebCore::JSXMLHttpRequest::send):
* bindings/js/ScriptCallStackFactory.cpp:
(WebCore::createScriptCallStack):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@153218 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/interpreter/StackIterator.cpp b/Source/JavaScriptCore/interpreter/StackIterator.cpp
new file mode 100644
index 0000000..e550070
--- /dev/null
+++ b/Source/JavaScriptCore/interpreter/StackIterator.cpp
@@ -0,0 +1,397 @@
+/*
+ * 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 "StackIterator.h"
+
+#include "Arguments.h"
+#include "CallFrameInlines.h"
+#include "Executable.h"
+#include "Operations.h"
+#include <wtf/DataLog.h>
+
+namespace JSC {
+
+StackIterator::StackIterator(CallFrame* frame, StackIterator::FrameFilter filter)
+ : m_filter(filter)
+{
+ ASSERT(frame);
+ m_frame = Frame::create(frame);
+ m_frame = m_frame->logicalFrame();
+}
+
+void StackIterator::gotoNextFrame()
+{
+ Frame* frame = m_frame;
+ while (frame) {
+ frame = frame->logicalCallerFrame();
+ if (!frame || !m_filter || !m_filter(frame))
+ break;
+ }
+ m_frame = frame;
+}
+
+void StackIterator::find(JSFunction* functionObj)
+{
+ ASSERT(functionObj);
+ JSObject* targetCallee = jsDynamicCast<JSObject*>(functionObj);
+ while (m_frame) {
+ if (m_frame->callee() == targetCallee)
+ break;
+ gotoNextFrame();
+ }
+}
+
+StackIterator::Frame::CodeType StackIterator::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 StackIterator::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 StackIterator::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 StackIterator::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();
+}
+
+unsigned StackIterator::Frame::bytecodeOffset()
+{
+ if (!isJSFrame())
+ return 0;
+ if (hasLocationAsCodeOriginIndex())
+ return bytecodeOffsetFromCodeOriginIndex();
+ return locationAsBytecodeOffset();
+}
+
+Arguments* StackIterator::Frame::arguments()
+{
+ CallFrame* callFrame = this->callFrame();
+ Arguments* arguments = Arguments::create(vm(), callFrame);
+ arguments->tearOff(callFrame);
+ return arguments;
+}
+
+void StackIterator::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 StackIterator::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();
+}
+
+
+StackIterator::Frame* StackIterator::Frame::logicalFrame()
+{
+#if !ENABLE(DFG_JIT)
+ return this;
+
+#else // !ENABLE(DFG_JIT)
+ if (isInlinedFrame())
+ return this;
+
+ // If I don't have a code block, then I'm not DFG code, so I'm the true call frame.
+ CodeBlock* codeBlock = this->codeBlock();
+ if (!codeBlock)
+ return this;
+
+ // If the code block does not have any code origins, then there was no inlining, so
+ // I'm done.
+ if (!codeBlock->hasCodeOrigins())
+ return this;
+
+ CodeBlock* outerMostCodeBlock = codeBlock;
+ unsigned index = locationAsCodeOriginIndex();
+ ASSERT(outerMostCodeBlock->canGetCodeOrigin(index));
+ if (!outerMostCodeBlock->canGetCodeOrigin(index)) {
+ // See above. In release builds, we try to protect ourselves from crashing even
+ // though stack walking will be goofed up.
+ return nullptr;
+ }
+
+ CodeOrigin codeOrigin = outerMostCodeBlock->codeOrigin(index);
+ if (!codeOrigin.inlineCallFrame)
+ return this; // Not currently in inlined code.
+
+ // We've got inlined frames. So, reify them so that the iterator can walk through them.
+ CallFrame* currFrame = this->callFrame();
+ CallFrame* innerMostLogicalFrame = currFrame + codeOrigin.inlineCallFrame->stackOffset;
+
+ CallFrame* logicalFrame = innerMostLogicalFrame;
+ while (logicalFrame != currFrame) {
+ InlineCallFrame* inlinedFrameInfo = codeOrigin.inlineCallFrame;
+
+ // Fill in the logical (i.e. inlined) frame
+ logicalFrame->setCodeBlock(inlinedFrameInfo->baselineCodeBlock());
+ logicalFrame->setInlineCallFrame(inlinedFrameInfo);
+ logicalFrame->setArgumentCountIncludingThis(inlinedFrameInfo->arguments.size());
+ logicalFrame->setLocationAsBytecodeOffset(codeOrigin.bytecodeIndex);
+ logicalFrame->setIsInlinedFrame();
+
+ JSFunction* callee = inlinedFrameInfo->callee.get();
+ if (callee) {
+ logicalFrame->setScope(callee->scope());
+ logicalFrame->setCallee(callee);
+ }
+
+ CodeOrigin* callerCodeOrigin = &inlinedFrameInfo->caller;
+ InlineCallFrame* callerInlinedFrameInfo = callerCodeOrigin->inlineCallFrame;
+ unsigned callerFrameOffset = callerInlinedFrameInfo ? callerInlinedFrameInfo->stackOffset : 0;
+ CallFrame* callerFrame = currFrame + callerFrameOffset;
+ logicalFrame->setCallerFrame(callerFrame);
+
+ codeOrigin = *callerCodeOrigin;
+ logicalFrame = callerFrame;
+ }
+
+ ASSERT(!innerMostLogicalFrame->hasHostCallFrameFlag());
+ return Frame::create(innerMostLogicalFrame);
+#endif // !ENABLE(DFG_JIT)
+}
+
+StackIterator::Frame* StackIterator::Frame::logicalCallerFrame()
+{
+ Frame* callerFrame = create(this->callerFrame()->removeHostCallFrameFlag());
+#if !ENABLE(DFG_JIT)
+ return callerFrame;
+
+#else // !ENABLE(DFG_JIT)
+ if (!isJSFrame() || !callerFrame)
+ return callerFrame;
+
+ // If I am known to be an inlined frame, then I've been reified already and
+ // have my caller.
+ if (isInlinedFrame())
+ return callerFrame;
+
+ // I am not an inlined frame. So the question is: is my caller a CallFrame
+ // that has inlines or a CallFrame that doesn't?
+
+ // If my caller is not a JS frame, it cannot have inlines, and we're done.
+ if (!callerFrame->isJSFrame())
+ return callerFrame;
+
+ ASSERT(!callerFrame->isInlinedFrame());
+ return callerFrame->logicalFrame();
+
+#endif // !ENABLE(DFG_JIT)
+}
+
+#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) && GCC_VERSION_AT_LEAST(4, 6, 0))
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+ WTF::dataLogFV(format, argList);
+
+#if COMPILER(CLANG) || (COMPILER(GCC) && GCC_VERSION_AT_LEAST(4, 6, 0))
+#pragma GCC diagnostic pop
+#endif
+
+ va_end(argList);
+}
+
+void StackIterator::Frame::print(int indentLevel)
+{
+ int i = indentLevel;
+
+ CodeBlock* codeBlock = this->codeBlock();
+ printif(i, "frame %p {\n", this);
+
+ CallFrame* callerFrame = this->callerFrame();
+ void* returnPC = hasReturnPC()? this->returnPC().value() : nullptr;
+
+ printif(i, " name '%s'\n", functionName().utf8().data());
+ printif(i, " sourceURL '%s'\n", sourceURL().utf8().data());
+ printif(i, " hostFlag %d\n", callerFrame->hasHostCallFrameFlag());
+ printif(i, " isInlinedFrame %d\n", isInlinedFrame());
+
+ if (isInlinedFrame())
+ printif(i, " InlineCallFrame %p\n", this->inlineCallFrame());
+
+ printif(i, " callee %p\n", callee());
+ printif(i, " returnPC %p\n", returnPC);
+ printif(i, " callerFrame %p\n", callerFrame->removeHostCallFrameFlag());
+ printif(i, " logicalCallerFrame %p\n", logicalCallerFrame());
+ printif(i, " rawLocationBits %u 0x%x\n", locationAsRawBits(), locationAsRawBits());
+ printif(i, " codeBlock %p\n", codeBlock);
+ if (codeBlock) {
+ JITCode::JITType jitType = codeBlock->jitType();
+ if (hasLocationAsBytecodeOffset()) {
+ unsigned bytecodeOffset = locationAsBytecodeOffset();
+ printif(i, " bytecodeOffset %u %p / %zu\n", bytecodeOffset, reinterpret_cast<void*>(bytecodeOffset), codeBlock->instructions().size());
+ } else {
+ unsigned codeOriginIndex = locationAsCodeOriginIndex();
+ printif(i, " codeOriginIdex %u %p / %zu\n", codeOriginIndex, reinterpret_cast<void*>(codeOriginIndex), codeBlock->codeOrigins().size());
+ }
+ 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));
+ 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());
+ }
+ }
+ printif(i, "}\n");
+}
+
+#endif // NDEBUG
+
+} // namespace JSC
+
+#ifndef NDEBUG
+// For use in the debugger
+void debugPrintCallFrame(JSC::CallFrame*);
+
+void debugPrintCallFrame(JSC::CallFrame* callFrame)
+{
+ if (!callFrame)
+ return;
+ JSC::StackIterator::Frame* frame = JSC::StackIterator::Frame::create(callFrame);
+ frame->print(2);
+}
+#endif // !NDEBUG