Skip to content

Commit e760ab7

Browse files
committed
src: bootstrap prepare stack trace callback in shadow realm
Bootstrap per-realm callbacks like `prepare_stack_trace_callback` in the ShadowRealm. This enables stack trace decoration in the ShadowRealm.
1 parent fa84657 commit e760ab7

7 files changed

Lines changed: 106 additions & 52 deletions

File tree

lib/internal/bootstrap/node.js

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Hello, and welcome to hacking node.js!
22
//
3-
// This file is invoked by `Realm::BootstrapNode()` in `src/node_realm.cc`,
3+
// This file is invoked by `Realm::BootstrapRealm()` in `src/node_realm.cc`,
44
// and is responsible for setting up Node.js core before main scripts
55
// under `lib/internal/main/` are executed.
66
//
@@ -35,6 +35,8 @@
3535
// - `lib/internal/bootstrap/loaders.js`: this sets up internal binding and
3636
// module loaders, including `process.binding()`, `process._linkedBinding()`,
3737
// `internalBinding()` and `BuiltinModule`.
38+
// - `lib/internal/bootstrap/realm.js`: this sets up per-realm internal states
39+
// and callbacks, including `prepare_stack_trace_callback`.
3840
//
3941
// The initialization done in this script is included in both the main thread
4042
// and the worker threads. After this, further initialization is done based
@@ -52,8 +54,6 @@
5254
// passed by `BuiltinLoader::CompileAndCall()`.
5355
/* global process, require, internalBinding, primordials */
5456

55-
setupPrepareStackTrace();
56-
5757
const {
5858
FunctionPrototypeCall,
5959
JSONParse,
@@ -336,25 +336,6 @@ process.emitWarning = emitWarning;
336336
// Note: only after this point are the timers effective
337337
}
338338

339-
function setupPrepareStackTrace() {
340-
const {
341-
setEnhanceStackForFatalException,
342-
setPrepareStackTraceCallback,
343-
} = internalBinding('errors');
344-
const {
345-
prepareStackTrace,
346-
fatalExceptionStackEnhancers: {
347-
beforeInspector,
348-
afterInspector,
349-
},
350-
} = require('internal/errors');
351-
// Tell our PrepareStackTraceCallback passed to the V8 API
352-
// to call prepareStackTrace().
353-
setPrepareStackTraceCallback(prepareStackTrace);
354-
// Set the function used to enhance the error stack for printing
355-
setEnhanceStackForFatalException(beforeInspector, afterInspector);
356-
}
357-
358339
function setupProcessObject() {
359340
const EventEmitter = require('events');
360341
const origProcProto = ObjectGetPrototypeOf(process);

lib/internal/bootstrap/realm.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use strict';
2+
3+
/**
4+
* This file is executed in every realm that is created by Node.js, including
5+
* the context of main thread, worker threads, and ShadowRealms.
6+
* Only per-realm internal states and callbacks should be bootstrapped in this
7+
* file and no globals should be exposed to the user code.
8+
*/
9+
10+
setupPrepareStackTrace();
11+
12+
function setupPrepareStackTrace() {
13+
const {
14+
setEnhanceStackForFatalException,
15+
setPrepareStackTraceCallback,
16+
} = internalBinding('errors');
17+
const {
18+
prepareStackTrace,
19+
fatalExceptionStackEnhancers: {
20+
beforeInspector,
21+
afterInspector,
22+
},
23+
} = require('internal/errors');
24+
// Tell our PrepareStackTraceCallback passed to the V8 API
25+
// to call prepareStackTrace().
26+
setPrepareStackTraceCallback(prepareStackTrace);
27+
// Set the function used to enhance the error stack for printing
28+
setEnhanceStackForFatalException(beforeInspector, afterInspector);
29+
}

src/api/environment.cc

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,20 @@ MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
6767
if (env == nullptr) {
6868
return exception->ToString(context).FromMaybe(Local<Value>());
6969
}
70-
// TODO(legendecas): Per-realm prepareStackTrace callback.
71-
// If we are in a Realm that is not the principal Realm (e.g. ShadowRealm),
72-
// skip the prepareStackTrace callback to avoid passing the JS objects (
73-
// the exception and trace) across the realm boundary with the
74-
// `Error.prepareStackTrace` override.
7570
Realm* current_realm = Realm::GetCurrent(context);
71+
Local<Function> prepare;
7672
if (current_realm != nullptr &&
7773
current_realm->kind() != Realm::Kind::kPrincipal) {
78-
return exception->ToString(context).FromMaybe(Local<Value>());
74+
// If we are in a Realm that is not the principal Realm (e.g. ShadowRealm),
75+
// call the realm specific prepareStackTrace callback to avoid passing the
76+
// JS objects (the exception and trace) across the realm boundary with the
77+
// `Error.prepareStackTrace` override.
78+
prepare = current_realm->prepare_stack_trace_callback();
79+
} else {
80+
// The context is created with ContextifyContext, call the principal
81+
// realm's prepareStackTrace callback.
82+
prepare = env->principal_realm()->prepare_stack_trace_callback();
7983
}
80-
Local<Function> prepare = env->prepare_stack_trace_callback();
8184
if (prepare.IsEmpty()) {
8285
return exception->ToString(context).FromMaybe(Local<Value>());
8386
}

src/node_errors.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -960,9 +960,9 @@ void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
960960
}
961961

962962
void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
963-
Environment* env = Environment::GetCurrent(args);
963+
Realm* realm = Realm::GetCurrent(args);
964964
CHECK(args[0]->IsFunction());
965-
env->set_prepare_stack_trace_callback(args[0].As<Function>());
965+
realm->set_prepare_stack_trace_callback(args[0].As<Function>());
966966
}
967967

