Skip to content

Commit 606185e

Browse files
committed
sea,vfs: address Joyee's code review feedback
- Rename unregisterVFS to deregisterVFS to match public API naming - Fix Buffer copy safety in SEA provider: use Buffer.from(new Uint8Array(content)) to ensure returned buffers are independent copies safe to mutate, not views over read-only memory segments - Consolidate C++ boolean getters (isSea, isVfsEnabled, isExperimentalSeaWarningNeeded) into boolean properties set once during Initialize(), avoiding repeated function call overhead - Use Module.createRequire() when VFS is enabled in SEA so that require has all standard properties (resolve, cache, etc.) and builtin loading flows through hooks
1 parent 4a66102 commit 606185e

File tree

9 files changed

+76
-71
lines changed

9 files changed

+76
-71
lines changed

lib/internal/main/embedding.js

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,14 @@
1212
const {
1313
prepareMainThreadExecution,
1414
} = require('internal/process/pre_execution');
15-
const { isExperimentalSeaWarningNeeded, isSea, isVfsEnabled } = internalBinding('sea');
15+
const {
16+
isSea: isLoadingSea,
17+
isVfsEnabled: isVfsEnabledFlag,
18+
isExperimentalSeaWarningNeeded,
19+
} = internalBinding('sea');
1620
const { emitExperimentalWarning } = require('internal/util');
1721
const { emitWarningSync } = require('internal/process/warning');
18-
const { Module, wrapModuleLoad } = require('internal/modules/cjs/loader');
22+
const { Module } = require('internal/modules/cjs/loader');
1923
const { compileFunctionForCJSLoader } = internalBinding('contextify');
2024
const { maybeCacheSourceMap } = require('internal/source_map/source_map_cache');
2125
const { BuiltinModule } = require('internal/bootstrap/realm');
@@ -33,9 +37,8 @@ const path = require('path');
3337
// command line (e.g. it could be provided via an API or bundled into the executable).
3438
prepareMainThreadExecution(false, true);
3539

