Skip to content

Commit

Permalink
src: retrieve binding data from the context
Browse files Browse the repository at this point in the history
Instead of passing them through the data bound to function
templates, store references to them in a list embedded inside
the context, and store the integer index
(which is context-independent) in the function template data.
This makes the function templates more context-independent,
and makes it possible to embed binding data in non-main contexts.
  • Loading branch information
joyeecheung authored and addaleax committed May 5, 2020
1 parent 8f87d25 commit de409d8
Show file tree
Hide file tree
Showing 17 changed files with 181 additions and 121 deletions.
10 changes: 5 additions & 5 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,13 +402,13 @@ Some internal bindings, such as the HTTP parser, maintain internal state that
only affects that particular binding. In that case, one common way to store
that state is through the use of `Environment::BindingScope`, which gives all
new functions created within it access to an object for storing such state.
That object is always a [`BaseObject`][].
That object is always a `BindingDataBase`.
```c++
// In the HTTP parser source code file:
class BindingData : public BaseObject {
class BindingData : public BindingDataBase {
public:
BindingData(Environment* env, Local<Object> obj) : BaseObject(env, obj) {}
BindingData(Environment* env, Local<Object> obj) : BindingDataBase(env, obj) {}
std::vector<char> parser_buffer;
bool parser_buffer_in_use = false;
Expand All @@ -418,7 +418,7 @@ class BindingData : public BaseObject {
// Available for binding functions, e.g. the HTTP Parser constructor:
static void New(const FunctionCallbackInfo<Value>& args) {
BindingData* binding_data = Unwrap<BindingData>(args.Data());
BindingData* binding_data = BindingDataBase::Unwrap<BindingData>(args);
new Parser(binding_data, args.This());
}
Expand All @@ -429,7 +429,7 @@ void InitializeHttpParser(Local<Object> target,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
Environment::BindingScope<BindingData> binding_scope(env);
Environment::BindingScope<BindingData> binding_scope(env, context, target);
if (!binding_scope) return;
BindingData* binding_data = binding_scope.data;
Expand Down
90 changes: 57 additions & 33 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,10 @@ inline void Environment::AssignToContext(v8::Local<v8::Context> context,
// Used by Environment::GetCurrent to know that we are on a node context.
context->SetAlignedPointerInEmbedderData(
ContextEmbedderIndex::kContextTag, Environment::kNodeContextTagPtr);
// Used to retrieve bindings
context->SetAlignedPointerInEmbedderData(
ContextEmbedderIndex::KBindingListIndex, &(this->bindings_));

#if HAVE_INSPECTOR
inspector_agent()->ContextCreated(context, info);
#endif // HAVE_INSPECTOR
Expand Down Expand Up @@ -318,55 +322,74 @@ inline Environment* Environment::GetCurrent(v8::Local<v8::Context> context) {

inline Environment* Environment::GetCurrent(
const v8::FunctionCallbackInfo<v8::Value>& info) {
return GetFromCallbackData(info.Data());
return BindingDataBase::Unwrap<BindingDataBase>(info)->env();
}

template <typename T>
inline Environment* Environment::GetCurrent(
const v8::PropertyCallbackInfo<T>& info) {
return GetFromCallbackData(info.Data());
return BindingDataBase::Unwrap<BindingDataBase>(info)->env();
}

Environment* Environment::GetFromCallbackData(v8::Local<v8::Value> val) {
DCHECK(val->IsObject());
v8::Local<v8::Object> obj = val.As<v8::Object>();
DCHECK_GE(obj->InternalFieldCount(),
BaseObject::kInternalFieldCount);
Environment* env = Unwrap<BaseObject>(obj)->env();
DCHECK(env->as_callback_data_template()->HasInstance(obj));
return env;
inline BindingDataBase::BindingDataBase(Environment* env,
v8::Local<v8::Object> target)
: BaseObject(env, target) {}

template <typename T, typename U>
inline T* BindingDataBase::Unwrap(const v8::PropertyCallbackInfo<U>& info) {
return Unwrap<T>(info.GetIsolate()->GetCurrentContext(), info.Data());
}

template <typename T>
Environment::BindingScope<T>::BindingScope(Environment* env) : env(env) {
v8::Local<v8::Object> callback_data;
if (!env->MakeBindingCallbackData<T>().ToLocal(&callback_data))
return;
data = Unwrap<T>(callback_data);
inline T* BindingDataBase::Unwrap(
const v8::FunctionCallbackInfo<v8::Value>& info) {
return Unwrap<T>(info.GetIsolate()->GetCurrentContext(), info.Data());
}

// No nesting allowed currently.
CHECK_EQ(env->current_callback_data(), env->as_callback_data());
env->set_current_callback_data(callback_data);
template <typename T>
inline T* BindingDataBase::Unwrap(v8::Local<v8::Context> context,
v8::Local<v8::Value> val) {
CHECK(val->IsUint32());
uint32_t index = val.As<v8::Uint32>()->Value();
std::vector<BindingDataBase*>* list =
static_cast<std::vector<BindingDataBase*>*>(
context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::KBindingListIndex));
CHECK_GT(list->size(), index);
T* result = static_cast<T*>(list->at(index));
return result;
}

template <typename T>
Environment::BindingScope<T>::~BindingScope() {
env->set_current_callback_data(env->as_callback_data());
inline v8::Local<v8::Uint32> BindingDataBase::New(
Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::Object> target) {
T* data = new T(env, target);
// This won't compile if T is not a BindingDataBase subclass.
BindingDataBase* item = static_cast<BindingDataBase*>(data);
std::vector<BindingDataBase*>* list =
static_cast<std::vector<BindingDataBase*>*>(
context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::KBindingListIndex));
size_t index = list->size();
list->push_back(item);
return v8::Integer::NewFromUnsigned(env->isolate(), index).As<v8::Uint32>();
}

template <typename T>
v8::MaybeLocal<v8::Object> Environment::MakeBindingCallbackData() {
v8::Local<v8::Function> ctor;
v8::Local<v8::Object> obj;
if (!as_callback_data_template()->GetFunction(context()).ToLocal(&ctor) ||
!ctor->NewInstance(context()).ToLocal(&obj)) {
return v8::MaybeLocal<v8::Object>();
}
T* data = new T(this, obj);
// This won't compile if T is not a BaseObject subclass.
CHECK_EQ(data, static_cast<BaseObject*>(data));
data->MakeWeak();
return obj;
Environment::BindingScope<T>::BindingScope(Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::Object> target)
: env(env) {
v8::Local<v8::Uint32> index = BindingDataBase::New<T>(env, context, target);
data = BindingDataBase::Unwrap<T>(context, index);
env->set_current_callback_data(index);
}

template <typename T>
Environment::BindingScope<T>::~BindingScope() {
env->set_current_callback_data(env->default_callback_data());
}

inline Environment* Environment::GetThreadLocalEnv() {
Expand Down Expand Up @@ -1085,7 +1108,7 @@ inline v8::Local<v8::FunctionTemplate>
v8::Local<v8::Signature> signature,
v8::ConstructorBehavior behavior,
v8::SideEffectType side_effect_type) {
v8::Local<v8::Object> external = current_callback_data();
v8::Local<v8::Value> external = current_callback_data();
return v8::FunctionTemplate::New(isolate(), callback, external,
signature, 0, behavior, side_effect_type);
}
Expand Down Expand Up @@ -1276,6 +1299,7 @@ void Environment::set_process_exit_handler(
}
ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_CALLBACK_DATA(V)
#undef V

inline v8::Local<v8::Context> Environment::context() const {
Expand Down
19 changes: 12 additions & 7 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ using v8::String;
using v8::Symbol;
using v8::TracingController;
using v8::TryCatch;
using v8::Uint32;
using v8::Undefined;
using v8::Value;
using worker::Worker;
Expand Down Expand Up @@ -261,9 +262,10 @@ void TrackingTraceStateObserver::UpdateTraceCategoryState() {
USE(cb->Call(env_->context(), Undefined(isolate), arraysize(args), args));
}

class NoBindingData : public BaseObject {
class NoBindingData : public BindingDataBase {
public:
NoBindingData(Environment* env, Local<Object> obj) : BaseObject(env, obj) {}
NoBindingData(Environment* env, Local<Object> obj)
: BindingDataBase(env, obj) {}

SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(NoBindingData)
Expand All @@ -273,17 +275,19 @@ class NoBindingData : public BaseObject {
void Environment::CreateProperties() {
HandleScope handle_scope(isolate_);
Local<Context> ctx = context();

{
Context::Scope context_scope(ctx);
Local<FunctionTemplate> templ = FunctionTemplate::New(isolate());
templ->InstanceTemplate()->SetInternalFieldCount(
BaseObject::kInternalFieldCount);
set_as_callback_data_template(templ);

Local<Object> obj = MakeBindingCallbackData<NoBindingData>()
.ToLocalChecked();
set_as_callback_data(obj);
set_current_callback_data(obj);
set_binding_data_ctor_template(templ);
Local<Function> ctor = templ->GetFunction(ctx).ToLocalChecked();
Local<Object> obj = ctor->NewInstance(ctx).ToLocalChecked();
Local<Uint32> index = BindingDataBase::New<NoBindingData>(this, ctx, obj);
set_default_callback_data(index);
set_current_callback_data(index);
}

// Store primordials setup by the per-context script in the environment.
Expand Down Expand Up @@ -1133,6 +1137,7 @@ void Environment::MemoryInfo(MemoryTracker* tracker) const {
#define V(PropertyName, TypeName) \
tracker->TrackField(#PropertyName, PropertyName());
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_CALLBACK_DATA(V)
#undef V

// FIXME(joyeecheung): track other fields in Environment.
Expand Down
43 changes: 34 additions & 9 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,9 @@ constexpr size_t kFsStatsBufferLength =
V(zero_return_string, "ZERO_RETURN")

#define ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V) \
V(as_callback_data_template, v8::FunctionTemplate) \
V(async_wrap_ctor_template, v8::FunctionTemplate) \
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
V(binding_data_ctor_template, v8::FunctionTemplate) \
V(compiled_fn_entry_template, v8::ObjectTemplate) \
V(dir_instance_template, v8::ObjectTemplate) \
V(fd_constructor_template, v8::ObjectTemplate) \
Expand All @@ -419,8 +419,11 @@ constexpr size_t kFsStatsBufferLength =
V(write_wrap_template, v8::ObjectTemplate) \
V(worker_heap_snapshot_taker_template, v8::ObjectTemplate)

#define ENVIRONMENT_CALLBACK_DATA(V) \
V(default_callback_data, v8::Uint32) \
V(current_callback_data, v8::Uint32)

#define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V) \
V(as_callback_data, v8::Object) \
V(async_hooks_after_function, v8::Function) \
V(async_hooks_before_function, v8::Function) \
V(async_hooks_binding, v8::Object) \
Expand All @@ -429,7 +432,6 @@ constexpr size_t kFsStatsBufferLength =
V(async_hooks_promise_resolve_function, v8::Function) \
V(buffer_prototype_object, v8::Object) \
V(crypto_key_object_constructor, v8::Function) \
V(current_callback_data, v8::Object) \
V(domain_callback, v8::Function) \
V(domexception_function, v8::Function) \
V(enhance_fatal_stack_after_inspector, v8::Function) \
Expand Down Expand Up @@ -824,6 +826,28 @@ class CleanupHookCallback {
uint64_t insertion_order_counter_;
};

class BindingDataBase : public BaseObject {
public:
// Exposed for subclasses
inline BindingDataBase(Environment* env, v8::Local<v8::Object> target);
// Unwrap a subclass T object from a v8::Value which needs to be an
// v8::Uint32
template <typename T, typename U>
static inline T* Unwrap(const v8::PropertyCallbackInfo<U>& info);
template <typename T>
static inline T* Unwrap(const v8::FunctionCallbackInfo<v8::Value>& info);

template <typename T>
static inline T* Unwrap(v8::Local<v8::Context> context,
v8::Local<v8::Value> val);
// Create a BindingData of subclass T, put it into the context binding list,
// return the index as v8::Integer
template <typename T>
static inline v8::Local<v8::Uint32> New(Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::Object> target);
};

class Environment : public MemoryRetainer {
public:
Environment(const Environment&) = delete;
Expand Down Expand Up @@ -864,14 +888,14 @@ class Environment : public MemoryRetainer {
static inline Environment* GetCurrent(
const v8::PropertyCallbackInfo<T>& info);

static inline Environment* GetFromCallbackData(v8::Local<v8::Value> val);

// Methods created using SetMethod(), SetPrototypeMethod(), etc. inside
// this scope can access the created T* object using
// Unwrap<T>(args.Data()) later.
template <typename T>
struct BindingScope {
explicit inline BindingScope(Environment* env);
explicit inline BindingScope(Environment* env,
v8::Local<v8::Context> context,
v8::Local<v8::Object> target);
inline ~BindingScope();

T* data = nullptr;
Expand All @@ -881,9 +905,6 @@ class Environment : public MemoryRetainer {
inline bool operator !() const { return data == nullptr; }
};

template <typename T>
inline v8::MaybeLocal<v8::Object> MakeBindingCallbackData();

static uv_key_t thread_local_env;
static inline Environment* GetThreadLocalEnv();

Expand Down Expand Up @@ -1128,6 +1149,7 @@ class Environment : public MemoryRetainer {
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> PropertyName() const; \
inline void set_ ## PropertyName(v8::Local<TypeName> value);
ENVIRONMENT_CALLBACK_DATA(V)
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
#undef V
Expand Down Expand Up @@ -1428,6 +1450,8 @@ class Environment : public MemoryRetainer {
void RequestInterruptFromV8();
static void CheckImmediate(uv_check_t* handle);

std::vector<BindingDataBase*> bindings_;

// Use an unordered_set, so that we have efficient insertion and removal.
std::unordered_set<CleanupHookCallback,
CleanupHookCallback::Hash,
Expand All @@ -1446,6 +1470,7 @@ class Environment : public MemoryRetainer {
void ForEachBaseObject(T&& iterator);

#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName ## _;
ENVIRONMENT_CALLBACK_DATA(V)
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
#undef V
Expand Down
6 changes: 5 additions & 1 deletion src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ namespace node {

using v8::Context;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Local;
using v8::NewStringType;
Expand Down Expand Up @@ -556,8 +557,11 @@ inline struct node_module* FindModule(struct node_module* list,
static Local<Object> InitModule(Environment* env,
node_module* mod,
Local<String> module) {
Local<Object> exports = Object::New(env->isolate());
// Internal bindings don't have a "module" object, only exports.
Local<Function> ctor = env->binding_data_ctor_template()
->GetFunction(env->context())
.ToLocalChecked();
Local<Object> exports = ctor->NewInstance(env->context()).ToLocalChecked();
CHECK_NULL(mod->nm_register_func);
CHECK_NOT_NULL(mod->nm_context_register_func);
Local<Value> unused = Undefined(env->isolate());
Expand Down
5 changes: 5 additions & 0 deletions src/node_context_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ namespace node {
#define NODE_CONTEXT_TAG 35
#endif

#ifndef NODE_BINDING_LIST
#define NODE_BINDING_LIST_INDEX 36
#endif

enum ContextEmbedderIndex {
kEnvironment = NODE_CONTEXT_EMBEDDER_DATA_INDEX,
kSandboxObject = NODE_CONTEXT_SANDBOX_OBJECT_INDEX,
kAllowWasmCodeGeneration = NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX,
kContextTag = NODE_CONTEXT_TAG,
KBindingListIndex = NODE_BINDING_LIST_INDEX
};

} // namespace node
Expand Down
2 changes: 1 addition & 1 deletion src/node_env_var.cc
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) {

MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context,
Isolate* isolate,
Local<Object> data) {
Local<Value> data) {
EscapableHandleScope scope(isolate);
Local<ObjectTemplate> env_proxy_template = ObjectTemplate::New(isolate);
env_proxy_template->SetHandler(NamedPropertyHandlerConfiguration(
Expand Down
2 changes: 1 addition & 1 deletion src/node_file-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ FSReqBase* GetReqWrap(const v8::FunctionCallbackInfo<v8::Value>& args,
return Unwrap<FSReqBase>(value.As<v8::Object>());
}

BindingData* binding_data = Unwrap<BindingData>(args.Data());
BindingData* binding_data = BindingDataBase::Unwrap<BindingData>(args);
Environment* env = binding_data->env();
if (value->StrictEquals(env->fs_use_promises_symbol())) {
if (use_bigint) {
Expand Down
Loading

0 comments on commit de409d8

Please sign in to comment.