968968
static void SetSourceMapsEnabled(const FunctionCallbackInfo<Value>& args) {
@@ -987,11 +987,11 @@ static void SetMaybeCacheGeneratedSourceMap(
987987

988988
static void SetEnhanceStackForFatalException(
989989
const FunctionCallbackInfo<Value>& args) {
990-
Environment* env = Environment::GetCurrent(args);
990+
Realm* realm = Realm::GetCurrent(args);
991991
CHECK(args[0]->IsFunction());
992992
CHECK(args[1]->IsFunction());
993-
env->set_enhance_fatal_stack_before_inspector(args[0].As<Function>());
994-
env->set_enhance_fatal_stack_after_inspector(args[1].As<Function>());
993+
realm->set_enhance_fatal_stack_before_inspector(args[0].As<Function>());
994+
realm->set_enhance_fatal_stack_after_inspector(args[1].As<Function>());
995995
}
996996

997997
// Side effect-free stringification that will never throw exceptions.

src/node_realm.cc

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,10 @@ void PrincipalRealm::MemoryInfo(MemoryTracker* tracker) const {
335335
}
336336

337337
MaybeLocal<Value> PrincipalRealm::BootstrapRealm() {
338-
EscapableHandleScope scope(isolate_);
339-
340-
MaybeLocal<Value> result = ExecuteBootstrapper("internal/bootstrap/node");
338+
HandleScope scope(isolate_);
341339

342-
if (result.IsEmpty()) {
340+
if (ExecuteBootstrapper("internal/bootstrap/realm").IsEmpty() ||
341+
ExecuteBootstrapper("internal/bootstrap/node").IsEmpty()) {
343342
return MaybeLocal<Value>();
344343
}
345344

@@ -356,19 +355,15 @@ MaybeLocal<Value> PrincipalRealm::BootstrapRealm() {
356355
auto thread_switch_id =
357356
env_->is_main_thread() ? "internal/bootstrap/switches/is_main_thread"
358357
: "internal/bootstrap/switches/is_not_main_thread";
359-
result = ExecuteBootstrapper(thread_switch_id);
360-
361-
if (result.IsEmpty()) {
358+
if (ExecuteBootstrapper(thread_switch_id).IsEmpty()) {
362359
return MaybeLocal<Value>();
363360
}
364361

365362
auto process_state_switch_id =
366363
env_->owns_process_state()
367364
? "internal/bootstrap/switches/does_own_process_state"
368365
: "internal/bootstrap/switches/does_not_own_process_state";
369-
result = ExecuteBootstrapper(process_state_switch_id);
370-
371-
if (result.IsEmpty()) {
366+
if (ExecuteBootstrapper(process_state_switch_id).IsEmpty()) {
372367
return MaybeLocal<Value>();
373368
}
374369

@@ -380,7 +375,7 @@ MaybeLocal<Value> PrincipalRealm::BootstrapRealm() {
380375
return MaybeLocal<Value>();
381376
}
382377

383-
return scope.EscapeMaybe(result);
378+
return v8::True(isolate_);
384379
}
385380

386381
} // namespace node

src/node_shadow_realm.cc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,22 @@ PER_REALM_STRONG_PERSISTENT_VALUES(V)
9191
#undef V
9292

9393
v8::MaybeLocal<v8::Value> ShadowRealm::BootstrapRealm() {
94-
EscapableHandleScope scope(isolate_);
95-
MaybeLocal<Value> result = v8::True(isolate_);
94+
HandleScope scope(isolate_);
9695

9796
// Skip "internal/bootstrap/node" as it installs node globals and per-isolate
98-
// callbacks like PrepareStackTraceCallback.
97+
// callbacks.
98+
if (ExecuteBootstrapper("internal/bootstrap/realm").IsEmpty()) {
99+
return MaybeLocal<Value>();
100+
}
99101

100102
if (!env_->no_browser_globals()) {
101-
result = ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard");
102-
103-
if (result.IsEmpty()) {
103+
if (ExecuteBootstrapper("internal/bootstrap/web/exposed-wildcard")
104+
.IsEmpty()) {
104105
return MaybeLocal<Value>();
105106
}
106107
}
107108

108-
return result;
109+
return v8::True(isolate_);
109110
}
110111

111112
} // namespace shadow_realm
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Flags: --experimental-shadow-realm
2+
'use strict';
3+
4+
require('../common');
5+
const assert = require('assert');
6+
7+
{
8+
// Validates inner Error.prepareStackTrace can not leak into the outer realm.
9+
const shadowRealm = new ShadowRealm();
10+
11+
const stack = shadowRealm.evaluate(`
12+
Error.prepareStackTrace = (error, trace) => {
13+
globalThis.leaked = 'inner';
14+
return String(error);
15+
};
16+
17+
try {
18+
throw new Error('boom');
19+
} catch (e) {
20+
e.stack;
21+
}
22+
`);
23+
assert.strictEqual(stack, 'Error: boom');
24+
assert.strictEqual('leaked' in globalThis, false);
25+
}
26+
27+
{
28+
// Validates stacks can be generated in the ShadowRealm.
29+
const shadowRealm = new ShadowRealm();
30+
31+
const stack = shadowRealm.evaluate(`
32+
function myFunc() {
33+
throw new Error('boom');
34+
}
35+
36+
try {
37+
myFunc();
38+
} catch (e) {
39+
e.stack;
40+
}
41+
`);
42+
const lines = stack.split('\n');
43+
assert.strictEqual(lines[0], 'Error: boom');
44+
assert.match(lines[1], /^ {4}at myFunc \(.*\)/);
45+
}

0 commit comments

Comments
 (0)