Skip to content

Commit 293c732

Browse files
authored
Merge pull request #1075 from JckXia/handle-error-thrown
Add logic to handle graceful error handling when primitive errors are thrown by JS functions
2 parents 706b199 + e0567d0 commit 293c732

7 files changed

+121
-2
lines changed

doc/error_handling.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ If C++ exceptions are enabled (for more info see: [Setup](setup.md)), then the
1414
`Napi::Error` class extends `std::exception` and enables integrated
1515
error-handling for C++ exceptions and JavaScript exceptions.
1616

17+
Note, that due to limitations of the Node-API, if one attempts to cast the error object wrapping a primitive inside a C++ addon, the wrapped object
18+
will be received instead. (With property `4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal` containing the primitive value thrown)
19+
1720
The following sections explain the approach for each case:
1821

1922
- [Handling Errors With C++ Exceptions](#exceptions)

napi-inl.h

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2593,14 +2593,80 @@ inline Error::Error() : ObjectReference() {
25932593

25942594
inline Error::Error(napi_env env, napi_value value) : ObjectReference(env, nullptr) {
25952595
if (value != nullptr) {
2596+
// Attempting to create a reference on the error object.
2597+
// If it's not a Object/Function/Symbol, this call will return an error
2598+
// status.
25962599
napi_status status = napi_create_reference(env, value, 1, &_ref);
25972600

2601+
if (status != napi_ok) {
2602+
napi_value wrappedErrorObj;
2603+
2604+
// Create an error object
2605+
status = napi_create_object(env, &wrappedErrorObj);
2606+
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_object");
2607+
2608+
// property flag that we attach to show the error object is wrapped
2609+
napi_property_descriptor wrapObjFlag = {
2610+
ERROR_WRAP_VALUE, // Unique GUID identifier since Symbol isn't a
2611+
// viable option
2612+
nullptr,
2613+
nullptr,
2614+
nullptr,
2615+
nullptr,
2616+
Value::From(env, value),
2617+
napi_enumerable,
2618+
nullptr};
2619+
2620+
status = napi_define_properties(env, wrappedErrorObj, 1, &wrapObjFlag);
2621+
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_define_properties");
2622+
2623+
// Create a reference on the newly wrapped object
2624+
status = napi_create_reference(env, wrappedErrorObj, 1, &_ref);
2625+
}
2626+
25982627
// Avoid infinite recursion in the failure case.
2599-
// Don't try to construct & throw another Error instance.
26002628
NAPI_FATAL_IF_FAILED(status, "Error::Error", "napi_create_reference");
26012629
}
26022630
}
26032631

2632+
inline Object Error::Value() const {
2633+
if (_ref == nullptr) {
2634+
return Object(_env, nullptr);
2635+
}
2636+
2637+
napi_value refValue;
2638+
napi_status status = napi_get_reference_value(_env, _ref, &refValue);
2639+
NAPI_THROW_IF_FAILED(_env, status, Object());
2640+
2641+
napi_valuetype type;
2642+
status = napi_typeof(_env, refValue, &type);
2643+
NAPI_THROW_IF_FAILED(_env, status, Object());
2644+
2645+
// If refValue isn't a symbol, then we proceed to whether the refValue has the
2646+
// wrapped error flag
2647+
if (type != napi_symbol) {
2648+
// We are checking if the object is wrapped
2649+
bool isWrappedObject = false;
2650+
2651+
status = napi_has_property(
2652+
_env, refValue, String::From(_env, ERROR_WRAP_VALUE), &isWrappedObject);
2653+
2654+
// Don't care about status
2655+
if (isWrappedObject) {
2656+
napi_value unwrappedValue;
2657+
status = napi_get_property(_env,
2658+
refValue,
2659+
String::From(_env, ERROR_WRAP_VALUE),
2660+
&unwrappedValue);
2661+
NAPI_THROW_IF_FAILED(_env, status, Object());
2662+
2663+
return Object(_env, unwrappedValue);
2664+
}
2665+
}
2666+
2667+
return Object(_env, refValue);
2668+
}
2669+
26042670
inline Error::Error(Error&& other) : ObjectReference(std::move(other)) {
26052671
}
26062672

@@ -2651,6 +2717,7 @@ inline const std::string& Error::Message() const NAPI_NOEXCEPT {
26512717
return _message;
26522718
}
26532719

2720+
// we created an object on the &_ref
26542721
inline void Error::ThrowAsJavaScriptException() const {
26552722
HandleScope scope(_env);
26562723
if (!IsEmpty()) {

napi.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1699,6 +1699,8 @@ namespace Napi {
16991699
const std::string& Message() const NAPI_NOEXCEPT;
17001700
void ThrowAsJavaScriptException() const;
17011701

1702+
Object Value() const;
1703+
17021704
#ifdef NAPI_CPP_EXCEPTIONS
17031705
const char* what() const NAPI_NOEXCEPT override;
17041706
#endif // NAPI_CPP_EXCEPTIONS
@@ -1718,7 +1720,9 @@ namespace Napi {
17181720
/// !endcond
17191721

17201722
private:
1721-
mutable std::string _message;
1723+
const char* ERROR_WRAP_VALUE =
1724+
"4bda9e7e-4913-4dbc-95de-891cbf66598e-errorVal";
1725+
mutable std::string _message;
17221726
};
17231727

17241728
class TypeError : public Error {

test/binding.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Object InitDate(Env env);
3131
Object InitDataView(Env env);
3232
Object InitDataViewReadWrite(Env env);
3333
Object InitEnvCleanup(Env env);
34+
Object InitErrorHandlingPrim(Env env);
3435
Object InitError(Env env);
3536
Object InitExternal(Env env);
3637
Object InitFunction(Env env);
@@ -113,6 +114,7 @@ Object Init(Env env, Object exports) {
113114
exports.Set("env_cleanup", InitEnvCleanup(env));
114115
#endif
115116
exports.Set("error", InitError(env));
117+
exports.Set("errorHandlingPrim", InitErrorHandlingPrim(env));
116118
exports.Set("external", InitExternal(env));
117119
exports.Set("function", InitFunction(env));
118120
exports.Set("functionreference", InitFunctionReference(env));

test/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
'dataview/dataview_read_write.cc',
2626
'env_cleanup.cc',
2727
'error.cc',
28+
'error_handling_for_primitives.cc',
2829
'external.cc',
2930
'function.cc',
3031
'function_reference.cc',

test/error_handling_for_primitives.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <napi.h>
2+
3+
namespace {
4+
void Test(const Napi::CallbackInfo& info) {
5+
info[0].As<Napi::Function>().Call({});
6+
}
7+
8+
} // namespace
9+
Napi::Object InitErrorHandlingPrim(Napi::Env env) {
10+
Napi::Object exports = Napi::Object::New(env);
11+
exports.Set("errorHandlingPrim", Napi::Function::New<Test>(env));
12+
return exports;
13+
}

test/error_handling_for_primitives.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
const assert = require('assert');
4+
5+
module.exports = require('./common').runTest((binding) => {
6+
test(binding.errorHandlingPrim);
7+
});
8+
9+
function canThrow (binding, errorMessage, errorType) {
10+
try {
11+
binding.errorHandlingPrim(() => {
12+
throw errorMessage;
13+
});
14+
} catch (e) {
15+
// eslint-disable-next-line valid-typeof
16+
assert(typeof e === errorType);
17+
assert(e === errorMessage);
18+
}
19+
}
20+
21+
function test (binding) {
22+
canThrow(binding, '404 server not found!', 'string');
23+
canThrow(binding, 42, 'number');
24+
canThrow(binding, Symbol.for('newSym'), 'symbol');
25+
canThrow(binding, false, 'boolean');
26+
canThrow(binding, BigInt(123), 'bigint');
27+
canThrow(binding, () => { console.log('Logger shutdown incorrectly'); }, 'function');
28+
canThrow(binding, { status: 403, errorMsg: 'Not authenticated' }, 'object');
29+
}

0 commit comments

Comments
 (0)