-
Notifications
You must be signed in to change notification settings - Fork 459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Napi::TypeError::New is not an instanceof TypeError #743
Comments
I don't see anything obviously wrong with the node-addon-api or N-API code in core, looks like they should create a TypeError. What kind of object are you receiving and any more info in it that might help figure out what is going on? |
@mhdawson I've created an example rynz/node-addon-api-exception-example That's the thing, it is receiving an Error/TypeError and the prototype chain looks okay. I've added a test to compare N-API vs Javascript debug.
|
Thanks for putting together the example, I'll try to take a look next week. |
Hi everybody, 'use strict'
const addon = require('./lib/rwar')
const assert = require('assert')
try {
addon.rwar()
} catch (err) {
assert.strictEqual(typeof TypeError == 'function', true)
assert.strictEqual(err instanceof TypeError, true)
assert.strictEqual(err instanceof Error, true)
assert.strictEqual(typeof err.constructor === 'function' && err.constructor == TypeError, true)
assert.strictEqual(typeof err.constructor === 'function' && err.constructor == Error, false)
} I tried to replicate the checks that |
I was also just looking at this. Using plain old instanceof gives the expected answer: const addon = require('../rwar')
try {
addon.rwar()
} catch (err) {
console.log(err);
console.log(err instanceof TypeError);
} -sh-4.2$ node test.js |
I'm trying to execute the some tests with other test suite like:
'use strict'
const tap = require('tap')
const addon = require('./lib/rwar')
const test = tap.test
test('test 1', (t) => {
try {
addon.rwar()
} catch (err) {
t.match(err, TypeError)
}
t.done()
})
test('test 2', (t) => {
const fn = function() {
return addon.rwar()
}
t.throws(fn, TypeError)
t.done()
})
'use strict'
const addon = require('./lib/rwar')
describe('Test addon', function () {
it('Schould be TypeError', function() {
try {
addon.hello()
} catch (err) {
expect(err).toBeInstanceOf(Error)
expect(err).toBeInstanceOf(TypeError)
}
})
}) All continue to work as expected. Do you know if with jest we need some particular configuration? |
I'm wondering if jest does some instrumentation of TypeError that's getting in the way. |
Hi everybody, |
@NickNaso great idea, I think we need somebody with knowledge of jest to comment. |
The root cause may be related to the same issue in #764. The effects of JEST using different contexts. |
Does seems related to JEST using contexts. This test recreates the behavior reported: const addon = require('../rwar')
const vm = require('vm');
try {
addon.rwar();
} catch (err) {
console.log('Regular context:' + (err instanceof TypeError));
}
try {
const contextObject = { };
vm.runInNewContext("addon.rwar()", contextObject);
} catch (err) {
console.log('New context:' + (err instanceof TypeError));
} |
@mhdawson thank you. What is the use-case of multi-context with node.js? |
In terms of "multi-context" I think "context" might be a bit overloaded. I do know that there is/was work to let add-ons work nicely with Worker Threads such that you have ways to easily store data which is not global but instead specific to the worker thread from which the add-on is called. Sometimes I think I've seen this referred to as multi-context or something similar as well. That's one use case. In terms of the question you raised in this issue, the use case is doing things like calling in vm.runInNewContext and getting what you expect if it had been JavaScript code versus native code. Not sure this helped to clarify. @gabrielschulhof do you have a better explanation? |
Some discussion in the N-API meeting today and its a bit less clear as I may not have gotten a good test case. Will try to update the test case a bit later today. |
Ok so the test case I had above was missing the right version (which I had checked as well) to replicate. So the move complete test case is: |
const addon = require('../rwar')
const vm = require('vm');
try {
addon.rwar();
} catch (err) {
console.log('Regular context:' + (err instanceof TypeError));
}
try {
const contextObject = { };
vm.runInNewContext("addon.rwar()", contextObject);
} catch (err) {
console.log('New context:' + (err instanceof TypeError));
}
const contextObject = { console };
vm.runInNewContext('try {addon.rwar() } catch (err) {console.log("New context:" + (err instanceof TypeError))}', contextObject); |
And more complete with an explanation of the expected results for each case. Still same conclusion that the native addon not being aware of the context when run under const addon = require('../rwar')
const vm = require('vm');
const contextObject = { console };
/////////////////////////
// plain JavaScript
/////////////////////////
console.log('Plain JavaScript');
// expect err to be instnace of TypeError as only one context
try {
throw new TypeError();
} catch (err) {
console.log('Regular context:' + (err instanceof TypeError));
}
// Don't expect err to be instance of TypeError as it was
// created in a different context from the context in which the
// check was made
try {
vm.runInNewContext("throw new TypeError()", contextObject);
} catch (err) {
console.log('New context:' + (err instanceof TypeError));
}
// expect err to be instance of TypeError as check is in
// same context as where err wsa created
vm.runInNewContext('try {throw new TypeError()} catch (err) {console.log("New context:" + (err instanceof TypeError))}', contextObject);
/////////////////////////
// Addons
/////////////////////////
console.log();
console.log('Addons');
// expect err to be instnace of TypeError as only one context
try {
addon.rwar();
} catch (err) {
console.log('Regular context:' + (err instanceof TypeError));
}
// Don't expect err to be instance of TypeError as it was
// created in a different context from the context in which the
// check was made
try {
vm.runInNewContext("addon.rwar()", contextObject);
} catch (err) {
console.log('New context:' + (err instanceof TypeError));
}
// expect err to be instance of TypeError as check is in
// same context as where err wsa created
vm.runInNewContext('try {addon.rwar() } catch (err) {console.log("New context:" + (err instanceof TypeError))}', contextObject); with the output being: bash-4.2$ node test.js
Plain JavaScript
Regular context:true
New context:false
New context:true
Addons
Regular context:true
New context:false
New context:false where the last one recreates the different between plain JavaScript and an exception thrown in a native, checked in JavaScript while running under |
@gabrielschulhof updated test case as discussed in the N-API team meeting today. From some additional experimental is seems like for addons the context used to create the error when a native method is run under For example const copyOfTypeError = TypeError;
const contextObject = { console, TypeError, copyOfTypeError };
vm.runInNewContext('try {addon.rwar() } catch (err) {console.log("New context:" + (err instanceof copyOfTypeError))}', contextObject); Still results in false, where as doing the same for plan JavaScript reports true. |
Fantastic work @mhdawson! |
@mhdawson I also did some testing, and it looks like the addon always produces objects descended from the built-ins present in the context of its creation: #include <node_api.h>
#include "../../js-native-api/common.h"
static napi_value newError(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value message, error;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &message, NULL, NULL));
NAPI_CALL(env, napi_create_error(env, NULL, message, &error));
return error;
}
NAPI_MODULE_INIT() {
napi_property_descriptor methods[] = {
DECLARE_NAPI_PROPERTY("newError", newError),
};
NAPI_CALL(env, napi_define_properties(
env, exports, sizeof(methods) / sizeof(methods[0]), methods));
return exports;
} 'use strict';
const binding = require(`./build/${common.buildType}/test_vm_context`);
const vm = require('vm');
const jsError = new Error('JS Error');
const nativeError = binding.newError('Native Error');
console.log('js:');
console.log(' created in: normal context, checked in: normal context: ' +
(jsError instanceof Error));
console.log(' created in: normal context, checked in: vm context: ' +
vm.runInNewContext('jsError instanceof Error', { jsError }));
console.log(' created in: vm context, checked in: normal context: ' +
(vm.runInNewContext('new Error(\'JS Error\');') instanceof Error));
console.log(' created in: vm context, checked in: vm context: ' +
vm.runInNewContext('(new Error(\'JS Error\')) instanceof Error;'));
console.log('');
console.log('native:');
console.log(' created in: normal context, checked in: normal context: ' +
(nativeError instanceof Error));
console.log(' created in: normal context, checked in: vm context: ' +
vm.runInNewContext('nativeError instanceof Error', { nativeError }));
console.log(' created in: vm context, checked in: normal context: ' +
(vm.runInNewContext('binding.newError(\'nativeError\');', { binding })
instanceof Error));
console.log(' created in: vm context, checked in: vm context: ' +
vm.runInNewContext('binding.newError(\'nativeError\') instanceof Error;',
{ binding })); and the output was:
Notice that I had to pass Basically, |
I looked at the V8 documentation and, AFAICT it doesn't even have the facilities for creating a context-sensitive error: https://v8docs.nodesource.com/node-14.1/da/d6a/classv8_1_1_exception.html |
from the limited time I'd taken to look at it, I was thinking the same thing since the creation of TypeError had no way to pass in a context. |
I did wonder though if there was some other way that the current context used by TypeError() could be set on the isolate or through some other way but had not had time to look at the code for that. |
@mhdawson @gabrielschulhof You could enter another context while creating the error object, if you want, but that’s probably not a good approach to take here, partially because N-API functions are more or less baked in stone and partially because this is not a typical use case (and because it would be better addressed through actually making N-API addons context-sensitive). What you can do in this situation is passing the |
+1 I think @gabrielschulhof and myself were just tying to think about how you might do that, as we don't have a good enough understanding of that yet. |
Going to close this out, please let us know if this is not the right thing to do. |
Consider:
For whatever reason I am getting:
Whenever I check
err instanceof Error
orerr instanceof TypeError
both returnfalse
. Is there something I am doing wrong, or is this a bug?The text was updated successfully, but these errors were encountered: