Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0fb5c42

Browse files
committedMar 29, 2022
node-api: cctest on v8impl::Reference
PR-URL: nodejs#38970 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Gabriel Schulhof <[email protected]>
1 parent b9d99d7 commit 0fb5c42

11 files changed

+635
-331
lines changed
 

‎LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,7 @@ The externally maintained libraries used by Node.js are:
12941294
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12951295
"""
12961296

1297-
- gtest, located at test/cctest/gtest, is licensed as follows:
1297+
- gtest, located at src/gtest and test/cctest/gtest, is licensed as follows:
12981298
"""
12991299
Copyright 2008, Google Inc.
13001300
All rights reserved.

‎node.gyp

+2
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,9 @@
12321232
'test/cctest/test_base_object_ptr.cc',
12331233
'test/cctest/test_node_postmortem_metadata.cc',
12341234
'test/cctest/test_environment.cc',
1235+
'test/cctest/test_js_native_api_v8.cc',
12351236
'test/cctest/test_linked_binding.cc',
1237+
'test/cctest/test_node_api.cc',
12361238
'test/cctest/test_per_process.cc',
12371239
'test/cctest/test_platform.cc',
12381240
'test/cctest/test_json_utils.cc',

‎src/gtest/LICENSE

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Copyright 2008, Google Inc.
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are
6+
met:
7+
8+
* Redistributions of source code must retain the above copyright
9+
notice, this list of conditions and the following disclaimer.
10+
* Redistributions in binary form must reproduce the above
11+
copyright notice, this list of conditions and the following disclaimer
12+
in the documentation and/or other materials provided with the
13+
distribution.
14+
* Neither the name of Google Inc. nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

‎src/gtest/gtest_prod.h

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2006, Google Inc.
2+
// All rights reserved.
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are
6+
// met:
7+
//
8+
// * Redistributions of source code must retain the above copyright
9+
// notice, this list of conditions and the following disclaimer.
10+
// * Redistributions in binary form must reproduce the above
11+
// copyright notice, this list of conditions and the following disclaimer
12+
// in the documentation and/or other materials provided with the
13+
// distribution.
14+
// * Neither the name of Google Inc. nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19+
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20+
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21+
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22+
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23+
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24+
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26+
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
30+
//
31+
// Google C++ Testing and Mocking Framework definitions useful in production
32+
// code. GOOGLETEST_CM0003 DO NOT DELETE
33+
34+
#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ // NOLINT
35+
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_
36+
37+
// When you need to test the private or protected members of a class,
38+
// use the FRIEND_TEST macro to declare your tests as friends of the
39+
// class. For example:
40+
//
41+
// class MyClass {
42+
// private:
43+
// void PrivateMethod();
44+
// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
45+
// };
46+
//
47+
// class MyClassTest : public testing::Test {
48+
// // ...
49+
// };
50+
//
51+
// TEST_F(MyClassTest, PrivateMethodWorks) {
52+
// // Can call MyClass::PrivateMethod() here.
53+
// }
54+
//
55+
// Note: The test class must be in the same namespace as the class being tested.
56+
// For example, putting MyClassTest in an anonymous namespace will not work.
57+
58+
#define FRIEND_TEST(test_case_name, test_name) \
59+
friend class test_case_name##_##test_name##_Test
60+
61+
#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ // NOLINT

‎src/js_native_api_v8.cc

+267-289
Large diffs are not rendered by default.

‎src/js_native_api_v8.h

+75
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,81 @@ class TryCatch : public v8::TryCatch {
366366
napi_env _env;
367367
};
368368

