Skip to content

Commit

Permalink
src: add internalBindings for binding isolation
Browse files Browse the repository at this point in the history
This commit adds a method to internal/process that allows access to
bindings that are not intended to be used by user code. It has a
separate cache object and modlist in order to avoid collisions.

You can use NODE_MODULE_CONTEXT_AWARE_INTERNAL to register a C++
module as an internal.

PR-URL: #15759
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Brian White <mscdex@mscdex.net>
Reviewed-By: Refael Ackermann <refack@gmail.com>
  • Loading branch information
bmeck committed Oct 12, 2017
1 parent 75d41cf commit a36aa04
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 49 deletions.
3 changes: 2 additions & 1 deletion lib/internal/loader/ModuleWrap.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const { ModuleWrap } = process.binding('module_wrap');
const { ModuleWrap } =
require('internal/process').internalBinding('module_wrap');
const debug = require('util').debuglog('esm');
const ArrayJoin = Function.call.bind(Array.prototype.join);
const ArrayMap = Function.call.bind(Array.prototype.map);
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/loader/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const { URL } = require('url');
const CJSmodule = require('module');
const errors = require('internal/errors');
const { resolve } = process.binding('module_wrap');
const { resolve } = require('internal/process').internalBinding('module_wrap');

module.exports = (target, base) => {
if (base === undefined) {
Expand Down
6 changes: 5 additions & 1 deletion lib/internal/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const errors = require('internal/errors');
const util = require('util');
const constants = process.binding('constants').os.signals;

const internalBinding = process._internalBinding;
delete process._internalBinding;

const assert = process.assert = function(x, msg) {
if (!x) throw new errors.Error('ERR_ASSERTION', msg || 'assertion error');
};
Expand Down Expand Up @@ -256,5 +259,6 @@ module.exports = {
setupKillAndExit,
setupSignalHandlers,
setupChannel,
setupRawDebug
setupRawDebug,
internalBinding
};
12 changes: 11 additions & 1 deletion src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,17 @@ inline Environment::Environment(IsolateData* isolate_data,
v8::HandleScope handle_scope(isolate());
v8::Context::Scope context_scope(context);
set_as_external(v8::External::New(isolate(), this));
set_binding_cache_object(v8::Object::New(isolate()));

v8::Local<v8::Primitive> null = v8::Null(isolate());
v8::Local<v8::Object> binding_cache_object = v8::Object::New(isolate());
CHECK(binding_cache_object->SetPrototype(context, null).FromJust());
set_binding_cache_object(binding_cache_object);

v8::Local<v8::Object> internal_binding_cache_object =
v8::Object::New(isolate());
CHECK(internal_binding_cache_object->SetPrototype(context, null).FromJust());
set_internal_binding_cache_object(internal_binding_cache_object);

set_module_load_list_array(v8::Array::New(isolate()));

AssignToContext(context);
Expand Down
1 change: 1 addition & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ class ModuleWrap;
V(async_hooks_after_function, v8::Function) \
V(async_hooks_promise_resolve_function, v8::Function) \
V(binding_cache_object, v8::Object) \
V(internal_binding_cache_object, v8::Object) \
V(buffer_prototype_object, v8::Object) \
V(context, v8::Context) \
V(domain_array, v8::Array) \
Expand Down
5 changes: 3 additions & 2 deletions src/module_wrap.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "node_url.h"
#include "util.h"
#include "util-inl.h"
#include "node_internals.h"

namespace node {
namespace loader {
Expand Down Expand Up @@ -523,5 +524,5 @@ void ModuleWrap::Initialize(Local<Object> target,
} // namespace loader
} // namespace node

NODE_MODULE_CONTEXT_AWARE_BUILTIN(module_wrap,
node::loader::ModuleWrap::Initialize)
NODE_MODULE_CONTEXT_AWARE_INTERNAL(module_wrap,
node::loader::ModuleWrap::Initialize)
134 changes: 96 additions & 38 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ static bool v8_is_profiling = false;
static bool node_is_initialized = false;
static node_module* modpending;
static node_module* modlist_builtin;
static node_module* modlist_internal;
static node_module* modlist_linked;
static node_module* modlist_addon;
static bool trace_enabled = false;
Expand Down Expand Up @@ -2574,6 +2575,9 @@ extern "C" void node_module_register(void* m) {
if (mp->nm_flags & NM_F_BUILTIN) {
mp->nm_link = modlist_builtin;
modlist_builtin = mp;
} else if (mp->nm_flags & NM_F_INTERNAL) {
mp->nm_link = modlist_internal;
modlist_internal = mp;
} else if (!node_is_initialized) {
// "Linked" modules are included as part of the node project.
// Like builtins they are registered *before* node::Init runs.
Expand All @@ -2585,28 +2589,28 @@ extern "C" void node_module_register(void* m) {
}
}

struct node_module* get_builtin_module(const char* name) {
inline struct node_module* FindModule(struct node_module* list,
const char* name,
int flag) {
struct node_module* mp;

for (mp = modlist_builtin; mp != nullptr; mp = mp->nm_link) {
for (mp = list; mp != nullptr; mp = mp->nm_link) {
if (strcmp(mp->nm_modname, name) == 0)
break;
}

CHECK(mp == nullptr || (mp->nm_flags & NM_F_BUILTIN) != 0);
return (mp);
CHECK(mp == nullptr || (mp->nm_flags & flag) != 0);
return mp;
}

struct node_module* get_linked_module(const char* name) {
struct node_module* mp;

for (mp = modlist_linked; mp != nullptr; mp = mp->nm_link) {
if (strcmp(mp->nm_modname, name) == 0)
break;
}

CHECK(mp == nullptr || (mp->nm_flags & NM_F_LINKED) != 0);
return mp;
node_module* get_builtin_module(const char* name) {
return FindModule(modlist_builtin, name, NM_F_BUILTIN);
}
node_module* get_internal_module(const char* name) {
return FindModule(modlist_internal, name, NM_F_INTERNAL);
}
node_module* get_linked_module(const char* name) {
return FindModule(modlist_linked, name, NM_F_LINKED);
}

struct DLib {
Expand Down Expand Up @@ -2880,66 +2884,119 @@ void ProcessEmitWarning(Environment* env, const char* fmt, ...) {
f.As<v8::Function>()->Call(process, 1, &arg);
}

static bool PullFromCache(Environment* env,
const FunctionCallbackInfo<Value>& args,
Local<String> module,
Local<Object> cache) {
Local<Context> context = env->context();
Local<Value> exports_v;
Local<Object> exports;
if (cache->Get(context, module).ToLocal(&exports_v) &&
exports_v->IsObject() &&
exports_v->ToObject(context).ToLocal(&exports)) {
args.GetReturnValue().Set(exports);
return true;
}
return false;
}

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.
CHECK_EQ(mod->nm_register_func, nullptr);
CHECK_NE(mod->nm_context_register_func, nullptr);
Local<Value> unused = Undefined(env->isolate());
mod->nm_context_register_func(exports,
unused,
env->context(),
mod->nm_priv);
return exports;
}

static void ThrowIfNoSuchModule(Environment* env, const char* module_v) {
char errmsg[1024];
snprintf(errmsg,
sizeof(errmsg),
"No such module: %s",
module_v);
env->ThrowError(errmsg);
}

static void Binding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Local<String> module = args[0]->ToString(env->isolate());
node::Utf8Value module_v(env->isolate(), module);
Local<String> module;
if (!args[0]->ToString(env->context()).ToLocal(&module)) return;

Local<Object> cache = env->binding_cache_object();
Local<Object> exports;

if (cache->Has(env->context(), module).FromJust()) {
exports = cache->Get(module)->ToObject(env->isolate());
args.GetReturnValue().Set(exports);
if (PullFromCache(env, args, module, cache))
return;
}

// Append a string to process.moduleLoadList
char buf[1024];
node::Utf8Value module_v(env->isolate(), module);
snprintf(buf, sizeof(buf), "Binding %s", *module_v);

Local<Array> modules = env->module_load_list_array();
uint32_t l = modules->Length();
modules->Set(l, OneByteString(env->isolate(), buf));

node_module* mod = get_builtin_module(*module_v);
Local<Object> exports;
if (mod != nullptr) {
exports = Object::New(env->isolate());
// Internal bindings don't have a "module" object, only exports.
CHECK_EQ(mod->nm_register_func, nullptr);
CHECK_NE(mod->nm_context_register_func, nullptr);
Local<Value> unused = Undefined(env->isolate());
mod->nm_context_register_func(exports, unused,
env->context(), mod->nm_priv);
cache->Set(module, exports);
exports = InitModule(env, mod, module);
} else if (!strcmp(*module_v, "constants")) {
exports = Object::New(env->isolate());
CHECK(exports->SetPrototype(env->context(),
Null(env->isolate())).FromJust());
DefineConstants(env->isolate(), exports);
cache->Set(module, exports);
} else if (!strcmp(*module_v, "natives")) {
exports = Object::New(env->isolate());
DefineJavaScript(env, exports);
cache->Set(module, exports);
} else {
char errmsg[1024];
snprintf(errmsg,
sizeof(errmsg),
"No such module: %s",
*module_v);
return env->ThrowError(errmsg);
return ThrowIfNoSuchModule(env, *module_v);
}
cache->Set(module, exports);

args.GetReturnValue().Set(exports);
}

static void InternalBinding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Local<String> module;
if (!args[0]->ToString(env->context()).ToLocal(&module)) return;

Local<Object> cache = env->internal_binding_cache_object();

if (PullFromCache(env, args, module, cache))
return;

// Append a string to process.moduleLoadList
char buf[1024];
node::Utf8Value module_v(env->isolate(), module);
snprintf(buf, sizeof(buf), "Internal Binding %s", *module_v);

Local<Array> modules = env->module_load_list_array();
uint32_t l = modules->Length();
modules->Set(l, OneByteString(env->isolate(), buf));

node_module* mod = get_internal_module(*module_v);
if (mod == nullptr) return ThrowIfNoSuchModule(env, *module_v);
Local<Object> exports = InitModule(env, mod, module);
cache->Set(module, exports);

args.GetReturnValue().Set(exports);
}

static void LinkedBinding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args.GetIsolate());

Local<String> module_name = args[0]->ToString(env->isolate());
Local<String> module_name;
if (!args[0]->ToString(env->context()).ToLocal(&module_name)) return;

Local<Object> cache = env->binding_cache_object();
Local<Value> exports_v = cache->Get(module_name);
Expand Down Expand Up @@ -3674,6 +3731,7 @@ void SetupProcessObject(Environment* env,

env->SetMethod(process, "binding", Binding);
env->SetMethod(process, "_linkedBinding", LinkedBinding);
env->SetMethod(process, "_internalBinding", InternalBinding);

env->SetMethod(process, "_setupProcessObject", SetupProcessObject);
env->SetMethod(process, "_setupNextTick", SetupNextTick);
Expand Down
8 changes: 3 additions & 5 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,8 +421,9 @@ typedef void (*addon_context_register_func)(
v8::Local<v8::Context> context,
void* priv);

#define NM_F_BUILTIN 0x01
#define NM_F_LINKED 0x02
#define NM_F_BUILTIN 0x01
#define NM_F_LINKED 0x02
#define NM_F_INTERNAL 0x04

struct node_module {
int nm_version;
Expand All @@ -436,9 +437,6 @@ struct node_module {
struct node_module* nm_link;
};

node_module* get_builtin_module(const char *name);
node_module* get_linked_module(const char *name);

extern "C" NODE_EXTERN void node_module_register(void* mod);

#ifdef _WIN32
Expand Down
3 changes: 3 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ class InternalCallbackScope {
bool closed_ = false;
};

#define NODE_MODULE_CONTEXT_AWARE_INTERNAL(modname, regfunc) \
NODE_MODULE_CONTEXT_AWARE_X(modname, regfunc, NULL, NM_F_INTERNAL) \

} // namespace node


Expand Down
10 changes: 10 additions & 0 deletions test/parallel/test-internal-process-binding.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

require('../common');
const assert = require('assert');

assert.strictEqual(undefined, process._internalBinding);
assert.strictEqual(undefined, process.internalBinding);
assert.throws(() => {
process.binding('module_wrap');
}, /No such module/);

0 comments on commit a36aa04

Please sign in to comment.