diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2018-12-06 23:05:36 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-12-06 23:05:36 -0500 |
commit | c113df1bb8a0c7d0c560ad32c0291c918c7da7b4 (patch) | |
tree | 0d15de448be602c22aecb2ec65ac7667c437a209 /libdeno | |
parent | 568ac0c9026b6f4012e2511a026bb5eb31a06020 (diff) |
Process source maps in Rust instead of JS (#1280)
- Improves speed and binary size significantly.
- Makes deno_last_exception() output a JSON structure.
- Isolate::execute and Isolate::event_loop now return
structured, mapped JSError objects on errors.
- Removes libdeno functions:
libdeno.setGlobalErrorHandler()
libdeno.setPromiseRejectHandler()
libdeno.setPromiseErrorExaminer()
In collaboration with Ryan Dahl.
Diffstat (limited to 'libdeno')
-rw-r--r-- | libdeno/api.cc | 37 | ||||
-rw-r--r-- | libdeno/binding.cc | 312 | ||||
-rw-r--r-- | libdeno/deno.h | 14 | ||||
-rw-r--r-- | libdeno/internal.h | 19 | ||||
-rw-r--r-- | libdeno/libdeno_test.cc | 65 | ||||
-rw-r--r-- | libdeno/libdeno_test.js | 48 | ||||
-rw-r--r-- | libdeno/snapshot_creator.cc | 4 |
7 files changed, 168 insertions, 331 deletions
diff --git a/libdeno/api.cc b/libdeno/api.cc index 6f4ac826f..388ab6146 100644 --- a/libdeno/api.cc +++ b/libdeno/api.cc @@ -38,7 +38,7 @@ Deno* deno_new(deno_buf snapshot, deno_config config) { if (!snapshot.data_ptr) { // If no snapshot is provided, we initialize the context with empty // main source code and source maps. - deno::InitializeContext(isolate, context, "", "", ""); + deno::InitializeContext(isolate, context, "", ""); } d->context_.Reset(isolate, context); } @@ -47,7 +47,7 @@ Deno* deno_new(deno_buf snapshot, deno_config config) { } Deno* deno_new_snapshotter(deno_config config, const char* js_filename, - const char* js_source, const char* source_map) { + const char* js_source) { auto* creator = new v8::SnapshotCreator(deno::external_references); auto* isolate = creator->GetIsolate(); auto* d = new deno::DenoIsolate(deno::empty_buf, config); @@ -61,8 +61,7 @@ Deno* deno_new_snapshotter(deno_config config, const char* js_filename, creator->SetDefaultContext(context, v8::SerializeInternalFieldsCallback( deno::SerializeInternalFields, nullptr)); - deno::InitializeContext(isolate, context, js_filename, js_source, - source_map); + deno::InitializeContext(isolate, context, js_filename, js_source); } return reinterpret_cast<Deno*>(d); } @@ -96,7 +95,11 @@ void deno_set_v8_flags(int* argc, char** argv) { const char* deno_last_exception(Deno* d_) { auto* d = unwrap(d_); - return d->last_exception_.c_str(); + if (d->last_exception_.length() > 0) { + return d->last_exception_.c_str(); + } else { + return nullptr; + } } int deno_execute(Deno* d_, void* user_data, const char* js_filename, @@ -154,31 +157,19 @@ int deno_respond(Deno* d_, void* user_data, int32_t req_id, deno_buf buf) { void deno_check_promise_errors(Deno* d_) { auto* d = unwrap(d_); - if (d->pending_promise_events_ > 0) { + if (d->pending_promise_map_.size() > 0) { auto* isolate = d->isolate_; v8::Locker locker(isolate); v8::Isolate::Scope isolate_scope(isolate); v8::HandleScope handle_scope(isolate); - auto context = d->context_.Get(d->isolate_); v8::Context::Scope context_scope(context); - v8::TryCatch try_catch(d->isolate_); - auto promise_error_examiner_ = d->promise_error_examiner_.Get(d->isolate_); - if (promise_error_examiner_.IsEmpty()) { - d->last_exception_ = - "libdeno.setPromiseErrorExaminer has not been called."; - return; - } - v8::Local<v8::Value> args[0]; - auto result = promise_error_examiner_->Call(context->Global(), 0, args); - if (try_catch.HasCaught()) { - deno::HandleException(context, try_catch.Exception()); - } - d->pending_promise_events_ = 0; // reset - if (!result->BooleanValue(context).FromJust()) { - // Has uncaught promise reject error, exiting... - exit(1); + auto it = d->pending_promise_map_.begin(); + while (it != d->pending_promise_map_.end()) { + auto error = it->second.Get(isolate); + deno::HandleException(context, error); + it = d->pending_promise_map_.erase(it); } } } diff --git a/libdeno/binding.cc b/libdeno/binding.cc index b5d7ecafa..2a68d89aa 100644 --- a/libdeno/binding.cc +++ b/libdeno/binding.cc @@ -72,95 +72,94 @@ static inline v8::Local<v8::String> v8_str(const char* x) { .ToLocalChecked(); } -void HandleExceptionStr(v8::Local<v8::Context> context, - v8::Local<v8::Value> exception, - std::string* exception_str) { +std::string EncodeExceptionAsJSON(v8::Local<v8::Context> context, + v8::Local<v8::Value> exception) { auto* isolate = context->GetIsolate(); - DenoIsolate* d = FromIsolate(isolate); - v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context); auto message = v8::Exception::CreateMessage(isolate, exception); auto stack_trace = message->GetStackTrace(); - auto line = - v8::Integer::New(isolate, message->GetLineNumber(context).FromJust()); - auto column = - v8::Integer::New(isolate, message->GetStartColumn(context).FromJust()); - - auto global_error_handler_ = d->global_error_handler_.Get(isolate); - - if (!global_error_handler_.IsEmpty()) { - // global_error_handler_ is set so we try to handle the exception in - // javascript. - v8::Local<v8::Value> args[5]; - args[0] = exception->ToString(context).ToLocalChecked(); - args[1] = message->GetScriptResourceName(); - args[2] = line; - args[3] = column; - args[4] = exception; - global_error_handler_->Call(context->Global(), 5, args); - /* message, source, lineno, colno, error */ - return; - } + // Encode the exception into a JS object, which we will then turn into JSON. + auto json_obj = v8::Object::New(isolate); + + auto exception_str = exception->ToString(context).ToLocalChecked(); + // Alternate and very similar string. Not sure which is appropriate. + // auto exception_str = message->Get(); + CHECK(json_obj->Set(context, v8_str("message"), exception_str).FromJust()); - char buf[12 * 1024]; + v8::Local<v8::Array> frames; if (!stack_trace.IsEmpty()) { - // No javascript error handler, but we do have a stack trace. Format it - // into a string and add to last_exception_. - std::string msg; - v8::String::Utf8Value exceptionStr(isolate, exception); - msg += ToCString(exceptionStr); - msg += "\n"; - - for (int i = 0; i < stack_trace->GetFrameCount(); ++i) { + uint32_t count = static_cast<uint32_t>(stack_trace->GetFrameCount()); + frames = v8::Array::New(isolate, count); + + for (uint32_t i = 0; i < count; ++i) { auto frame = stack_trace->GetFrame(isolate, i); - v8::String::Utf8Value script_name(isolate, frame->GetScriptName()); - int l = frame->GetLineNumber(); - int c = frame->GetColumn(); - snprintf(buf, sizeof(buf), "%s %d:%d\n", ToCString(script_name), l, c); - msg += buf; + auto frame_obj = v8::Object::New(isolate); + CHECK(frames->Set(context, i, frame_obj).FromJust()); + auto line = v8::Integer::New(isolate, frame->GetLineNumber()); + auto column = v8::Integer::New(isolate, frame->GetColumn()); + CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust()); + CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("functionName"), frame->GetFunctionName()) + .FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("scriptName"), + frame->GetScriptNameOrSourceURL()) + .FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("isEval"), + v8::Boolean::New(isolate, frame->IsEval())) + .FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("isConstructor"), + v8::Boolean::New(isolate, frame->IsConstructor())) + .FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("isWasm"), + v8::Boolean::New(isolate, frame->IsWasm())) + .FromJust()); } - *exception_str += msg; } else { - // No javascript error handler, no stack trace. Format the little info we - // have into a string and add to last_exception_. - v8::String::Utf8Value exceptionStr(isolate, exception); - v8::String::Utf8Value script_name(isolate, - message->GetScriptResourceName()); - v8::String::Utf8Value line_str(isolate, line); - v8::String::Utf8Value col_str(isolate, column); - snprintf(buf, sizeof(buf), "%s\n%s %s:%s\n", ToCString(exceptionStr), - ToCString(script_name), ToCString(line_str), ToCString(col_str)); - *exception_str += buf; + // No stack trace. We only have one stack frame of info.. + frames = v8::Array::New(isolate, 1); + + auto frame_obj = v8::Object::New(isolate); + CHECK(frames->Set(context, 0, frame_obj).FromJust()); + + auto line = + v8::Integer::New(isolate, message->GetLineNumber(context).FromJust()); + auto column = + v8::Integer::New(isolate, message->GetStartColumn(context).FromJust()); + + CHECK(frame_obj->Set(context, v8_str("line"), line).FromJust()); + CHECK(frame_obj->Set(context, v8_str("column"), column).FromJust()); + CHECK(frame_obj + ->Set(context, v8_str("scriptName"), + message->GetScriptResourceName()) + .FromJust()); } + + CHECK(json_obj->Set(context, v8_str("frames"), frames).FromJust()); + + auto json_string = v8::JSON::Stringify(context, json_obj).ToLocalChecked(); + v8::String::Utf8Value json_string_(isolate, json_string); + return std::string(ToCString(json_string_)); } void HandleException(v8::Local<v8::Context> context, v8::Local<v8::Value> exception) { v8::Isolate* isolate = context->GetIsolate(); DenoIsolate* d = FromIsolate(isolate); - std::string exception_str; - HandleExceptionStr(context, exception, &exception_str); + std::string json_str = EncodeExceptionAsJSON(context, exception); if (d != nullptr) { - d->last_exception_ = exception_str; + d->last_exception_ = json_str; } else { - std::cerr << "Pre-Deno Exception " << exception_str << std::endl; - exit(1); - } -} - -const char* PromiseRejectStr(enum v8::PromiseRejectEvent e) { - switch (e) { - case v8::PromiseRejectEvent::kPromiseRejectWithNoHandler: - return "RejectWithNoHandler"; - case v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject: - return "HandlerAddedAfterReject"; - case v8::PromiseRejectEvent::kPromiseResolveAfterResolved: - return "ResolveAfterResolved"; - case v8::PromiseRejectEvent::kPromiseRejectAfterResolved: - return "RejectAfterResolved"; + // This shouldn't happen in normal circumstances. Added for debugging. + std::cerr << "Pre-Deno Exception " << json_str << std::endl; + CHECK(false); } } @@ -169,40 +168,35 @@ void PromiseRejectCallback(v8::PromiseRejectMessage promise_reject_message) { DenoIsolate* d = static_cast<DenoIsolate*>(isolate->GetData(0)); DCHECK_EQ(d->isolate_, isolate); v8::HandleScope handle_scope(d->isolate_); - auto exception = promise_reject_message.GetValue(); + auto error = promise_reject_message.GetValue(); auto context = d->context_.Get(d->isolate_); auto promise = promise_reject_message.GetPromise(); - auto event = promise_reject_message.GetEvent(); v8::Context::Scope context_scope(context); - auto promise_reject_handler = d->promise_reject_handler_.Get(isolate); - - if (!promise_reject_handler.IsEmpty()) { - v8::Local<v8::Value> args[3]; - args[1] = v8_str(PromiseRejectStr(event)); - args[2] = promise; - /* error, event, promise */ - if (event == v8::PromiseRejectEvent::kPromiseRejectWithNoHandler) { - d->pending_promise_events_++; - // exception only valid for kPromiseRejectWithNoHandler - args[0] = exception; - } else if (event == - v8::PromiseRejectEvent::kPromiseHandlerAddedAfterReject) { - d->pending_promise_events_--; // unhandled event cancelled - if (d->pending_promise_events_ < 0) { - d->pending_promise_events_ = 0; - } - // Placeholder, not actually used - args[0] = v8_str("Promise handler added"); - } else if (event == v8::PromiseRejectEvent::kPromiseResolveAfterResolved) { - d->pending_promise_events_++; - args[0] = v8_str("Promise resolved after resolved"); - } else if (event == v8::PromiseRejectEvent::kPromiseRejectAfterResolved) { - d->pending_promise_events_++; - args[0] = v8_str("Promise rejected after resolved"); - } - promise_reject_handler->Call(context->Global(), 3, args); - return; + + int promise_id = promise->GetIdentityHash(); + switch (promise_reject_message.GetEvent()) { + case v8::kPromiseRejectWithNoHandler: + // Insert the error into the pending_promise_map_ using the promise's id + // as the key. + d->pending_promise_map_.emplace(std::piecewise_construct, + std::make_tuple(promise_id), + std::make_tuple(d->isolate_, error)); + break; + + case v8::kPromiseHandlerAddedAfterReject: + d->pending_promise_map_.erase(promise_id); + break; + + case v8::kPromiseRejectAfterResolved: + break; + + case v8::kPromiseResolveAfterResolved: + // Should not warn. See #1272 + break; + + default: + CHECK(false && "unreachable"); } } @@ -359,69 +353,6 @@ void Shared(v8::Local<v8::Name> property, info.GetReturnValue().Set(ab); } -// Sets the global error handler. -void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Isolate* isolate = args.GetIsolate(); - DenoIsolate* d = FromIsolate(isolate); - 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); -} - -// Sets the promise uncaught reject handler -void SetPromiseRejectHandler(const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Isolate* isolate = args.GetIsolate(); - DenoIsolate* d = FromIsolate(isolate); - DCHECK_EQ(d->isolate_, isolate); - - v8::HandleScope handle_scope(isolate); - - if (!d->promise_reject_handler_.IsEmpty()) { - isolate->ThrowException( - v8_str("libdeno.setPromiseRejectHandler 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->promise_reject_handler_.Reset(isolate, func); -} - -// Sets the promise uncaught reject handler -void SetPromiseErrorExaminer(const v8::FunctionCallbackInfo<v8::Value>& args) { - v8::Isolate* isolate = args.GetIsolate(); - DenoIsolate* d = FromIsolate(isolate); - DCHECK_EQ(d->isolate_, isolate); - - v8::HandleScope handle_scope(isolate); - - if (!d->promise_error_examiner_.IsEmpty()) { - isolate->ThrowException( - v8_str("libdeno.setPromiseErrorExaminer 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->promise_error_examiner_.Reset(isolate, func); -} - bool ExecuteV8StringSource(v8::Local<v8::Context> context, const char* js_filename, v8::Local<v8::String> source) { @@ -466,8 +397,7 @@ bool Execute(v8::Local<v8::Context> context, const char* js_filename, } void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context, - const char* js_filename, const char* js_source, - const char* source_map) { + const char* js_filename, const char* js_source) { CHECK_NE(js_source, nullptr); CHECK_NE(js_filename, nullptr); v8::HandleScope handle_scope(isolate); @@ -493,61 +423,8 @@ void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context, CHECK(deno_val->SetAccessor(context, deno::v8_str("shared"), Shared) .FromJust()); - 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 set_promise_reject_handler_tmpl = - v8::FunctionTemplate::New(isolate, SetPromiseRejectHandler); - auto set_promise_reject_handler_val = - set_promise_reject_handler_tmpl->GetFunction(context).ToLocalChecked(); - CHECK(deno_val - ->Set(context, deno::v8_str("setPromiseRejectHandler"), - set_promise_reject_handler_val) - .FromJust()); - - auto set_promise_error_examiner_tmpl = - v8::FunctionTemplate::New(isolate, SetPromiseErrorExaminer); - auto set_promise_error_examiner_val = - set_promise_error_examiner_tmpl->GetFunction(context).ToLocalChecked(); - CHECK(deno_val - ->Set(context, deno::v8_str("setPromiseErrorExaminer"), - set_promise_error_examiner_val) - .FromJust()); - { - if (source_map != nullptr) { - v8::TryCatch try_catch(isolate); - v8::ScriptOrigin origin(v8_str("set_source_map.js")); - std::string source_map_parens = - std::string("(") + std::string(source_map) + std::string(")"); - auto source_map_v8_str = deno::v8_str(source_map_parens.c_str()); - auto script = v8::Script::Compile(context, source_map_v8_str, &origin); - if (script.IsEmpty()) { - DCHECK(try_catch.HasCaught()); - HandleException(context, try_catch.Exception()); - return; - } - auto source_map_obj = script.ToLocalChecked()->Run(context); - if (source_map_obj.IsEmpty()) { - DCHECK(try_catch.HasCaught()); - HandleException(context, try_catch.Exception()); - return; - } - CHECK(deno_val - ->Set(context, deno::v8_str("mainSourceMap"), - source_map_obj.ToLocalChecked()) - .FromJust()); - } - auto source = deno::v8_str(js_source); - CHECK( - deno_val->Set(context, deno::v8_str("mainSource"), source).FromJust()); bool r = deno::ExecuteV8StringSource(context, js_filename, source); CHECK(r); @@ -558,10 +435,11 @@ void DenoIsolate::AddIsolate(v8::Isolate* isolate) { isolate_ = isolate; // Leaving this code here because it will probably be useful later on, but // disabling it now as I haven't got tests for the desired behavior. - // d->isolate->SetCaptureStackTraceForUncaughtExceptions(true); // d->isolate->SetAbortOnUncaughtExceptionCallback(AbortOnUncaughtExceptionCallback); // d->isolate->AddMessageListener(MessageCallback2); // d->isolate->SetFatalErrorHandler(FatalErrorCallback2); + isolate_->SetCaptureStackTraceForUncaughtExceptions( + true, 10, v8::StackTrace::kDetailed); isolate_->SetPromiseRejectCallback(deno::PromiseRejectCallback); isolate_->SetData(0, this); } diff --git a/libdeno/deno.h b/libdeno/deno.h index fb0b13746..48356bc86 100644 --- a/libdeno/deno.h +++ b/libdeno/deno.h @@ -37,7 +37,7 @@ typedef struct { Deno* deno_new(deno_buf snapshot, deno_config config); Deno* deno_new_snapshotter(deno_config config, const char* js_filename, - const char* js_source, const char* source_map); + const char* js_source); // Generate a snapshot. The resulting buf can be used with deno_new. // The caller must free the returned data by calling delete[] buf.data_ptr. @@ -48,6 +48,12 @@ void deno_delete(Deno* d); // Returns false on error. // Get error text with deno_last_exception(). // 0 = fail, 1 = success +// +// TODO change return value to be const char*. On success the return +// value is nullptr, on failure it is the JSON exception text that +// is returned by deno_last_exception(). Remove deno_last_exception(). +// The return string is valid until the next execution of deno_execute or +// deno_respond (as deno_last_exception is now). int deno_execute(Deno* d, void* user_data, const char* js_filename, const char* js_source); @@ -69,6 +75,12 @@ int deno_execute(Deno* d, void* user_data, const char* js_filename, // // A non-zero return value, means a JS exception was encountered during the // libdeno.recv() callback. Check deno_last_exception() for exception text. +// +// TODO change return value to be const char*. On success the return +// value is nullptr, on failure it is the JSON exception text that +// is returned by deno_last_exception(). Remove deno_last_exception(). +// The return string is valid until the next execution of deno_execute or +// deno_respond (as deno_last_exception is now). int deno_respond(Deno* d, void* user_data, int32_t req_id, deno_buf buf); void deno_check_promise_errors(Deno* d); diff --git a/libdeno/internal.h b/libdeno/internal.h index 8a366d515..3dc4d8d0e 100644 --- a/libdeno/internal.h +++ b/libdeno/internal.h @@ -19,7 +19,6 @@ class DenoIsolate { current_args_(nullptr), snapshot_creator_(nullptr), global_import_buf_ptr_(nullptr), - pending_promise_events_(0), recv_cb_(config.recv_cb), next_req_id_(0), user_data_(nullptr) { @@ -47,13 +46,13 @@ class DenoIsolate { const v8::FunctionCallbackInfo<v8::Value>* current_args_; v8::SnapshotCreator* snapshot_creator_; void* global_import_buf_ptr_; - int32_t pending_promise_events_; deno_recv_cb recv_cb_; int32_t next_req_id_; void* user_data_; v8::Persistent<v8::Context> context_; std::map<int32_t, v8::Persistent<v8::Value>> async_data_map_; + std::map<int, v8::Persistent<v8::Value>> pending_promise_map_; std::string last_exception_; v8::Persistent<v8::Function> recv_; v8::Persistent<v8::Function> global_error_handler_; @@ -91,26 +90,16 @@ void Recv(const v8::FunctionCallbackInfo<v8::Value>& args); void Send(const v8::FunctionCallbackInfo<v8::Value>& args); void Shared(v8::Local<v8::Name> property, const v8::PropertyCallbackInfo<v8::Value>& info); -void SetGlobalErrorHandler(const v8::FunctionCallbackInfo<v8::Value>& args); -void SetPromiseRejectHandler(const v8::FunctionCallbackInfo<v8::Value>& args); -void SetPromiseErrorExaminer(const v8::FunctionCallbackInfo<v8::Value>& args); static intptr_t external_references[] = { - reinterpret_cast<intptr_t>(Print), - reinterpret_cast<intptr_t>(Recv), - reinterpret_cast<intptr_t>(Send), - reinterpret_cast<intptr_t>(Shared), - reinterpret_cast<intptr_t>(SetGlobalErrorHandler), - reinterpret_cast<intptr_t>(SetPromiseRejectHandler), - reinterpret_cast<intptr_t>(SetPromiseErrorExaminer), - 0}; + reinterpret_cast<intptr_t>(Print), reinterpret_cast<intptr_t>(Recv), + reinterpret_cast<intptr_t>(Send), reinterpret_cast<intptr_t>(Shared), 0}; static const deno_buf empty_buf = {nullptr, 0, nullptr, 0}; Deno* NewFromSnapshot(void* user_data, deno_recv_cb cb); void InitializeContext(v8::Isolate* isolate, v8::Local<v8::Context> context, - const char* js_filename, const char* js_source, - const char* source_map); + const char* js_filename, const char* js_source); void HandleException(v8::Local<v8::Context> context, v8::Local<v8::Value> exception); diff --git a/libdeno/libdeno_test.cc b/libdeno/libdeno_test.cc index f7173210a..33a4702ae 100644 --- a/libdeno/libdeno_test.cc +++ b/libdeno/libdeno_test.cc @@ -15,14 +15,14 @@ TEST(LibDenoTest, InitializesCorrectlyWithoutSnapshot) { } TEST(LibDenoTest, SnapshotterInitializesCorrectly) { - Deno* d = deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", - "a = 1 + 2", nullptr); + Deno* d = + deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", "a = 1 + 2"); deno_delete(d); } TEST(LibDenoTest, Snapshotter) { - Deno* d1 = deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", - "a = 1 + 2", nullptr); + Deno* d1 = + deno_new_snapshotter(deno_config{empty, nullptr}, "a.js", "a = 1 + 2"); deno_buf test_snapshot = deno_get_snapshot(d1); deno_delete(d1); @@ -182,23 +182,18 @@ TEST(LibDenoTest, SnapshotBug) { } TEST(LibDenoTest, GlobalErrorHandling) { - static int count = 0; - auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) { - assert_null(data_buf); - count++; - EXPECT_EQ(static_cast<size_t>(1), buf.data_len); - EXPECT_EQ(buf.data_ptr[0], 42); - }; - Deno* d = deno_new(snapshot, deno_config{empty, recv_cb}); - EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()")); - EXPECT_EQ(count, 1); - deno_delete(d); -} - -TEST(LibDenoTest, DoubleGlobalErrorHandlingFails) { Deno* d = deno_new(snapshot, deno_config{empty, nullptr}); - EXPECT_FALSE( - deno_execute(d, nullptr, "a.js", "DoubleGlobalErrorHandlingFails()")); + EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "GlobalErrorHandling()")); + // We only check that it starts with this string, so we don't have to check + // the second frame, which contains line numbers in libdeno_test.js and may + // change over time. + std::string expected = + "{\"message\":\"ReferenceError: notdefined is not defined\"," + "\"frames\":[{\"line\":3,\"column\":2,\"functionName\":\"\"," + "\"scriptName\":\"helloworld.js\",\"isEval\":true," + "\"isConstructor\":false,\"isWasm\":false},"; + std::string actual(deno_last_exception(d), 0, expected.length()); + EXPECT_STREQ(expected.c_str(), actual.c_str()); deno_delete(d); } @@ -225,17 +220,31 @@ TEST(LibDenoTest, DataBuf) { deno_delete(d); } -TEST(LibDenoTest, PromiseRejectCatchHandling) { +TEST(LibDenoTest, CheckPromiseErrors) { static int count = 0; - auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) { - // If no error, nothing should be sent, and count should - // not increment - count++; - }; + auto recv_cb = [](auto _, int req_id, auto buf, auto data_buf) { count++; }; Deno* d = deno_new(snapshot, deno_config{empty, recv_cb}); - EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "PromiseRejectCatchHandling()")); + EXPECT_EQ(deno_last_exception(d), nullptr); + EXPECT_TRUE(deno_execute(d, nullptr, "a.js", "CheckPromiseErrors()")); + EXPECT_EQ(deno_last_exception(d), nullptr); + EXPECT_EQ(count, 1); + // We caught the exception. So still no errors after calling + // deno_check_promise_errors(). + deno_check_promise_errors(d); + EXPECT_EQ(deno_last_exception(d), nullptr); + deno_delete(d); +} - EXPECT_EQ(count, 0); +TEST(LibDenoTest, LastException) { + Deno* d = deno_new(empty, deno_config{empty, nullptr}); + EXPECT_EQ(deno_last_exception(d), nullptr); + EXPECT_FALSE(deno_execute(d, nullptr, "a.js", "\n\nthrow Error('boo');\n\n")); + EXPECT_STREQ(deno_last_exception(d), + "{\"message\":\"Error: boo\"," + "\"frames\":[{\"line\":3,\"column\":7," + "\"functionName\":\"\",\"scriptName\":\"a.js\"," + "\"isEval\":false," + "\"isConstructor\":false,\"isWasm\":false}]}"); deno_delete(d); } diff --git a/libdeno/libdeno_test.js b/libdeno/libdeno_test.js index c9eaaa0ca..bb36b02d9 100644 --- a/libdeno/libdeno_test.js +++ b/libdeno/libdeno_test.js @@ -99,23 +99,9 @@ global.SnapshotBug = () => { }; global.GlobalErrorHandling = () => { - libdeno.setGlobalErrorHandler((message, source, line, col, error) => { - libdeno.print(`line ${line} col ${col}`, true); - assert("ReferenceError: notdefined is not defined" === message); - assert(source === "helloworld.js"); - assert(line === 3); - assert(col === 1); - assert(error instanceof Error); - libdeno.send(new Uint8Array([42])); - }); eval("\n\n notdefined()\n//# sourceURL=helloworld.js"); }; -global.DoubleGlobalErrorHandlingFails = () => { - libdeno.setGlobalErrorHandler((message, source, line, col, error) => {}); - libdeno.setGlobalErrorHandler((message, source, line, col, error) => {}); -}; - // Allocate this buf at the top level to avoid GC. const dataBuf = new Uint8Array([3, 4]); @@ -134,33 +120,7 @@ global.DataBuf = () => { b[1] = 8; }; -global.PromiseRejectCatchHandling = () => { - let count = 0; - let promiseRef = null; - // When we have an error, libdeno sends something - function assertOrSend(cond) { - if (!cond) { - libdeno.send(new Uint8Array([42])); - } - } - libdeno.setPromiseErrorExaminer(() => { - assertOrSend(count === 2); - }); - libdeno.setPromiseRejectHandler((error, event, promise) => { - count++; - if (event === "RejectWithNoHandler") { - assertOrSend(error instanceof Error); - assertOrSend(error.message === "message"); - assertOrSend(count === 1); - promiseRef = promise; - } else if (event === "HandlerAddedAfterReject") { - assertOrSend(count === 2); - assertOrSend(promiseRef === promise); - } - // Should never reach 3! - assertOrSend(count !== 3); - }); - +global.CheckPromiseErrors = () => { async function fn() { throw new Error("message"); } @@ -169,10 +129,10 @@ global.PromiseRejectCatchHandling = () => { try { await fn(); } catch (e) { - assertOrSend(count === 2); + libdeno.send(new Uint8Array([42])); } })(); -} +}; global.Shared = () => { const ab = libdeno.shared; @@ -185,4 +145,4 @@ global.Shared = () => { ui8[0] = 42; ui8[1] = 43; ui8[2] = 44; -} +}; diff --git a/libdeno/snapshot_creator.cc b/libdeno/snapshot_creator.cc index cfdd35804..fe9011c9d 100644 --- a/libdeno/snapshot_creator.cc +++ b/libdeno/snapshot_creator.cc @@ -30,9 +30,7 @@ int main(int argc, char** argv) { deno_init(); deno_config config = {deno::empty_buf, nullptr}; - Deno* d = deno_new_snapshotter( - config, js_fn, js_source.c_str(), - source_map_fn != nullptr ? source_map.c_str() : nullptr); + Deno* d = deno_new_snapshotter(config, js_fn, js_source.c_str()); auto snapshot = deno_get_snapshot(d); |