Bug 1306121 - Add support for emulating V8 stack frame formatting to SpiderMonkey; r=fitzgen

This commit is contained in:
Ehsan Akhgari 2016-09-30 15:26:12 -04:00
Родитель b7aec8ba57
Коммит cf60002f3c
9 изменённых файлов: 248 добавлений и 21 удалений

Просмотреть файл

@ -29,3 +29,8 @@ function ErrorToString()
/* Step 11. */
return name + ": " + msg;
}
function ErrorToStringWithTrailingNewline()
{
return FUN_APPLY(ErrorToString, this, []) + "\n";
}

Просмотреть файл

@ -108,10 +108,108 @@ BEGIN_TEST(testSavedStacks_RangeBasedForLoops)
}
CHECK(rf == nullptr);
// Stack string
const char* SpiderMonkeyStack = "three@filename.js:4:14\n"
"two@filename.js:3:22\n"
"one@filename.js:2:20\n"
"@filename.js:1:11\n";
const char* V8Stack = " at three (filename.js:4:14)\n"
" at two (filename.js:3:22)\n"
" at one (filename.js:2:20)\n"
" at filename.js:1:11";
struct {
js::StackFormat format;
const char* expected;
} expectations[] = {
{js::StackFormat::Default, SpiderMonkeyStack},
{js::StackFormat::SpiderMonkey, SpiderMonkeyStack},
{js::StackFormat::V8, V8Stack}
};
auto CheckStacks = [&]() {
for (auto& expectation : expectations) {
JS::RootedString str(cx);
CHECK(JS::BuildStackString(cx, savedFrame, &str, 0, expectation.format));
JSLinearString* lin = str->ensureLinear(cx);
CHECK(lin);
CHECK(js::StringEqualsAscii(lin, expectation.expected));
}
return true;
};
CHECK(CheckStacks());
js::SetStackFormat(cx, js::StackFormat::V8);
expectations[0].expected = V8Stack;
CHECK(CheckStacks());
return true;
}
END_TEST(testSavedStacks_RangeBasedForLoops)
BEGIN_TEST(testSavedStacks_ErrorStackSpiderMonkey)
{
JS::RootedValue val(cx);
CHECK(evaluate("(function one() { \n" // 1
" return (function two() { \n" // 2
" return (function three() { \n" // 3
" return new Error('foo'); \n" // 4
" }()); \n" // 5
" }()); \n" // 6
"}()).stack \n", // 7
"filename.js",
1,
&val));
CHECK(val.isString());
JS::RootedString stack(cx, val.toString());
// Stack string
const char* SpiderMonkeyStack = "three@filename.js:4:14\n"
"two@filename.js:3:22\n"
"one@filename.js:2:20\n"
"@filename.js:1:11\n";
JSLinearString* lin = stack->ensureLinear(cx);
CHECK(lin);
CHECK(js::StringEqualsAscii(lin, SpiderMonkeyStack));
return true;
}
END_TEST(testSavedStacks_ErrorStackSpiderMonkey)
BEGIN_TEST(testSavedStacks_ErrorStackV8)
{
js::SetStackFormat(cx, js::StackFormat::V8);
JS::RootedValue val(cx);
CHECK(evaluate("(function one() { \n" // 1
" return (function two() { \n" // 2
" return (function three() { \n" // 3
" return new Error('foo'); \n" // 4
" }()); \n" // 5
" }()); \n" // 6
"}()).stack \n", // 7
"filename.js",
1,
&val));
CHECK(val.isString());
JS::RootedString stack(cx, val.toString());
// Stack string
const char* V8Stack = "Error: foo\n"
" at three (filename.js:4:14)\n"
" at two (filename.js:3:22)\n"
" at one (filename.js:2:20)\n"
" at filename.js:1:11";
JSLinearString* lin = stack->ensureLinear(cx);
CHECK(lin);
CHECK(js::StringEqualsAscii(lin, V8Stack));
return true;
}
END_TEST(testSavedStacks_ErrorStackV8)
BEGIN_TEST(testSavedStacks_selfHostedFrames)
{
CHECK(js::DefineTestingFunctions(cx, global, false, false));

Просмотреть файл

@ -6706,3 +6706,15 @@ JS::GCThingTraceKind(void* thing)
MOZ_ASSERT(thing);
return static_cast<js::gc::Cell*>(thing)->getTraceKind();
}
JS_PUBLIC_API(void)
js::SetStackFormat(JSContext* cx, js::StackFormat format)
{
cx->setStackFormat(format);
}
JS_PUBLIC_API(js::StackFormat)
js::GetStackFormat(JSContext* cx)
{
return cx->stackFormat();
}

Просмотреть файл

@ -5848,6 +5848,25 @@ JS_DecodeScript(JSContext* cx, const void* data, uint32_t length);
extern JS_PUBLIC_API(JSObject*)
JS_DecodeInterpretedFunction(JSContext* cx, const void* data, uint32_t length);
namespace js {
enum class StackFormat { SpiderMonkey, V8, Default };
/*
* Sets the format used for stringifying Error stacks.
*
* The default format is StackFormat::SpiderMonkey. Use StackFormat::V8
* in order to emulate V8's stack formatting. StackFormat::Default can't be
* used here.
*/
extern JS_PUBLIC_API(void)
SetStackFormat(JSContext* cx, StackFormat format);
extern JS_PUBLIC_API(StackFormat)
GetStackFormat(JSContext* cx);
}
namespace JS {
/*
@ -6259,7 +6278,8 @@ GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject
* each line.
*/
extern JS_PUBLIC_API(bool)
BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp, size_t indent = 0);
BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
size_t indent = 0, js::StackFormat stackFormat = js::StackFormat::Default);
/**
* Return true iff the given object is either a SavedFrame object or wrapper

Просмотреть файл

@ -95,6 +95,7 @@
macro(enumerable, enumerable, "enumerable") \
macro(enumerate, enumerate, "enumerate") \
macro(era, era, "era") \
macro(ErrorToStringWithTrailingNewline, ErrorToStringWithTrailingNewline, "ErrorToStringWithTrailingNewline") \
macro(escape, escape, "escape") \
macro(eval, eval, "eval") \
macro(exec, exec, "exec") \

Просмотреть файл

@ -216,6 +216,26 @@ js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp)
RootedString stackString(cx);
if (!BuildStackString(cx, savedFrameObj, &stackString))
return false;
if (cx->stackFormat() == js::StackFormat::V8) {
// When emulating V8 stack frames, we also need to prepend the
// stringified Error to the stack string.
HandlePropertyName name = cx->names().ErrorToStringWithTrailingNewline;
RootedValue val(cx);
if (!GlobalObject::getSelfHostedFunction(cx, cx->global(), name, name, 0, &val))
return false;
RootedValue rval(cx);
if (!js::Call(cx, val, args.thisv(), &rval))
return false;
if (!rval.isString())
return false;
RootedString stringified(cx, rval.toString());
stackString = ConcatStrings<CanGC>(cx, stringified, stackString);
}
args.rval().setString(stackString);
return true;
}

Просмотреть файл

@ -244,7 +244,9 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
debuggerMallocSizeOf(ReturnZeroSize),
lastAnimationTime(0),
performanceMonitoring(thisFromCtor()),
ionLazyLinkListSize_(0)
ionLazyLinkListSize_(0),
stackFormat_(parentRuntime ? js::StackFormat::Default
: js::StackFormat::SpiderMonkey)
{
setGCStoreBufferPtr(&gc.storeBuffer);

Просмотреть файл

@ -1265,6 +1265,27 @@ struct JSRuntime : public JS::shadow::Runtime,
void ionLazyLinkListRemove(js::jit::IonBuilder* builder);
void ionLazyLinkListAdd(js::jit::IonBuilder* builder);
private:
/* The stack format for the current runtime. Only valid on non-child
* runtimes. */
mozilla::Atomic<js::StackFormat, mozilla::ReleaseAcquire> stackFormat_;
public:
js::StackFormat stackFormat() const {
const JSRuntime* rt = this;
while (rt->parentRuntime) {
MOZ_ASSERT(rt->stackFormat_ == js::StackFormat::Default);
rt = rt->parentRuntime;
}
MOZ_ASSERT(rt->stackFormat_ != js::StackFormat::Default);
return rt->stackFormat_;
}
void setStackFormat(js::StackFormat format) {
MOZ_ASSERT(!parentRuntime);
MOZ_ASSERT(format != js::StackFormat::Default);
stackFormat_ = format;
}
};
namespace js {

Просмотреть файл

@ -892,8 +892,52 @@ GetSavedFrameParent(JSContext* cx, HandleObject savedFrame, MutableHandleObject
return SavedFrameResult::Ok;
}
static bool
FormatSpiderMonkeyStackFrame(JSContext* cx, js::StringBuffer& sb,
js::HandleSavedFrame frame, size_t indent,
bool skippedAsync)
{
RootedString asyncCause(cx, frame->getAsyncCause());
if (!asyncCause && skippedAsync)
asyncCause.set(cx->names().Async);
js::RootedAtom name(cx, frame->getFunctionDisplayName());
return (!indent || sb.appendN(' ', indent))
&& (!asyncCause || (sb.append(asyncCause) && sb.append('*')))
&& (!name || sb.append(name))
&& sb.append('@')
&& sb.append(frame->getSource())
&& sb.append(':')
&& NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
&& sb.append(':')
&& NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
&& sb.append('\n');
}
static bool
FormatV8StackFrame(JSContext* cx, js::StringBuffer& sb,
js::HandleSavedFrame frame, size_t indent, bool lastFrame)
{
js::RootedAtom name(cx, frame->getFunctionDisplayName());
return sb.appendN(' ', indent + 4)
&& sb.append('a')
&& sb.append('t')
&& sb.append(' ')
&& (!name || (sb.append(name) &&
sb.append(' ') &&
sb.append('(')))
&& sb.append(frame->getSource())
&& sb.append(':')
&& NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
&& sb.append(':')
&& NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
&& (!name || sb.append(')'))
&& (lastFrame || sb.append('\n'));
}
JS_PUBLIC_API(bool)
BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp, size_t indent)
BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
size_t indent, js::StackFormat format)
{
AssertHeapIsIdle(cx);
CHECK_REQUEST(cx);
@ -901,6 +945,10 @@ BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
js::StringBuffer sb(cx);
if (format == js::StackFormat::Default)
format = cx->stackFormat();
MOZ_ASSERT(format != js::StackFormat::Default);
// Enter a new block to constrain the scope of possibly entering the stack's
// compartment. This ensures that when we finish the StringBuffer, we are
// back in the cx's original compartment, and fulfill our contract with
@ -920,27 +968,27 @@ BuildStackString(JSContext* cx, HandleObject stack, MutableHandleString stringp,
MOZ_ASSERT(SavedFrameSubsumedByCaller(cx, frame));
MOZ_ASSERT(!frame->isSelfHosted(cx));
RootedString asyncCause(cx, frame->getAsyncCause());
if (!asyncCause && skippedAsync)
asyncCause.set(cx->names().Async);
parent = frame->getParent();
bool skippedNextAsync;
js::RootedSavedFrame nextFrame(cx, js::GetFirstSubsumedFrame(cx, parent,
SavedFrameSelfHosted::Exclude, skippedNextAsync));
js::RootedAtom name(cx, frame->getFunctionDisplayName());
if ((indent && !sb.appendN(' ', indent))
|| (asyncCause && (!sb.append(asyncCause) || !sb.append('*')))
|| (name && !sb.append(name))
|| !sb.append('@')
|| !sb.append(frame->getSource())
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getLine()), sb)
|| !sb.append(':')
|| !NumberValueToStringBuffer(cx, NumberValue(frame->getColumn()), sb)
|| !sb.append('\n'))
{
return false;
switch (format) {
case js::StackFormat::SpiderMonkey:
if (!FormatSpiderMonkeyStackFrame(cx, sb, frame, indent, skippedAsync))
return false;
break;
case js::StackFormat::V8:
if (!FormatV8StackFrame(cx, sb, frame, indent, !nextFrame))
return false;
break;
case js::StackFormat::Default:
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unexpected value");
break;
}
parent = frame->getParent();
frame = js::GetFirstSubsumedFrame(cx, parent, SavedFrameSelfHosted::Exclude, skippedAsync);
frame = nextFrame;
skippedAsync = skippedNextAsync;
} while (frame);
}