36-
const isLoadingSea = isSea();
37-
const isBuiltinWarningNeeded = isLoadingSea && isExperimentalSeaWarningNeeded();
38-
if (isExperimentalSeaWarningNeeded()) {
40+
const isBuiltinWarningNeeded = isLoadingSea && isExperimentalSeaWarningNeeded;
41+
if (isExperimentalSeaWarningNeeded) {
3942
emitExperimentalWarning('Single executable application');
4043
}
4144

@@ -86,14 +89,21 @@ function embedderRunCjs(content, filename) {
8689

8790
// Patch the module to make it look almost like a regular CJS module
8891
// instance. When VFS is active, set the filename to a VFS path so that
89-
// relative require('./foo.js') resolves under the VFS mount point.
92+
// relative require('./foo.js') resolves under the VFS mount point,
93+
// and use createRequire for a fully-featured require function.
94+
let requireFn;
9095
if (seaVfsActive && seaVfsMountPoint) {
9196
customModule.filename = seaVfsMountPoint + '/' + path.basename(filename);
9297
customModule.paths = Module._nodeModulePaths(
9398
path.dirname(customModule.filename));
99+
// Use createRequire so that require has all standard properties
100+
// (resolve, cache, etc.) and builtin loading flows through hooks.
101+
requireFn = Module.createRequire(customModule.filename);
102+
requireFn.main = customModule;
94103
} else {
95104
customModule.filename = process.execPath;
96105
customModule.paths = Module._nodeModulePaths(process.execPath);
106+
requireFn = embedderRequire;
97107
}
98108
embedderRequire.main = customModule;
99109

@@ -104,7 +114,7 @@ function embedderRunCjs(content, filename) {
104114
// out parameter.
105115
return compiledWrapper(
106116
customModule.exports, // exports
107-
embedderRequire, // require
117+
requireFn, // require
108118
customModule, // module
109119
customModule.filename, // __filename
110120
path.dirname(customModule.filename), // __dirname
@@ -134,7 +144,7 @@ function initSeaVfs() {
134144
if (seaVfsInitialized) return;
135145
seaVfsInitialized = true;
136146

137-
if (!isLoadingSea || !isVfsEnabled()) return;
147+
if (!isLoadingSea || !isVfsEnabledFlag) return;
138148

139149
// Check if SEA has VFS support
140150
const { getSeaVfs } = require('internal/vfs/sea');
@@ -148,18 +158,11 @@ function initSeaVfs() {
148158
function embedderRequire(id) {
149159
const normalizedId = normalizeRequirableId(id);
150160
if (normalizedId) {
151-
// Built-in module
152161
return require(normalizedId);
153162
}
154163

155-
// When VFS is active, use wrapModuleLoad which flows through the
156-
// registered Module.registerHooks({ resolve, load }) hooks in
157-
// module_hooks.js, handling both absolute VFS paths and relative requires.
158-
if (isLoadingSea && seaVfsActive) {
159-
return wrapModuleLoad(id, embedderRequire.main, false);
160-
}
161-
162-
// No VFS - show warning and throw
164+
// When VFS is not active, only built-in modules are supported.
165+
// VFS-enabled SEAs use createRequire instead of embedderRequire.
163166
warnNonBuiltinInSEA();
164167
throw new ERR_UNKNOWN_BUILTIN_MODULE(id);
165168
}

lib/internal/main/mksnapshot.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function main() {
202202
return fn(requireForUserSnapshot, filename, dirname);
203203
}
204204

205-
if (isExperimentalSeaWarningNeeded()) {
205+
if (isExperimentalSeaWarningNeeded) {
206206
emitExperimentalWarning('Single executable application');
207207
}
208208

lib/internal/modules/helpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ let isSEABuiltinWarningNeeded_;
132132
function isSEABuiltinWarningNeeded() {
133133
if (isSEABuiltinWarningNeeded_ === undefined) {
134134
const { isExperimentalSeaWarningNeeded, isSea } = internalBinding('sea');
135-
isSEABuiltinWarningNeeded_ = isSea() && isExperimentalSeaWarningNeeded();
135+
isSEABuiltinWarningNeeded_ = isSea && isExperimentalSeaWarningNeeded;
136136
}
137137
return isSEABuiltinWarningNeeded_;
138138
}

lib/internal/vfs/file_system.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ const kOriginalCwd = Symbol('kOriginalCwd');
7575

7676
// Lazy-loaded module hooks
7777
let registerVFS;
78-
let unregisterVFS;
78+
let deregisterVFS;
7979

8080
function loadModuleHooks() {
8181
if (!registerVFS) {
8282
const hooks = require('internal/vfs/module_hooks');
8383
registerVFS = hooks.registerVFS;
84-
unregisterVFS = hooks.unregisterVFS;
84+
deregisterVFS = hooks.deregisterVFS;
8585
}
8686
}
8787

@@ -290,7 +290,7 @@ class VirtualFileSystem {
290290
this.#unhookProcessCwd();
291291
if (this[kModuleHooks]) {
292292
loadModuleHooks();
293-
unregisterVFS(this);
293+
deregisterVFS(this);
294294
}
295295
this[kMountPoint] = null;
296296
this[kMounted] = false;

lib/internal/vfs/module_hooks.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ function registerVFS(vfs) {
7878
}
7979

8080
/**
81-
* Unregisters a VFS instance.
81+
* Deregisters a VFS instance.
8282
* @param {VirtualFileSystem} vfs The VFS instance to unregister
8383
*/
84-
function unregisterVFS(vfs) {
84+
function deregisterVFS(vfs) {
8585
const index = ArrayPrototypeIndexOf(activeVFSList, vfs);
8686
if (index !== -1) {
8787
ArrayPrototypeSplice(activeVFSList, index, 1);
@@ -699,7 +699,7 @@ function installHooks() {
699699

700700
module.exports = {
701701
registerVFS,
702-
unregisterVFS,
702+
deregisterVFS,
703703
findVFSForStat,
704704
findVFSForRead,
705705
};

lib/internal/vfs/providers/sea.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class SEAProvider extends VirtualProvider {
147147
// Lazy-load SEA bindings
148148
const { isSea, getAsset, getAssetKeys } = internalBinding('sea');
149149

150-
if (!isSea()) {
150+
if (!isSea) {
151151
throw new ERR_INVALID_STATE('SEAProvider can only be used in a Single Executable Application');
152152
}
153153

@@ -252,7 +252,10 @@ class SEAProvider extends VirtualProvider {
252252
throw createENOENT('open', path);
253253
}
254254
const content = this[kGetAsset](key);
255-
return Buffer.from(content);
255+
// Create a copy so the returned buffer is independently mutable.
256+
// Buffer.from(ArrayBuffer) creates a view sharing the same memory,
257+
// which may be backed by read-only segments in the executable.
258+
return Buffer.from(new Uint8Array(content));
256259
}
257260

258261
openSync(path, flags, mode) {

lib/internal/vfs/sea.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const lazySEAProvider = getLazy(
3030
* @returns {VirtualFileSystem|null} The VFS instance, or null if not running as SEA or no assets
3131
*/
3232
function createSeaVfs(options = kEmptyObject) {
33-
if (!isSea() || !isVfsEnabled()) {
33+
if (!isSea || !isVfsEnabled) {
3434
return null;
3535
}
3636

lib/sea.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const {
33
ArrayBufferPrototypeSlice,
44
} = primordials;
55

6-
const { isSea, getAsset: getAssetInternal, getAssetKeys: getAssetKeysInternal } = internalBinding('sea');
6+
const { isSea: isSeaFlag, getAsset: getAssetInternal, getAssetKeys: getAssetKeysInternal } = internalBinding('sea');
77
const { TextDecoder } = require('internal/encoding');
88
const { validateString } = require('internal/validators');
99
const {
@@ -23,7 +23,7 @@ const { Blob } = require('internal/blob');
2323
function getRawAsset(key) {
2424
validateString(key, 'key');
2525

26-
if (!isSea()) {
26+
if (!isSeaFlag) {
2727
throw new ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION();
2828
}
2929

@@ -74,13 +74,17 @@ function getAssetAsBlob(key, options) {
7474
* @returns {string[]}
7575
*/
7676
function getAssetKeys() {
77-
if (!isSea()) {
77+
if (!isSeaFlag) {
7878
throw new ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION();
7979
}
8080

8181
return getAssetKeysInternal() || [];
8282
}
8383

84+
function isSea() {
85+
return isSeaFlag;
86+
}
87+
8488
module.exports = {
8589
isSea,
8690
getAsset,

src/node_sea.cc

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -258,37 +258,7 @@ SeaResource FindSingleExecutableResource() {
258258
return sea_resource;
259259
}
260260

261-
void IsSea(const FunctionCallbackInfo<Value>& args) {
262-
args.GetReturnValue().Set(IsSingleExecutable());
263-
}
264-
265-
void IsVfsEnabled(const FunctionCallbackInfo<Value>& args) {
266-
if (!IsSingleExecutable()) {
267-
args.GetReturnValue().Set(false);
268-
return;
269-
}
270-
SeaResource sea_resource = FindSingleExecutableResource();
271-
args.GetReturnValue().Set(
272-
static_cast<bool>(sea_resource.flags & SeaFlags::kEnableVfs));
273-
}
274-
275-
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
276-
bool is_building_sea =
277-
!per_process::cli_options->experimental_sea_config.empty();
278-
if (is_building_sea) {
279-
args.GetReturnValue().Set(true);
280-
return;
281-
}
282-
283-
if (!IsSingleExecutable()) {
284-
args.GetReturnValue().Set(false);
285-
return;
286-
}
287-
288-
SeaResource sea_resource = FindSingleExecutableResource();
289-
args.GetReturnValue().Set(!static_cast<bool>(
290-
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
291-
}
261+
// Boolean flag getters removed - flags are set as properties in Initialize().
292262

293263
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
294264
// Repeats argv[0] at position 1 on argv as a replacement for the missing
@@ -909,20 +879,45 @@ void Initialize(Local<Object> target,
909879
Local<Value> unused,
910880
Local<Context> context,
911881
void* priv) {
912-
SetMethod(context, target, "isSea", IsSea);
913-
SetMethod(context,
914-
target,
915-
"isExperimentalSeaWarningNeeded",
916-
IsExperimentalSeaWarningNeeded);
917-
SetMethod(context, target, "isVfsEnabled", IsVfsEnabled);
882+
Isolate* isolate = context->GetIsolate();
883+
884+
// Set boolean flags as properties (computed once, avoids repeated calls).
885+
bool is_sea = IsSingleExecutable();
886+
bool is_vfs_enabled = false;
887+
bool is_experimental_warning_needed =
888+
!per_process::cli_options->experimental_sea_config.empty();
889+
890+
if (is_sea) {
891+
SeaResource sea_resource = FindSingleExecutableResource();
892+
is_vfs_enabled =
893+
static_cast<bool>(sea_resource.flags & SeaFlags::kEnableVfs);
894+
if (!static_cast<bool>(
895+
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning)) {
896+
is_experimental_warning_needed = true;
897+
}
898+
}
899+
900+
target
901+
->Set(context,
902+
FIXED_ONE_BYTE_STRING(isolate, "isSea"),
903+
Boolean::New(isolate, is_sea))
904+
.Check();
905+
target
906+
->Set(context,
907+
FIXED_ONE_BYTE_STRING(isolate, "isVfsEnabled"),
908+
Boolean::New(isolate, is_vfs_enabled))
909+
.Check();
910+
target
911+
->Set(context,
912+
FIXED_ONE_BYTE_STRING(isolate, "isExperimentalSeaWarningNeeded"),
913+
Boolean::New(isolate, is_experimental_warning_needed))
914+
.Check();
915+
918916
SetMethod(context, target, "getAsset", GetAsset);
919917
SetMethod(context, target, "getAssetKeys", GetAssetKeys);
920918
}
921919

922920
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
923-
registry->Register(IsSea);
924-
registry->Register(IsExperimentalSeaWarningNeeded);
925-
registry->Register(IsVfsEnabled);
926921
registry->Register(GetAsset);
927922
registry->Register(GetAssetKeys);
928923
}

0 commit comments

Comments
 (0)