369+
// Wrapper around v8impl::Persistent that implements reference counting.
370+
class RefBase : protected Finalizer, RefTracker {
371+
protected:
372+
RefBase(napi_env env,
373+
uint32_t initial_refcount,
374+
bool delete_self,
375+
napi_finalize finalize_callback,
376+
void* finalize_data,
377+
void* finalize_hint);
378+
379+
public:
380+
static RefBase* New(napi_env env,
381+
uint32_t initial_refcount,
382+
bool delete_self,
383+
napi_finalize finalize_callback,
384+
void* finalize_data,
385+
void* finalize_hint);
386+
387+
static inline void Delete(RefBase* reference);
388+
389+
virtual ~RefBase();
390+
void* Data();
391+
uint32_t Ref();
392+
uint32_t Unref();
393+
uint32_t RefCount();
394+
395+
protected:
396+
void Finalize(bool is_env_teardown = false) override;
397+
398+
private:
399+
uint32_t _refcount;
400+
bool _delete_self;
401+
};
402+
403+
class Reference : public RefBase {
404+
using SecondPassCallParameterRef = Reference*;
405+
406+
protected:
407+
template <typename... Args>
408+
Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args);
409+
410+
public:
411+
static Reference* New(napi_env env,
412+
v8::Local<v8::Value> value,
413+
uint32_t initial_refcount,
414+
bool delete_self,
415+
napi_finalize finalize_callback = nullptr,
416+
void* finalize_data = nullptr,
417+
void* finalize_hint = nullptr);
418+
419+
virtual ~Reference();
420+
uint32_t Ref();
421+
uint32_t Unref();
422+
v8::Local<v8::Value> Get();
423+
424+
protected:
425+
void Finalize(bool is_env_teardown = false) override;
426+
427+
private:
428+
void ClearWeak();
429+
void SetWeak();
430+
431+
static void FinalizeCallback(
432+
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data);
433+
static void SecondPassCallback(
434+
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data);
435+
436+
bool env_teardown_finalize_started_ = false;
437+
v8impl::Persistent<v8::Value> _persistent;
438+
SecondPassCallParameterRef* _secondPassParameter;
439+
bool _secondPassScheduled;
440+
441+
FRIEND_TEST(JsNativeApiV8Test, Reference);
442+
};
443+
369444
} // end of namespace v8impl
370445

371446
#endif // SRC_JS_NATIVE_API_V8_H_

‎src/js_native_api_v8_internals.h

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "node_version.h"
1717
#include "env.h"
1818
#include "node_internals.h"
19+
#include "gtest/gtest_prod.h"
1920

2021
#define NAPI_ARRAYSIZE(array) \
2122
node::arraysize((array))

‎src/node_api.cc

+27-41
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define NAPI_EXPERIMENTAL
33
#include "js_native_api_v8.h"
44
#include "node_api.h"
5+
#include "node_api_internals.h"
56
#include "node_binding.h"
67
#include "node_buffer.h"
78
#include "node_errors.h"
@@ -11,51 +12,36 @@
1112

1213
#include <memory>
1314

14-
struct node_napi_env__ : public napi_env__ {
15-
explicit node_napi_env__(v8::Local<v8::Context> context,
16-
const std::string& module_filename):
17-
napi_env__(context), filename(module_filename) {
18-
CHECK_NOT_NULL(node_env());
19-
}
20-
21-
inline node::Environment* node_env() const {
22-
return node::Environment::GetCurrent(context());
23-
}
15+
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
16+
const std::string& module_filename)
17+
: napi_env__(context), filename(module_filename) {
18+
CHECK_NOT_NULL(node_env());
19+
}
2420

25-
bool can_call_into_js() const override {
26-
return node_env()->can_call_into_js();
27-
}
21+
bool node_napi_env__::can_call_into_js() const {
22+
return node_env()->can_call_into_js();
23+
}
2824

29-
v8::Maybe<bool> mark_arraybuffer_as_untransferable(
30-
v8::Local<v8::ArrayBuffer> ab) const override {
31-
return ab->SetPrivate(
32-
context(),
33-
node_env()->untransferable_object_private_symbol(),
34-
v8::True(isolate));
35-
}
25+
v8::Maybe<bool> node_napi_env__::mark_arraybuffer_as_untransferable(
26+
v8::Local<v8::ArrayBuffer> ab) const {
27+
return ab->SetPrivate(context(),
28+
node_env()->untransferable_object_private_symbol(),
29+
v8::True(isolate));
30+
}
3631

