a9b6f3f7e6
As outlined in the design document linked below, we're removing the support for the non-standard Function.displayName property for the purpose of Error.stack and DevTools Inspector stack traces. The motivation here is that the negative lookup is costly, and we have Function.name as a standard alternative (configurable since ES6 for exactly this reason). I dediced to go with JSFunction::GetDebugName(), since JSFunction::GetName() was confusing in that it'd only get the "name" property's value if it's a data property, but not with accessors. JSFunction::GetDebugName() makes it clear that this is really a debug helper function and might not give you the "name" property value. Doc: https://bit.ly/devtools-function-displayName-removal Bug: v8:8742, chromium:1177685, chromium:1077657, chromium:17356 Change-Id: I7717585cbace626174b2f2ed2a4f68f75429eca1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2692189 Commit-Queue: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#72715}
793 lines
30 KiB
C++
793 lines
30 KiB
C++
// Copyright 2019 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "test/cctest/test-api.h"
|
|
|
|
#include "src/api/api-inl.h"
|
|
|
|
using ::v8::Array;
|
|
using ::v8::Context;
|
|
using ::v8::Local;
|
|
using ::v8::ObjectTemplate;
|
|
using ::v8::String;
|
|
using ::v8::TryCatch;
|
|
using ::v8::Value;
|
|
|
|
static v8::MaybeLocal<Value> PrepareStackTrace42(v8::Local<Context> context,
|
|
v8::Local<Value> error,
|
|
v8::Local<Array> trace) {
|
|
return v8::Number::New(context->GetIsolate(), 42);
|
|
}
|
|
|
|
static v8::MaybeLocal<Value> PrepareStackTraceThrow(v8::Local<Context> context,
|
|
v8::Local<Value> error,
|
|
v8::Local<Array> trace) {
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::Local<String> message = v8_str("42");
|
|
isolate->ThrowException(v8::Exception::Error(message));
|
|
return v8::MaybeLocal<Value>();
|
|
}
|
|
|
|
THREADED_TEST(IsolatePrepareStackTrace) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPrepareStackTraceCallback(PrepareStackTrace42);
|
|
|
|
v8::Local<Value> v = CompileRun("new Error().stack");
|
|
|
|
CHECK(v->IsNumber());
|
|
CHECK_EQ(v.As<v8::Number>()->Int32Value(context.local()).FromJust(), 42);
|
|
}
|
|
|
|
THREADED_TEST(IsolatePrepareStackTraceThrow) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
isolate->SetPrepareStackTraceCallback(PrepareStackTraceThrow);
|
|
|
|
v8::Local<Value> v = CompileRun("try { new Error().stack } catch (e) { e }");
|
|
|
|
CHECK(v->IsNativeError());
|
|
|
|
v8::Local<String> message = v8::Exception::CreateMessage(isolate, v)->Get();
|
|
|
|
CHECK(message->StrictEquals(v8_str("Uncaught Error: 42")));
|
|
}
|
|
|
|
static void ThrowV8Exception(const v8::FunctionCallbackInfo<v8::Value>& info) {
|
|
ApiTestFuzzer::Fuzz();
|
|
v8::Local<String> foo = v8_str("foo");
|
|
v8::Local<String> message = v8_str("message");
|
|
v8::Local<Value> error = v8::Exception::Error(foo);
|
|
CHECK(error->IsObject());
|
|
v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext();
|
|
CHECK(error.As<v8::Object>()
|
|
->Get(context, message)
|
|
.ToLocalChecked()
|
|
->Equals(context, foo)
|
|
.FromJust());
|
|
info.GetIsolate()->ThrowException(error);
|
|
info.GetReturnValue().SetUndefined();
|
|
}
|
|
|
|
THREADED_TEST(ExceptionCreateMessage) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
v8::Local<String> foo_str = v8_str("foo");
|
|
v8::Local<String> message_str = v8_str("message");
|
|
|
|
context->GetIsolate()->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
|
|
Local<v8::FunctionTemplate> fun =
|
|
v8::FunctionTemplate::New(context->GetIsolate(), ThrowV8Exception);
|
|
v8::Local<v8::Object> global = context->Global();
|
|
CHECK(global
|
|
->Set(context.local(), v8_str("throwV8Exception"),
|
|
fun->GetFunction(context.local()).ToLocalChecked())
|
|
.FromJust());
|
|
|
|
TryCatch try_catch(context->GetIsolate());
|
|
CompileRun(
|
|
"function f1() {\n"
|
|
" throwV8Exception();\n"
|
|
"};\n"
|
|
"f1();");
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
v8::Local<v8::Value> error = try_catch.Exception();
|
|
CHECK(error->IsObject());
|
|
CHECK(error.As<v8::Object>()
|
|
->Get(context.local(), message_str)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo_str)
|
|
.FromJust());
|
|
|
|
v8::Local<v8::Message> message =
|
|
v8::Exception::CreateMessage(context->GetIsolate(), error);
|
|
CHECK(!message.IsEmpty());
|
|
CHECK_EQ(2, message->GetLineNumber(context.local()).FromJust());
|
|
CHECK_EQ(2, message->GetStartColumn(context.local()).FromJust());
|
|
|
|
v8::Local<v8::StackTrace> stackTrace = message->GetStackTrace();
|
|
CHECK(!stackTrace.IsEmpty());
|
|
CHECK_EQ(2, stackTrace->GetFrameCount());
|
|
|
|
stackTrace = v8::Exception::GetStackTrace(error);
|
|
CHECK(!stackTrace.IsEmpty());
|
|
CHECK_EQ(2, stackTrace->GetFrameCount());
|
|
|
|
context->GetIsolate()->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
|
|
// Now check message location when SetCaptureStackTraceForUncaughtExceptions
|
|
// is false.
|
|
try_catch.Reset();
|
|
|
|
CompileRun(
|
|
"function f2() {\n"
|
|
" return throwV8Exception();\n"
|
|
"};\n"
|
|
"f2();");
|
|
CHECK(try_catch.HasCaught());
|
|
|
|
error = try_catch.Exception();
|
|
CHECK(error->IsObject());
|
|
CHECK(error.As<v8::Object>()
|
|
->Get(context.local(), message_str)
|
|
.ToLocalChecked()
|
|
->Equals(context.local(), foo_str)
|
|
.FromJust());
|
|
|
|
message = v8::Exception::CreateMessage(context->GetIsolate(), error);
|
|
CHECK(!message.IsEmpty());
|
|
CHECK_EQ(2, message->GetLineNumber(context.local()).FromJust());
|
|
CHECK_EQ(9, message->GetStartColumn(context.local()).FromJust());
|
|
|
|
// Should be empty stack trace.
|
|
stackTrace = message->GetStackTrace();
|
|
CHECK(stackTrace.IsEmpty());
|
|
CHECK(v8::Exception::GetStackTrace(error).IsEmpty());
|
|
}
|
|
|
|
// TODO(szuend): Re-enable as a threaded test once investigated and fixed.
|
|
// THREADED_TEST(StackTrace) {
|
|
TEST(StackTrace) {
|
|
LocalContext context;
|
|
v8::Isolate* isolate = context->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::TryCatch try_catch(isolate);
|
|
const char* source = "function foo() { FAIL.FAIL; }; foo();";
|
|
v8::Local<v8::String> src = v8_str(source);
|
|
v8::Local<v8::String> origin = v8_str("stack-trace-test");
|
|
v8::ScriptCompiler::Source script_source(src,
|
|
v8::ScriptOrigin(isolate, origin));
|
|
CHECK(v8::ScriptCompiler::CompileUnboundScript(context->GetIsolate(),
|
|
&script_source)
|
|
.ToLocalChecked()
|
|
->BindToCurrentContext()
|
|
->Run(context.local())
|
|
.IsEmpty());
|
|
CHECK(try_catch.HasCaught());
|
|
v8::String::Utf8Value stack(
|
|
context->GetIsolate(),
|
|
try_catch.StackTrace(context.local()).ToLocalChecked());
|
|
CHECK_NOT_NULL(strstr(*stack, "at foo (stack-trace-test"));
|
|
}
|
|
|
|
// Checks that a StackFrame has certain expected values.
|
|
static void checkStackFrame(const char* expected_script_name,
|
|
const char* expected_func_name,
|
|
int expected_line_number, int expected_column,
|
|
bool is_eval, bool is_constructor,
|
|
v8::Local<v8::StackFrame> frame) {
|
|
v8::HandleScope scope(CcTest::isolate());
|
|
v8::String::Utf8Value func_name(CcTest::isolate(), frame->GetFunctionName());
|
|
v8::String::Utf8Value script_name(CcTest::isolate(), frame->GetScriptName());
|
|
if (*script_name == nullptr) {
|
|
// The situation where there is no associated script, like for evals.
|
|
CHECK_NULL(expected_script_name);
|
|
} else {
|
|
CHECK_NOT_NULL(strstr(*script_name, expected_script_name));
|
|
}
|
|
if (!frame->GetFunctionName().IsEmpty()) {
|
|
CHECK_NOT_NULL(strstr(*func_name, expected_func_name));
|
|
}
|
|
CHECK_EQ(expected_line_number, frame->GetLineNumber());
|
|
CHECK_EQ(expected_column, frame->GetColumn());
|
|
CHECK_EQ(is_eval, frame->IsEval());
|
|
CHECK_EQ(is_constructor, frame->IsConstructor());
|
|
CHECK(frame->IsUserJavaScript());
|
|
}
|
|
|
|
static void AnalyzeStackInNativeCode(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
const char* origin = "capture-stack-trace-test";
|
|
const int kOverviewTest = 1;
|
|
const int kDetailedTest = 2;
|
|
const int kFunctionName = 3;
|
|
const int kFunctionNameAndDisplayName = 4;
|
|
const int kFunctionNameIsNotString = 5;
|
|
|
|
CHECK_EQ(args.Length(), 1);
|
|
|
|
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
|
v8::Isolate* isolate = args.GetIsolate();
|
|
int testGroup = args[0]->Int32Value(context).FromJust();
|
|
if (testGroup == kOverviewTest) {
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kOverview);
|
|
CHECK_EQ(4, stackTrace->GetFrameCount());
|
|
checkStackFrame(origin, "bar", 2, 10, false, false,
|
|
stackTrace->GetFrame(args.GetIsolate(), 0));
|
|
checkStackFrame(origin, "foo", 6, 3, false, true,
|
|
stackTrace->GetFrame(isolate, 1));
|
|
// This is the source string inside the eval which has the call to foo.
|
|
checkStackFrame(nullptr, "", 1, 1, true, false,
|
|
stackTrace->GetFrame(isolate, 2));
|
|
// The last frame is an anonymous function which has the initial eval call.
|
|
checkStackFrame(origin, "", 8, 7, false, false,
|
|
stackTrace->GetFrame(isolate, 3));
|
|
} else if (testGroup == kDetailedTest) {
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kDetailed);
|
|
CHECK_EQ(4, stackTrace->GetFrameCount());
|
|
checkStackFrame(origin, "bat", 4, 22, false, false,
|
|
stackTrace->GetFrame(isolate, 0));
|
|
checkStackFrame(origin, "baz", 8, 3, false, true,
|
|
stackTrace->GetFrame(isolate, 1));
|
|
bool is_eval = true;
|
|
// This is the source string inside the eval which has the call to baz.
|
|
checkStackFrame(nullptr, "", 1, 1, is_eval, false,
|
|
stackTrace->GetFrame(isolate, 2));
|
|
// The last frame is an anonymous function which has the initial eval call.
|
|
checkStackFrame(origin, "", 10, 1, false, false,
|
|
stackTrace->GetFrame(isolate, 3));
|
|
} else if (testGroup == kFunctionName) {
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 5, v8::StackTrace::kOverview);
|
|
CHECK_EQ(3, stackTrace->GetFrameCount());
|
|
checkStackFrame(nullptr, "function.name", 3, 1, true, false,
|
|
stackTrace->GetFrame(isolate, 0));
|
|
} else if (testGroup == kFunctionNameAndDisplayName) {
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 5, v8::StackTrace::kOverview);
|
|
CHECK_EQ(3, stackTrace->GetFrameCount());
|
|
checkStackFrame(nullptr, "function.name", 3, 1, true, false,
|
|
stackTrace->GetFrame(isolate, 0));
|
|
} else if (testGroup == kFunctionNameIsNotString) {
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 5, v8::StackTrace::kOverview);
|
|
CHECK_EQ(3, stackTrace->GetFrameCount());
|
|
checkStackFrame(nullptr, "", 3, 1, true, false,
|
|
stackTrace->GetFrame(isolate, 0));
|
|
}
|
|
}
|
|
|
|
// Tests the C++ StackTrace API.
|
|
// TODO(3074796): Reenable this as a THREADED_TEST once it passes.
|
|
// THREADED_TEST(CaptureStackTrace) {
|
|
TEST(CaptureStackTrace) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
v8::Local<v8::String> origin = v8_str("capture-stack-trace-test");
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "AnalyzeStackInNativeCode",
|
|
v8::FunctionTemplate::New(isolate, AnalyzeStackInNativeCode));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
// Test getting OVERVIEW information. Should ignore information that is not
|
|
// script name, function name, line number, and column offset.
|
|
const char* overview_source =
|
|
"function bar() {\n"
|
|
" var y; AnalyzeStackInNativeCode(1);\n"
|
|
"}\n"
|
|
"function foo() {\n"
|
|
"\n"
|
|
" bar();\n"
|
|
"}\n"
|
|
"var x;eval('new foo();');";
|
|
v8::Local<v8::String> overview_src = v8_str(overview_source);
|
|
v8::ScriptCompiler::Source script_source(overview_src,
|
|
v8::ScriptOrigin(isolate, origin));
|
|
v8::Local<Value> overview_result(
|
|
v8::ScriptCompiler::CompileUnboundScript(isolate, &script_source)
|
|
.ToLocalChecked()
|
|
->BindToCurrentContext()
|
|
->Run(context.local())
|
|
.ToLocalChecked());
|
|
CHECK(!overview_result.IsEmpty());
|
|
CHECK(overview_result->IsObject());
|
|
|
|
// Test getting DETAILED information.
|
|
const char* detailed_source =
|
|
"function bat() {AnalyzeStackInNativeCode(2);\n"
|
|
"}\n"
|
|
"\n"
|
|
"function baz() {\n"
|
|
" bat();\n"
|
|
"}\n"
|
|
"eval('new baz();');";
|
|
v8::Local<v8::String> detailed_src = v8_str(detailed_source);
|
|
// Make the script using a non-zero line and column offset.
|
|
v8::ScriptOrigin detailed_origin(isolate, origin, 3, 5);
|
|
v8::ScriptCompiler::Source script_source2(detailed_src, detailed_origin);
|
|
v8::Local<v8::UnboundScript> detailed_script(
|
|
v8::ScriptCompiler::CompileUnboundScript(isolate, &script_source2)
|
|
.ToLocalChecked());
|
|
v8::Local<Value> detailed_result(detailed_script->BindToCurrentContext()
|
|
->Run(context.local())
|
|
.ToLocalChecked());
|
|
CHECK(!detailed_result.IsEmpty());
|
|
CHECK(detailed_result->IsObject());
|
|
|
|
// Test using function.name and function.displayName in stack trace
|
|
const char function_name_source[] =
|
|
"function bar(function_name, display_name, testGroup) {\n"
|
|
" var f = new Function(`AnalyzeStackInNativeCode(${testGroup});`);\n"
|
|
" if (function_name) {\n"
|
|
" Object.defineProperty(f, 'name', { value: function_name });\n"
|
|
" }\n"
|
|
" if (display_name) {\n"
|
|
" f.displayName = display_name;"
|
|
" }\n"
|
|
" f()\n"
|
|
"}\n"
|
|
"bar('function.name', undefined, 3);\n"
|
|
"bar('function.name', 'function.displayName', 4);\n"
|
|
"bar(239, undefined, 5);\n";
|
|
v8::Local<v8::String> function_name_src =
|
|
v8::String::NewFromUtf8Literal(isolate, function_name_source);
|
|
v8::ScriptCompiler::Source script_source3(function_name_src,
|
|
v8::ScriptOrigin(isolate, origin));
|
|
v8::Local<Value> function_name_result(
|
|
v8::ScriptCompiler::CompileUnboundScript(isolate, &script_source3)
|
|
.ToLocalChecked()
|
|
->BindToCurrentContext()
|
|
->Run(context.local())
|
|
.ToLocalChecked());
|
|
CHECK(!function_name_result.IsEmpty());
|
|
}
|
|
|
|
static int report_count = 0;
|
|
static void StackTraceForUncaughtExceptionListener(
|
|
v8::Local<v8::Message> message, v8::Local<Value>) {
|
|
report_count++;
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
CHECK_EQ(2, stack_trace->GetFrameCount());
|
|
checkStackFrame("origin", "foo", 2, 3, false, false,
|
|
stack_trace->GetFrame(message->GetIsolate(), 0));
|
|
checkStackFrame("origin", "bar", 5, 3, false, false,
|
|
stack_trace->GetFrame(message->GetIsolate(), 1));
|
|
}
|
|
|
|
TEST(CaptureStackTraceForUncaughtException) {
|
|
report_count = 0;
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->AddMessageListener(StackTraceForUncaughtExceptionListener);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
|
|
CompileRunWithOrigin(
|
|
"function foo() {\n"
|
|
" throw 1;\n"
|
|
"};\n"
|
|
"function bar() {\n"
|
|
" foo();\n"
|
|
"};",
|
|
"origin");
|
|
v8::Local<v8::Object> global = env->Global();
|
|
Local<Value> trouble =
|
|
global->Get(env.local(), v8_str("bar")).ToLocalChecked();
|
|
CHECK(trouble->IsFunction());
|
|
CHECK(v8::Function::Cast(*trouble)
|
|
->Call(env.local(), global, 0, nullptr)
|
|
.IsEmpty());
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(StackTraceForUncaughtExceptionListener);
|
|
CHECK_EQ(1, report_count);
|
|
}
|
|
|
|
TEST(CaptureStackTraceForUncaughtExceptionAndSetters) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 1024,
|
|
v8::StackTrace::kDetailed);
|
|
|
|
CompileRun(
|
|
"var setters = ['column', 'lineNumber', 'scriptName',\n"
|
|
" 'scriptNameOrSourceURL', 'functionName', 'isEval',\n"
|
|
" 'isConstructor'];\n"
|
|
"for (var i = 0; i < setters.length; i++) {\n"
|
|
" var prop = setters[i];\n"
|
|
" Object.prototype.__defineSetter__(prop, function() { throw prop; });\n"
|
|
"}\n");
|
|
CompileRun("throw 'exception';");
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
}
|
|
|
|
static void StackTraceFunctionNameListener(v8::Local<v8::Message> message,
|
|
v8::Local<Value>) {
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
v8::Isolate* isolate = message->GetIsolate();
|
|
CHECK_EQ(5, stack_trace->GetFrameCount());
|
|
checkStackFrame("origin", "foo:0", 4, 7, false, false,
|
|
stack_trace->GetFrame(isolate, 0));
|
|
checkStackFrame("origin", "foo:1", 5, 27, false, false,
|
|
stack_trace->GetFrame(isolate, 1));
|
|
checkStackFrame("origin", "foo", 5, 27, false, false,
|
|
stack_trace->GetFrame(isolate, 2));
|
|
checkStackFrame("origin", "foo", 5, 27, false, false,
|
|
stack_trace->GetFrame(isolate, 3));
|
|
checkStackFrame("origin", "", 1, 14, false, false,
|
|
stack_trace->GetFrame(isolate, 4));
|
|
}
|
|
|
|
TEST(GetStackTraceContainsFunctionsWithFunctionName) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
|
|
CompileRunWithOrigin(
|
|
"function gen(name, counter) {\n"
|
|
" var f = function foo() {\n"
|
|
" if (counter === 0)\n"
|
|
" throw 1;\n"
|
|
" gen(name, counter - 1)();\n"
|
|
" };\n"
|
|
" if (counter == 3) {\n"
|
|
" Object.defineProperty(f, 'name', {get: function(){ throw 239; }});\n"
|
|
" } else {\n"
|
|
" Object.defineProperty(f, 'name', {writable:true});\n"
|
|
" if (counter == 2)\n"
|
|
" f.name = 42;\n"
|
|
" else\n"
|
|
" f.name = name + ':' + counter;\n"
|
|
" }\n"
|
|
" return f;\n"
|
|
"};",
|
|
"origin");
|
|
|
|
isolate->AddMessageListener(StackTraceFunctionNameListener);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
CompileRunWithOrigin("gen('foo', 3)();", "origin");
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(StackTraceFunctionNameListener);
|
|
}
|
|
|
|
static void RethrowStackTraceHandler(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
// Use the frame where JavaScript is called from.
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
CHECK(!stack_trace.IsEmpty());
|
|
int frame_count = stack_trace->GetFrameCount();
|
|
CHECK_EQ(3, frame_count);
|
|
int line_number[] = {1, 2, 5};
|
|
for (int i = 0; i < frame_count; i++) {
|
|
CHECK_EQ(line_number[i],
|
|
stack_trace->GetFrame(message->GetIsolate(), i)->GetLineNumber());
|
|
}
|
|
}
|
|
|
|
// Test that we only return the stack trace at the site where the exception
|
|
// is first thrown (not where it is rethrown).
|
|
TEST(RethrowStackTrace) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
// We make sure that
|
|
// - the stack trace of the ReferenceError in g() is reported.
|
|
// - the stack trace is not overwritten when e1 is rethrown by t().
|
|
// - the stack trace of e2 does not overwrite that of e1.
|
|
const char* source =
|
|
"function g() { error; } \n"
|
|
"function f() { g(); } \n"
|
|
"function t(e) { throw e; } \n"
|
|
"try { \n"
|
|
" f(); \n"
|
|
"} catch (e1) { \n"
|
|
" try { \n"
|
|
" error; \n"
|
|
" } catch (e2) { \n"
|
|
" t(e1); \n"
|
|
" } \n"
|
|
"} \n";
|
|
isolate->AddMessageListener(RethrowStackTraceHandler);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
CompileRun(source);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(RethrowStackTraceHandler);
|
|
}
|
|
|
|
static void RethrowPrimitiveStackTraceHandler(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
CHECK(!stack_trace.IsEmpty());
|
|
int frame_count = stack_trace->GetFrameCount();
|
|
CHECK_EQ(2, frame_count);
|
|
int line_number[] = {3, 7};
|
|
for (int i = 0; i < frame_count; i++) {
|
|
CHECK_EQ(line_number[i],
|
|
stack_trace->GetFrame(message->GetIsolate(), i)->GetLineNumber());
|
|
}
|
|
}
|
|
|
|
// Test that we do not recognize identity for primitive exceptions.
|
|
TEST(RethrowPrimitiveStackTrace) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
// We do not capture stack trace for non Error objects on creation time.
|
|
// Instead, we capture the stack trace on last throw.
|
|
const char* source =
|
|
"function g() { throw 404; } \n"
|
|
"function f() { g(); } \n"
|
|
"function t(e) { throw e; } \n"
|
|
"try { \n"
|
|
" f(); \n"
|
|
"} catch (e1) { \n"
|
|
" t(e1) \n"
|
|
"} \n";
|
|
isolate->AddMessageListener(RethrowPrimitiveStackTraceHandler);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
CompileRun(source);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(RethrowPrimitiveStackTraceHandler);
|
|
}
|
|
|
|
static void RethrowExistingStackTraceHandler(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
// Use the frame where JavaScript is called from.
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
CHECK(!stack_trace.IsEmpty());
|
|
CHECK_EQ(1, stack_trace->GetFrameCount());
|
|
CHECK_EQ(1, stack_trace->GetFrame(message->GetIsolate(), 0)->GetLineNumber());
|
|
}
|
|
|
|
// Test that the stack trace is captured when the error object is created and
|
|
// not where it is thrown.
|
|
TEST(RethrowExistingStackTrace) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
const char* source =
|
|
"var e = new Error(); \n"
|
|
"throw e; \n";
|
|
isolate->AddMessageListener(RethrowExistingStackTraceHandler);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
CompileRun(source);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(RethrowExistingStackTraceHandler);
|
|
}
|
|
|
|
static void RethrowBogusErrorStackTraceHandler(v8::Local<v8::Message> message,
|
|
v8::Local<v8::Value> data) {
|
|
// Use the frame where JavaScript is called from.
|
|
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
|
|
CHECK(!stack_trace.IsEmpty());
|
|
CHECK_EQ(1, stack_trace->GetFrameCount());
|
|
CHECK_EQ(2, stack_trace->GetFrame(message->GetIsolate(), 0)->GetLineNumber());
|
|
}
|
|
|
|
// Test that the stack trace is captured where the bogus Error object is thrown.
|
|
TEST(RethrowBogusErrorStackTrace) {
|
|
LocalContext env;
|
|
v8::Isolate* isolate = env->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
const char* source =
|
|
"var e = {__proto__: new Error()} \n"
|
|
"throw e; \n";
|
|
isolate->AddMessageListener(RethrowBogusErrorStackTraceHandler);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true);
|
|
CompileRun(source);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(false);
|
|
isolate->RemoveMessageListeners(RethrowBogusErrorStackTraceHandler);
|
|
}
|
|
|
|
void AnalyzeStackOfEvalWithSourceURL(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kDetailed);
|
|
CHECK_EQ(5, stackTrace->GetFrameCount());
|
|
v8::Local<v8::String> url = v8_str("eval_url");
|
|
for (int i = 0; i < 3; i++) {
|
|
v8::Local<v8::String> name =
|
|
stackTrace->GetFrame(args.GetIsolate(), i)->GetScriptNameOrSourceURL();
|
|
CHECK(!name.IsEmpty());
|
|
CHECK(url->Equals(args.GetIsolate()->GetCurrentContext(), name).FromJust());
|
|
}
|
|
}
|
|
|
|
TEST(SourceURLInStackTrace) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(
|
|
isolate, "AnalyzeStackOfEvalWithSourceURL",
|
|
v8::FunctionTemplate::New(isolate, AnalyzeStackOfEvalWithSourceURL));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
const char* source =
|
|
"function outer() {\n"
|
|
"function bar() {\n"
|
|
" AnalyzeStackOfEvalWithSourceURL();\n"
|
|
"}\n"
|
|
"function foo() {\n"
|
|
"\n"
|
|
" bar();\n"
|
|
"}\n"
|
|
"foo();\n"
|
|
"}\n"
|
|
"eval('(' + outer +')()%s');";
|
|
|
|
i::ScopedVector<char> code(1024);
|
|
i::SNPrintF(code, source, "//# sourceURL=eval_url");
|
|
CHECK(CompileRun(code.begin())->IsUndefined());
|
|
i::SNPrintF(code, source, "//@ sourceURL=eval_url");
|
|
CHECK(CompileRun(code.begin())->IsUndefined());
|
|
}
|
|
|
|
static int scriptIdInStack[2];
|
|
|
|
void AnalyzeScriptIdInStack(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kScriptId);
|
|
CHECK_EQ(2, stackTrace->GetFrameCount());
|
|
for (int i = 0; i < 2; i++) {
|
|
scriptIdInStack[i] =
|
|
stackTrace->GetFrame(args.GetIsolate(), i)->GetScriptId();
|
|
}
|
|
}
|
|
|
|
TEST(ScriptIdInStackTrace) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "AnalyzeScriptIdInStack",
|
|
v8::FunctionTemplate::New(isolate, AnalyzeScriptIdInStack));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
v8::Local<v8::String> scriptSource = v8_str(
|
|
"function foo() {\n"
|
|
" AnalyzeScriptIdInStack();"
|
|
"}\n"
|
|
"foo();\n");
|
|
v8::Local<v8::Script> script = CompileWithOrigin(scriptSource, "test", false);
|
|
script->Run(context.local()).ToLocalChecked();
|
|
for (int i = 0; i < 2; i++) {
|
|
CHECK_NE(scriptIdInStack[i], v8::Message::kNoScriptIdInfo);
|
|
CHECK_EQ(scriptIdInStack[i], script->GetUnboundScript()->GetId());
|
|
}
|
|
}
|
|
|
|
void AnalyzeStackOfInlineScriptWithSourceURL(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kDetailed);
|
|
CHECK_EQ(4, stackTrace->GetFrameCount());
|
|
v8::Local<v8::String> url = v8_str("source_url");
|
|
for (int i = 0; i < 3; i++) {
|
|
v8::Local<v8::String> name =
|
|
stackTrace->GetFrame(args.GetIsolate(), i)->GetScriptNameOrSourceURL();
|
|
CHECK(!name.IsEmpty());
|
|
CHECK(url->Equals(args.GetIsolate()->GetCurrentContext(), name).FromJust());
|
|
}
|
|
}
|
|
|
|
TEST(InlineScriptWithSourceURLInStackTrace) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "AnalyzeStackOfInlineScriptWithSourceURL",
|
|
v8::FunctionTemplate::New(
|
|
CcTest::isolate(), AnalyzeStackOfInlineScriptWithSourceURL));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
const char* source =
|
|
"function outer() {\n"
|
|
"function bar() {\n"
|
|
" AnalyzeStackOfInlineScriptWithSourceURL();\n"
|
|
"}\n"
|
|
"function foo() {\n"
|
|
"\n"
|
|
" bar();\n"
|
|
"}\n"
|
|
"foo();\n"
|
|
"}\n"
|
|
"outer()\n%s";
|
|
|
|
i::ScopedVector<char> code(1024);
|
|
i::SNPrintF(code, source, "//# sourceURL=source_url");
|
|
CHECK(CompileRunWithOrigin(code.begin(), "url", 0, 1)->IsUndefined());
|
|
i::SNPrintF(code, source, "//@ sourceURL=source_url");
|
|
CHECK(CompileRunWithOrigin(code.begin(), "url", 0, 1)->IsUndefined());
|
|
}
|
|
|
|
void AnalyzeStackOfDynamicScriptWithSourceURL(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
v8::HandleScope scope(args.GetIsolate());
|
|
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
|
|
args.GetIsolate(), 10, v8::StackTrace::kDetailed);
|
|
CHECK_EQ(4, stackTrace->GetFrameCount());
|
|
v8::Local<v8::String> url = v8_str("source_url");
|
|
for (int i = 0; i < 3; i++) {
|
|
v8::Local<v8::String> name =
|
|
stackTrace->GetFrame(args.GetIsolate(), i)->GetScriptNameOrSourceURL();
|
|
CHECK(!name.IsEmpty());
|
|
CHECK(url->Equals(args.GetIsolate()->GetCurrentContext(), name).FromJust());
|
|
}
|
|
}
|
|
|
|
TEST(DynamicWithSourceURLInStackTrace) {
|
|
v8::Isolate* isolate = CcTest::isolate();
|
|
v8::HandleScope scope(isolate);
|
|
Local<ObjectTemplate> templ = ObjectTemplate::New(isolate);
|
|
templ->Set(isolate, "AnalyzeStackOfDynamicScriptWithSourceURL",
|
|
v8::FunctionTemplate::New(
|
|
CcTest::isolate(), AnalyzeStackOfDynamicScriptWithSourceURL));
|
|
LocalContext context(nullptr, templ);
|
|
|
|
const char* source =
|
|
"function outer() {\n"
|
|
"function bar() {\n"
|
|
" AnalyzeStackOfDynamicScriptWithSourceURL();\n"
|
|
"}\n"
|
|
"function foo() {\n"
|
|
"\n"
|
|
" bar();\n"
|
|
"}\n"
|
|
"foo();\n"
|
|
"}\n"
|
|
"outer()\n%s";
|
|
|
|
i::ScopedVector<char> code(1024);
|
|
i::SNPrintF(code, source, "//# sourceURL=source_url");
|
|
CHECK(CompileRunWithOrigin(code.begin(), "url", 0, 0)->IsUndefined());
|
|
i::SNPrintF(code, source, "//@ sourceURL=source_url");
|
|
CHECK(CompileRunWithOrigin(code.begin(), "url", 0, 0)->IsUndefined());
|
|
}
|
|
|
|
TEST(DynamicWithSourceURLInStackTraceString) {
|
|
LocalContext context;
|
|
v8::HandleScope scope(context->GetIsolate());
|
|
|
|
const char* source =
|
|
"function outer() {\n"
|
|
" function foo() {\n"
|
|
" FAIL.FAIL;\n"
|
|
" }\n"
|
|
" foo();\n"
|
|
"}\n"
|
|
"outer()\n%s";
|
|
|
|
i::ScopedVector<char> code(1024);
|
|
i::SNPrintF(code, source, "//# sourceURL=source_url");
|
|
v8::TryCatch try_catch(context->GetIsolate());
|
|
CompileRunWithOrigin(code.begin(), "", 0, 0);
|
|
CHECK(try_catch.HasCaught());
|
|
v8::String::Utf8Value stack(
|
|
context->GetIsolate(),
|
|
try_catch.StackTrace(context.local()).ToLocalChecked());
|
|
CHECK_NOT_NULL(strstr(*stack, "at foo (source_url:3:5)"));
|
|
}
|
|
|
|
TEST(CaptureStackTraceForStackOverflow) {
|
|
v8::internal::FLAG_stack_size = 150;
|
|
LocalContext current;
|
|
v8::Isolate* isolate = current->GetIsolate();
|
|
v8::HandleScope scope(isolate);
|
|
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 10,
|
|
v8::StackTrace::kDetailed);
|
|
v8::TryCatch try_catch(isolate);
|
|
CompileRun("(function f(x) { f(x+1); })(0)");
|
|
CHECK(try_catch.HasCaught());
|
|
}
|