Skip to content
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

[v2.x] Backport ObjectWrap reference-related changes #723

Merged

Conversation

gabrielschulhof
Copy link
Contributor

No description provided.

@gabrielschulhof
Copy link
Contributor Author

Re #646

@gabrielschulhof gabrielschulhof changed the title Backport ObjectWrap reference-related changes [v2.x] Backport ObjectWrap reference-related changes May 11, 2020
Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@gabrielschulhof
Copy link
Contributor Author

@addaleax I actually had to add another commit, also cleanly applied – the one that updates the BigInt test for changes to the expected error message in core – since we're testing 2.x again.

@gabrielschulhof
Copy link
Contributor Author

@addaleax this crashes on Node.js v8.x because we didn't backport the reference tracking changes before it went EOL.

@addaleax
Copy link
Member

@gabrielschulhof Sigh … yeah, not sure what to do about that. I don’t suppose we want to detect the Node.js version in here and skip the relevant fixes for Node < 10? I would be okay with that but I’d also understand that it’s not a great solution.

(I mean, it’s also not clear that N-API had breaking changes in core. But that’s not really something we can do anything about…)

@gabrielschulhof
Copy link
Contributor Author

@addaleax the detection would have to happen at runtime because anything built against Node.js 8.x must also run without recompilation on later versions.

@addaleax
Copy link
Member

@gabrielschulhof Yes, I was talking about VersionManagement::GetNodeVersion().

@gabrielschulhof
Copy link
Contributor Author

@addaleax I started implementing it but then I realized that we cannot just call Napi::VersionManagement::GetNodeVersion() from the destructor because that would slow down the destructor which can, after all, be called quite often if its instances are created/destroyed in a tight loop. We'd have to store the result once and reduce it to a boolean which evaluates to true only on old versions of Node.js (< 10.15.3, to be precise). That, in turn requires that we store the value somewhere. A global static is not a good option because that would break the whole context-awareness. My best guess would be a global static thread_local.

@addaleax
Copy link
Member

A global static is not a good option because that would break the whole context-awareness.

I don’t quite see how it does that? It’s not like the Node.js version is going to vary between contexts, or is that a concern here?

@gabrielschulhof
Copy link
Contributor Author

@addaleax no, but it should at least be assigned in a thread-safe fashion.

@addaleax
Copy link
Member

@gabrielschulhof Do we assume C++11 here? It would come with std::atomic which solves that for us. Without it, thread_local won’t be available either…

@gabrielschulhof
Copy link
Contributor Author

@addaleax I think it's safe to assume C++11.

@@ -10,6 +10,7 @@
// Note: Do not include this file directly! Include "napi.h" instead.

#include <algorithm>
#include <atomic>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new change, not part of any cherry-pick.

Comment on lines +23 to +24
extern std::atomic_bool needs_objectwrap_destructor_fix;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new change, not part of any cherry-pick.

Comment on lines +258 to +262
namespace Napi { \
namespace details { \
std::atomic_bool needs_objectwrap_destructor_fix(false); \
} \
} \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new change, not part of any cherry-pick.

Comment on lines +275 to +280
const napi_node_version* nver = Napi::VersionManagement::GetNodeVersion(env);
Napi::details::needs_objectwrap_destructor_fix =
(nver->major < 10 ||
(nver->major == 10 && nver->minor < 15) ||
(nver->major == 10 && nver->minor == 15 && nver->patch < 3));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new change, not part of any cherry-pick

Comment on lines +3003 to +3011
if (Napi::details::needs_objectwrap_destructor_fix) {
// If construction failed we delete the reference via
// `napi_remove_wrap()`, not via `napi_delete_reference()` in the
// `Reference<Object>` destructor. This will prevent the
// `Reference<Object>` destructor from doing a double delete of this
// reference.
_ref = nullptr;
_env = nullptr;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new change, not part of any cherry-pick.

@gabrielschulhof
Copy link
Contributor Author

@addaleax PTAL. I pointed out the changes introduced by the commit that is not a cherry-pick.

@gabrielschulhof
Copy link
Contributor Author

Oh, but wait! 2.x doesn't support Node.js 14.

@gabrielschulhof
Copy link
Contributor Author

@mhdawson I think we're good to land this PR and make a final 2.x release. PTAL!

Gabriel Schulhof and others added 4 commits June 12, 2020 11:55
Ensure that no native instance pointer is associated with the
JavaScript object under construction if the native constructor causes a
JavaScript exception to be thrown.

Two different cases must be taken into consideration:

1. The exception in the constructor was caused by
   `ObjectWrap<T>::ObjectWrap` when the call to `napi_wrap()` failed.
2. The exception in the constructor was caused by the constructor of
   the subclass of `ObjectWrap<T>` after `napi_wrap()` was already
   successful.

Fixes: nodejs#599
Co-authored-by: blagoev <lubo@blagoev.com>
PR-URL: nodejs#600
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Currently, when the `ObjectWrap` constructor runs, it calls
`napi_wrap()`, adding a finalize callback to the freshly created
JS object.

However, if the `ObjectWrap` instance is prematurely deleted,
for example because a subclass constructor throws – which seems
like a reasonable scenario – that finalize callback was not removed,
possibly leading to a use-after-free crash.

This commit adds a call `napi_remove_wrap()` from the `ObjectWrap`
destructor,  and a test for that scenario.

This also changes the code to use the correct pointer type
in `FinalizeCallback`, which may not match the incorretct one
in cases of multiple inheritance.

Fixes: node-ffi-napi/weak-napi#16
PR-URL: nodejs#475
Reviewed-By: Hitesh Kanwathirtha <digitalinfinity@gmail.com>
Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Co-authored-by: Gabriel Schulhof <gabriel.schulhof@intel.com>
`napi_remove_wrap()` was intended for objects that are alive for which
the native addon wishes to withdraw its native pointer, and perhaps
replace it with another.

Therefore we need not `napi_remove_wrap()` during gc/env-cleanup. It is
sufficient to `napi_delete_reference()`, as `Reference<Object>`
already does. We need only `napi_remove_wrap()` if the construction
failed and therefore no gc callback will ever happen.

This change also removes references to `ObjectWrapConstructionContext`
from the header because the class is not used anymore.

Fixes: nodejs#660
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
Reviewed-By: Kevin Eady <kevin.c.eady@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
nodejs/node@689ab46
changed the expected error for one of the BigInt test cases. This is
ok because BigInt is still in experimental. However, the
result was a failed/hanging test. See
nodejs/build#2131

This changes the test to accept either the new or old behaviour.
We need that so that older versions of Node.js will also pass the
test until the the node core commit above is backported.

PR-URL: nodejs#649
Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com>
Reviewed-By: Nicola Del Gobbo <nicoladelgobbo@gmail.com>
On Node.js versions earlier than 10.15.3 references were being cleaned
up differently. This has resulted in a segfault because of a double
free when the `ObjectWrap<T>` subclass constructor throws. We need a
fix that only runs on Node.js < 10.15.3. We use
`Napi::VersionManagement::GetNodeVersion()` at addon load time to
determine whether we need the workaround. We store the result in an
atomic boolean and consult its value whenever we destroy an
`ObjectWrap<T>` instance.
@gabrielschulhof gabrielschulhof force-pushed the backport-4e885069-pr-475 branch from 31be779 to 470f130 Compare June 12, 2020 18:55
@gabrielschulhof
Copy link
Contributor Author

Rebased because of the security release.

Copy link
Member

@mhdawson mhdawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@NickNaso NickNaso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@NickNaso NickNaso merged commit 5abf602 into nodejs:v2.x-staging Jul 1, 2020
@gabrielschulhof gabrielschulhof deleted the backport-4e885069-pr-475 branch February 1, 2021 16:39
@gabrielschulhof gabrielschulhof restored the backport-4e885069-pr-475 branch February 1, 2021 16:40
@gabrielschulhof gabrielschulhof deleted the backport-4e885069-pr-475 branch March 7, 2021 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants