mirror of
https://github.com/nodejs/node.git
synced 2024-12-01 07:53:06 +01:00
30950864d3
This patch: - Introduce an internal GetCurrentStackTrace() utility to get the current JavaScript stack trace with best effort. - Indent the assertion message so that is separated from the native stack trace for redability - Print the JS stack trace when it's available Previoiusly the abort message looks like this: ``` out/Release/node[24458]: ../../src/node_file.cc:2008:void node::fs::Ope n(const FunctionCallbackInfo<v8::Value> &): Assertion `(argc) >= (3)' f ailed. 1: 0x1043fb9a4 node::Abort() [node] 2: 0x1043fb6e4 node::PrintCaughtException(v8::Isolate*, v8::Local<v8:: Context>, v8::TryCatch const&) [node] 3: 0x104407708 node::fs::Open(v8::FunctionCallbackInfo<v8::Value> cons t&) [node] 4: 0x104611e74 v8::internal::MaybeHandle<v8::internal::Object> v8::int ernal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal:: Isolate*, v8::internal::Handle<v8::internal::HeapObject >, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::intern al::Handle<v8::internal::Object>, unsigned long*, int) [node ] 5: 0x1046116c8 v8::internal::Builtin_HandleApiCall(int, unsigned long* , v8::internal::Isolate*) [node] 6: 0x104e9cb24 Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit [node] 7: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 8: 0x104e1250c Builtins_JSEntryTrampoline [node] 9: 0x104e121f4 Builtins_JSEntry [node] 10: 0x1046ed54c v8::internal::(anonymous namespace)::Invoke(v8::interna l::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [node] 11: 0x1046edb60 v8::internal::Execution::CallScript(v8::internal::Isola te*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Hand le<v8::internal::Object>, v8::internal::Handle<v8::in ternal::Object>) [node] 12: 0x1045a9fa0 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::D ata>) [node] 13: 0x1043efb68 node::contextify::ContextifyScript::EvalMachine(v8::Loc al<v8::Context>, node::Environment*, long long, bool, bool, bool, v8::M icrotaskQueue*, v8::FunctionCallbackInfo<v8::Value> const&) [node ] 14: 0x1043ef3e0 node::contextify::ContextifyScript::RunInContext(v8::Fu nctionCallbackInfo<v8::Value> const&) [node] 15: 0x104611e74 v8::internal::MaybeHandle<v8::internal::Object> v8::int ernal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal:: Isolate*, v8::internal::Handle<v8::internal::HeapObject> , v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::interna l::Handle<v8::internal::Object>, unsigned long*, int) [node ] 16: 0x1046116c8 v8::internal::Builtin_HandleApiCall(int, unsigned long* , v8::internal::Isolate*) [node] 17: 0x104e9cb24 Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit [node] 18: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 19: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 20: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 21: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 22: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 23: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 24: 0x104e143e4 Builtins_InterpreterEntryTrampoline [node] 25: 0x104e1250c Builtins_JSEntryTrampoline [node] 26: 0x104e121f4 Builtins_JSEntry [node] 27: 0x1046ed54c v8::internal::(anonymous namespace)::Invoke(v8::interna l::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [node] 28: 0x1046ecdc8 v8::internal::Execution::Call(v8::internal::Isolate*, v 8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::int ernal::Object>, int, v8::internal::Handle<v 8::internal::Object>*) [node] 29: 0x1045be23c v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8 ::Value>, int, v8::Local<v8::Value>*) [node] 30: 0x1043df704 node::builtins::BuiltinLoader::CompileAndCall(v8::Local <v8::Context>, char const*, node::Realm*) [node] 31: 0x10446f2d4 node::Realm::ExecuteBootstrapper(char const*) [node] 32: 0x1043c3378 node::StartExecution(node::Environment*, std::__1::func tion<v8::MaybeLocal<v8::Value> (node::StartExecutionCallbackInfo const& )>) [node] 33: 0x10432dc28 node::LoadEnvironment(node::Environment*, std::__1::fun ction<v8::MaybeLocal<v8::Value> (node::StartExecutionCallbackInfo const &)>) [node] 34: 0x10443d1f4 node::NodeMainInstance::Run(node::ExitCode*, node::Envi ronment*) [node] 35: 0x10443cfd0 node::NodeMainInstance::Run() [node] 36: 0x1043c5d18 node::Start(int, char**) [node] 37: 0x19a027f28 start [/usr/lib/dyld] [1] 24458 abort out/Release/node -p "process.binding('fs').open ()" ``` Now it looks like this: ``` # out/Release/node[24856]: void node::fs::Open(const FunctionCallbac kInfo<v8::Value> &) at ../../src/node_file.cc:2008 # Assertion failed: (argc) >= (3) ----- Native stack trace ----- 1: 0x1001efe64 node::Abort() [node] 2: 0x1001efba4 node::PrintCaughtException(v8::Isolate*, v8::Local<v8:: Context>, v8::TryCatch const&) [node] 3: 0x1001fb868 node::fs::Open(v8::FunctionCallbackInfo<v8::Value> cons t&) [node] 4: 0x100405fd4 v8::internal::MaybeHandle<v8::internal::Object> v8::int ernal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal:: Isolate*, v8::internal::Handle<v8::internal::HeapObject >, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::intern al::Handle<v8::internal::Object>, unsigned long*, int) [node ] 5: 0x100405828 v8::internal::Builtin_HandleApiCall(int, unsigned long* , v8::internal::Isolate*) [node] 6: 0x100c90b24 Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit [node] 7: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 8: 0x100c0650c Builtins_JSEntryTrampoline [node] 9: 0x100c061f4 Builtins_JSEntry [node] 10: 0x1004e16ac v8::internal::(anonymous namespace)::Invoke(v8::interna l::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [node] 11: 0x1004e1cc0 v8::internal::Execution::CallScript(v8::internal::Isola te*, v8::internal::Handle<v8::internal::JSFunction>, v8::internal::Hand le<v8::internal::Object>, v8::internal::Handle<v8::in ternal::Object>) [node] 12: 0x10039e100 v8::Script::Run(v8::Local<v8::Context>, v8::Local<v8::D ata>) [node] 13: 0x1001e4028 node::contextify::ContextifyScript::EvalMachine(v8::Loc al<v8::Context>, node::Environment*, long long, bool, bool, bool, v8::M icrotaskQueue*, v8::FunctionCallbackInfo<v8::Value> const&) [node ] 14: 0x1001e38a0 node::contextify::ContextifyScript::RunInContext(v8::Fu nctionCallbackInfo<v8::Value> const&) [node] 15: 0x100405fd4 v8::internal::MaybeHandle<v8::internal::Object> v8::int ernal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal:: Isolate*, v8::internal::Handle<v8::internal::HeapObject> , v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::interna l::Handle<v8::internal::Object>, unsigned long*, int) [node ] 16: 0x100405828 v8::internal::Builtin_HandleApiCall(int, unsigned long* , v8::internal::Isolate*) [node] 17: 0x100c90b24 Builtins_CEntry_Return1_ArgvOnStack_BuiltinExit [node] 18: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 19: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 20: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 21: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 22: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 23: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 24: 0x100c083e4 Builtins_InterpreterEntryTrampoline [node] 25: 0x100c0650c Builtins_JSEntryTrampoline [node] 26: 0x100c061f4 Builtins_JSEntry [node] 27: 0x1004e16ac v8::internal::(anonymous namespace)::Invoke(v8::interna l::Isolate*, v8::internal::(anonymous namespace)::InvokeParams const&) [node] 28: 0x1004e0f28 v8::internal::Execution::Call(v8::internal::Isolate*, v 8::internal::Handle<v8::internal::Object>, v8::internal::Handle<v8::int ernal::Object>, int, v8::internal::Handle<v 8::internal::Object>*) [node] 29: 0x1003b239c v8::Function::Call(v8::Local<v8::Context>, v8::Local<v8 ::Value>, int, v8::Local<v8::Value>*) [node] 30: 0x1001d3bc4 node::builtins::BuiltinLoader::CompileAndCall(v8::Local <v8::Context>, char const*, node::Realm*) [node] 31: 0x100263434 node::Realm::ExecuteBootstrapper(char const*) [node] 32: 0x1001b7838 node::StartExecution(node::Environment*, std::__1::func tion<v8::MaybeLocal<v8::Value> (node::StartExecutionCallbackInfo const& )>) [node] 33: 0x100121c28 node::LoadEnvironment(node::Environment*, std::__1::fun ction<v8::MaybeLocal<v8::Value> (node::StartExecutionCallbackInfo const &)>) [node] 34: 0x100231354 node::NodeMainInstance::Run(node::ExitCode*, node::Envi ronment*) [node] 35: 0x100231130 node::NodeMainInstance::Run() [node] 36: 0x1001ba1d8 node::Start(int, char**) [node] 37: 0x19a027f28 start [/usr/lib/dyld] ----- JavaScript stack trace ----- 1: [eval]:1:23 2: runScriptInThisContext (node:internal/vm:144:10) 3: node:internal/process/execution:109:14 4: [eval]-wrapper:6:24 5: runScript (node:internal/process/execution:92:62) 6: evalScript (node:internal/process/execution:123:10) 7: node:internal/main/eval_string:51:3 [1] 24856 abort out/Release/node -p "process.binding('fs').open ()" ``` PR-URL: https://github.com/nodejs/node/pull/50242 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
137 lines
4.2 KiB
JavaScript
137 lines
4.2 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const cp = require('child_process');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
// This is a sibling test to test/addons/uv-handle-leak.
|
|
|
|
const bindingPath = path.resolve(
|
|
__dirname, '..', 'addons', 'uv-handle-leak', 'build',
|
|
`${common.buildType}/binding.node`);
|
|
|
|
if (!fs.existsSync(bindingPath))
|
|
common.skip('binding not built yet');
|
|
|
|
if (process.argv[2] === 'child') {
|
|
|
|
const { Worker } = require('worker_threads');
|
|
|
|
// The worker thread loads and then unloads `bindingPath`. Because of this the
|
|
// symbols in `bindingPath` are lost when the worker thread quits, but the
|
|
// number of open handles in the worker thread's event loop is assessed in the
|
|
// main thread afterwards, and the names of the callbacks associated with the
|
|
// open handles is retrieved at that time as well. Thus, we require
|
|
// `bindingPath` here so that the symbols and their names survive the life
|
|
// cycle of the worker thread.
|
|
require(bindingPath);
|
|
|
|
new Worker(`
|
|
const binding = require(${JSON.stringify(bindingPath)});
|
|
|
|
binding.leakHandle();
|
|
binding.leakHandle(0);
|
|
binding.leakHandle(0x42);
|
|
`, { eval: true });
|
|
} else {
|
|
const child = cp.spawnSync(process.execPath, [__filename, 'child']);
|
|
const stderr = child.stderr.toString();
|
|
|
|
assert.strictEqual(child.stdout.toString(), '');
|
|
|
|
const lines = stderr.split('\n');
|
|
|
|
let state = 'initial';
|
|
|
|
// Parse output that is formatted like this:
|
|
|
|
// uv loop at [0x559b65ed5770] has open handles:
|
|
// [0x7f2de0018430] timer (active)
|
|
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
|
|
// Data: 0x7f2df33df140 example_instance [...]
|
|
// (First field): 0x7f2df33dedc0 vtable for ExampleOwnerClass [...]
|
|
// [0x7f2de000b870] timer
|
|
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
|
|
// Data: (nil)
|
|
// [0x7f2de000b910] timer
|
|
// Close callback: 0x7f2df31de220 CloseCallback(uv_handle_s*) [...]
|
|
// Data: 0x42
|
|
// uv loop at [0x559b65ed5770] has 3 open handles in total
|
|
|
|
function isGlibc() {
|
|
try {
|
|
const lddOut = spawnSync('ldd', [process.execPath]).stdout;
|
|
const libcInfo = lddOut.toString().split('\n').map(
|
|
(line) => line.match(/libc\.so.+=>\s*(\S+)\s/)).filter((info) => info);
|
|
if (libcInfo.length === 0)
|
|
return false;
|
|
const nmOut = spawnSync('nm', ['-D', libcInfo[0][1]]).stdout;
|
|
if (/gnu_get_libc_version/.test(nmOut))
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
if (!(common.isFreeBSD ||
|
|
common.isAIX ||
|
|
common.isIBMi ||
|
|
(common.isLinux && !isGlibc()) ||
|
|
common.isWindows)) {
|
|
assert(stderr.includes('ExampleOwnerClass'), stderr);
|
|
assert(stderr.includes('CloseCallback'), stderr);
|
|
assert(stderr.includes('example_instance'), stderr);
|
|
}
|
|
|
|
while (lines.length > 0) {
|
|
const line = lines.shift().trim();
|
|
if (line.length === 0) {
|
|
continue; // Skip empty lines.
|
|
}
|
|
|
|
switch (state) {
|
|
case 'initial':
|
|
assert.match(line, /^uv loop at \[.+\] has open handles:$/);
|
|
state = 'handle-start';
|
|
break;
|
|
case 'handle-start':
|
|
if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) {
|
|
state = 'source-line';
|
|
break;
|
|
}
|
|
assert.match(line, /^\[.+\] timer( \(active\))?$/);
|
|
state = 'close-callback';
|
|
break;
|
|
case 'close-callback':
|
|
assert.match(line, /^Close callback:/);
|
|
state = 'data';
|
|
break;
|
|
case 'data':
|
|
assert.match(line, /^Data: .+$/);
|
|
state = 'maybe-first-field';
|
|
break;
|
|
case 'maybe-first-field':
|
|
if (!/^\(First field\)/.test(line)) {
|
|
lines.unshift(line);
|
|
}
|
|
state = 'handle-start';
|
|
break;
|
|
case 'source-line':
|
|
assert.match(line, /CheckedUvLoopClose/);
|
|
state = 'assertion-failure';
|
|
break;
|
|
case 'assertion-failure':
|
|
assert.match(line, /Assertion failed:/);
|
|
state = 'done';
|
|
break;
|
|
case 'done':
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert.strictEqual(state, 'done');
|
|
}
|