Commit c613eb97 authored by Alex Kodat's avatar Alex Kodat Committed by Commit Bot

[api] Add StackFrame GetScriptSource and GetScriptSourceMappingURL

These simplify production of extra information in stack traces or
dereferencing source maps in processing stack traces. While these
can be managed externally, this can be very complicated in
environments where scripts come from many different sources,
possibly not even under embedder control. Since V8 already has
easy access to this information, it's nice to share it with
embedders.

Bug: v8:11509
Change-Id: Ic5a1685adf4cdf456bdf7191ce815f728cf491e2
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2724571Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#73148}
parent d0d9a33b
...@@ -2167,6 +2167,8 @@ class V8_EXPORT Message { ...@@ -2167,6 +2167,8 @@ class V8_EXPORT Message {
*/ */
Isolate* GetIsolate() const; Isolate* GetIsolate() const;
V8_WARN_UNUSED_RESULT MaybeLocal<String> GetSource(
Local<Context> context) const;
V8_WARN_UNUSED_RESULT MaybeLocal<String> GetSourceLine( V8_WARN_UNUSED_RESULT MaybeLocal<String> GetSourceLine(
Local<Context> context) const; Local<Context> context) const;
...@@ -2341,6 +2343,17 @@ class V8_EXPORT StackFrame { ...@@ -2341,6 +2343,17 @@ class V8_EXPORT StackFrame {
*/ */
Local<String> GetScriptNameOrSourceURL() const; Local<String> GetScriptNameOrSourceURL() const;
/**
* Returns the source of the script for the function for this StackFrame.
*/
Local<String> GetScriptSource() const;
/**
* Returns the source mapping URL (if one is present) of the script for
* the function for this StackFrame.
*/
Local<String> GetScriptSourceMappingURL() const;
/** /**
* Returns the name of the function associated with this stack frame. * Returns the name of the function associated with this stack frame.
*/ */
......
...@@ -2902,6 +2902,15 @@ bool Message::IsOpaque() const { ...@@ -2902,6 +2902,15 @@ bool Message::IsOpaque() const {
return self->script().origin_options().IsOpaque(); return self->script().origin_options().IsOpaque();
} }
MaybeLocal<String> Message::GetSource(Local<Context> context) const {
auto self = Utils::OpenHandle(this);
i::Isolate* isolate = self->GetIsolate();
ENTER_V8_NO_SCRIPT_NO_EXCEPTION(isolate);
EscapableHandleScope handle_scope(reinterpret_cast<Isolate*>(isolate));
i::Handle<i::String> source(self->GetSource(), isolate);
RETURN_ESCAPED(Utils::ToLocal(source));
}
MaybeLocal<String> Message::GetSourceLine(Local<Context> context) const { MaybeLocal<String> Message::GetSourceLine(Local<Context> context) const {
auto self = Utils::OpenHandle(this); auto self = Utils::OpenHandle(this);
i::Isolate* isolate = self->GetIsolate(); i::Isolate* isolate = self->GetIsolate();
...@@ -2971,6 +2980,23 @@ Local<String> StackFrame::GetScriptNameOrSourceURL() const { ...@@ -2971,6 +2980,23 @@ Local<String> StackFrame::GetScriptNameOrSourceURL() const {
return Local<String>::Cast(Utils::ToLocal(name_or_url)); return Local<String>::Cast(Utils::ToLocal(name_or_url));
} }
Local<String> StackFrame::GetScriptSource() const {
auto self = Utils::OpenHandle(this);
auto isolate = self->GetIsolate();
i::Handle<i::Object> source(self->GetScriptSource(), isolate);
if (!source->IsString()) return {};
return Local<String>::Cast(Utils::ToLocal(source));
}
Local<String> StackFrame::GetScriptSourceMappingURL() const {
auto self = Utils::OpenHandle(this);
auto isolate = self->GetIsolate();
i::Handle<i::Object> sourceMappingURL(self->GetScriptSourceMappingURL(),
isolate);
if (!sourceMappingURL->IsString()) return {};
return Local<String>::Cast(Utils::ToLocal(sourceMappingURL));
}
Local<String> StackFrame::GetFunctionName() const { Local<String> StackFrame::GetFunctionName() const {
auto self = Utils::OpenHandle(this); auto self = Utils::OpenHandle(this);
auto name = i::StackFrameInfo::GetFunctionName(self); auto name = i::StackFrameInfo::GetFunctionName(self);
......
...@@ -5314,6 +5314,15 @@ int JSMessageObject::GetColumnNumber() const { ...@@ -5314,6 +5314,15 @@ int JSMessageObject::GetColumnNumber() const {
return info.column; // Note: No '+1' in contrast to GetLineNumber. return info.column; // Note: No '+1' in contrast to GetLineNumber.
} }
String JSMessageObject::GetSource() const {
Script script_object = script();
if (script_object.HasValidSource()) {
Object source = script_object.source();
if (source.IsString()) return String::cast(source);
}
return ReadOnlyRoots(GetIsolate()).empty_string();
}
Handle<String> JSMessageObject::GetSourceLine() const { Handle<String> JSMessageObject::GetSourceLine() const {
Isolate* isolate = GetIsolate(); Isolate* isolate = GetIsolate();
Handle<Script> the_script(script(), isolate); Handle<Script> the_script(script(), isolate);
......
...@@ -1118,6 +1118,9 @@ class JSMessageObject : public JSObject { ...@@ -1118,6 +1118,9 @@ class JSMessageObject : public JSObject {
// EnsureSourcePositionsAvailable must have been called before calling this. // EnsureSourcePositionsAvailable must have been called before calling this.
V8_EXPORT_PRIVATE int GetColumnNumber() const; V8_EXPORT_PRIVATE int GetColumnNumber() const;
// Returns the source code
V8_EXPORT_PRIVATE String GetSource() const;
// Returns the source code line containing the given source // Returns the source code line containing the given source
// position, or the empty string if the position is invalid. // position, or the empty string if the position is invalid.
// EnsureSourcePositionsAvailable must have been called before calling this. // EnsureSourcePositionsAvailable must have been called before calling this.
......
...@@ -145,6 +145,22 @@ Object StackFrameInfo::GetScriptNameOrSourceURL() const { ...@@ -145,6 +145,22 @@ Object StackFrameInfo::GetScriptNameOrSourceURL() const {
return ReadOnlyRoots(GetIsolate()).null_value(); return ReadOnlyRoots(GetIsolate()).null_value();
} }
Object StackFrameInfo::GetScriptSource() const {
if (auto script = GetScript()) {
if (script->HasValidSource()) {
return script->source();
}
}
return ReadOnlyRoots(GetIsolate()).null_value();
}
Object StackFrameInfo::GetScriptSourceMappingURL() const {
if (auto script = GetScript()) {
return script->source_mapping_url();
}
return ReadOnlyRoots(GetIsolate()).null_value();
}
namespace { namespace {
MaybeHandle<String> FormatEvalOrigin(Isolate* isolate, Handle<Script> script) { MaybeHandle<String> FormatEvalOrigin(Isolate* isolate, Handle<Script> script) {
......
...@@ -56,6 +56,8 @@ class StackFrameInfo ...@@ -56,6 +56,8 @@ class StackFrameInfo
int GetScriptId() const; int GetScriptId() const;
Object GetScriptName() const; Object GetScriptName() const;
Object GetScriptNameOrSourceURL() const; Object GetScriptNameOrSourceURL() const;
Object GetScriptSource() const;
Object GetScriptSourceMappingURL() const;
static Handle<PrimitiveHeapObject> GetEvalOrigin(Handle<StackFrameInfo> info); static Handle<PrimitiveHeapObject> GetEvalOrigin(Handle<StackFrameInfo> info);
static Handle<Object> GetFunctionName(Handle<StackFrameInfo> info); static Handle<Object> GetFunctionName(Handle<StackFrameInfo> info);
......
...@@ -179,6 +179,8 @@ TEST(StackTrace) { ...@@ -179,6 +179,8 @@ TEST(StackTrace) {
// Checks that a StackFrame has certain expected values. // Checks that a StackFrame has certain expected values.
static void checkStackFrame(const char* expected_script_name, static void checkStackFrame(const char* expected_script_name,
const char* expected_script_source,
const char* expected_script_source_mapping_url,
const char* expected_func_name, const char* expected_func_name,
int expected_line_number, int expected_column, int expected_line_number, int expected_column,
bool is_eval, bool is_constructor, bool is_eval, bool is_constructor,
...@@ -186,12 +188,24 @@ static void checkStackFrame(const char* expected_script_name, ...@@ -186,12 +188,24 @@ static void checkStackFrame(const char* expected_script_name,
v8::HandleScope scope(CcTest::isolate()); v8::HandleScope scope(CcTest::isolate());
v8::String::Utf8Value func_name(CcTest::isolate(), frame->GetFunctionName()); v8::String::Utf8Value func_name(CcTest::isolate(), frame->GetFunctionName());
v8::String::Utf8Value script_name(CcTest::isolate(), frame->GetScriptName()); v8::String::Utf8Value script_name(CcTest::isolate(), frame->GetScriptName());
v8::String::Utf8Value script_source(CcTest::isolate(),
frame->GetScriptSource());
v8::String::Utf8Value script_source_mapping_url(
CcTest::isolate(), frame->GetScriptSourceMappingURL());
if (*script_name == nullptr) { if (*script_name == nullptr) {
// The situation where there is no associated script, like for evals. // The situation where there is no associated script, like for evals.
CHECK_NULL(expected_script_name); CHECK_NULL(expected_script_name);
} else { } else {
CHECK_NOT_NULL(strstr(*script_name, expected_script_name)); CHECK_NOT_NULL(strstr(*script_name, expected_script_name));
} }
CHECK_NOT_NULL(strstr(*script_source, expected_script_source));
if (*script_source_mapping_url == nullptr) {
CHECK_NULL(expected_script_source_mapping_url);
} else {
CHECK_NOT_NULL(expected_script_source_mapping_url);
CHECK_NOT_NULL(
strstr(*script_source_mapping_url, expected_script_source_mapping_url));
}
if (!frame->GetFunctionName().IsEmpty()) { if (!frame->GetFunctionName().IsEmpty()) {
CHECK_NOT_NULL(strstr(*func_name, expected_func_name)); CHECK_NOT_NULL(strstr(*func_name, expected_func_name));
} }
...@@ -202,6 +216,67 @@ static void checkStackFrame(const char* expected_script_name, ...@@ -202,6 +216,67 @@ static void checkStackFrame(const char* expected_script_name,
CHECK(frame->IsUserJavaScript()); CHECK(frame->IsUserJavaScript());
} }
// Tests the C++ StackTrace API.
// Test getting OVERVIEW information. Should ignore information that is not
// script name, function name, line number, and column offset.
const char* overview_source_eval = "new foo();";
const char* overview_source =
"function bar() {\n"
" var y; AnalyzeStackInNativeCode(1);\n"
"}\n"
"function foo() {\n"
"\n"
" bar();\n"
"}\n"
"//# sourceMappingURL=http://foobar.com/overview.ts\n"
"var x;eval('new foo();');";
// Test getting DETAILED information.
const char* detailed_source =
"function bat() {AnalyzeStackInNativeCode(2);\n"
"}\n"
"\n"
"function baz() {\n"
" bat();\n"
"}\n"
"eval('new baz();');";
// 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";
// Maybe it's a bit pathological to depend on the exact format of the wrapper
// the Function constructor puts around it's input string. If this becomes a
// hassle, maybe come up with some regex matching approach?
const char function_name_source_anon3[] =
"(function anonymous(\n"
") {\n"
"AnalyzeStackInNativeCode(3);\n"
"})";
const char function_name_source_anon4[] =
"(function anonymous(\n"
") {\n"
"AnalyzeStackInNativeCode(4);\n"
"})";
const char function_name_source_anon5[] =
"(function anonymous(\n"
") {\n"
"AnalyzeStackInNativeCode(5);\n"
"})";
static void AnalyzeStackInNativeCode( static void AnalyzeStackInNativeCode(
const v8::FunctionCallbackInfo<v8::Value>& args) { const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::HandleScope scope(args.GetIsolate()); v8::HandleScope scope(args.GetIsolate());
...@@ -221,53 +296,55 @@ static void AnalyzeStackInNativeCode( ...@@ -221,53 +296,55 @@ static void AnalyzeStackInNativeCode(
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace( v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
args.GetIsolate(), 10, v8::StackTrace::kOverview); args.GetIsolate(), 10, v8::StackTrace::kOverview);
CHECK_EQ(4, stackTrace->GetFrameCount()); CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bar", 2, 10, false, false, checkStackFrame(origin, overview_source, "//foobar.com/overview.ts", "bar",
2, 10, false, false,
stackTrace->GetFrame(args.GetIsolate(), 0)); stackTrace->GetFrame(args.GetIsolate(), 0));
checkStackFrame(origin, "foo", 6, 3, false, true, checkStackFrame(origin, overview_source, "//foobar.com/overview.ts", "foo",
stackTrace->GetFrame(isolate, 1)); 6, 3, false, true, stackTrace->GetFrame(isolate, 1));
// This is the source string inside the eval which has the call to foo. // This is the source string inside the eval which has the call to foo.
checkStackFrame(nullptr, "", 1, 1, true, false, checkStackFrame(nullptr, "new foo();", nullptr, "", 1, 1, true, false,
stackTrace->GetFrame(isolate, 2)); stackTrace->GetFrame(isolate, 2));
// The last frame is an anonymous function which has the initial eval call. // The last frame is an anonymous function which has the initial eval call.
checkStackFrame(origin, "", 8, 7, false, false, checkStackFrame(origin, overview_source, "//foobar.com/overview.ts", "", 9,
stackTrace->GetFrame(isolate, 3)); 7, false, false, stackTrace->GetFrame(isolate, 3));
} else if (testGroup == kDetailedTest) { } else if (testGroup == kDetailedTest) {
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace( v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
args.GetIsolate(), 10, v8::StackTrace::kDetailed); args.GetIsolate(), 10, v8::StackTrace::kDetailed);
CHECK_EQ(4, stackTrace->GetFrameCount()); CHECK_EQ(4, stackTrace->GetFrameCount());
checkStackFrame(origin, "bat", 4, 22, false, false, checkStackFrame(origin, detailed_source, nullptr, "bat", 4, 22, false,
stackTrace->GetFrame(isolate, 0)); false, stackTrace->GetFrame(isolate, 0));
checkStackFrame(origin, "baz", 8, 3, false, true, checkStackFrame(origin, detailed_source, nullptr, "baz", 8, 3, false, true,
stackTrace->GetFrame(isolate, 1)); stackTrace->GetFrame(isolate, 1));
bool is_eval = true; bool is_eval = true;
// This is the source string inside the eval which has the call to baz. // This is the source string inside the eval which has the call to baz.
checkStackFrame(nullptr, "", 1, 1, is_eval, false, checkStackFrame(nullptr, "new baz();", nullptr, "", 1, 1, is_eval, false,
stackTrace->GetFrame(isolate, 2)); stackTrace->GetFrame(isolate, 2));
// The last frame is an anonymous function which has the initial eval call. // The last frame is an anonymous function which has the initial eval call.
checkStackFrame(origin, "", 10, 1, false, false, checkStackFrame(origin, detailed_source, nullptr, "", 10, 1, false, false,
stackTrace->GetFrame(isolate, 3)); stackTrace->GetFrame(isolate, 3));
} else if (testGroup == kFunctionName) { } else if (testGroup == kFunctionName) {
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace( v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
args.GetIsolate(), 5, v8::StackTrace::kOverview); args.GetIsolate(), 5, v8::StackTrace::kOverview);
CHECK_EQ(3, stackTrace->GetFrameCount()); CHECK_EQ(3, stackTrace->GetFrameCount());
checkStackFrame(nullptr, "function.name", 3, 1, true, false, checkStackFrame(nullptr, function_name_source_anon3, nullptr,
"function.name", 3, 1, true, false,
stackTrace->GetFrame(isolate, 0)); stackTrace->GetFrame(isolate, 0));
} else if (testGroup == kFunctionNameAndDisplayName) { } else if (testGroup == kFunctionNameAndDisplayName) {
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace( v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
args.GetIsolate(), 5, v8::StackTrace::kOverview); args.GetIsolate(), 5, v8::StackTrace::kOverview);
CHECK_EQ(3, stackTrace->GetFrameCount()); CHECK_EQ(3, stackTrace->GetFrameCount());
checkStackFrame(nullptr, "function.name", 3, 1, true, false, checkStackFrame(nullptr, function_name_source_anon4, nullptr,
"function.name", 3, 1, true, false,
stackTrace->GetFrame(isolate, 0)); stackTrace->GetFrame(isolate, 0));
} else if (testGroup == kFunctionNameIsNotString) { } else if (testGroup == kFunctionNameIsNotString) {
v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace( v8::Local<v8::StackTrace> stackTrace = v8::StackTrace::CurrentStackTrace(
args.GetIsolate(), 5, v8::StackTrace::kOverview); args.GetIsolate(), 5, v8::StackTrace::kOverview);
CHECK_EQ(3, stackTrace->GetFrameCount()); CHECK_EQ(3, stackTrace->GetFrameCount());
checkStackFrame(nullptr, "", 3, 1, true, false, checkStackFrame(nullptr, function_name_source_anon5, nullptr, "", 3, 1,
stackTrace->GetFrame(isolate, 0)); true, false, stackTrace->GetFrame(isolate, 0));
} }
} }
// Tests the C++ StackTrace API.
// TODO(3074796): Reenable this as a THREADED_TEST once it passes. // TODO(3074796): Reenable this as a THREADED_TEST once it passes.
// THREADED_TEST(CaptureStackTrace) { // THREADED_TEST(CaptureStackTrace) {
TEST(CaptureStackTrace) { TEST(CaptureStackTrace) {
...@@ -279,17 +356,6 @@ TEST(CaptureStackTrace) { ...@@ -279,17 +356,6 @@ TEST(CaptureStackTrace) {
v8::FunctionTemplate::New(isolate, AnalyzeStackInNativeCode)); v8::FunctionTemplate::New(isolate, AnalyzeStackInNativeCode));
LocalContext context(nullptr, templ); 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::Local<v8::String> overview_src = v8_str(overview_source);
v8::ScriptCompiler::Source script_source(overview_src, v8::ScriptCompiler::Source script_source(overview_src,
v8::ScriptOrigin(isolate, origin)); v8::ScriptOrigin(isolate, origin));
...@@ -302,15 +368,6 @@ TEST(CaptureStackTrace) { ...@@ -302,15 +368,6 @@ TEST(CaptureStackTrace) {
CHECK(!overview_result.IsEmpty()); CHECK(!overview_result.IsEmpty());
CHECK(overview_result->IsObject()); 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); v8::Local<v8::String> detailed_src = v8_str(detailed_source);
// Make the script using a non-zero line and column offset. // Make the script using a non-zero line and column offset.
v8::ScriptOrigin detailed_origin(isolate, origin, 3, 5); v8::ScriptOrigin detailed_origin(isolate, origin, 3, 5);
...@@ -324,21 +381,6 @@ TEST(CaptureStackTrace) { ...@@ -324,21 +381,6 @@ TEST(CaptureStackTrace) {
CHECK(!detailed_result.IsEmpty()); CHECK(!detailed_result.IsEmpty());
CHECK(detailed_result->IsObject()); 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::Local<v8::String> function_name_src =
v8::String::NewFromUtf8Literal(isolate, function_name_source); v8::String::NewFromUtf8Literal(isolate, function_name_source);
v8::ScriptCompiler::Source script_source3(function_name_src, v8::ScriptCompiler::Source script_source3(function_name_src,
...@@ -353,33 +395,37 @@ TEST(CaptureStackTrace) { ...@@ -353,33 +395,37 @@ TEST(CaptureStackTrace) {
} }
static int report_count = 0; static int report_count = 0;
// Test uncaught exception
const char uncaught_exception_source[] =
"function foo() {\n"
" throw 1;\n"
"};\n"
"function bar() {\n"
" foo();\n"
"};";
static void StackTraceForUncaughtExceptionListener( static void StackTraceForUncaughtExceptionListener(
v8::Local<v8::Message> message, v8::Local<Value>) { v8::Local<v8::Message> message, v8::Local<Value>) {
report_count++; report_count++;
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace(); v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK_EQ(2, stack_trace->GetFrameCount()); CHECK_EQ(2, stack_trace->GetFrameCount());
checkStackFrame("origin", "foo", 2, 3, false, false, checkStackFrame("origin", uncaught_exception_source, nullptr, "foo", 2, 3,
false, false,
stack_trace->GetFrame(message->GetIsolate(), 0)); stack_trace->GetFrame(message->GetIsolate(), 0));
checkStackFrame("origin", "bar", 5, 3, false, false, checkStackFrame("origin", uncaught_exception_source, nullptr, "bar", 5, 3,
false, false,
stack_trace->GetFrame(message->GetIsolate(), 1)); stack_trace->GetFrame(message->GetIsolate(), 1));
} }
TEST(CaptureStackTraceForUncaughtException) { TEST(CaptureStackTraceForUncaughtException) {
report_count = 0;
LocalContext env; LocalContext env;
v8::Isolate* isolate = env->GetIsolate(); v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
isolate->AddMessageListener(StackTraceForUncaughtExceptionListener); isolate->AddMessageListener(StackTraceForUncaughtExceptionListener);
isolate->SetCaptureStackTraceForUncaughtExceptions(true); isolate->SetCaptureStackTraceForUncaughtExceptions(true);
CompileRunWithOrigin( CompileRunWithOrigin(uncaught_exception_source, "origin");
"function foo() {\n"
" throw 1;\n"
"};\n"
"function bar() {\n"
" foo();\n"
"};",
"origin");
v8::Local<v8::Object> global = env->Global(); v8::Local<v8::Object> global = env->Global();
Local<Value> trouble = Local<Value> trouble =
global->Get(env.local(), v8_str("bar")).ToLocalChecked(); global->Get(env.local(), v8_str("bar")).ToLocalChecked();
...@@ -392,40 +438,100 @@ TEST(CaptureStackTraceForUncaughtException) { ...@@ -392,40 +438,100 @@ TEST(CaptureStackTraceForUncaughtException) {
CHECK_EQ(1, report_count); CHECK_EQ(1, report_count);
} }
// Test uncaught exception in a setter
const char uncaught_setter_exception_source[] =
"var setters = ['column', 'lineNumber', 'scriptName',\n"
" 'scriptNameOrSourceURL', 'functionName', 'isEval',\n"
" 'isConstructor'];\n"
"for (let i = 0; i < setters.length; i++) {\n"
" let prop = setters[i];\n"
" Object.prototype.__defineSetter__(prop, function() { throw prop; });\n"
"}\n";
static void StackTraceForUncaughtExceptionAndSettersListener(
v8::Local<v8::Message> message, v8::Local<Value> value) {
CHECK(value->IsObject());
v8::Isolate* isolate = message->GetIsolate();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
report_count++;
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
CHECK_EQ(1, stack_trace->GetFrameCount());
checkStackFrame(nullptr, "throw 'exception';", nullptr, nullptr, 1, 1, false,
false, stack_trace->GetFrame(isolate, 0));
v8::Local<v8::StackFrame> stack_frame = stack_trace->GetFrame(isolate, 0);
v8::Local<v8::Object> object = v8::Local<v8::Object>::Cast(value);
CHECK(object
->Set(context,
v8::String::NewFromUtf8Literal(isolate, "lineNumber"),
v8::Integer::New(isolate, stack_frame->GetLineNumber()))
.IsNothing());
}
TEST(CaptureStackTraceForUncaughtExceptionAndSetters) { TEST(CaptureStackTraceForUncaughtExceptionAndSetters) {
report_count = 0;
LocalContext env; LocalContext env;
v8::Isolate* isolate = env->GetIsolate(); v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
v8::Local<v8::Object> object = v8::Object::New(isolate);
isolate->AddMessageListener(StackTraceForUncaughtExceptionAndSettersListener,
object);
isolate->SetCaptureStackTraceForUncaughtExceptions(true, 1024, isolate->SetCaptureStackTraceForUncaughtExceptions(true, 1024,
v8::StackTrace::kDetailed); v8::StackTrace::kDetailed);
CompileRun( CompileRun(uncaught_setter_exception_source);
"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';"); CompileRun("throw 'exception';");
isolate->SetCaptureStackTraceForUncaughtExceptions(false); isolate->SetCaptureStackTraceForUncaughtExceptions(false);
} isolate->RemoveMessageListeners(
StackTraceForUncaughtExceptionAndSettersListener);
CHECK(object
->Get(isolate->GetCurrentContext(),
v8::String::NewFromUtf8Literal(isolate, "lineNumber"))
.ToLocalChecked()
->IsUndefined());
CHECK_EQ(report_count, 1);
}
const char functions_with_function_name[] =
"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"
"};"
"//# sourceMappingURL=local/functional.sc";
const char functions_with_function_name_caller[] = "gen('foo', 3)();";
static void StackTraceFunctionNameListener(v8::Local<v8::Message> message, static void StackTraceFunctionNameListener(v8::Local<v8::Message> message,
v8::Local<Value>) { v8::Local<Value>) {
v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace(); v8::Local<v8::StackTrace> stack_trace = message->GetStackTrace();
v8::Isolate* isolate = message->GetIsolate(); v8::Isolate* isolate = message->GetIsolate();
CHECK_EQ(5, stack_trace->GetFrameCount()); CHECK_EQ(5, stack_trace->GetFrameCount());
checkStackFrame("origin", "foo:0", 4, 7, false, false, checkStackFrame("origin", functions_with_function_name, "local/functional.sc",
"foo:0", 4, 7, false, false,
stack_trace->GetFrame(isolate, 0)); stack_trace->GetFrame(isolate, 0));
checkStackFrame("origin", "foo:1", 5, 27, false, false, checkStackFrame("origin", functions_with_function_name, "local/functional.sc",
"foo:1", 5, 27, false, false,
stack_trace->GetFrame(isolate, 1)); stack_trace->GetFrame(isolate, 1));
checkStackFrame("origin", "foo", 5, 27, false, false, checkStackFrame("origin", functions_with_function_name, "local/functional.sc",
"foo", 5, 27, false, false,
stack_trace->GetFrame(isolate, 2)); stack_trace->GetFrame(isolate, 2));
checkStackFrame("origin", "foo", 5, 27, false, false, checkStackFrame("origin", functions_with_function_name, "local/functional.sc",
"foo", 5, 27, false, false,
stack_trace->GetFrame(isolate, 3)); stack_trace->GetFrame(isolate, 3));
checkStackFrame("origin", "", 1, 14, false, false, checkStackFrame("origin", functions_with_function_name_caller, nullptr, "", 1,
stack_trace->GetFrame(isolate, 4)); 14, false, false, stack_trace->GetFrame(isolate, 4));
} }
TEST(GetStackTraceContainsFunctionsWithFunctionName) { TEST(GetStackTraceContainsFunctionsWithFunctionName) {
...@@ -433,29 +539,11 @@ TEST(GetStackTraceContainsFunctionsWithFunctionName) { ...@@ -433,29 +539,11 @@ TEST(GetStackTraceContainsFunctionsWithFunctionName) {
v8::Isolate* isolate = env->GetIsolate(); v8::Isolate* isolate = env->GetIsolate();
v8::HandleScope scope(isolate); v8::HandleScope scope(isolate);
CompileRunWithOrigin( CompileRunWithOrigin(functions_with_function_name, "origin");
"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->AddMessageListener(StackTraceFunctionNameListener);
isolate->SetCaptureStackTraceForUncaughtExceptions(true); isolate->SetCaptureStackTraceForUncaughtExceptions(true);
CompileRunWithOrigin("gen('foo', 3)();", "origin"); CompileRunWithOrigin(functions_with_function_name_caller, "origin");
isolate->SetCaptureStackTraceForUncaughtExceptions(false); isolate->SetCaptureStackTraceForUncaughtExceptions(false);
isolate->RemoveMessageListeners(StackTraceFunctionNameListener); isolate->RemoveMessageListeners(StackTraceFunctionNameListener);
} }
......
...@@ -4732,6 +4732,13 @@ namespace { ...@@ -4732,6 +4732,13 @@ namespace {
// some particular way by calling the supplied |tester| function. The tests that // some particular way by calling the supplied |tester| function. The tests that
// use this purposely test only a single getter as the getter updates the cached // use this purposely test only a single getter as the getter updates the cached
// state of the object which could affect the results of other functions. // state of the object which could affect the results of other functions.
const char message_attributes_script[] =
R"javascript(
(function() {
throw new Error();
})();
)javascript";
void CheckMessageAttributes(std::function<void(v8::Local<v8::Context> context, void CheckMessageAttributes(std::function<void(v8::Local<v8::Context> context,
v8::Local<v8::Message> message)> v8::Local<v8::Message> message)>
tester) { tester) {
...@@ -4739,12 +4746,7 @@ void CheckMessageAttributes(std::function<void(v8::Local<v8::Context> context, ...@@ -4739,12 +4746,7 @@ void CheckMessageAttributes(std::function<void(v8::Local<v8::Context> context,
v8::HandleScope scope(context->GetIsolate()); v8::HandleScope scope(context->GetIsolate());
TryCatch try_catch(context->GetIsolate()); TryCatch try_catch(context->GetIsolate());
CompileRun( CompileRun(message_attributes_script);
R"javascript(
(function() {
throw new Error();
})();
)javascript");
CHECK(try_catch.HasCaught()); CHECK(try_catch.HasCaught());
v8::Local<v8::Value> error = try_catch.Exception(); v8::Local<v8::Value> error = try_catch.Exception();
...@@ -4767,38 +4769,47 @@ TEST(MessageGetLineNumber) { ...@@ -4767,38 +4769,47 @@ TEST(MessageGetLineNumber) {
TEST(MessageGetStartColumn) { TEST(MessageGetStartColumn) {
CheckMessageAttributes( CheckMessageAttributes(
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) { [](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
CHECK_EQ(14, message->GetStartColumn(context).FromJust()); CHECK_EQ(12, message->GetStartColumn(context).FromJust());
}); });
} }
TEST(MessageGetEndColumn) { TEST(MessageGetEndColumn) {
CheckMessageAttributes( CheckMessageAttributes(
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) { [](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
CHECK_EQ(15, message->GetEndColumn(context).FromJust()); CHECK_EQ(13, message->GetEndColumn(context).FromJust());
}); });
} }
TEST(MessageGetStartPosition) { TEST(MessageGetStartPosition) {
CheckMessageAttributes( CheckMessageAttributes(
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) { [](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
CHECK_EQ(35, message->GetStartPosition()); CHECK_EQ(31, message->GetStartPosition());
}); });
} }
TEST(MessageGetEndPosition) { TEST(MessageGetEndPosition) {
CheckMessageAttributes( CheckMessageAttributes(
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) { [](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
CHECK_EQ(36, message->GetEndPosition()); CHECK_EQ(32, message->GetEndPosition());
}); });
} }
TEST(MessageGetSource) {
CheckMessageAttributes([](v8::Local<v8::Context> context,
v8::Local<v8::Message> message) {
std::string result(*v8::String::Utf8Value(
context->GetIsolate(), message->GetSource(context).ToLocalChecked()));
CHECK_EQ(message_attributes_script, result);
});
}
TEST(MessageGetSourceLine) { TEST(MessageGetSourceLine) {
CheckMessageAttributes( CheckMessageAttributes(
[](v8::Local<v8::Context> context, v8::Local<v8::Message> message) { [](v8::Local<v8::Context> context, v8::Local<v8::Message> message) {
std::string result(*v8::String::Utf8Value( std::string result(*v8::String::Utf8Value(
context->GetIsolate(), context->GetIsolate(),
message->GetSourceLine(context).ToLocalChecked())); message->GetSourceLine(context).ToLocalChecked()));
CHECK_EQ(" throw new Error();", result); CHECK_EQ(" throw new Error();", result);
}); });
} }
...@@ -74,13 +74,21 @@ void CheckExceptionInfos(v8::internal::Isolate* i_isolate, Handle<Object> exc, ...@@ -74,13 +74,21 @@ void CheckExceptionInfos(v8::internal::Isolate* i_isolate, Handle<Object> exc,
// Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo. // Line and column are 1-based in v8::StackFrame, just as in ExceptionInfo.
CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber()); CHECK_EQ(excInfos[frameNr].line_nr, frame->GetLineNumber());
CHECK_EQ(excInfos[frameNr].column, frame->GetColumn()); CHECK_EQ(excInfos[frameNr].column, frame->GetColumn());
v8::Local<v8::String> scriptSource = frame->GetScriptSource();
if (frame->IsWasm()) {
CHECK(scriptSource.IsEmpty());
} else {
CHECK(scriptSource->IsString());
}
} }
CheckComputeLocation(i_isolate, exc, excInfos[0]); CheckComputeLocation(i_isolate, exc, excInfos[0],
stack->GetFrame(v8_isolate, 0));
} }
void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc, void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc,
const ExceptionInfo& topLocation) { const ExceptionInfo& topLocation,
const v8::Local<v8::StackFrame> stackFrame) {
MessageLocation loc; MessageLocation loc;
CHECK(i_isolate->ComputeLocationFromStackTrace(&loc, exc)); CHECK(i_isolate->ComputeLocationFromStackTrace(&loc, exc));
printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos()); printf("loc start: %d, end: %d\n", loc.start_pos(), loc.end_pos());
...@@ -97,6 +105,13 @@ void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc, ...@@ -97,6 +105,13 @@ void CheckComputeLocation(v8::internal::Isolate* i_isolate, Handle<Object> exc,
// whether Script::PositionInfo.column should be the offset // whether Script::PositionInfo.column should be the offset
// relative to the module or relative to the function. // relative to the module or relative to the function.
// CHECK_EQ(topLocation.column - 1, message->GetColumnNumber()); // CHECK_EQ(topLocation.column - 1, message->GetColumnNumber());
String scriptSource = message->GetSource();
CHECK(scriptSource.IsString());
if (stackFrame->IsWasm()) {
CHECK_EQ(scriptSource.length(), 0);
} else {
CHECK_GT(scriptSource.length(), 0);
}
} }
#undef CHECK_CSTREQ #undef CHECK_CSTREQ
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment