diff options
author | Michael Dawson <mdawson@devrus.com> | 2018-11-16 21:42:31 +0000 |
---|---|---|
committer | Michael Dawson <michael_dawson@ca.ibm.com> | 2018-11-23 10:01:54 -0500 |
commit | d45e303ac67c027bbad709f465e479ac789302a7 (patch) | |
tree | 8d1a0c369f5a1c59f2cd0a5ff5f5b463fe5e9450 /test | |
parent | f85b43537d6dff4004081fcc81f8aa771efbe1e5 (diff) | |
download | android-node-v8-d45e303ac67c027bbad709f465e479ac789302a7.tar.gz android-node-v8-d45e303ac67c027bbad709f465e479ac789302a7.tar.bz2 android-node-v8-d45e303ac67c027bbad709f465e479ac789302a7.zip |
n-api: handle reference delete before finalize
Crashes were reported during finalization due to
the memory for a reference being deleted and the
finalizer running after the deletion.
This change ensures the deletion of the memory for
the reference only occurs after the finalizer has run.
Fixes: https://github.com/nodejs/node-addon-api/issues/393
PR-URL: https://github.com/nodejs/node/pull/24494
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
Diffstat (limited to 'test')
-rw-r--r-- | test/addons-napi/test_reference/test.js | 26 | ||||
-rw-r--r-- | test/addons-napi/test_reference/test_reference.c | 36 |
2 files changed, 62 insertions, 0 deletions
diff --git a/test/addons-napi/test_reference/test.js b/test/addons-napi/test_reference/test.js index 14932a74ca..389ee11d7e 100644 --- a/test/addons-napi/test_reference/test.js +++ b/test/addons-napi/test_reference/test.js @@ -118,3 +118,29 @@ runTests(0, undefined, [ assert.strictEqual(test_reference.finalizeCount, 1); }, ]); + +// This test creates a napi_ref on an object that has +// been wrapped by napi_wrap and for which the finalizer +// for the wrap calls napi_delete_ref on that napi_ref. +// +// Since both the wrap and the reference use the same +// object the finalizer for the wrap and reference +// may run in the same gc and in any order. +// +// It does that to validate that napi_delete_ref can be +// called before the finalizer has been run for the +// reference (there is a finalizer behind the scenes even +// though it cannot be passed to napi_create_reference). +// +// Since the order is not guarranteed, run the +// test a number of times maximize the chance that we +// get a run with the desired order for the test. +// +// 1000 reliably recreated the problem without the fix +// required to ensure delete could be called before +// the finalizer in manual testing. +for (let i = 0; i < 1000; i++) { + const wrapObject = new Object(); + test_reference.validateDeleteBeforeFinalize(wrapObject); + global.gc(); +} diff --git a/test/addons-napi/test_reference/test_reference.c b/test/addons-napi/test_reference/test_reference.c index 75abc49ad3..f3dc364477 100644 --- a/test/addons-napi/test_reference/test_reference.c +++ b/test/addons-napi/test_reference/test_reference.c @@ -1,3 +1,4 @@ +#include <stdlib.h> #include <node_api.h> #include "../common.h" @@ -131,6 +132,39 @@ static napi_value GetReferenceValue(napi_env env, napi_callback_info info) { return result; } +static void DeleteBeforeFinalizeFinalizer( + napi_env env, void* finalize_data, void* finalize_hint) { + napi_ref* ref = (napi_ref*)finalize_data; + napi_delete_reference(env, *ref); + free(ref); +} + +static napi_value ValidateDeleteBeforeFinalize(napi_env env, napi_callback_info info) { + napi_value wrapObject; + size_t argc = 1; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &wrapObject, NULL, NULL)); + + napi_ref* ref_t = malloc(sizeof(napi_ref)); + NAPI_CALL(env, napi_wrap(env, + wrapObject, + ref_t, + DeleteBeforeFinalizeFinalizer, + NULL, + NULL)); + + // Create a reference that will be eligible for collection at the same + // time as the wrapped object by passing in the same wrapObject. + // This means that the FinalizeOrderValidation callback may be run + // before the finalizer for the newly created reference (there is a finalizer + // behind the scenes even though it cannot be passed to napi_create_reference) + // The Finalizer for the wrap (which is different than the finalizer + // for the reference) calls napi_delete_reference validating that + // napi_delete_reference can be called before the finalizer for the + // reference runs. + NAPI_CALL(env, napi_create_reference(env, wrapObject, 0, ref_t)); + return wrapObject; +} + static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor descriptors[] = { DECLARE_NAPI_GETTER("finalizeCount", GetFinalizeCount), @@ -143,6 +177,8 @@ static napi_value Init(napi_env env, napi_value exports) { DECLARE_NAPI_PROPERTY("incrementRefcount", IncrementRefcount), DECLARE_NAPI_PROPERTY("decrementRefcount", DecrementRefcount), DECLARE_NAPI_GETTER("referenceValue", GetReferenceValue), + DECLARE_NAPI_PROPERTY("validateDeleteBeforeFinalize", + ValidateDeleteBeforeFinalize), }; NAPI_CALL(env, napi_define_properties( |