Skip to content

Commit 0e2fbe4

Browse files
joyeecheungaddaleax
authored andcommitted
process: specialize building and storage of process.config
Instead of treating config.gypi as a JavaScript file, specialize the processing in js2c and make the serialized result a real JSON string (with 'true' and 'false' converted to boolean values) so we don't have to use a custom deserializer during bootstrap. In addition, store the JSON string separately in NativeModuleLoader, and keep it separate from the map of the builtin source code, so we don't have to put it onto `NativeModule._source` and delete it later, though we still preserve it in `process.binding('natives')`, which we don't use anymore. This patch also makes the map of builtin source code and the config.gypi string available through side-effect-free getters in C++. PR-URL: #24816 Reviewed-By: Gus Caplan <me@gus.host>
1 parent 6528ce6 commit 0e2fbe4

11 files changed

+114
-42
lines changed

lib/internal/bootstrap/cache.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,16 @@
66
// cannot be tampered with even with --expose-internals.
77

88
const { NativeModule } = require('internal/bootstrap/loaders');
9-
const { getSource, compileCodeCache } = internalBinding('native_module');
9+
const { source, compileCodeCache } = internalBinding('native_module');
1010
const { hasTracing } = process.binding('config');
1111

12-
const source = getSource();
1312
const depsModule = Object.keys(source).filter(
1413
(key) => NativeModule.isDepsModule(key) || key.startsWith('internal/deps')
1514
);
1615

1716
// Modules with source code compiled in js2c that
1817
// cannot be compiled with the code cache.
1918
const cannotUseCache = [
20-
'config',
2119
'sys', // Deprecated.
2220
'internal/v8_prof_polyfill',
2321
'internal/v8_prof_processor',

lib/internal/bootstrap/loaders.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -169,10 +169,15 @@ function NativeModule(id) {
169169
this.loading = false;
170170
}
171171

172-
NativeModule._source = getInternalBinding('natives');
172+
const {
173+
source,
174+
compileFunction
175+
} = internalBinding('native_module');
176+
177+
NativeModule._source = source;
173178
NativeModule._cache = {};
174179

175-
const config = getInternalBinding('config');
180+
const config = internalBinding('config');
176181

177182
// Think of this as module.exports in this file even though it is not
178183
// written in CommonJS style.
@@ -323,7 +328,6 @@ NativeModule.prototype.proxifyExports = function() {
323328
this.exports = new Proxy(this.exports, handler);
324329
};
325330

326-
const { compileFunction } = getInternalBinding('native_module');
327331
NativeModule.prototype.compile = function() {
328332
const id = this.id;
329333

lib/internal/bootstrap/node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function startup() {
5959
}
6060

6161
perThreadSetup.setupAssert();
62-
perThreadSetup.setupConfig(NativeModule._source);
62+
perThreadSetup.setupConfig();
6363

6464
if (isMainThread) {
6565
mainThreadSetup.setupSignalHandlers(internalBinding);

lib/internal/process/per_thread.js

+3-11
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,9 @@ function setupMemoryUsage(_memoryUsage) {
143143
};
144144
}
145145

146-
function setupConfig(_source) {
147-
// NativeModule._source
148-
// used for `process.config`, but not a real module
149-
const config = _source.config;
150-
delete _source.config;
151-
152-
process.config = JSON.parse(config, function(key, value) {
153-
if (value === 'true') return true;
154-
if (value === 'false') return false;
155-
return value;
156-
});
146+
function setupConfig() {
147+
// Serialized config.gypi
148+
process.config = JSON.parse(internalBinding('native_module').config);
157149
}
158150

159151

src/env.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
140140
V(channel_string, "channel") \
141141
V(chunks_sent_since_last_write_string, "chunksSentSinceLastWrite") \
142142
V(code_string, "code") \
143+
V(config_string, "config") \
143144
V(constants_string, "constants") \
144145
V(crypto_dsa_string, "dsa") \
145146
V(crypto_ec_string, "ec") \
@@ -312,7 +313,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
312313
V(write_host_object_string, "_writeHostObject") \
313314
V(write_queue_size_string, "writeQueueSize") \
314315
V(x_forwarded_string, "x-forwarded-for") \
315-
V(zero_return_string, "ZERO_RETURN") \
316+
V(zero_return_string, "ZERO_RETURN")
316317

317318
#define ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) \
318319
V(as_external, v8::External) \

src/node_binding.cc

+7
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,13 @@ void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {
390390
DefineConstants(env->isolate(), exports);
391391
} else if (!strcmp(*module_v, "natives")) {
392392
exports = per_process_loader.GetSourceObject(env->context());
393+
// Legacy feature: process.binding('natives').config contains stringified
394+
// config.gypi
395+
CHECK(exports
396+
->Set(env->context(),
397+
env->config_string(),
398+
per_process_loader.GetConfigString(env->isolate()))
399+
.FromJust());
393400
} else {
394401
return ThrowIfNoSuchModule(env, *module_v);
395402
}

src/node_native_module.cc

+41-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ using v8::Array;
99
using v8::ArrayBuffer;
1010
using v8::ArrayBufferCreationMode;
1111
using v8::Context;
12+
using v8::DEFAULT;
1213
using v8::EscapableHandleScope;
1314
using v8::Function;
1415
using v8::FunctionCallbackInfo;
@@ -19,11 +20,15 @@ using v8::Isolate;
1920
using v8::Local;
2021
using v8::Maybe;
2122
using v8::MaybeLocal;
23+
using v8::Name;
24+
using v8::None;
2225
using v8::Object;
26+
using v8::PropertyCallbackInfo;
2327
using v8::Script;
2428
using v8::ScriptCompiler;
2529
using v8::ScriptOrigin;
2630
using v8::Set;
31+
using v8::SideEffectType;
2732
using v8::String;
2833
using v8::Uint8Array;
2934
using v8::Value;
@@ -70,25 +75,35 @@ void NativeModuleLoader::GetCacheUsage(
7075
args.GetReturnValue().Set(result);
7176
}
7277

73-
void NativeModuleLoader::GetSourceObject(
74-
const FunctionCallbackInfo<Value>& args) {
75-
Environment* env = Environment::GetCurrent(args);
76-
args.GetReturnValue().Set(per_process_loader.GetSourceObject(env->context()));
78+
void NativeModuleLoader::SourceObjectGetter(
79+
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
80+
Local<Context> context = info.GetIsolate()->GetCurrentContext();
81+
info.GetReturnValue().Set(per_process_loader.GetSourceObject(context));
82+
}
83+
84+
void NativeModuleLoader::ConfigStringGetter(
85+
Local<Name> property, const PropertyCallbackInfo<Value>& info) {
86+
info.GetReturnValue().Set(
87+
per_process_loader.GetConfigString(info.GetIsolate()));
7788
}
7889

7990
Local<Object> NativeModuleLoader::GetSourceObject(
8091
Local<Context> context) const {
8192
return MapToObject(context, source_);
8293
}
8394

95+
Local<String> NativeModuleLoader::GetConfigString(Isolate* isolate) const {
96+
return config_.ToStringChecked(isolate);
97+
}
98+
8499
Local<String> NativeModuleLoader::GetSource(Isolate* isolate,
85100
const char* id) const {
86101
const auto it = source_.find(id);
87102
CHECK_NE(it, source_.end());
88103
return it->second.ToStringChecked(isolate);
89104
}
90105

91-
NativeModuleLoader::NativeModuleLoader() {
106+
NativeModuleLoader::NativeModuleLoader() : config_(GetConfig()) {
92107
LoadJavaScriptSource();
93108
LoadJavaScriptHash();
94109
LoadCodeCache();
@@ -321,8 +336,27 @@ void NativeModuleLoader::Initialize(Local<Object> target,
321336
void* priv) {
322337
Environment* env = Environment::GetCurrent(context);
323338

324-
env->SetMethod(
325-
target, "getSource", NativeModuleLoader::GetSourceObject);
339+
CHECK(target
340+
->SetAccessor(env->context(),
341+
env->config_string(),
342+
ConfigStringGetter,
343+
nullptr,
344+
MaybeLocal<Value>(),
345+
DEFAULT,
346+
None,
347+
SideEffectType::kHasNoSideEffect)
348+
.FromJust());
349+
CHECK(target
350+
->SetAccessor(env->context(),
351+
env->source_string(),
352+
SourceObjectGetter,
353+
nullptr,
354+
MaybeLocal<Value>(),
355+
DEFAULT,
356+
None,
357+
SideEffectType::kHasNoSideEffect)
358+
.FromJust());
359+
326360
env->SetMethod(
327361
target, "getCacheUsage", NativeModuleLoader::GetCacheUsage);
328362
env->SetMethod(

src/node_native_module.h

+15-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class NativeModuleLoader {
4040
v8::Local<v8::Context> context,
4141
void* priv);
4242
v8::Local<v8::Object> GetSourceObject(v8::Local<v8::Context> context) const;
43+
// Returns config.gypi as a JSON string
44+
v8::Local<v8::String> GetConfigString(v8::Isolate* isolate) const;
45+
4346
v8::Local<v8::String> GetSource(v8::Isolate* isolate, const char* id) const;
4447

4548
// Run a script with JS source bundled inside the binary as if it's wrapped
@@ -56,9 +59,15 @@ class NativeModuleLoader {
5659

5760
private:
5861
static void GetCacheUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
59-
// For legacy process.binding('natives') which is mutable, and for
60-
// internalBinding('native_module').source for internal use
61-
static void GetSourceObject(const v8::FunctionCallbackInfo<v8::Value>& args);
62+
// Passing map of builtin module source code into JS land as
63+
// internalBinding('native_module').source
64+
static void SourceObjectGetter(
65+
v8::Local<v8::Name> property,
66+
const v8::PropertyCallbackInfo<v8::Value>& info);
67+
// Passing config.gypi into JS land as internalBinding('native_module').config
68+
static void ConfigStringGetter(
69+
v8::Local<v8::Name> property,
70+
const v8::PropertyCallbackInfo<v8::Value>& info);
6271
// Compile code cache for a specific native module
6372
static void CompileCodeCache(const v8::FunctionCallbackInfo<v8::Value>& args);
6473
// Compile a specific native module as a function
@@ -67,6 +76,7 @@ class NativeModuleLoader {
6776
// Generated by tools/js2c.py as node_javascript.cc
6877
void LoadJavaScriptSource(); // Loads data into source_
6978
void LoadJavaScriptHash(); // Loads data into source_hash_
79+
UnionBytes GetConfig(); // Return data for config.gypi
7080

7181
// Generated by tools/generate_code_cache.js as node_code_cache.cc when
7282
// the build is configured with --code-cache-path=.... They are noops
@@ -94,6 +104,8 @@ class NativeModuleLoader {
94104
bool has_code_cache_ = false;
95105
NativeModuleRecordMap source_;
96106
NativeModuleRecordMap code_cache_;
107+
UnionBytes config_;
108+
97109
NativeModuleHashMap source_hash_;
98110
NativeModuleHashMap code_cache_hash_;
99111
};

src/node_union_bytes.h

+10
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,31 @@ class UnionBytes {
5555
: is_one_byte_(false), two_bytes_(data), length_(length) {}
5656
UnionBytes(const uint8_t* data, size_t length)
5757
: is_one_byte_(true), one_bytes_(data), length_(length) {}
58+
59+
UnionBytes(const UnionBytes&) = default;
60+
UnionBytes& operator=(const UnionBytes&) = default;
61+
UnionBytes(UnionBytes&&) = default;
62+
UnionBytes& operator=(UnionBytes&&) = default;
63+
5864
bool is_one_byte() const { return is_one_byte_; }
5965
const uint16_t* two_bytes_data() const {
6066
CHECK(!is_one_byte_);
67+
CHECK_NE(two_bytes_, nullptr);
6168
return two_bytes_;
6269
}
6370
const uint8_t* one_bytes_data() const {
6471
CHECK(is_one_byte_);
72+
CHECK_NE(one_bytes_, nullptr);
6573
return one_bytes_;
6674
}
6775
v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) const {
6876
if (is_one_byte_) {
77+
CHECK_NE(one_bytes_, nullptr);
6978
NonOwningExternalOneByteResource* source =
7079
new NonOwningExternalOneByteResource(one_bytes_, length_);
7180
return v8::String::NewExternalOneByte(isolate, source).ToLocalChecked();
7281
} else {
82+
CHECK_NE(two_bytes_, nullptr);
7383
NonOwningExternalTwoByteResource* source =
7484
new NonOwningExternalTwoByteResource(two_bytes_, length_);
7585
return v8::String::NewExternalTwoByte(isolate, source).ToLocalChecked();

test/parallel/test-bootstrap-modules.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const common = require('../common');
99
const assert = require('assert');
1010

1111
const isMainThread = common.isMainThread;
12-
const kMaxModuleCount = isMainThread ? 59 : 80;
12+
const kMaxModuleCount = isMainThread ? 61 : 82;
1313

1414
assert(list.length <= kMaxModuleCount,
1515
`Total length: ${list.length}\n` + list.join('\n')

tools/js2c.py

+26-12
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,10 @@ def ReadMacros(lines):
193193
{hash_initializers}
194194
}}
195195
196+
UnionBytes NativeModuleLoader::GetConfig() {{
197+
return UnionBytes(config_raw, arraysize(config_raw)); // config.gypi
198+
}}
199+
196200
}} // namespace native_module
197201
198202
}} // namespace node
@@ -248,21 +252,23 @@ def JS2C(source, target):
248252
definitions = []
249253
initializers = []
250254
hash_initializers = []
255+
config_initializers = []
251256

252-
def AddModule(module, source):
253-
var = '%s_raw' % (module.replace('-', '_').replace('/', '_'))
254-
source_hash = hashlib.sha256(source).hexdigest()
255-
257+
def GetDefinition(var, source):
256258
# Treat non-ASCII as UTF-8 and convert it to UTF-16.
257259
if any(ord(c) > 127 for c in source):
258260
source = map(ord, source.decode('utf-8').encode('utf-16be'))
259261
source = [source[i] * 256 + source[i+1] for i in xrange(0, len(source), 2)]
260262
source = ToCArray(source)
261-
definition = TWO_BYTE_STRING.format(var=var, data=source)
263+
return TWO_BYTE_STRING.format(var=var, data=source)
262264
else:
263265
source = ToCArray(map(ord, source), step=20)
264-
definition = ONE_BYTE_STRING.format(var=var, data=source)
266+
return ONE_BYTE_STRING.format(var=var, data=source)
265267

268+
def AddModule(module, source):
269+
var = '%s_raw' % (module.replace('-', '_').replace('/', '_'))
270+
source_hash = hashlib.sha256(source).hexdigest()
271+
definition = GetDefinition(var, source)
266272
initializer = INITIALIZER.format(module=module,
267273
var=var)
268274
hash_initializer = HASH_INITIALIZER.format(module=module,
@@ -292,11 +298,17 @@ def AddModule(module, source):
292298

293299
# if its a gypi file we're going to want it as json
294300
# later on anyway, so get it out of the way now
295-
if name.endswith(".gypi"):
301+
if name.endswith('.gypi'):
302+
# Currently only config.gypi is allowed
303+
assert name == 'config.gypi'
304+
lines = re.sub(r'\'true\'', 'true', lines)
305+
lines = re.sub(r'\'false\'', 'false', lines)
296306
lines = re.sub(r'#.*?\n', '', lines)
297307
lines = re.sub(r'\'', '"', lines)
298-
299-
AddModule(name.split('.', 1)[0], lines)
308+
definition = GetDefinition('config_raw', lines)
309+
definitions.append(definition)
310+
else:
311+
AddModule(name.split('.', 1)[0], lines)
300312

301313
# Add deprecated aliases for deps without 'deps/'
302314
if deprecated_deps is not None:
@@ -306,9 +318,11 @@ def AddModule(module, source):
306318

307319
# Emit result
308320
output = open(str(target[0]), "w")
309-
output.write(TEMPLATE.format(definitions=''.join(definitions),
310-
initializers=''.join(initializers),
311-
hash_initializers=''.join(hash_initializers)))
321+
output.write(
322+
TEMPLATE.format(definitions=''.join(definitions),
323+
initializers=''.join(initializers),
324+
hash_initializers=''.join(hash_initializers),
325+
config_initializers=''.join(config_initializers)))
312326
output.close()
313327

314328
def main():

0 commit comments

Comments
 (0)