Skip to content

Commit 6f70054

Browse files
devsnekBridgeAR
authored andcommitted
src, lib: take control of prepareStackTrace
Refs https://crbug.com/v8/7848 PR-URL: #23926 Reviewed-By: Refael Ackermann <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent aed74cc commit 6f70054

File tree

7 files changed

+94
-1
lines changed

7 files changed

+94
-1
lines changed

Diff for: lib/internal/bootstrap/node.js

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
// passed by node::RunBootstrapping()
3636
/* global process, require, internalBinding, isMainThread, ownsProcessState */
3737

38+
setupPrepareStackTrace();
39+
3840
const { JSON, Object, Symbol } = primordials;
3941
const config = internalBinding('config');
4042
const { deprecate } = require('internal/util');
@@ -290,6 +292,12 @@ process.emitWarning = emitWarning;
290292
// Note: only after this point are the timers effective
291293
}
292294

295+
function setupPrepareStackTrace() {
296+
const { setPrepareStackTraceCallback } = internalBinding('errors');
297+
const { prepareStackTrace } = require('internal/errors');
298+
setPrepareStackTraceCallback(prepareStackTrace);
299+
}
300+
293301
function setupProcessObject() {
294302
const EventEmitter = require('events');
295303
const origProcProto = Object.getPrototypeOf(process);

Diff for: lib/internal/errors.js

+27-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,31 @@ const codes = {};
1919

2020
const { kMaxLength } = internalBinding('buffer');
2121

22+
const MainContextError = Error;
23+
const ErrorToString = Error.prototype.toString;
24+
// Polyfill of V8's Error.prepareStackTrace API.
25+
// https://crbug.com/v8/7848
26+
const prepareStackTrace = (globalThis, error, trace) => {
27+
// `globalThis` is the global that contains the constructor which
28+
// created `error`.
29+
if (typeof globalThis.Error.prepareStackTrace === 'function') {
30+
return globalThis.Error.prepareStackTrace(error, trace);
31+
}
32+
// We still have legacy usage that depends on the main context's `Error`
33+
// being used, even when the error is from a different context.
34+
// TODO(devsnek): evaluate if this can be eventually deprecated/removed.
35+
if (typeof MainContextError.prepareStackTrace === 'function') {
36+
return MainContextError.prepareStackTrace(error, trace);
37+
}
38+
39+
const errorString = ErrorToString.call(error);
40+
if (trace.length === 0) {
41+
return errorString;
42+
}
43+
return `${errorString}\n at ${trace.join('\n at ')}`;
44+
};
45+
46+
2247
let excludedStackFn;
2348

2449
// Lazily loaded
@@ -598,7 +623,8 @@ module.exports = {
598623
uvExceptionWithHostPort,
599624
SystemError,
600625
// This is exported only to facilitate testing.
601-
E
626+
E,
627+
prepareStackTrace,
602628
};
603629

604630
// To declare an error message, use the E(sym, val, def) function above. The sym

Diff for: src/api/environment.cc

+38
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#endif
1414

1515
namespace node {
16+
using errors::TryCatchScope;
17+
using v8::Array;
1618
using v8::Context;
1719
using v8::EscapableHandleScope;
1820
using v8::Function;
@@ -45,6 +47,41 @@ static bool ShouldAbortOnUncaughtException(Isolate* isolate) {
4547
!env->inside_should_not_abort_on_uncaught_scope();
4648
}
4749

50+
static MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
51+
Local<Value> exception,
52+
Local<Array> trace) {
53+
Environment* env = Environment::GetCurrent(context);
54+
if (env == nullptr) {
55+
MaybeLocal<String> s = exception->ToString(context);
56+
return s.IsEmpty() ?
57+
MaybeLocal<Value>() :
58+
MaybeLocal<Value>(s.ToLocalChecked());
59+
}
60+
Local<Function> prepare = env->prepare_stack_trace_callback();
61+
if (prepare.IsEmpty()) {
62+
MaybeLocal<String> s = exception->ToString(context);
63+
return s.IsEmpty() ?
64+
MaybeLocal<Value>() :
65+
MaybeLocal<Value>(s.ToLocalChecked());
66+
}
67+
Local<Value> args[] = {
68+
context->Global(),
69+
exception,
70+
trace,
71+
};
72+
// This TryCatch + Rethrow is required by V8 due to details around exception
73+
// handling there. For C++ callbacks, V8 expects a scheduled exception (which
74+
// is what ReThrow gives us). Just returning the empty MaybeLocal would leave
75+
// us with a pending exception.
76+
TryCatchScope try_catch(env);
77+
MaybeLocal<Value> result = prepare->Call(
78+
context, Undefined(env->isolate()), arraysize(args), args);
79+
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
80+
try_catch.ReThrow();
81+
}
82+
return result;
83+
}
84+
4885
void* NodeArrayBufferAllocator::Allocate(size_t size) {
4986
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
5087
return UncheckedCalloc(size);
@@ -166,6 +203,7 @@ void SetIsolateUpForNode(v8::Isolate* isolate, IsolateSettingCategories cat) {
166203
isolate->SetAbortOnUncaughtExceptionCallback(
167204
ShouldAbortOnUncaughtException);
168205
isolate->SetFatalErrorHandler(OnFatalError);
206+
isolate->SetPrepareStackTraceCallback(PrepareStackTraceCallback);
169207
break;
170208
case IsolateSettingCategories::kMisc:
171209
isolate->SetMicrotasksPolicy(MicrotasksPolicy::kExplicit);

Diff for: src/env.h

+1
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
402402
V(native_module_require, v8::Function) \
403403
V(performance_entry_callback, v8::Function) \
404404
V(performance_entry_template, v8::Function) \
405+
V(prepare_stack_trace_callback, v8::Function) \
405406
V(process_object, v8::Object) \
406407
V(primordials, v8::Object) \
407408
V(promise_reject_callback, v8::Function) \

Diff for: src/node_binding.cc

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
V(contextify) \
4949
V(credentials) \
5050
V(domain) \
51+
V(errors) \
5152
V(fs) \
5253
V(fs_event_wrap) \
5354
V(heap_utils) \

Diff for: src/node_errors.cc

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ using v8::Boolean;
1717
using v8::Context;
1818
using v8::Exception;
1919
using v8::Function;
20+
using v8::FunctionCallbackInfo;
2021
using v8::HandleScope;
2122
using v8::Int32;
2223
using v8::Isolate;
@@ -767,6 +768,21 @@ void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
767768
}
768769
}
769770

771+
void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
772+
Environment* env = Environment::GetCurrent(args);
773+
CHECK(args[0]->IsFunction());
774+
env->set_prepare_stack_trace_callback(args[0].As<Function>());
775+
}
776+
777+
void Initialize(Local<Object> target,
778+
Local<Value> unused,
779+
Local<Context> context,
780+
void* priv) {
781+
Environment* env = Environment::GetCurrent(context);
782+
env->SetMethod(
783+
target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback);
784+
}
785+
770786
} // namespace errors
771787

772788
void DecorateErrorStack(Environment* env,
@@ -880,3 +896,5 @@ void FatalException(Isolate* isolate,
880896
}
881897

882898
} // namespace node
899+
900+
NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize)

Diff for: test/parallel/test-bootstrap-modules.js

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

1111
const expectedModules = new Set([
12+
'Internal Binding errors',
1213
'Internal Binding async_wrap',
1314
'Internal Binding buffer',
1415
'Internal Binding config',

0 commit comments

Comments
 (0)