refactor: add and use libdeno.setGlobalErrorHandler instead of window.onerror

This commit is contained in:
Yoshiya Hinosawa 2018-08-26 16:57:16 +09:00 committed by Ryan Dahl
parent 3a5cf9ca8b
commit 17d6d6b336
7 changed files with 88 additions and 56 deletions

View file

@ -1,7 +1,6 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license. // Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { Console } from "./console"; import { Console } from "./console";
import { exit } from "./os";
import * as timers from "./timers"; import * as timers from "./timers";
import { TextDecoder, TextEncoder } from "./text_encoding"; import { TextDecoder, TextEncoder } from "./text_encoding";
import * as fetch_ from "./fetch"; import * as fetch_ from "./fetch";
@ -12,13 +11,6 @@ declare global {
interface Window { interface Window {
console: Console; console: Console;
define: Readonly<unknown>; define: Readonly<unknown>;
onerror?: (
message: string,
source: string,
lineno: number,
colno: number,
error: Error
) => void;
} }
const clearTimeout: typeof timers.clearTimer; const clearTimeout: typeof timers.clearTimer;
@ -43,30 +35,12 @@ window.window = window;
window.libdeno = null; window.libdeno = null;
// import "./url";
window.setTimeout = timers.setTimeout; window.setTimeout = timers.setTimeout;
window.setInterval = timers.setInterval; window.setInterval = timers.setInterval;
window.clearTimeout = timers.clearTimer; window.clearTimeout = timers.clearTimer;
window.clearInterval = timers.clearTimer; window.clearInterval = timers.clearTimer;
window.console = new Console(libdeno.print); window.console = new Console(libdeno.print);
// Uncaught exceptions are sent to window.onerror by the privileged binding.
window.onerror = (
message: string,
source: string,
lineno: number,
colno: number,
error: Error
) => {
// TODO Currently there is a bug in v8_source_maps.ts that causes a
// segfault if it is used within window.onerror. To workaround we
// uninstall the Error.prepareStackTrace handler. Users will get unmapped
// stack traces on uncaught exceptions until this issue is fixed.
//Error.prepareStackTrace = null;
console.log(error.stack);
exit(1);
};
window.TextEncoder = TextEncoder; window.TextEncoder = TextEncoder;
window.TextDecoder = TextDecoder; window.TextDecoder = TextDecoder;

View file

@ -10,6 +10,16 @@ interface Libdeno {
print(x: string): void; print(x: string): void;
setGlobalErrorHandler: (
handler: (
message: string,
source: string,
line: number,
col: number,
error: Error
) => void
) => void;
mainSource: string; mainSource: string;
mainSourceMap: RawSourceMap; mainSourceMap: RawSourceMap;
} }

View file

