Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: fix backtrace with [[noreturn]] abort #50849

Merged
merged 4 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ Maybe<bool> InitializeContextRuntime(Local<Context> context) {
}
} else if (per_process::cli_options->disable_proto != "") {
// Validated in ProcessGlobalArgs
OnFatalError("InitializeContextRuntime()", "invalid --disable-proto mode");
UNREACHABLE("invalid --disable-proto mode");
}

return Just(true);
Expand Down
6 changes: 1 addition & 5 deletions src/inspector_agent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@
namespace node {
namespace inspector {
namespace {

using node::OnFatalError;

using v8::Context;
using v8::Function;
using v8::HandleScope;
Expand Down Expand Up @@ -917,8 +914,7 @@ void Agent::ToggleAsyncHook(Isolate* isolate, Local<Function> fn) {
USE(fn->Call(context, Undefined(isolate), 0, nullptr));
if (try_catch.HasCaught() && !try_catch.HasTerminated()) {
PrintCaughtException(isolate, context, try_catch);
OnFatalError("\nnode::inspector::Agent::ToggleAsyncHook",
"Cannot toggle Inspector's AsyncHook, please report this.");
UNREACHABLE("Cannot toggle Inspector's AsyncHook, please report this.");
}
}

Expand Down
20 changes: 6 additions & 14 deletions src/node_errors.cc
Original file line number Diff line number Diff line change
Expand Up @@ -376,14 +376,7 @@ void AppendExceptionLine(Environment* env,
.FromMaybe(false));
}

[[noreturn]] void Abort() {
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
DumpNativeBacktrace(stderr);
DumpJavaScriptBacktrace(stderr);
fflush(stderr);
ABORT_NO_BACKTRACE();
}

[[noreturn]] void Assert(const AssertionInfo& info) {
void Assert(const AssertionInfo& info) {
std::string name = GetHumanReadableProcessName();

fprintf(stderr,
Expand All @@ -396,15 +389,15 @@ void AppendExceptionLine(Environment* env,
info.message);

fflush(stderr);
Abort();
ABORT();
}

enum class EnhanceFatalException { kEnhance, kDontEnhance };

/**
* Report the exception to the inspector, then print it to stderr.
* This should only be used when the Node.js instance is about to exit
* (i.e. this should be followed by a env->Exit() or an Abort()).
* (i.e. this should be followed by a env->Exit() or an ABORT()).
*
* Use enhance_stack = EnhanceFatalException::kDontEnhance
* when it's unsafe to call into JavaScript.
Expand Down Expand Up @@ -576,8 +569,7 @@ static void ReportFatalException(Environment* env,
ABORT();
}

[[noreturn]] void OOMErrorHandler(const char* location,
const v8::OOMDetails& details) {
void OOMErrorHandler(const char* location, const v8::OOMDetails& details) {
// We should never recover from this handler so once it's true it's always
// true.
is_in_oom.store(true);
Expand Down Expand Up @@ -1063,7 +1055,7 @@ static void TriggerUncaughtException(const FunctionCallbackInfo<Value>& args) {
if (env != nullptr && env->abort_on_uncaught_exception()) {
ReportFatalException(
env, exception, message, EnhanceFatalException::kEnhance);
Abort();
ABORT();
}
bool from_promise = args[1]->IsTrue();
errors::TriggerUncaughtException(isolate, exception, message, from_promise);
Expand Down Expand Up @@ -1174,7 +1166,7 @@ void TriggerUncaughtException(Isolate* isolate,
// much we can do, so we just print whatever is useful and crash.
PrintToStderrAndFlush(
FormatCaughtException(isolate, context, error, message));
Abort();
ABORT();
}

// Invoke process._fatalException() to give user a chance to handle it.
Expand Down
9 changes: 7 additions & 2 deletions src/node_errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@ void AppendExceptionLine(Environment* env,
v8::Local<v8::Message> message,
enum ErrorHandlingMode mode);

// This function calls backtrace, it should have not be marked as [[noreturn]].
// But it is a public API, removing the attribute can break.
// Prefer UNREACHABLE() internally instead, it doesn't need manually set
// location.
[[noreturn]] void OnFatalError(const char* location, const char* message);
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
[[noreturn]] void OOMErrorHandler(const char* location,
const v8::OOMDetails& details);
// This function calls backtrace, do not mark as [[noreturn]]. Read more in the
// ABORT macro.
void OOMErrorHandler(const char* location, const v8::OOMDetails& details);

// Helpers to construct errors similar to the ones provided by
// lib/internal/errors.js.
Expand Down
2 changes: 1 addition & 1 deletion src/node_process_methods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Mutex umask_mutex;
#define NANOS_PER_SEC 1000000000

static void Abort(const FunctionCallbackInfo<Value>& args) {
Abort();
ABORT();
}

// For internal testing only, not exposed to userland.
Expand Down
2 changes: 1 addition & 1 deletion src/node_watchdog.cc
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Watchdog::Watchdog(v8::Isolate* isolate, uint64_t ms, bool* timed_out)
int rc;
rc = uv_loop_init(&loop_);
if (rc != 0) {
OnFatalError("node::Watchdog::Watchdog()", "Failed to initialize uv loop.");
UNREACHABLE("Failed to initialize uv loop.");
}

rc = uv_async_init(&loop_, &async_, [](uv_async_t* signal) {
Expand Down
37 changes: 26 additions & 11 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,9 @@ struct AssertionInfo {
const char* message;
const char* function;
};
[[noreturn]] void NODE_EXTERN_PRIVATE Assert(const AssertionInfo& info);
[[noreturn]] void NODE_EXTERN_PRIVATE Abort();

// This indirectly calls backtrace so it can not be marked as [[noreturn]].
void NODE_EXTERN_PRIVATE Assert(const AssertionInfo& info);
void DumpNativeBacktrace(FILE* fp);
void DumpJavaScriptBacktrace(FILE* fp);

Expand All @@ -125,16 +126,30 @@ void DumpJavaScriptBacktrace(FILE* fp);
#define ABORT_NO_BACKTRACE() abort()
#endif

#define ABORT() node::Abort()
// A function calls a [[noreturn]] function will print an incorrect
// call stack because the frame pc was advanced to an invalid op at the call
// site, which may vary on different platforms.
legendecas marked this conversation as resolved.
Show resolved Hide resolved
// `ABORT` must be a macro and not a [[noreturn]] function to make sure the
// backtrace is correct.
#define ABORT() \
legendecas marked this conversation as resolved.
Show resolved Hide resolved
do { \
node::DumpNativeBacktrace(stderr); \
node::DumpJavaScriptBacktrace(stderr); \
fflush(stderr); \
ABORT_NO_BACKTRACE(); \
} while (0)

#define ERROR_AND_ABORT(expr) \
do { \
/* Make sure that this struct does not end up in inline code, but */ \
/* rather in a read-only data section when modifying this code. */ \
static const node::AssertionInfo args = { \
__FILE__ ":" STRINGIFY(__LINE__), #expr, PRETTY_FUNCTION_NAME \
}; \
node::Assert(args); \
#define ERROR_AND_ABORT(expr) \
do { \
/* Make sure that this struct does not end up in inline code, but */ \
/* rather in a read-only data section when modifying this code. */ \
static const node::AssertionInfo args = { \
__FILE__ ":" STRINGIFY(__LINE__), #expr, PRETTY_FUNCTION_NAME}; \
node::Assert(args); \
/* `node::Assert` doesn't return. Add an [[noreturn]] abort() here to */ \
/* make the compiler happy about no return value in the caller */ \
/* function when calling ERROR_AND_ABORT. */ \
ABORT_NO_BACKTRACE(); \
} while (0)

#ifdef __GNUC__
Expand Down
Loading