src: refactor uncaught exception handling

The C++ land `node::FatalException()` is not in fact fatal anymore.
It gives the user a chance to handle the uncaught exception
globally by listening to the `uncaughtException` event. This patch
renames it to `TriggerUncaughtException` in C++ to avoid the confusion.

In addition rename the JS land handler to `onGlobalUncaughtException`
to reflect its purpose - we have to keep the alias
`process._fatalException` and use that for now since it has been
monkey-patchable in the user land.

This patch also

- Adds more comments to the global uncaught exception handling routine
- Puts a few other C++ error handling functions into the `errors`
  namespace
- Moves error-handling-related bindings to the `errors` binding.

Refs: 2b252acea4

PR-URL: https://github.com/nodejs/node/pull/28257
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
This commit is contained in:
Joyee Cheung 2019-06-15 08:07:15 +08:00
parent 1c23b6f2be
commit a33c3c6d33
No known key found for this signature in database
GPG Key ID: 92B78A53C8303B8D
16 changed files with 190 additions and 158 deletions

View File

@ -254,12 +254,18 @@ Object.defineProperty(process, 'features', {
{
const {
fatalException,
onGlobalUncaughtException,
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
} = require('internal/process/execution');
process._fatalException = fatalException;
// For legacy reasons this is still called `_fatalException`, even
// though it is now a global uncaught exception handler.
// The C++ land node::errors::TriggerUncaughtException grabs it
// from the process object because it has been monkey-patchable.
// TODO(joyeecheung): investigate whether process._fatalException
// can be deprecated.
process._fatalException = onGlobalUncaughtException;
process.setUncaughtExceptionCaptureCallback =
setUncaughtExceptionCaptureCallback;
process.hasUncaughtExceptionCaptureCallback =

View File

@ -42,7 +42,7 @@ const {
} = workerIo;
const {
fatalException: originalFatalException
onGlobalUncaughtException
} = require('internal/process/execution');
const publicWorker = require('worker_threads');
@ -156,24 +156,23 @@ port.on('message', (message) => {
}
});
// Overwrite fatalException
process._fatalException = (error, fromPromise) => {
debug(`[${threadId}] gets fatal exception`);
let caught = false;
function workerOnGlobalUncaughtException(error, fromPromise) {
debug(`[${threadId}] gets uncaught exception`);
let handled = false;
try {
caught = originalFatalException.call(this, error, fromPromise);
handled = onGlobalUncaughtException(error, fromPromise);
} catch (e) {
error = e;
}
debug(`[${threadId}] fatal exception caught = ${caught}`);
debug(`[${threadId}] uncaught exception handled = ${handled}`);
if (!caught) {
if (!handled) {
let serialized;
try {
const { serializeError } = require('internal/error-serdes');
serialized = serializeError(error);
} catch {}
debug(`[${threadId}] fatal exception serialized = ${!!serialized}`);
debug(`[${threadId}] uncaught exception serialized = ${!!serialized}`);
if (serialized)
port.postMessage({
type: ERROR_MESSAGE,
@ -187,7 +186,11 @@ process._fatalException = (error, fromPromise) => {
process.exit();
}
};
}
// Patch the global uncaught exception handler so it gets picked up by
// node::errors::TriggerUncaughtException().
process._fatalException = workerOnGlobalUncaughtException;
markBootstrapComplete();

View File

@ -832,7 +832,7 @@ Module.runMain = function() {
return loader.import(pathToFileURL(process.argv[1]).pathname);
})
.catch((e) => {
internalBinding('task_queue').triggerFatalException(
internalBinding('errors').triggerUncaughtException(
e,
true /* fromPromise */
);

View File

@ -115,10 +115,14 @@ function noop() {}
// and exported to be written to process._fatalException, it has to be
// returned as an *anonymous function* wrapped inside a factory function,
// otherwise it breaks the test-timers.setInterval async hooks test -
// this may indicate that node::FatalException should fix up the callback scope
// before calling into process._fatalException, or this function should
// take extra care of the async hooks before it schedules a setImmediate.
function createFatalException() {
// this may indicate that node::errors::TriggerUncaughtException() should
// fix up the callback scope before calling into process._fatalException,
// or this function should take extra care of the async hooks before it
// schedules a setImmediate.
function createOnGlobalUncaughtException() {
// The C++ land node::errors::TriggerUncaughtException() will
// exit the process if it returns false, and continue execution if it
// returns true (which indicates that the exception is handled by the user).
return (er, fromPromise) => {
// It's possible that defaultTriggerAsyncId was set for a constructor
// call that threw and was never cleared. So clear it now.
@ -206,7 +210,7 @@ module.exports = {
tryGetCwd,
evalModule,
evalScript,
fatalException: createFatalException(),
onGlobalUncaughtException: createOnGlobalUncaughtException(),
setUncaughtExceptionCaptureCallback,
hasUncaughtExceptionCaptureCallback
};

View File

@ -2,9 +2,6 @@
const { Object } = primordials;
const {
safeToString
} = internalBinding('util');
const {
tickInfo,
promiseRejectEvents: {
@ -13,10 +10,14 @@ const {
kPromiseResolveAfterResolved,
kPromiseRejectAfterResolved
},
setPromiseRejectCallback,
triggerFatalException
setPromiseRejectCallback
} = internalBinding('task_queue');
const {
noSideEffectsToString,
triggerUncaughtException
} = internalBinding('errors');
// *Must* match Environment::TickInfo::Fields in src/env.h.
const kHasRejectionToWarn = 1;
@ -127,7 +128,7 @@ function handledRejection(promise) {
const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning';
function emitUnhandledRejectionWarning(uid, reason) {
const warning = getError(
const warning = getErrorWithoutStack(
unhandledRejectionErrName,
'Unhandled promise rejection. This error originated either by ' +
'throwing inside of an async function without a catch block, ' +
@ -139,7 +140,8 @@ function emitUnhandledRejectionWarning(uid, reason) {
warning.stack = reason.stack;
process.emitWarning(reason.stack, unhandledRejectionErrName);
} else {
process.emitWarning(safeToString(reason), unhandledRejectionErrName);
process.emitWarning(
noSideEffectsToString(reason), unhandledRejectionErrName);
}
} catch {}
@ -179,7 +181,9 @@ function processPromiseRejections() {
const { reason, uid } = promiseInfo;
switch (unhandledRejectionsMode) {
case kThrowUnhandledRejections: {
fatalException(reason);
const err = reason instanceof Error ?
reason : generateUnhandledRejectionError(reason);
triggerUncaughtException(err, true /* fromPromise */);
const handled = process.emit('unhandledRejection', reason, promise);
if (!handled) emitUnhandledRejectionWarning(uid, reason);
break;
@ -209,7 +213,7 @@ function processPromiseRejections() {
pendingUnhandledRejections.length !== 0;
}
function getError(name, message) {
function getErrorWithoutStack(name, message) {
// Reset the stack to prevent any overhead.
const tmp = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
@ -225,21 +229,17 @@ function getError(name, message) {
return err;
}
function fatalException(reason) {
let err;
if (reason instanceof Error) {
err = reason;
} else {
err = getError(
'UnhandledPromiseRejection',
'This error originated either by ' +
'throwing inside of an async function without a catch block, ' +
'or by rejecting a promise which was not handled with .catch().' +
` The promise rejected with the reason "${safeToString(reason)}".`
);
err.code = 'ERR_UNHANDLED_REJECTION';
}
triggerFatalException(err, true /* fromPromise */);
function generateUnhandledRejectionError(reason) {
const message =
'This error originated either by ' +
'throwing inside of an async function without a catch block, ' +
'or by rejecting a promise which was not handled with .catch().' +
' The promise rejected with the reason ' +
`"${noSideEffectsToString(reason)}".`;
const err = getErrorWithoutStack('UnhandledPromiseRejection', message);
err.code = 'ERR_UNHANDLED_REJECTION';
return err;
}
function listenForRejections() {

View File

@ -9,10 +9,13 @@ const {
// Used to run V8's micro task queue.
runMicrotasks,
setTickCallback,
enqueueMicrotask,
triggerFatalException
enqueueMicrotask
} = internalBinding('task_queue');
const {
triggerUncaughtException
} = internalBinding('errors');
const {
setHasRejectionToWarn,
hasRejectionToWarn,
@ -143,11 +146,9 @@ function runMicrotask() {
try {
callback();
} catch (error) {
// TODO(devsnek): Remove this if
// https://bugs.chromium.org/p/v8/issues/detail?id=8326
// is resolved such that V8 triggers the fatal exception
// handler for microtasks.
triggerFatalException(error, false /* fromPromise */);
// runInAsyncScope() swallows the error so we need to catch
// it and handle it here.
triggerUncaughtException(error, false /* fromPromise */);
} finally {
this.emitDestroy();
}

View File

@ -244,17 +244,12 @@ Local<Value> WinapiErrnoException(Isolate* isolate,
}
#endif
// Implement the legacy name exposed in node.h. This has not been in fact
// fatal any more, as the user can handle the exception in the
// TryCatch by listening to `uncaughtException`.
// TODO(joyeecheung): deprecate it in favor of a more accurate name.
void FatalException(Isolate* isolate, const v8::TryCatch& try_catch) {
// If we try to print out a termination exception, we'd just get 'null',
// so just crashing here with that information seems like a better idea,
// and in particular it seems like we should handle terminations at the call
// site for this function rather than by printing them out somewhere.
CHECK(!try_catch.HasTerminated());
HandleScope scope(isolate);
if (!try_catch.IsVerbose()) {
FatalException(isolate, try_catch.Exception(), try_catch.Message());
}
errors::TriggerUncaughtException(isolate, try_catch);
}
} // namespace node

View File

@ -666,7 +666,7 @@ void Environment::RunAndClearNativeImmediates() {
ref_count++;
if (UNLIKELY(try_catch.HasCaught())) {
if (!try_catch.HasTerminated())
FatalException(isolate(), try_catch);
errors::TriggerUncaughtException(isolate(), try_catch);
// We are done with the current callback. Increase the counter so that
// the steps below make everything *after* the current item part of

View File

@ -49,7 +49,7 @@ bool JSStream::IsClosing() {
Local<Value> value;
if (!MakeCallback(env()->isclosing_string(), 0, nullptr).ToLocal(&value)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated())
FatalException(env()->isolate(), try_catch);
errors::TriggerUncaughtException(env()->isolate(), try_catch);
return true;
}
return value->IsTrue();
@ -65,7 +65,7 @@ int JSStream::ReadStart() {
if (!MakeCallback(env()->onreadstart_string(), 0, nullptr).ToLocal(&value) ||
!value->Int32Value(env()->context()).To(&value_int)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated())
FatalException(env()->isolate(), try_catch);
errors::TriggerUncaughtException(env()->isolate(), try_catch);
}
return value_int;
}
@ -80,7 +80,7 @@ int JSStream::ReadStop() {
if (!MakeCallback(env()->onreadstop_string(), 0, nullptr).ToLocal(&value) ||
!value->Int32Value(env()->context()).To(&value_int)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated())
FatalException(env()->isolate(), try_catch);
errors::TriggerUncaughtException(env()->isolate(), try_catch);
}
return value_int;
}
@ -102,7 +102,7 @@ int JSStream::DoShutdown(ShutdownWrap* req_wrap) {
argv).ToLocal(&value) ||
!value->Int32Value(env()->context()).To(&value_int)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated())
FatalException(env()->isolate(), try_catch);
errors::TriggerUncaughtException(env()->isolate(), try_catch);
}
return value_int;
}
@ -137,7 +137,7 @@ int JSStream::DoWrite(WriteWrap* w,
argv).ToLocal(&value) ||
!value->Int32Value(env()->context()).To(&value_int)) {
if (try_catch.HasCaught() && !try_catch.HasTerminated())
FatalException(env()->isolate(), try_catch);
errors::TriggerUncaughtException(env()->isolate(), try_catch);
}
return value_int;
}

View File

@ -132,7 +132,6 @@ using options_parser::kDisallowedInEnvironment;
using v8::Boolean;
using v8::EscapableHandleScope;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::HandleScope;
@ -218,10 +217,9 @@ MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
arguments->size(),
arguments->data());
// If there was an error during bootstrap then it was either handled by the
// FatalException handler or it's unrecoverable (e.g. max call stack
// exceeded). Either way, clear the stack so that the AsyncCallbackScope
// destructor doesn't fail on the id check.
// If there was an error during bootstrap, it must be unrecoverable
// (e.g. max call stack exceeded). Clear the stack so that the
// AsyncCallbackScope destructor doesn't fail on the id check.
// There are only two ways to have a stack size > 1: 1) the user manually
// called MakeCallback or 2) user awaited during bootstrap, which triggered
// _tickCallback().

View File

@ -115,7 +115,7 @@ static inline void trigger_fatal_exception(
napi_env env, v8::Local<v8::Value> local_err) {
v8::Local<v8::Message> local_msg =
v8::Exception::CreateMessage(env->isolate, local_err);
node::FatalException(env->isolate, local_err, local_msg);
node::errors::TriggerUncaughtException(env->isolate, local_err, local_msg);
}
class ThreadSafeFunction : public node::AsyncResource {

View File

@ -734,7 +734,7 @@ void ContextifyScript::New(const FunctionCallbackInfo<Value>& args) {
compile_options);
if (v8_script.IsEmpty()) {
DecorateErrorStack(env, try_catch);
errors::DecorateErrorStack(env, try_catch);
no_abort_scope.Close();
if (!try_catch.HasTerminated())
try_catch.ReThrow();
@ -946,7 +946,7 @@ bool ContextifyScript::EvalMachine(Environment* env,
if (try_catch.HasCaught()) {
if (!timed_out && !received_signal && display_errors) {
// We should decorate non-termination exceptions
DecorateErrorStack(env, try_catch);
errors::DecorateErrorStack(env, try_catch);
}
// If there was an exception thrown during script execution, re-throw it.
@ -1110,7 +1110,7 @@ void ContextifyContext::CompileFunction(
if (maybe_fn.IsEmpty()) {
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
DecorateErrorStack(env, try_catch);
errors::DecorateErrorStack(env, try_catch);
try_catch.ReThrow();
}
return;

View File

@ -343,10 +343,6 @@ void ReportException(Environment* env,
#endif
}
void ReportException(Environment* env, const v8::TryCatch& try_catch) {
ReportException(env, try_catch.Exception(), try_catch.Message());
}
void PrintErrorString(const char* format, ...) {
va_list ap;
va_start(ap, format);
@ -764,7 +760,7 @@ void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
break;
}
case Isolate::MessageErrorLevel::kMessageError:
FatalException(isolate, error, message);
TriggerUncaughtException(isolate, error, message);
break;
}
}
@ -775,6 +771,27 @@ void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) {
env->set_prepare_stack_trace_callback(args[0].As<Function>());
}
// Side effect-free stringification that will never throw exceptions.
static void NoSideEffectsToString(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = args.GetIsolate()->GetCurrentContext();
Local<String> detail_string;
if (args[0]->ToDetailString(context).ToLocal(&detail_string))
args.GetReturnValue().Set(detail_string);
}
static void TriggerUncaughtException(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Environment* env = Environment::GetCurrent(isolate);
Local<Value> exception = args[0];
Local<Message> message = Exception::CreateMessage(isolate, exception);
if (env != nullptr && env->abort_on_uncaught_exception()) {
ReportException(env, exception, message);
Abort();
}
bool from_promise = args[1]->IsTrue();
errors::TriggerUncaughtException(isolate, exception, message, from_promise);
}
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
@ -782,10 +799,11 @@ void Initialize(Local<Object> target,
Environment* env = Environment::GetCurrent(context);
env->SetMethod(
target, "setPrepareStackTraceCallback", SetPrepareStackTraceCallback);
env->SetMethodNoSideEffect(
target, "noSideEffectsToString", NoSideEffectsToString);
env->SetMethod(target, "triggerUncaughtException", TriggerUncaughtException);
}
} // namespace errors
void DecorateErrorStack(Environment* env,
const errors::TryCatchScope& try_catch) {
Local<Value> exception = try_catch.Exception();
@ -822,10 +840,10 @@ void DecorateErrorStack(Environment* env,
env->context(), env->decorated_private_symbol(), True(env->isolate()));
}
void FatalException(Isolate* isolate,
Local<Value> error,
Local<Message> message,
bool from_promise) {
void TriggerUncaughtException(Isolate* isolate,
Local<Value> error,
Local<Message> message,
bool from_promise) {
CHECK(!error.IsEmpty());
HandleScope scope(isolate);
@ -843,59 +861,95 @@ void FatalException(Isolate* isolate,
Abort();
}
// Invoke process._fatalException() to give user a chance to handle it.
// We have to grab it from the process object since this has been
// monkey-patchable.
Local<Object> process_object = env->process_object();
Local<String> fatal_exception_string = env->fatal_exception_string();
Local<Value> fatal_exception_function =
process_object->Get(env->context(),
fatal_exception_string).ToLocalChecked();
// If the exception happens before process._fatalException is attached
// during bootstrap, or if the user has patched it incorrectly, exit
// the current Node.js instance.
if (!fatal_exception_function->IsFunction()) {
// Failed before the process._fatalException function was added!
// this is probably pretty bad. Nothing to do but report and exit.
ReportException(env, error, message);
env->Exit(6);
} else {
errors::TryCatchScope fatal_try_catch(env);
// Do not call FatalException when _fatalException handler throws
fatal_try_catch.SetVerbose(false);
return;
}
MaybeLocal<Value> handled;
{
// We do not expect the global uncaught exception itself to throw any more
// exceptions. If it does, exit the current Node.js instance.
errors::TryCatchScope try_catch(env,
errors::TryCatchScope::CatchMode::kFatal);
// Explicitly disable verbose exception reporting -
// if process._fatalException() throws an error, we don't want it to
// trigger the per-isolate message listener which will call this
// function and recurse.
try_catch.SetVerbose(false);
Local<Value> argv[2] = { error,
Boolean::New(env->isolate(), from_promise) };
// This will return true if the JS layer handled it, false otherwise
MaybeLocal<Value> caught = fatal_exception_function.As<Function>()->Call(
handled = fatal_exception_function.As<Function>()->Call(
env->context(), process_object, arraysize(argv), argv);
}
if (fatal_try_catch.HasTerminated()) return;
// If process._fatalException() throws, we are now exiting the Node.js
// instance so return to continue the exit routine.
// TODO(joyeecheung): return a Maybe here to prevent the caller from
// stepping on the exit.
if (handled.IsEmpty()) {
return;
}
if (fatal_try_catch.HasCaught()) {
// The fatal exception function threw, so we must exit
ReportException(env, fatal_try_catch);
env->Exit(7);
// The global uncaught exception handler returns true if the user handles it
// by e.g. listening to `uncaughtException`. In that case, continue program
// execution.
// TODO(joyeecheung): This has been only checking that the return value is
// exactly false. Investigate whether this can be turned to an "if true"
// similar to how the worker global uncaught exception handler handles it.
if (!handled.ToLocalChecked()->IsFalse()) {
return;
}
} else if (caught.ToLocalChecked()->IsFalse()) {
ReportException(env, error, message);
// fatal_exception_function call before may have set a new exit code ->
// read it again, otherwise use default for uncaughtException 1
Local<String> exit_code = env->exit_code_string();
Local<Value> code;
if (!process_object->Get(env->context(), exit_code).ToLocal(&code) ||
!code->IsInt32()) {
env->Exit(1);
}
env->Exit(code.As<Int32>()->Value());
}
ReportException(env, error, message);
// If the global uncaught exception handler sets process.exitCode,
// exit with that code. Otherwise, exit with 1.
Local<String> exit_code = env->exit_code_string();
Local<Value> code;
if (process_object->Get(env->context(), exit_code).ToLocal(&code) &&
code->IsInt32()) {
env->Exit(code.As<Int32>()->Value());
} else {
env->Exit(1);
}
}
void FatalException(Isolate* isolate,
Local<Value> error,
Local<Message> message) {
FatalException(isolate, error, message, false /* from_promise */);
void TriggerUncaughtException(Isolate* isolate, const v8::TryCatch& try_catch) {
// If the try_catch is verbose, the per-isolate message listener is going to
// handle it (which is going to call into another overload of
// TriggerUncaughtException()).
if (try_catch.IsVerbose()) {
return;
}
// If the user calls TryCatch::TerminateExecution() on this TryCatch
// they must call CancelTerminateExecution() again before invoking
// TriggerUncaughtException() because it will invoke
// process._fatalException() in the JS land.
CHECK(!try_catch.HasTerminated());
CHECK(try_catch.HasCaught());
HandleScope scope(isolate);
TriggerUncaughtException(isolate,
try_catch.Exception(),
try_catch.Message(),
false /* from_promise */);
}
} // namespace errors
} // namespace node
NODE_MODULE_CONTEXT_AWARE_INTERNAL(errors, node::errors::Initialize)

View File

@ -29,21 +29,6 @@ void OnFatalError(const char* location, const char* message);
void PrintErrorString(const char* format, ...);
void ReportException(Environment* env, const v8::TryCatch& try_catch);
void ReportException(Environment* env,
Local<Value> er,
Local<Message> message);
void FatalException(v8::Isolate* isolate,
Local<Value> error,
Local<Message> message);
void FatalException(v8::Isolate* isolate,
Local<Value> error,
Local<Message> message,
bool from_promise);
// Helpers to construct errors similar to the ones provided by
// lib/internal/errors.js.
// Example: with `V(ERR_INVALID_ARG_TYPE, TypeError)`, there will be
@ -190,14 +175,24 @@ class TryCatchScope : public v8::TryCatch {
CatchMode mode_;
};
// Trigger the global uncaught exception handler `process._fatalException`
// in JS land (which emits the 'uncaughtException' event). If that returns
// true, continue program execution, otherwise exit the process.
void TriggerUncaughtException(v8::Isolate* isolate,
const v8::TryCatch& try_catch);
void TriggerUncaughtException(v8::Isolate* isolate,
Local<Value> error,
Local<Message> message,
bool from_promise = false);
const char* errno_string(int errorno);
void PerIsolateMessageListener(v8::Local<v8::Message> message,
v8::Local<v8::Value> error);
} // namespace errors
void DecorateErrorStack(Environment* env,
const errors::TryCatchScope& try_catch);
} // namespace errors
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -12,7 +12,6 @@ namespace node {
using v8::Array;
using v8::Context;
using v8::Exception;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
@ -123,19 +122,6 @@ static void SetPromiseRejectCallback(
env->set_promise_reject_callback(args[0].As<Function>());
}
static void TriggerFatalException(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Environment* env = Environment::GetCurrent(isolate);
Local<Value> exception = args[0];
Local<Message> message = Exception::CreateMessage(isolate, exception);
if (env != nullptr && env->abort_on_uncaught_exception()) {
ReportException(env, exception, message);
Abort();
}
bool from_promise = args[1]->IsTrue();
FatalException(isolate, exception, message, from_promise);
}
static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
@ -143,7 +129,6 @@ static void Initialize(Local<Object> target,
Environment* env = Environment::GetCurrent(context);
Isolate* isolate = env->isolate();
env->SetMethod(target, "triggerFatalException", TriggerFatalException);
env->SetMethod(target, "enqueueMicrotask", EnqueueMicrotask);
env->SetMethod(target, "setTickCallback", SetTickCallback);
env->SetMethod(target, "runMicrotasks", RunMicrotasks);

View File

@ -123,14 +123,6 @@ static void PreviewEntries(const FunctionCallbackInfo<Value>& args) {
Array::New(env->isolate(), ret, arraysize(ret)));
}
// Side effect-free stringification that will never throw exceptions.
static void SafeToString(const FunctionCallbackInfo<Value>& args) {
Local<Context> context = args.GetIsolate()->GetCurrentContext();
Local<String> detail_string;
if (args[0]->ToDetailString(context).ToLocal(&detail_string))
args.GetReturnValue().Set(detail_string);
}
inline Local<Private> IndexToPrivateSymbol(Environment* env, uint32_t index) {
#define V(name, _) &Environment::name,
static Local<Private> (Environment::*const methods[])() const = {
@ -270,7 +262,6 @@ void Initialize(Local<Object> target,
env->SetMethod(target, "setHiddenValue", SetHiddenValue);
env->SetMethodNoSideEffect(target, "getPromiseDetails", GetPromiseDetails);
env->SetMethodNoSideEffect(target, "getProxyDetails", GetProxyDetails);
env->SetMethodNoSideEffect(target, "safeToString", SafeToString);
env->SetMethodNoSideEffect(target, "previewEntries", PreviewEntries);
env->SetMethodNoSideEffect(target, "getOwnNonIndexProperties",
GetOwnNonIndexProperties);