diff --git a/node.gyp b/node.gyp index 43c0de6f8504a8..51fe580af7d33c 100644 --- a/node.gyp +++ b/node.gyp @@ -466,6 +466,7 @@ [ 'OS=="win"', { 'sources': [ + 'src/backtrace_win32.cc', 'src/res/node.rc', ], 'defines!': [ @@ -480,6 +481,7 @@ 'libraries': [ '-lpsapi.lib' ] }, { # POSIX 'defines': [ '__POSIX__' ], + 'sources': [ 'src/backtrace_posix.cc' ], }], [ 'OS=="mac"', { # linking Corefoundation is needed since certain OSX debugging tools diff --git a/src/backtrace_posix.cc b/src/backtrace_posix.cc new file mode 100644 index 00000000000000..468d9784528b0f --- /dev/null +++ b/src/backtrace_posix.cc @@ -0,0 +1,50 @@ +#include "node.h" + +#if defined(__linux__) +#include +#endif + +#if defined(__linux__) && !defined(__GLIBC__) +#define HAVE_EXECINFO_H 0 +#else +#define HAVE_EXECINFO_H 1 +#endif + +#if HAVE_EXECINFO_H +#include +#include +#include +#include +#endif + +namespace node { + +void DumpBacktrace(FILE* fp) { +#if HAVE_EXECINFO_H + void* frames[256]; + const int size = backtrace(frames, arraysize(frames)); + if (size <= 0) { + return; + } + for (int i = 1; i < size; i += 1) { + void* frame = frames[i]; + fprintf(fp, "%2d: ", i); + Dl_info info; + const bool have_info = dladdr(frame, &info); + if (!have_info || info.dli_sname == nullptr) { + fprintf(fp, "%p", frame); + } else if (char* demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, 0)) { + fprintf(fp, "%s", demangled); + free(demangled); + } else { + fprintf(fp, "%s", info.dli_sname); + } + if (have_info && info.dli_fname != nullptr) { + fprintf(fp, " [%s]", info.dli_fname); + } + fprintf(fp, "\n"); + } +#endif // HAVE_EXECINFO_H +} + +} // namespace node diff --git a/src/backtrace_win32.cc b/src/backtrace_win32.cc new file mode 100644 index 00000000000000..71610fd663bb3c --- /dev/null +++ b/src/backtrace_win32.cc @@ -0,0 +1,8 @@ +#include "node.h" + +namespace node { + +void DumpBacktrace(FILE* fp) { +} + +} // namespace node diff --git a/src/node.cc b/src/node.cc index eaf642b76a9975..511a4a25fd6dd9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1734,8 +1734,40 @@ void GetActiveHandles(const FunctionCallbackInfo& args) { } +NO_RETURN void Abort() { + DumpBacktrace(stderr); + fflush(stderr); + ABORT_NO_BACKTRACE(); +} + + +NO_RETURN void Assert(const char* const (*args)[4]) { + auto filename = (*args)[0]; + auto linenum = (*args)[1]; + auto message = (*args)[2]; + auto function = (*args)[3]; + + char exepath[256]; + size_t exepath_size = sizeof(exepath); + if (uv_exepath(exepath, &exepath_size)) + snprintf(exepath, sizeof(exepath), "node"); + + char pid[12] = {0}; +#ifndef _WIN32 + snprintf(pid, sizeof(pid), "[%u]", getpid()); +#endif + + fprintf(stderr, "%s%s: %s:%s:%s%s Assertion `%s' failed.\n", + exepath, pid, filename, linenum, + function, *function ? ":" : "", message); + fflush(stderr); + + Abort(); +} + + static void Abort(const FunctionCallbackInfo& args) { - ABORT(); + Abort(); } diff --git a/src/node_internals.h b/src/node_internals.h index d215feb53731fb..3f2c0f02cc58cb 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -127,12 +127,10 @@ constexpr size_t arraysize(const T(&)[N]) { return N; } # define ROUND_UP(a, b) ((a) % (b) ? ((a) + (b)) - ((a) % (b)) : (a)) #endif -#if defined(__GNUC__) && __GNUC__ >= 4 +#ifdef __GNUC__ # define MUST_USE_RESULT __attribute__((warn_unused_result)) -# define NO_RETURN __attribute__((noreturn)) #else # define MUST_USE_RESULT -# define NO_RETURN #endif bool IsExceptionDecorated(Environment* env, v8::Local er); diff --git a/src/util.h b/src/util.h index 1570c57f195c66..e57651c3a6981d 100644 --- a/src/util.h +++ b/src/util.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #ifdef __APPLE__ @@ -18,6 +19,18 @@ namespace node { +#ifdef __GNUC__ +#define NO_RETURN __attribute__((noreturn)) +#else +#define NO_RETURN +#endif + +// The slightly odd function signature for Assert() is to ease +// instruction cache pressure in calls from ASSERT and CHECK. +NO_RETURN void Abort(); +NO_RETURN void Assert(const char* const (*args)[4]); +void DumpBacktrace(FILE* fp); + #ifdef __APPLE__ template using remove_reference = std::tr1::remove_reference; #else @@ -35,20 +48,47 @@ template using remove_reference = std::remove_reference; // Windows 8+ does not like abort() in Release mode #ifdef _WIN32 -#define ABORT() raise(SIGABRT) +#define ABORT_NO_BACKTRACE() raise(SIGABRT) +#else +#define ABORT_NO_BACKTRACE() abort() +#endif + +#define ABORT() node::Abort() + +#ifdef __GNUC__ +#define LIKELY(expr) __builtin_expect(!!(expr), 1) +#define UNLIKELY(expr) __builtin_expect(!!(expr), 0) +#define PRETTY_FUNCTION_NAME __PRETTY_FUNCTION__ #else -#define ABORT() abort() +#define LIKELY(expr) expr +#define UNLIKELY(expr) expr +#define PRETTY_FUNCTION_NAME "" #endif -#if defined(NDEBUG) -# define ASSERT(expression) -# define CHECK(expression) \ +#define STRINGIFY_(x) #x +#define STRINGIFY(x) STRINGIFY_(x) + +#define CHECK(expr) \ do { \ - if (!(expression)) ABORT(); \ + if (UNLIKELY(!(expr))) { \ + static const char* const args[] = { __FILE__, STRINGIFY(__LINE__), \ + #expr, PRETTY_FUNCTION_NAME }; \ + node::Assert(&args); \ + } \ } while (0) + +// FIXME(bnoordhuis) cctests don't link in node::Abort() and node::Assert(). +#ifdef GTEST_DONT_DEFINE_ASSERT_EQ +#undef ABORT +#undef CHECK +#define ABORT ABORT_NO_BACKTRACE +#define CHECK assert +#endif + +#ifdef NDEBUG +#define ASSERT(expr) #else -# define ASSERT(expression) assert(expression) -# define CHECK(expression) assert(expression) +#define ASSERT(expr) CHECK(expr) #endif #define ASSERT_EQ(a, b) ASSERT((a) == (b)) diff --git a/test/abort/test-abort-backtrace.js b/test/abort/test-abort-backtrace.js new file mode 100644 index 00000000000000..7f72ee8e71c6a5 --- /dev/null +++ b/test/abort/test-abort-backtrace.js @@ -0,0 +1,24 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const cp = require('child_process'); + +if (common.isWindows) { + common.skip('Backtraces unimplemented on Windows.'); + return; +} + +if (process.argv[2] === 'child') { + process.abort(); +} else { + const child = cp.spawnSync(`${process.execPath}`, [`${__filename}`, 'child']); + const frames = + child.stderr.toString().trimRight().split('\n').map((s) => s.trim()); + + assert.strictEqual(child.stdout.toString(), ''); + assert.ok(frames.length > 0); + // All frames should start with a frame number. + assert.ok(frames.every((frame, index) => frame.startsWith(`${index + 1}:`))); + // At least some of the frames should include the binary name. + assert.ok(frames.some((frame) => frame.includes(`[${process.execPath}]`))); +} diff --git a/test/abort/test-abort-uncaught-exception.js b/test/abort/test-abort-uncaught-exception.js index 0d9fa6884df9dc..f9847193769d2d 100644 --- a/test/abort/test-abort-uncaught-exception.js +++ b/test/abort/test-abort-uncaught-exception.js @@ -9,23 +9,26 @@ if (process.argv[2] === 'child') { throw new Error('child error'); } else { run('', null); - run('--abort-on-uncaught-exception', 'SIGABRT'); + run('--abort-on-uncaught-exception', ['SIGABRT', 'SIGILL']); } -function run(flags, signal) { +function run(flags, signals) { const args = [__filename, 'child']; if (flags) args.unshift(flags); const child = spawn(node, args); child.on('exit', common.mustCall(function(code, sig) { - if (!common.isWindows) { - assert.strictEqual(sig, signal); - } else { - if (signal) + if (common.isWindows) { + if (signals) assert.strictEqual(code, 3); else assert.strictEqual(code, 1); + } else { + if (signals) + assert.strictEqual(signals.includes(sig), true); + else + assert.strictEqual(sig, null); } })); } diff --git a/test/abort/testcfg.py b/test/abort/testcfg.py new file mode 100644 index 00000000000000..5e441845681c52 --- /dev/null +++ b/test/abort/testcfg.py @@ -0,0 +1,6 @@ +import sys, os +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) +import testpy + +def GetConfiguration(context, root): + return testpy.SimpleTestConfiguration(context, root, 'abort') diff --git a/test/addons/buffer-free-callback/binding.cc b/test/addons/buffer-free-callback/binding.cc index 7bfd5e061d8b0c..a72386fdd627ab 100644 --- a/test/addons/buffer-free-callback/binding.cc +++ b/test/addons/buffer-free-callback/binding.cc @@ -1,8 +1,9 @@ #include #include -#include #include +#include + static int alive; static char buf[1024]; @@ -32,7 +33,7 @@ void Check(const v8::FunctionCallbackInfo& args) { v8::Isolate* isolate = args.GetIsolate(); isolate->RequestGarbageCollectionForTesting( v8::Isolate::kFullGarbageCollection); - CHECK_GT(alive, 0); + assert(alive > 0); } void init(v8::Local target) { diff --git a/test/addons/buffer-free-callback/binding.gyp b/test/addons/buffer-free-callback/binding.gyp index 50d094746c474e..7ede63d94a0d77 100644 --- a/test/addons/buffer-free-callback/binding.gyp +++ b/test/addons/buffer-free-callback/binding.gyp @@ -2,10 +2,7 @@ 'targets': [ { 'target_name': 'binding', - 'defines': [ - 'NODE_WANT_INTERNALS=1', - 'V8_DEPRECATION_WARNINGS=1', - ], + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], 'sources': [ 'binding.cc' ] } ] diff --git a/test/addons/make-callback-recurse/binding.cc b/test/addons/make-callback-recurse/binding.cc index 1195dbe2ff7e4c..3e3a1464930477 100644 --- a/test/addons/make-callback-recurse/binding.cc +++ b/test/addons/make-callback-recurse/binding.cc @@ -1,7 +1,7 @@ #include "node.h" #include "v8.h" -#include "../../../src/util.h" +#include using v8::Function; using v8::FunctionCallbackInfo; @@ -13,8 +13,8 @@ using v8::Value; namespace { void MakeCallback(const FunctionCallbackInfo& args) { - CHECK(args[0]->IsObject()); - CHECK(args[1]->IsFunction()); + assert(args[0]->IsObject()); + assert(args[1]->IsFunction()); Isolate* isolate = args.GetIsolate(); Local recv = args[0].As(); Local method = args[1].As(); diff --git a/test/addons/make-callback-recurse/binding.gyp b/test/addons/make-callback-recurse/binding.gyp index 50d094746c474e..7ede63d94a0d77 100644 --- a/test/addons/make-callback-recurse/binding.gyp +++ b/test/addons/make-callback-recurse/binding.gyp @@ -2,10 +2,7 @@ 'targets': [ { 'target_name': 'binding', - 'defines': [ - 'NODE_WANT_INTERNALS=1', - 'V8_DEPRECATION_WARNINGS=1', - ], + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], 'sources': [ 'binding.cc' ] } ] diff --git a/test/addons/make-callback/binding.cc b/test/addons/make-callback/binding.cc index a1adb997bbf9b9..8970e9fb1af76c 100644 --- a/test/addons/make-callback/binding.cc +++ b/test/addons/make-callback/binding.cc @@ -1,15 +1,14 @@ #include "node.h" #include "v8.h" -#include "../../../src/util.h" - +#include #include namespace { void MakeCallback(const v8::FunctionCallbackInfo& args) { - CHECK(args[0]->IsObject()); - CHECK(args[1]->IsFunction() || args[1]->IsString()); + assert(args[0]->IsObject()); + assert(args[1]->IsFunction() || args[1]->IsString()); auto isolate = args.GetIsolate(); auto recv = args[0].As(); std::vector> argv; @@ -26,7 +25,7 @@ void MakeCallback(const v8::FunctionCallbackInfo& args) { result = node::MakeCallback(isolate, recv, method, argv.size(), argv.data()); } else { - UNREACHABLE(); + assert(0 && "unreachable"); } args.GetReturnValue().Set(result); } diff --git a/test/addons/make-callback/binding.gyp b/test/addons/make-callback/binding.gyp index 50d094746c474e..7ede63d94a0d77 100644 --- a/test/addons/make-callback/binding.gyp +++ b/test/addons/make-callback/binding.gyp @@ -2,10 +2,7 @@ 'targets': [ { 'target_name': 'binding', - 'defines': [ - 'NODE_WANT_INTERNALS=1', - 'V8_DEPRECATION_WARNINGS=1', - ], + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], 'sources': [ 'binding.cc' ] } ] diff --git a/test/addons/null-buffer-neuter/binding.cc b/test/addons/null-buffer-neuter/binding.cc index da75919011f58b..532e22883383dd 100644 --- a/test/addons/null-buffer-neuter/binding.cc +++ b/test/addons/null-buffer-neuter/binding.cc @@ -1,12 +1,13 @@ #include #include -#include #include +#include + static int alive; static void FreeCallback(char* data, void* hint) { - CHECK_EQ(data, nullptr); + assert(data == nullptr); alive--; } @@ -24,13 +25,13 @@ void Run(const v8::FunctionCallbackInfo& args) { nullptr).ToLocalChecked(); char* data = node::Buffer::Data(buf); - CHECK_EQ(data, nullptr); + assert(data == nullptr); } isolate->RequestGarbageCollectionForTesting( v8::Isolate::kFullGarbageCollection); - CHECK_EQ(alive, 0); + assert(alive == 0); } void init(v8::Local target) { diff --git a/test/addons/null-buffer-neuter/binding.gyp b/test/addons/null-buffer-neuter/binding.gyp index 50d094746c474e..7ede63d94a0d77 100644 --- a/test/addons/null-buffer-neuter/binding.gyp +++ b/test/addons/null-buffer-neuter/binding.gyp @@ -2,10 +2,7 @@ 'targets': [ { 'target_name': 'binding', - 'defines': [ - 'NODE_WANT_INTERNALS=1', - 'V8_DEPRECATION_WARNINGS=1', - ], + 'defines': [ 'V8_DEPRECATION_WARNINGS=1' ], 'sources': [ 'binding.cc' ] } ]