Skip to content

Commit

Permalink
node-api: cctest on v8impl::Reference
Browse files Browse the repository at this point in the history
PR-URL: #38970
Reviewed-By: Michael Dawson <midawson@redhat.com>
Reviewed-By: Gabriel Schulhof <gabrielschulhof@gmail.com>
legendecas authored and targos committed Jul 11, 2021
1 parent 2227c13 commit 63f8702
Showing 12 changed files with 636 additions and 333 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1251,7 +1251,7 @@ The externally maintained libraries used by Node.js are:
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

- gtest, located at test/cctest/gtest, is licensed as follows:
- gtest, located at src/gtest and test/cctest/gtest, is licensed as follows:
"""
Copyright 2008, Google Inc.
All rights reserved.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -983,7 +983,7 @@ $(PKG): release-only
ifneq ($(OSTYPE),darwin)
$(warning Invalid OSTYPE)
$(error OSTYPE should be `darwin` currently is $(OSTYPE))
endif
endif
ifneq ($(ARCHTYPE),arm64)
$(warning Invalid ARCHTYPE)
$(error ARCHTYPE should be `arm64` currently is $(ARCHTYPE))
2 changes: 2 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
@@ -1105,7 +1105,9 @@
'test/cctest/test_base_object_ptr.cc',
'test/cctest/test_node_postmortem_metadata.cc',
'test/cctest/test_environment.cc',
'test/cctest/test_js_native_api_v8.cc',
'test/cctest/test_linked_binding.cc',
'test/cctest/test_node_api.cc',
'test/cctest/test_per_process.cc',
'test/cctest/test_platform.cc',
'test/cctest/test_json_utils.cc',
28 changes: 28 additions & 0 deletions src/gtest/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Copyright 2008, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
61 changes: 61 additions & 0 deletions src/gtest/gtest_prod.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

//
// Google C++ Testing and Mocking Framework definitions useful in production
// code. GOOGLETEST_CM0003 DO NOT DELETE

#ifndef GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ // NOLINT
#define GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_

// When you need to test the private or protected members of a class,
// use the FRIEND_TEST macro to declare your tests as friends of the
// class. For example:
//
// class MyClass {
// private:
// void PrivateMethod();
// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
// };
//
// class MyClassTest : public testing::Test {
// // ...
// };
//
// TEST_F(MyClassTest, PrivateMethodWorks) {
// // Can call MyClass::PrivateMethod() here.
// }
//
// Note: The test class must be in the same namespace as the class being tested.
// For example, putting MyClassTest in an anonymous namespace will not work.

#define FRIEND_TEST(test_case_name, test_name) \
friend class test_case_name##_##test_name##_Test

#endif // GOOGLETEST_INCLUDE_GTEST_GTEST_PROD_H_ // NOLINT
557 changes: 267 additions & 290 deletions src/js_native_api_v8.cc

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions src/js_native_api_v8.h
Original file line number Diff line number Diff line change
@@ -366,6 +366,81 @@ class TryCatch : public v8::TryCatch {
napi_env _env;
};

// Wrapper around v8impl::Persistent that implements reference counting.
class RefBase : protected Finalizer, RefTracker {
protected:
RefBase(napi_env env,
uint32_t initial_refcount,
bool delete_self,
napi_finalize finalize_callback,
void* finalize_data,
void* finalize_hint);

public:
static RefBase* New(napi_env env,
uint32_t initial_refcount,
bool delete_self,
napi_finalize finalize_callback,
void* finalize_data,
void* finalize_hint);

static inline void Delete(RefBase* reference);

virtual ~RefBase();
void* Data();
uint32_t Ref();
uint32_t Unref();
uint32_t RefCount();

protected:
void Finalize(bool is_env_teardown = false) override;

private:
uint32_t _refcount;
bool _delete_self;
};

class Reference : public RefBase {
using SecondPassCallParameterRef = Reference*;

protected:
template <typename... Args>
Reference(napi_env env, v8::Local<v8::Value> value, Args&&... args);

public:
static Reference* New(napi_env env,
v8::Local<v8::Value> value,
uint32_t initial_refcount,
bool delete_self,
napi_finalize finalize_callback = nullptr,
void* finalize_data = nullptr,
void* finalize_hint = nullptr);

virtual ~Reference();
uint32_t Ref();
uint32_t Unref();
v8::Local<v8::Value> Get();

protected:
void Finalize(bool is_env_teardown = false) override;

private:
void ClearWeak();
void SetWeak();

static void FinalizeCallback(
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data);
static void SecondPassCallback(
const v8::WeakCallbackInfo<SecondPassCallParameterRef>& data);

bool env_teardown_finalize_started_ = false;
v8impl::Persistent<v8::Value> _persistent;
SecondPassCallParameterRef* _secondPassParameter;
bool _secondPassScheduled;

FRIEND_TEST(JsNativeApiV8Test, Reference);
};

} // end of namespace v8impl

#define STATUS_CALL(call) \
1 change: 1 addition & 0 deletions src/js_native_api_v8_internals.h
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@
#include "node_version.h"
#include "env.h"
#include "node_internals.h"
#include "gtest/gtest_prod.h"

#define NAPI_ARRAYSIZE(array) \
node::arraysize((array))
68 changes: 27 additions & 41 deletions src/node_api.cc
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
#include "js_native_api_v8.h"
#include "memory_tracker-inl.h"
#include "node_api.h"
#include "node_api_internals.h"
#include "node_binding.h"
#include "node_buffer.h"
#include "node_errors.h"
@@ -15,51 +16,36 @@
#include <atomic>
#include <memory>

struct node_napi_env__ : public napi_env__ {
explicit node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename):
napi_env__(context), filename(module_filename) {
CHECK_NOT_NULL(node_env());
}

inline node::Environment* node_env() const {
return node::Environment::GetCurrent(context());
}
node_napi_env__::node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename)
: napi_env__(context), filename(module_filename) {
CHECK_NOT_NULL(node_env());
}

bool can_call_into_js() const override {
return node_env()->can_call_into_js();
}
bool node_napi_env__::can_call_into_js() const {
return node_env()->can_call_into_js();
}

v8::Maybe<bool> mark_arraybuffer_as_untransferable(
v8::Local<v8::ArrayBuffer> ab) const override {
return ab->SetPrivate(
context(),
node_env()->untransferable_object_private_symbol(),
v8::True(isolate));
}
v8::Maybe<bool> node_napi_env__::mark_arraybuffer_as_untransferable(
v8::Local<v8::ArrayBuffer> ab) const {
return ab->SetPrivate(context(),
node_env()->untransferable_object_private_symbol(),
v8::True(isolate));
}

void CallFinalizer(napi_finalize cb, void* data, void* hint) override {
// we need to keep the env live until the finalizer has been run
// EnvRefHolder provides an exception safe wrapper to Ref and then
// Unref once the lamba is freed
EnvRefHolder liveEnv(static_cast<napi_env>(this));
node_env()->SetImmediate([=, liveEnv = std::move(liveEnv)]
(node::Environment* node_env) {
napi_env env = liveEnv.env();
v8::HandleScope handle_scope(env->isolate);
v8::Context::Scope context_scope(env->context());
env->CallIntoModule([&](napi_env env) {
cb(env, data, hint);
void node_napi_env__::CallFinalizer(napi_finalize cb, void* data, void* hint) {
// we need to keep the env live until the finalizer has been run
// EnvRefHolder provides an exception safe wrapper to Ref and then
// Unref once the lamba is freed
EnvRefHolder liveEnv(static_cast<napi_env>(this));
node_env()->SetImmediate(
[=, liveEnv = std::move(liveEnv)](node::Environment* node_env) {
napi_env env = liveEnv.env();
v8::HandleScope handle_scope(env->isolate);
v8::Context::Scope context_scope(env->context());
env->CallIntoModule([&](napi_env env) { cb(env, data, hint); });
});
});
}

const char* GetFilename() const { return filename.c_str(); }

std::string filename;
};

typedef node_napi_env__* node_napi_env;
}

namespace v8impl {

30 changes: 30 additions & 0 deletions src/node_api_internals.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#ifndef SRC_NODE_API_INTERNALS_H_
#define SRC_NODE_API_INTERNALS_H_

#include "v8.h"
#define NAPI_EXPERIMENTAL
#include "env-inl.h"
#include "js_native_api_v8.h"
#include "node_api.h"
#include "util-inl.h"

struct node_napi_env__ : public napi_env__ {
node_napi_env__(v8::Local<v8::Context> context,
const std::string& module_filename);

bool can_call_into_js() const override;
v8::Maybe<bool> mark_arraybuffer_as_untransferable(
v8::Local<v8::ArrayBuffer> ab) const override;
void CallFinalizer(napi_finalize cb, void* data, void* hint) override;

inline node::Environment* node_env() const {
return node::Environment::GetCurrent(context());
}
inline const char* GetFilename() const { return filename.c_str(); }

std::string filename;
};

using node_napi_env = node_napi_env__*;

#endif // SRC_NODE_API_INTERNALS_H_
102 changes: 102 additions & 0 deletions test/cctest/test_js_native_api_v8.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#include <stdio.h>
#include <cstdio>
#include <string>
#include "env-inl.h"
#include "gtest/gtest.h"
#include "js_native_api_v8.h"
#include "node_api_internals.h"
#include "node_binding.h"
#include "node_test_fixture.h"

namespace v8impl {

using v8::Local;
using v8::Object;

static napi_env addon_env;
static uint32_t finalizer_call_count = 0;

class JsNativeApiV8Test : public EnvironmentTestFixture {
private:
void SetUp() override {
EnvironmentTestFixture::SetUp();
finalizer_call_count = 0;
}

void TearDown() override { NodeTestFixture::TearDown(); }
};

TEST_F(JsNativeApiV8Test, Reference) {
const v8::HandleScope handle_scope(isolate_);
Argv argv;

napi_ref ref;
void* embedder_fields[v8::kEmbedderFieldsInWeakCallback] = {nullptr, nullptr};
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef>::Callback
callback;
Reference::SecondPassCallParameterRef* parameter = nullptr;

{
Env test_env{handle_scope, argv};

node::Environment* env = *test_env;
node::LoadEnvironment(env, "");

napi_addon_register_func init = [](napi_env env, napi_value exports) {
addon_env = env;
return exports;
};
Local<Object> module_obj = Object::New(isolate_);
Local<Object> exports_obj = Object::New(isolate_);
napi_module_register_by_symbol(
exports_obj, module_obj, env->context(), init);
ASSERT_NE(addon_env, nullptr);
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
EXPECT_EQ(internal_env->node_env(), env);

// Create a new scope to manage the handles.
{
const v8::HandleScope handle_scope(isolate_);
napi_value value;
napi_create_object(addon_env, &value);
// Create a weak reference;
napi_add_finalizer(
addon_env,
value,
nullptr,
[](napi_env env, void* finalize_data, void* finalize_hint) {
finalizer_call_count++;
},
nullptr,
&ref);
parameter = reinterpret_cast<Reference*>(ref)->_secondPassParameter;
}

// We can hardly trigger a non-forced Garbage Collection in a stable way.
// Here we just invoke the weak callbacks directly.
// The persistant handles should be reset in the weak callback in respect
// to the API contract of v8 weak callbacks.
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef> data(
reinterpret_cast<v8::Isolate*>(isolate_),
parameter,
embedder_fields,
&callback);
Reference::FinalizeCallback(data);
EXPECT_EQ(callback, &Reference::SecondPassCallback);
}
// Env goes out of scope, the environment has been teardown. All node-api ref
// trackers should have been destroyed.

// Now we call the second pass callback to verify the method do not abort with
// memory violations.
v8::WeakCallbackInfo<Reference::SecondPassCallParameterRef> data(
reinterpret_cast<v8::Isolate*>(isolate_),
parameter,
embedder_fields,
nullptr);
Reference::SecondPassCallback(data);

// After Environment Teardown
EXPECT_EQ(finalizer_call_count, uint32_t(1));
}
} // namespace v8impl
41 changes: 41 additions & 0 deletions test/cctest/test_node_api.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include <stdio.h>
#include <cstdio>
#include <string>
#include "env-inl.h"
#include "gtest/gtest.h"
#include "node_api_internals.h"
#include "node_binding.h"
#include "node_test_fixture.h"

using v8::Local;
using v8::Object;

static napi_env addon_env;

class NodeApiTest : public EnvironmentTestFixture {
private:
void SetUp() override { EnvironmentTestFixture::SetUp(); }

void TearDown() override { NodeTestFixture::TearDown(); }
};

TEST_F(NodeApiTest, CreateNodeApiEnv) {
const v8::HandleScope handle_scope(isolate_);
Argv argv;

Env test_env{handle_scope, argv};

node::Environment* env = *test_env;
node::LoadEnvironment(env, "");

napi_addon_register_func init = [](napi_env env, napi_value exports) {
addon_env = env;
return exports;
};
Local<Object> module_obj = Object::New(isolate_);
Local<Object> exports_obj = Object::New(isolate_);
napi_module_register_by_symbol(exports_obj, module_obj, env->context(), init);
ASSERT_NE(addon_env, nullptr);
node_napi_env internal_env = reinterpret_cast<node_napi_env>(addon_env);
EXPECT_EQ(internal_env->node_env(), env);
}

0 comments on commit 63f8702

Please sign in to comment.