@ -43,9 +43,21 @@ function onMessage(ui8: Uint8Array) {
} }
} }
function onGlobalError(
message: string,
source: string,
lineno: number,
colno: number,
error: Error
) {
console.log(error.stack);
os.exit(1);
}
/* tslint:disable-next-line:no-default-export */ /* tslint:disable-next-line:no-default-export */
export default function denoMain() { export default function denoMain() {
libdeno.recv(onMessage); libdeno.recv(onMessage);
libdeno.setGlobalErrorHandler(onGlobalError);
const compiler = DenoCompiler.instance(); const compiler = DenoCompiler.instance();
// First we send an empty "Start" message to let the privlaged side know we // First we send an empty "Start" message to let the privlaged side know we

View file

@ -13,8 +13,6 @@
namespace deno { namespace deno {
static bool skip_onerror = false;
Deno* FromIsolate(v8::Isolate* isolate) { Deno* FromIsolate(v8::Isolate* isolate) {
return static_cast<Deno*>(isolate->GetData(0)); return static_cast<Deno*>(isolate->GetData(0));
} }
@ -34,36 +32,37 @@ void HandleExceptionStr(v8::Local<v8::Context> context,
v8::Local<v8::Value> exception, v8::Local<v8::Value> exception,
std::string* exception_str) { std::string* exception_str) {
auto* isolate = context->GetIsolate(); auto* isolate = context->GetIsolate();
Deno* d = FromIsolate(isolate);
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(context); v8::Context::Scope context_scope(context);
auto message = v8::Exception::CreateMessage(isolate, exception); auto message = v8::Exception::CreateMessage(isolate, exception);
auto onerrorStr = v8::String::NewFromUtf8(isolate, "onerror");
auto onerror = context->Global()->Get(onerrorStr);
auto stack_trace = message->GetStackTrace(); auto stack_trace = message->GetStackTrace();
auto line = auto line =
v8::Integer::New(isolate, message->GetLineNumber(context).FromJust()); v8::Integer::New(isolate, message->GetLineNumber(context).FromJust());
auto column = auto column =
v8::Integer::New(isolate, message->GetStartColumn(context).FromJust()); v8::Integer::New(isolate, message->GetStartColumn(context).FromJust());
if (skip_onerror == false) { auto global_error_handler = d->global_error_handler.Get(isolate);
if (onerror->IsFunction()) {
// window.onerror is set so we try to handle the exception in javascript. if (!global_error_handler.IsEmpty()) {
auto func = v8::Local<v8::Function>::Cast(onerror); // global_error_handler is set so we try to handle the exception in javascript.
v8::Local<v8::Value> args[5]; v8::Local<v8::Value> args[5];
args[0] = exception->ToString(); args[0] = exception->ToString();
args[1] = message->GetScriptResourceName(); args[1] = message->GetScriptResourceName();
args[2] = line; args[2] = line;
args[3] = column; args[3] = column;
args[4] = exception; args[4] = exception;
func->Call(context->Global(), 5, args); global_error_handler->Call(context->Global(), 5, args);
/* message, source, lineno, colno, error */ /* message, source, lineno, colno, error */
}
return;
} }
char buf[12 * 1024]; char buf[12 * 1024];
if (!stack_trace.IsEmpty()) { if (!stack_trace.IsEmpty()) {
// No javascript onerror handler, but we do have a stack trace. Format it // No javascript error handler, but we do have a stack trace. Format it
// into a string and add to last_exception. // into a string and add to last_exception.
std::string msg; std::string msg;
v8::String::Utf8Value exceptionStr(isolate, exception); v8::String::Utf8Value exceptionStr(isolate, exception);
@ -80,7 +79,7 @@ void HandleExceptionStr(v8::Local<v8::Context> context,
} }
*exception_str += msg; *exception_str += msg;
} else { } else {
// No javascript onerror handler, no stack trace. Format the little info we // No javascript error handler, no stack trace. Format the little info we
// have into a string and add to last_exception. // have into a string and add to last_exception.
v8::String::Utf8Value exceptionStr(isolate, exception); v8::String::Utf8Value exceptionStr(isolate, exception);
v8::String::Utf8Value script_name(isolate, v8::String::Utf8Value script_name(isolate,
@ -188,7 +187,7 @@ void Recv(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::HandleScope handle_scope(isolate); v8::HandleScope handle_scope(isolate);
if (!d->recv.IsEmpty()) { if (!d->recv.IsEmpty()) {
isolate->ThrowException(v8_str("deno.recv already called.")); isolate->ThrowException(v8_str("libdeno.recv already called."));
return; return;
} }
@ -227,6 +226,27 @@ void Send(const v8::FunctionCallbackInfo<v8::Value>& args) {
d->currentArgs = nullptr; d->currentArgs = nullptr;
} }
// Sets the global error handler.
void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
Deno* d = reinterpret_cast<Deno*>(isolate->GetData(0));
DCHECK_EQ(d->isolate, isolate);
v8::HandleScope handle_scope(isolate);
if (!d->global_error_handler.IsEmpty()) {
isolate->ThrowException(v8_str("libdeno.setGlobalErrorHandler already called."));
return;
}
v8::Local<v8::Value> v = args[0];
CHECK(v->IsFunction());
v8::Local<v8::Function> func = v8::Local<v8::Function>::Cast(v);
d->global_error_handler.Reset(isolate, func);
}
bool ExecuteV8StringSource(v8::Local<v8::Context> context, bool ExecuteV8StringSource(v8::Local<v8::Context> context,
const char* js_filename, const char* js_filename,
v8::Local<v8::String> source) { v8::Local<v8::String> source) {
@ -293,7 +313,10 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
auto send_val = send_tmpl->GetFunction(context).ToLocalChecked(); auto send_val = send_tmpl->GetFunction(context).ToLocalChecked();
CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust()); CHECK(deno_val->Set(context, deno::v8_str("send"), send_val).FromJust());
skip_onerror = true; auto set_global_error_handler_tmpl = v8::FunctionTemplate::New(isolate, SetGlobalErrorHandler);
auto set_global_error_handler_val = set_global_error_handler_tmpl->GetFunction(context).ToLocalChecked();
CHECK(deno_val->Set(context, deno::v8_str("setGlobalErrorHandler"), set_global_error_handler_val).FromJust());
{ {
auto source = deno::v8_str(js_source.c_str()); auto source = deno::v8_str(js_source.c_str());
CHECK( CHECK(
@ -326,7 +349,6 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context,
.FromJust()); .FromJust());
} }
} }
skip_onerror = false;
} }
void AddIsolate(Deno* d, v8::Isolate* isolate) { void AddIsolate(Deno* d, v8::Isolate* isolate) {
@ -384,7 +406,7 @@ int deno_send(Deno* d, deno_buf buf) {
auto recv = d->recv.Get(d->isolate); auto recv = d->recv.Get(d->isolate);
if (recv.IsEmpty()) { if (recv.IsEmpty()) {
d->last_exception = "deno.recv has not been called."; d->last_exception = "libdeno.recv has not been called.";
return 0; return 0;
} }

View file

@ -13,6 +13,7 @@ struct deno_s {
const v8::FunctionCallbackInfo<v8::Value>* currentArgs; const v8::FunctionCallbackInfo<v8::Value>* currentArgs;
std::string last_exception; std::string last_exception;
v8::Persistent<v8::Function> recv; v8::Persistent<v8::Function> recv;
v8::Persistent<v8::Function> global_error_handler;
v8::Persistent<v8::Context> context; v8::Persistent<v8::Context> context;
deno_recv_cb cb; deno_recv_cb cb;
void* data; void* data;
@ -28,9 +29,11 @@ struct InternalFieldData {
void Print(const v8::FunctionCallbackInfo<v8::Value>& args); void Print(const v8::FunctionCallbackInfo<v8::Value>& args);
void Recv(const v8::FunctionCallbackInfo<v8::Value>& args); void Recv(const v8::FunctionCallbackInfo<v8::Value>& args);
void Send(const v8::FunctionCallbackInfo<v8::Value>& args); void Send(const v8::FunctionCallbackInfo<v8::Value>& args);
void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args);
static intptr_t external_references[] = {reinterpret_cast<intptr_t>(Print), static intptr_t external_references[] = {reinterpret_cast<intptr_t>(Print),
reinterpret_cast<intptr_t>(Recv), reinterpret_cast<intptr_t>(Recv),
reinterpret_cast<intptr_t>(Send), 0}; reinterpret_cast<intptr_t>(Send),
reinterpret_cast<intptr_t>(SetGlobalErrorHandler), 0};
Deno* NewFromSnapshot(void* data, deno_recv_cb cb); Deno* NewFromSnapshot(void* data, deno_recv_cb cb);

View file

@ -176,18 +176,24 @@ TEST(LibDenoTest, SnapshotBug) {
deno_delete(d); deno_delete(d);
} }
TEST(LibDenoTest, ErrorHandling) { TEST(LibDenoTest, GlobalErrorHandling) {
static int count = 0; static int count = 0;
Deno* d = deno_new(nullptr, [](auto deno, auto buf) { Deno* d = deno_new(nullptr, [](auto _, auto buf) {
count++; count++;
EXPECT_EQ(static_cast<size_t>(1), buf.data_len); EXPECT_EQ(static_cast<size_t>(1), buf.data_len);
EXPECT_EQ(buf.data_ptr[0], 42); EXPECT_EQ(buf.data_ptr[0], 42);
}); });
EXPECT_FALSE(deno_execute(d, "a.js", "ErrorHandling()")); EXPECT_FALSE(deno_execute(d, "a.js", "GlobalErrorHandling()"));
EXPECT_EQ(count, 1); EXPECT_EQ(count, 1);
deno_delete(d); deno_delete(d);
} }
TEST(LibDenoTest, DoubleGlobalErrorHandlingFails) {
Deno* d = deno_new(nullptr, nullptr);
EXPECT_FALSE(deno_execute(d, "a.js", "DoubleGlobalErrorHandlingFails()"));
deno_delete(d);
}
TEST(LibDenoTest, SendNullAllocPtr) { TEST(LibDenoTest, SendNullAllocPtr) {
static int count = 0; static int count = 0;
Deno* d = deno_new(nullptr, [](auto _, auto buf) { count++; }); Deno* d = deno_new(nullptr, [](auto _, auto buf) { count++; });

View file

@ -122,8 +122,8 @@ global.SnapshotBug = () => {
assert("1,2,3" === String([1, 2, 3])); assert("1,2,3" === String([1, 2, 3]));
}; };
global.ErrorHandling = () => { global.GlobalErrorHandling = () => {
global.onerror = (message, source, line, col, error) => { libdeno.setGlobalErrorHandler((message, source, line, col, error) => {
libdeno.print(`line ${line} col ${col}`); libdeno.print(`line ${line} col ${col}`);
assert("ReferenceError: notdefined is not defined" === message); assert("ReferenceError: notdefined is not defined" === message);
assert(source === "helloworld.js"); assert(source === "helloworld.js");
@ -131,10 +131,15 @@ global.ErrorHandling = () => {
assert(col === 1); assert(col === 1);
assert(error instanceof Error); assert(error instanceof Error);
libdeno.send(new Uint8Array([42])); libdeno.send(new Uint8Array([42]));
}; });
eval("\n\n notdefined()\n//# sourceURL=helloworld.js"); eval("\n\n notdefined()\n//# sourceURL=helloworld.js");
}; };
global.DoubleGlobalErrorHandlingFails = () => {
libdeno.setGlobalErrorHandler((message, source, line, col, error) => {});
libdeno.setGlobalErrorHandler((message, source, line, col, error) => {});
};
global.SendNullAllocPtr = () => { global.SendNullAllocPtr = () => {
libdeno.recv(msg => { libdeno.recv(msg => {
assert(msg instanceof Uint8Array); assert(msg instanceof Uint8Array);