37-
void CallFinalizer(napi_finalize cb, void* data, void* hint) override {
38-
// we need to keep the env live until the finalizer has been run
39-
// EnvRefHolder provides an exception safe wrapper to Ref and then
40-
// Unref once the lamba is freed
41-
EnvRefHolder liveEnv(static_cast<napi_env>(this));
42-
node_env()->SetImmediate([=, liveEnv = std::move(liveEnv)]
43-
(node::Environment* node_env) {
44-
napi_env env = liveEnv.env();
45-
v8::HandleScope handle_scope(env->isolate);
46-
v8::Context::Scope context_scope(env->context());
47-
env->CallIntoModule([&](napi_env env) {
48-
cb(env, data, hint);
32+
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {
33+
// we need to keep the env live until the finalizer has been run
34+
// EnvRefHolder provides an exception safe wrapper to Ref and then
35+
// Unref once the lamba is freed
36+
EnvRefHolder liveEnv(static_cast<napi_env>(this));
37+
node_env()->SetImmediate(
38+
[=, liveEnv = std::move(liveEnv)](node::Environment* node_env) {
39+
napi_env env = liveEnv.env();
40+
v8::HandleScope handle_scope(env->isolate);
41+
v8::Context::Scope context_scope(env->context());
42+
env->CallIntoModule([&](napi_env env) { cb(env, data, hint); });
4943
});
50-
});
51-
}
52-
53-
const char* GetFilename() const { return filename.c_str(); }
54-
55-
std::string filename;
56-
};
57-
58-
typedef node_napi_env__* node_napi_env;
44+
}
5945

6046
namespace v8impl {
6147

‎src/node_api_internals.h

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef SRC_NODE_API_INTERNALS_H_
2+
#define SRC_NODE_API_INTERNALS_H_
3+
4+
#include "v8.h"
5+
#define NAPI_EXPERIMENTAL
6+
#include "env-inl.h"
7+
#include "js_native_api_v8.h"
8+
#include "node_api.h"
9+
#include "util-inl.h"
10+
11+
struct node_napi_env__ : public napi_env__ {
12+
node_napi_env__(v8::Local<v8::Context> context,
13+
const std::string& module_filename);
14+
15+
bool can_call_into_js() const override;
16+
v8::Maybe<bool> mark_arraybuffer_as_untransferable(
17+
v8::Local<v8::ArrayBuffer> ab) const override;
18+
void CallFinalizer(napi_finalize cb, void* data, void* hint) override;
19+
20+
inline node::Environment* node_env() const {
21+
return node::Environment::GetCurrent(context());
22+
}
23+
inline const char* GetFilename() const { return filename.c_str(); }
24+
25+
std::string filename;
26+
};
27+
28+
using node_napi_env = node_napi_env__*;
29+
30+
#endif // SRC_NODE_API_INTERNALS_H_

‎test/cctest/test_js_native_api_v8.cc

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#include <stdio.h>
2+
#include <cstdio>
3+
#include <string>
4+
#include "env-inl.h"
5+
#include "gtest/gtest.h"
6+
#include "js_native_api_v8.h"
7+
#include "node_api_internals.h"
8+
#include "node_binding.h"
9+
#include "node_test_fixture.h"
10+
11+
namespace v8impl {
12+
13+
using v8::Local;
14+
using v8::Object;
15+
16+
static napi_env addon_env;
17+
static uint32_t finalizer_call_count = 0;
18+
19+
class JsNativeApiV8Test : public EnvironmentTestFixture {
20+
private:
21+
void SetUp() override {
22+
EnvironmentTestFixture::SetUp();
23+
finalizer_call_count = 0;
24+
}
25+
26+
void TearDown() override { NodeTestFixture::TearDown(); }
27+
};
28+
29+
TEST_F(JsNativeApiV8Test, Reference) {
30+
const v8::HandleScope handle_scope(isolate_);
31+
Argv argv;
32+
33+
napi_ref ref;
34+
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr, nullptr};
35+
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef>::Callback
36+
callback;
37+
Reference::SecondPassCallParameterRef* parameter = nullptr;
38+
39+
{
40+
Env test_env{handle_scope, argv};
41+
42+
node::Environment* env = *test_env;
43+
node::LoadEnvironment(env, "");
44+
45+
napi_addon_register_func init = [](napi_env env, napi_value exports) {
46+
addon_env = env;
47+
return exports;
48+
};
49+
Local<Object> module_obj = Object::New(isolate_);
50+
Local<Object> exports_obj = Object::New(isolate_);
51+
napi_module_register_by_symbol(
52+
exports_obj, module_obj, env->context(), init);
53+
ASSERT_NE(addon_env, nullptr);
54+
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
55+
EXPECT_EQ(internal_env->node_env(), env);
56+
57+
// Create a new scope to manage the handles.
58+
{
59+
const v8::HandleScope handle_scope(isolate_);
60+
napi_value value;
61+
napi_create_object(addon_env, &value);
62+
// Create a weak reference;
63+
napi_add_finalizer(
64+
addon_env,
65+
value,
66+
nullptr,
67+
[](napi_env env, void* finalize_data, void* finalize_hint) {
68+
finalizer_call_count++;
69+
},
70+
nullptr,
71+
&ref);
72+
parameter = reinterpret_cast<Reference*>(ref)->_secondPassParameter;
73+
}
74+
75+
// We can hardly trigger a non-forced Garbage Collection in a stable way.
76+
// Here we just invoke the weak callbacks directly.
77+
// The persistant handles should be reset in the weak callback in respect
78+
// to the API contract of v8 weak callbacks.
79+
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef> data(
80+
reinterpret_cast<v8::Isolate*>(isolate_),
81+
parameter,
82+
embedder_fields,
83+
&callback);
84+
Reference::FinalizeCallback(data);
85+
EXPECT_EQ(callback, &Reference::SecondPassCallback);
86+
}
87+
// Env goes out of scope, the environment has been teardown. All node-api ref
88+
// trackers should have been destroyed.
89+
90+
// Now we call the second pass callback to verify the method do not abort with
91+
// memory violations.
92+
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef> data(
93+
reinterpret_cast<v8::Isolate*>(isolate_),
94+
parameter,
95+
embedder_fields,
96+
nullptr);
97+
Reference::SecondPassCallback(data);
98+
99+
// After Environment Teardown
100+
EXPECT_EQ(finalizer_call_count, uint32_t(1));
101+
}
102+
} // namespace v8impl

‎test/cctest/test_node_api.cc

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include <stdio.h>
2+
#include <cstdio>
3+
#include <string>
4+
#include "env-inl.h"
5+
#include "gtest/gtest.h"
6+
#include "node_api_internals.h"
7+
#include "node_binding.h"
8+
#include "node_test_fixture.h"
9+
10+
using v8::Local;
11+
using v8::Object;
12+
13+
static napi_env addon_env;
14+
15+
class NodeApiTest : public EnvironmentTestFixture {
16+
private:
17+
void SetUp() override { EnvironmentTestFixture::SetUp(); }
18+
19+
void TearDown() override { NodeTestFixture::TearDown(); }
20+
};
21+
22+
TEST_F(NodeApiTest, CreateNodeApiEnv) {
23+
const v8::HandleScope handle_scope(isolate_);
24+
Argv argv;
25+
26+
Env test_env{handle_scope, argv};
27+
28+
node::Environment* env = *test_env;
29+
node::LoadEnvironment(env, "");
30+
31+
napi_addon_register_func init = [](napi_env env, napi_value exports) {
32+
addon_env = env;
33+
return exports;
34+
};
35+
Local<Object> module_obj = Object::New(isolate_);
36+
Local<Object> exports_obj = Object::New(isolate_);
37+
napi_module_register_by_symbol(exports_obj, module_obj, env->context(), init);
38+
ASSERT_NE(addon_env, nullptr);
39+
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
40+
EXPECT_EQ(internal_env->node_env(), env);
41+
}

0 commit comments

Comments
 (0)
Please sign in to comment.