Skip to content

Commit 24f90ee

Browse files
committed
Add support for Error::Fatal
* Added static method `Error::Fatal` to invoke `napi_fatal_error` * Added `node_internals.cc/h` to shim missing internal functions * Added a test for `Error::Fatal` * Replaced usage of assert with calls to `Error::Fatal`
1 parent 10ef293 commit 24f90ee

File tree

12 files changed

+350
-45
lines changed

12 files changed

+350
-45
lines changed

napi-inl.h

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
// Note: Do not include this file directly! Include "napi.h" instead.
1111

12-
#include <cassert>
1312
#include <cstring>
1413

1514
namespace Napi {
@@ -42,6 +41,13 @@ namespace details {
4241

4342
#endif // NAPI_CPP_EXCEPTIONS
4443

44+
#define NAPI_FATAL_IF_FAILED(status, location, message) \
45+
do { \
46+
if ((status) != napi_ok) { \
47+
Error::Fatal((location), (message)); \
48+
} \
49+
} while (0)
50+
4551
// For use in JS to C++ callback wrappers to catch any Napi::Error exceptions
4652
// and rethrow them as JavaScript exceptions before returning from the callback.
4753
template <typename Callable>
@@ -1418,24 +1424,24 @@ inline Error Error::New(napi_env env) {
14181424

14191425
const napi_extended_error_info* info;
14201426
status = napi_get_last_error_info(env, &info);
1421-
assert(status == napi_ok);
1427+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_last_error_info");
14221428

14231429
if (status == napi_ok) {
14241430
if (info->error_code == napi_pending_exception) {
14251431
status = napi_get_and_clear_last_exception(env, &error);
1426-
assert(status == napi_ok);
1432+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception");
14271433
}
14281434
else {
14291435
const char* error_message = info->error_message != nullptr ?
14301436
info->error_message : "Error in native callback";
14311437

14321438
bool isExceptionPending;
14331439
status = napi_is_exception_pending(env, &isExceptionPending);
1434-
assert(status == napi_ok);
1440+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_is_exception_pending");
14351441

14361442
if (isExceptionPending) {
14371443
status = napi_get_and_clear_last_exception(env, &error);
1438-
assert(status == napi_ok);
1444+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_get_and_clear_last_exception");
14391445
}
14401446

14411447
napi_value message;
@@ -1444,7 +1450,7 @@ inline Error Error::New(napi_env env) {
14441450
error_message,
14451451
std::strlen(error_message),
14461452
&message);
1447-
assert(status == napi_ok);
1453+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_string_utf8");
14481454

14491455
if (status == napi_ok) {
14501456
switch (info->error_code) {
@@ -1458,7 +1464,7 @@ inline Error Error::New(napi_env env) {
14581464
status = napi_create_error(env, message, &error);
14591465
break;
14601466
}
1461-
assert(status == napi_ok);
1467+
NAPI_FATAL_IF_FAILED(status, "Error::New", "napi_create_error");
14621468
}
14631469
}
14641470
}
@@ -1474,6 +1480,10 @@ inline Error Error::New(napi_env env, const std::string& message) {
14741480
return Error::New<Error>(env, message.c_str(), message.size(), napi_create_error);
14751481
}
14761482

1483+
inline NAPI_NO_RETURN void Error::Fatal(const char* location, const char* message) {
1484+
napi_fatal_error(location, message);
1485+
}
1486+
14771487
inline Error::Error() : ObjectReference(), _message(nullptr) {
14781488
}
14791489

@@ -1483,7 +1493,7 @@ inline Error::Error(napi_env env, napi_value value) : ObjectReference(env, nullp
14831493

14841494
// Avoid infinite recursion in the failure case.
14851495
// Don't try to construct & throw another Error instance.
1486-
assert(status == napi_ok);
1496+
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference");
14871497
}
14881498
}
14891499

@@ -1661,9 +1671,7 @@ inline Reference<T>::Reference(const Reference<T>& other)
16611671
// Copying is a limited scenario (currently only used for Error object) and always creates a
16621672
// strong reference to the given value even if the incoming reference is weak.
16631673
napi_status status = napi_create_reference(_env, value, 1, &_ref);
1664-
1665-
// TODO - Switch to napi_fatal_error() once it exists.
1666-
assert(status == napi_ok);
1674+
NAPI_FATAL_IF_FAILED(status, "Reference<T>::Reference", "napi_create_reference");
16671675
}
16681676
}
16691677

napi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,8 @@ namespace Napi {
10671067
static Error New(napi_env env, const char* message);
10681068
static Error New(napi_env env, const std::string& message);
10691069

1070+
static NAPI_NO_RETURN void Fatal(const char* location, const char* message);
1071+
10701072
Error();
10711073
Error(napi_env env, napi_value value);
10721074

src/node_api.cc

Lines changed: 109 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#include <vector>
1818
#include "uv.h"
1919
#include "node_api.h"
20+
#include "node_internals.h"
21+
22+
#define NAPI_VERSION 1
2023

2124
static
2225
napi_status napi_set_last_error(napi_env env, napi_status error_code,
@@ -154,14 +157,20 @@ class HandleScopeWrapper {
154157
// across different versions.
155158
class EscapableHandleScopeWrapper {
156159
public:
157-
explicit EscapableHandleScopeWrapper(v8::Isolate* isolate) : scope(isolate) {}
160+
explicit EscapableHandleScopeWrapper(v8::Isolate* isolate)
161+
: scope(isolate), escape_called_(false) {}
162+
bool escape_called() const {
163+
return escape_called_;
164+
}
158165
template <typename T>
159166
v8::Local<T> Escape(v8::Local<T> handle) {
167+
escape_called_ = true;
160168
return scope.Escape(handle);
161169
}
162170

163171
private:
164172
v8::EscapableHandleScope scope;
173+
bool escape_called_;
165174
};
166175

167176
napi_handle_scope JsHandleScopeFromV8HandleScope(HandleScopeWrapper* s) {
@@ -716,7 +725,8 @@ const char* error_messages[] = {nullptr,
716725
"An array was expected",
717726
"Unknown failure",
718727
"An exception is pending",
719-
"The async work item was cancelled"};
728+
"The async work item was cancelled",
729+
"napi_escape_handle already called on scope"};
720730

721731
static napi_status napi_clear_last_error(napi_env env) {
722732
CHECK_ENV(env);
@@ -744,10 +754,14 @@ napi_status napi_get_last_error_info(napi_env env,
744754
CHECK_ENV(env);
745755
CHECK_ARG(env, result);
746756

757+
// you must update this assert to reference the last message
758+
// in the napi_status enum each time a new error message is added.
759+
// We don't have a napi_status_last as this would result in an ABI
760+
// change each time a message was added.
747761
static_assert(
748-
(sizeof (error_messages) / sizeof (*error_messages)) == napi_status_last,
762+
node::arraysize(error_messages) == napi_escape_called_twice + 1,
749763
"Count of error messages must match count of error values");
750-
assert(env->last_error.error_code < napi_status_last);
764+
assert(env->last_error.error_code <= napi_escape_called_twice);
751765

752766
// Wait until someone requests the last error information to fetch the error
753767
// message string
@@ -758,6 +772,11 @@ napi_status napi_get_last_error_info(napi_env env,
758772
return napi_ok;
759773
}
760774

775+
NAPI_NO_RETURN void napi_fatal_error(const char* location,
776+
const char* message) {
777+
node::FatalError(location, message);
778+
}
779+
761780
napi_status napi_create_function(napi_env env,
762781
const char* utf8name,
763782
napi_callback cb,
@@ -817,9 +836,6 @@ napi_status napi_define_class(napi_env env,
817836
v8::Local<v8::FunctionTemplate> tpl = v8::FunctionTemplate::New(
818837
isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata);
819838

820-
// we need an internal field to stash the wrapped object
821-
tpl->InstanceTemplate()->SetInternalFieldCount(1);
822-
823839
v8::Local<v8::String> name_string;
824840
CHECK_NEW_FROM_UTF8(env, name_string, utf8name);
825841
tpl->SetClassName(name_string);
@@ -991,6 +1007,28 @@ napi_status napi_get_property(napi_env env,
9911007
return GET_RETURN_STATUS(env);
9921008
}
9931009

1010+
napi_status napi_delete_property(napi_env env,
1011+
napi_value object,
1012+
napi_value key,
1013+
bool* result) {
1014+
NAPI_PREAMBLE(env);
1015+
CHECK_ARG(env, key);
1016+
1017+
v8::Isolate* isolate = env->isolate;
1018+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
1019+
v8::Local<v8::Value> k = v8impl::V8LocalValueFromJsValue(key);
1020+
v8::Local<v8::Object> obj;
1021+
1022+
CHECK_TO_OBJECT(env, context, obj, object);
1023+
v8::Maybe<bool> delete_maybe = obj->Delete(context, k);
1024+
CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure);
1025+
1026+
if (result != NULL)
1027+
*result = delete_maybe.FromMaybe(false);
1028+
1029+
return GET_RETURN_STATUS(env);
1030+
}
1031+
9941032
napi_status napi_set_named_property(napi_env env,
9951033
napi_value object,
9961034
const char* utf8name,
@@ -1128,6 +1166,26 @@ napi_status napi_get_element(napi_env env,
11281166
return GET_RETURN_STATUS(env);
11291167
}
11301168

1169+
napi_status napi_delete_element(napi_env env,
1170+
napi_value object,
1171+
uint32_t index,
1172+
bool* result) {
1173+
NAPI_PREAMBLE(env);
1174+
1175+
v8::Isolate* isolate = env->isolate;
1176+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
1177+
v8::Local<v8::Object> obj;
1178+
1179+
CHECK_TO_OBJECT(env, context, obj, object);
1180+
v8::Maybe<bool> delete_maybe = obj->Delete(context, index);
1181+
CHECK_MAYBE_NOTHING(env, delete_maybe, napi_generic_failure);
1182+
1183+
if (result != NULL)
1184+
*result = delete_maybe.FromMaybe(false);
1185+
1186+
return GET_RETURN_STATUS(env);
1187+
}
1188+
11311189
napi_status napi_define_properties(napi_env env,
11321190
napi_value object,
11331191
size_t property_count,
@@ -1948,14 +2006,24 @@ napi_status napi_wrap(napi_env env,
19482006
CHECK_ARG(env, js_object);
19492007

19502008
v8::Isolate* isolate = env->isolate;
1951-
v8::Local<v8::Object> obj =
1952-
v8impl::V8LocalValueFromJsValue(js_object).As<v8::Object>();
2009+
v8::Local<v8::Context> context = isolate->GetCurrentContext();
2010+
2011+
v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(js_object);
2012+
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
2013+
v8::Local<v8::Object> obj = value.As<v8::Object>();
19532014

1954-
// Only objects that were created from a NAPI constructor's prototype
1955-
// via napi_define_class() can be (un)wrapped.
1956-
RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg);
2015+
// Create a wrapper object with an internal field to hold the wrapped pointer.
2016+
v8::Local<v8::ObjectTemplate> wrapperTemplate =
2017+
v8::ObjectTemplate::New(isolate);
2018+
wrapperTemplate->SetInternalFieldCount(1);
2019+
v8::Local<v8::Object> wrapper =
2020+
wrapperTemplate->NewInstance(context).ToLocalChecked();
2021+
wrapper->SetInternalField(0, v8::External::New(isolate, native_object));
19572022

1958-
obj->SetInternalField(0, v8::External::New(isolate, native_object));
2023+
// Insert the wrapper into the object's prototype chain.
2024+
v8::Local<v8::Value> proto = obj->GetPrototype();
2025+
CHECK(wrapper->SetPrototype(context, proto).FromJust());
2026+
CHECK(obj->SetPrototype(context, wrapper).FromJust());
19592027

19602028
if (result != nullptr) {
19612029
// The returned reference should be deleted via napi_delete_reference()
@@ -1986,11 +2054,18 @@ napi_status napi_unwrap(napi_env env, napi_value js_object, void** result) {
19862054
RETURN_STATUS_IF_FALSE(env, value->IsObject(), napi_invalid_arg);
19872055
v8::Local<v8::Object> obj = value.As<v8::Object>();
19882056

1989-
// Only objects that were created from a NAPI constructor's prototype
1990-
// via napi_define_class() can be (un)wrapped.
1991-
RETURN_STATUS_IF_FALSE(env, obj->InternalFieldCount() > 0, napi_invalid_arg);
1992-
1993-
v8::Local<v8::Value> unwrappedValue = obj->GetInternalField(0);
2057+
// Search the object's prototype chain for the wrapper with an internal field.
2058+
// Usually the wrapper would be the first in the chain, but it is OK for
2059+
// other objects to be inserted in the prototype chain.
2060+
v8::Local<v8::Object> wrapper = obj;
2061+
do {
2062+
v8::Local<v8::Value> proto = wrapper->GetPrototype();
2063+
RETURN_STATUS_IF_FALSE(
2064+
env, !proto.IsEmpty() && proto->IsObject(), napi_invalid_arg);
2065+
wrapper = proto.As<v8::Object>();
2066+
} while (wrapper->InternalFieldCount() != 1);
2067+
2068+
v8::Local<v8::Value> unwrappedValue = wrapper->GetInternalField(0);
19942069
RETURN_STATUS_IF_FALSE(env, unwrappedValue->IsExternal(), napi_invalid_arg);
19952070

19962071
*result = unwrappedValue.As<v8::External>()->Value();
@@ -2195,9 +2270,12 @@ napi_status napi_escape_handle(napi_env env,
21952270

21962271
v8impl::EscapableHandleScopeWrapper* s =
21972272
v8impl::V8EscapableHandleScopeFromJsEscapableHandleScope(scope);
2198-
*result = v8impl::JsValueFromV8LocalValue(
2199-
s->Escape(v8impl::V8LocalValueFromJsValue(escapee)));
2200-
return napi_clear_last_error(env);
2273+
if (!s->escape_called()) {
2274+
*result = v8impl::JsValueFromV8LocalValue(
2275+
s->Escape(v8impl::V8LocalValueFromJsValue(escapee)));
2276+
return napi_clear_last_error(env);
2277+
}
2278+
return napi_set_last_error(env, napi_escape_called_twice);
22012279
}
22022280

22032281
napi_status napi_new_instance(napi_env env,
@@ -2250,7 +2328,7 @@ napi_status napi_instanceof(napi_env env,
22502328
}
22512329

22522330
if (env->has_instance_available) {
2253-
napi_value value, js_result, has_instance = nullptr;
2331+
napi_value value, js_result = nullptr, has_instance = nullptr;
22542332
napi_status status = napi_generic_failure;
22552333
napi_valuetype value_type;
22562334

@@ -2530,7 +2608,7 @@ napi_status napi_create_arraybuffer(napi_env env,
25302608
v8::ArrayBuffer::New(isolate, byte_length);
25312609

25322610
// Optionally return a pointer to the buffer's data, to avoid another call to
2533-
// retreive it.
2611+
// retrieve it.
25342612
if (data != nullptr) {
25352613
*data = buffer->GetContents().Data();
25362614
}
@@ -2713,6 +2791,13 @@ napi_status napi_get_typedarray_info(napi_env env,
27132791
return napi_clear_last_error(env);
27142792
}
27152793

2794+
napi_status napi_get_version(napi_env env, uint32_t* result) {
2795+
CHECK_ENV(env);
2796+
CHECK_ARG(env, result);
2797+
*result = NAPI_VERSION;
2798+
return napi_clear_last_error(env);
2799+
}
2800+
27162801
namespace uvimpl {
27172802

27182803
static napi_status ConvertUVErrorCode(int code) {
@@ -2781,7 +2866,7 @@ class Work {
27812866
// report it as a fatal exception. (There is no JavaScript on the
27822867
// callstack that can possibly handle it.)
27832868
if (!env->last_exception.IsEmpty()) {
2784-
v8::TryCatch try_catch;
2869+
v8::TryCatch try_catch(env->isolate);
27852870
env->isolate->ThrowException(
27862871
v8::Local<v8::Value>::New(env->isolate, env->last_exception));
27872872
node::FatalException(env->isolate, try_catch);

src/node_api.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
'type': 'static_library',
99
'sources': [
1010
'node_api.cc',
11+
'node_internals.cc',
1112
],
1213
'defines': [
1314
'EXTERNAL_NAPI',

0 commit comments

Comments
 (0)