| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 98cfa3a commit 6e20e08
13 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,6 +59,9 @@ namespace per_process { | |||
| 59 | 59 | EnabledDebugList enabled_debug_list; | |
| 60 | 60 | } | |
| 61 | 61 | ||
| 62 | + using v8::Local; | ||
| 63 | + using v8::StackTrace; | ||
| 64 | + | ||
| 62 | 65 | void EnabledDebugList::Parse(std::shared_ptr<KVStore> env_vars, | |
| 63 | 66 | v8::Isolate* isolate) { | |
| 64 | 67 | std::string cats; | |
@@ -303,7 +306,8 @@ std::string NativeSymbolDebuggingContext::SymbolInfo::Display() const { | |||
| 303 | 306 | return oss.str(); | |
| 304 | 307 | } | |
| 305 | 308 | ||
| 306 | - void DumpBacktrace(FILE* fp) { | ||
| 309 | + void DumpNativeBacktrace(FILE* fp) { | ||
| 310 | + fprintf(fp, "----- Native stack trace -----\n\n"); | ||
| 307 | 311 | auto sym_ctx = NativeSymbolDebuggingContext::New(); | |
| 308 | 312 | void* frames[256]; | |
| 309 | 313 | const int size = sym_ctx->GetStackTrace(frames, arraysize(frames)); | |
@@ -314,6 +318,22 @@ void DumpBacktrace(FILE* fp) { | |||
| 314 | 318 | } | |
| 315 | 319 | } | |
| 316 | 320 | ||
| 321 | + void DumpJavaScriptBacktrace(FILE* fp) { | ||
| 322 | + v8::Isolate* isolate = v8::Isolate::GetCurrent(); | ||
| 323 | + if (isolate == nullptr) { | ||
| 324 | + return; | ||
| 325 | + } | ||
| 326 | + | ||
| 327 | + Local<StackTrace> stack; | ||
| 328 | + if (!GetCurrentStackTrace(isolate).ToLocal(&stack)) { | ||
| 329 | + return; | ||
| 330 | + } | ||
| 331 | + | ||
| 332 | + fprintf(fp, "\n----- JavaScript stack trace -----\n\n"); | ||
| 333 | + PrintStackTrace(isolate, stack, StackTracePrefix::kNumber); | ||
| 334 | + fprintf(fp, "\n"); | ||
| 335 | + } | ||
| 336 | + | ||
| 317 | 337 | void CheckedUvLoopClose(uv_loop_t* loop) { | |
| 318 | 338 | if (uv_loop_close(loop) == 0) return; | |
| 319 | 339 | ||
@@ -514,5 +534,6 @@ void FWrite(FILE* file, const std::string& str) { | |||
| 514 | 534 | } // namespace node | |
| 515 | 535 | ||
| 516 | 536 | extern "C" void __DumpBacktrace(FILE* fp) { | |
| 517 | - node::DumpBacktrace(fp); | ||
| 537 | + node::DumpNativeBacktrace(fp); | ||
| 538 | + node::DumpJavaScriptBacktrace(fp); | ||
| 518 | 539 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1662,7 +1662,8 @@ void AsyncHooks::FailWithCorruptedAsyncStack(double expected_async_id) { | |||
| 1662 | 1662 | "actual: %.f, expected: %.f)\n", | |
| 1663 | 1663 | async_id_fields_.GetValue(kExecutionAsyncId), | |
| 1664 | 1664 | expected_async_id); | |
| 1665 | - DumpBacktrace(stderr); | ||
| 1665 | + DumpNativeBacktrace(stderr); | ||
| 1666 | + DumpJavaScriptBacktrace(stderr); | ||
| 1666 | 1667 | fflush(stderr); | |
| 1667 | 1668 | // TODO(joyeecheung): should this exit code be more specific? | |
| 1668 | 1669 | if (!env()->abort_on_uncaught_exception()) Exit(ExitCode::kGenericUserError); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | #include <cerrno> | |
| 2 | 2 | #include <cstdarg> | |
| 3 | + #include <sstream> | ||
| 3 | 4 | ||
| 4 | 5 | #include "debug_utils-inl.h" | |
| 5 | 6 | #include "node_errors.h" | |
@@ -15,6 +16,7 @@ namespace node { | |||
| 15 | 16 | using errors::TryCatchScope; | |
| 16 | 17 | using v8::Boolean; | |
| 17 | 18 | using v8::Context; | |
| 19 | + using v8::EscapableHandleScope; | ||
| 18 | 20 | using v8::Exception; | |
| 19 | 21 | using v8::Function; | |
| 20 | 22 | using v8::FunctionCallbackInfo; | |
@@ -185,23 +187,65 @@ static std::string GetErrorSource(Isolate* isolate, | |||
| 185 | 187 | return buf + std::string(underline_buf, off); | |
| 186 | 188 | } | |
| 187 | 189 | ||
| 188 | - static std::string FormatStackTrace(Isolate* isolate, Local<StackTrace> stack) { | ||
| 190 | + static std::atomic<bool> is_in_oom{false}; | ||
| 191 | + static std::atomic<bool> is_retrieving_js_stacktrace{false}; | ||
| 192 | + MaybeLocal<StackTrace> GetCurrentStackTrace(Isolate* isolate, int frame_count) { | ||
| 193 | + if (isolate == nullptr) { | ||
| 194 | + return MaybeLocal<StackTrace>(); | ||
| 195 | + } | ||
| 196 | + // Generating JavaScript stack trace can result in V8 fatal error, | ||
| 197 | + // which can re-enter this function. | ||
| 198 | + if (is_retrieving_js_stacktrace.load()) { | ||
| 199 | + return MaybeLocal<StackTrace>(); | ||
| 200 | + } | ||
| 201 | + | ||
| 202 | + // Can not capture the stacktrace when the isolate is in a OOM state or no | ||
| 203 | + // context is entered. | ||
| 204 | + if (is_in_oom.load() || !isolate->InContext()) { | ||
| 205 | + return MaybeLocal<StackTrace>(); | ||
| 206 | + } | ||
| 207 | + | ||
| 208 | + constexpr StackTrace::StackTraceOptions options = | ||
| 209 | + static_cast<StackTrace::StackTraceOptions>( | ||
| 210 | + StackTrace::kDetailed | | ||
| 211 | + StackTrace::kExposeFramesAcrossSecurityOrigins); | ||
| 212 | + | ||
| 213 | + is_retrieving_js_stacktrace.store(true); | ||
| 214 | + EscapableHandleScope scope(isolate); | ||
| 215 | + Local<StackTrace> stack = | ||
| 216 | + StackTrace::CurrentStackTrace(isolate, frame_count, options); | ||
| 217 | + | ||
| 218 | + is_retrieving_js_stacktrace.store(false); | ||
| 219 | + if (stack->GetFrameCount() == 0) { | ||
| 220 | + return MaybeLocal<StackTrace>(); | ||
| 221 | + } | ||
| 222 | + | ||
| 223 | + return scope.Escape(stack); | ||
| 224 | + } | ||
| 225 | + | ||
| 226 | + static std::string FormatStackTrace( | ||
| 227 | + Isolate* isolate, | ||
| 228 | + Local<StackTrace> stack, | ||
| 229 | + StackTracePrefix prefix = StackTracePrefix::kAt) { | ||
| 189 | 230 | std::string result; | |
| 190 | 231 | for (int i = 0; i < stack->GetFrameCount(); i++) { | |
| 191 | 232 | Local<StackFrame> stack_frame = stack->GetFrame(isolate, i); | |
| 192 | 233 | node::Utf8Value fn_name_s(isolate, stack_frame->GetFunctionName()); | |
| 193 | 234 | node::Utf8Value script_name(isolate, stack_frame->GetScriptName()); | |
| 194 | 235 | const int line_number = stack_frame->GetLineNumber(); | |
| 195 | 236 | const int column = stack_frame->GetColumn(); | |
| 196 | - | ||
| 237 | + std::string prefix_str = prefix == StackTracePrefix::kAt | ||
| 238 | + ? " at " | ||
| 239 | + : std::to_string(i + 1) + ": "; | ||
| 197 | 240 | if (stack_frame->IsEval()) { | |
| 198 | 241 | if (stack_frame->GetScriptId() == Message::kNoScriptIdInfo) { | |
| 199 | - result += SPrintF(" at [eval]:%i:%i\n", line_number, column); | ||
| 242 | + result += SPrintF("%s[eval]:%i:%i\n", prefix_str, line_number, column); | ||
| 200 | 243 | } else { | |
| 201 | 244 | std::vector<char> buf(script_name.length() + 64); | |
| 202 | 245 | snprintf(buf.data(), | |
| 203 | 246 | buf.size(), | |
| 204 | - " at [eval] (%s:%i:%i)\n", | ||
| 247 | + "%s[eval] (%s:%i:%i)\n", | ||
| 248 | + prefix_str.c_str(), | ||
| 205 | 249 | *script_name, | |
| 206 | 250 | line_number, | |
| 207 | 251 | column); | |
@@ -214,7 +258,8 @@ static std::string FormatStackTrace(Isolate* isolate, Local<StackTrace> stack) { | |||
| 214 | 258 | std::vector<char> buf(script_name.length() + 64); | |
| 215 | 259 | snprintf(buf.data(), | |
| 216 | 260 | buf.size(), | |
| 217 | - " at %s:%i:%i\n", | ||
| 261 | + "%s%s:%i:%i\n", | ||
| 262 | + prefix_str.c_str(), | ||
| 218 | 263 | *script_name, | |
| 219 | 264 | line_number, | |
| 220 | 265 | column); | |
@@ -223,7 +268,8 @@ static std::string FormatStackTrace(Isolate* isolate, Local<StackTrace> stack) { | |||
| 223 | 268 | std::vector<char> buf(fn_name_s.length() + script_name.length() + 64); | |
| 224 | 269 | snprintf(buf.data(), | |
| 225 | 270 | buf.size(), | |
| 226 | - " at %s (%s:%i:%i)\n", | ||
| 271 | + "%s%s (%s:%i:%i)\n", | ||
| 272 | + prefix_str.c_str(), | ||
| 227 | 273 | *fn_name_s, | |
| 228 | 274 | *script_name, | |
| 229 | 275 | line_number, | |
@@ -239,8 +285,10 @@ static void PrintToStderrAndFlush(const std::string& str) { | |||
| 239 | 285 | fflush(stderr); | |
| 240 | 286 | } | |
| 241 | 287 | ||
| 242 | - void PrintStackTrace(Isolate* isolate, Local<StackTrace> stack) { | ||
| 243 | - PrintToStderrAndFlush(FormatStackTrace(isolate, stack)); | ||
| 288 | + void PrintStackTrace(Isolate* isolate, | ||
| 289 | + Local<StackTrace> stack, | ||
| 290 | + StackTracePrefix prefix) { | ||
| 291 | + PrintToStderrAndFlush(FormatStackTrace(isolate, stack, prefix)); | ||
| 244 | 292 | } | |
| 245 | 293 | ||
| 246 | 294 | std::string FormatCaughtException(Isolate* isolate, | |
@@ -329,7 +377,8 @@ void AppendExceptionLine(Environment* env, | |||
| 329 | 377 | } | |
| 330 | 378 | ||
| 331 | 379 | [[noreturn]] void Abort() { | |
| 332 | - DumpBacktrace(stderr); | ||
| 380 | + DumpNativeBacktrace(stderr); | ||
| 381 | + DumpJavaScriptBacktrace(stderr); | ||
| 333 | 382 | fflush(stderr); | |
| 334 | 383 | ABORT_NO_BACKTRACE(); | |
| 335 | 384 | } | |
@@ -338,14 +387,15 @@ void AppendExceptionLine(Environment* env, | |||
| 338 | 387 | std::string name = GetHumanReadableProcessName(); | |
| 339 | 388 | ||
| 340 | 389 | fprintf(stderr, | |
| 341 | - "%s: %s:%s%s Assertion `%s' failed.\n", | ||
| 390 | + "\n" | ||
| 391 | + " # %s: %s at %s\n" | ||
| 392 | + " # Assertion failed: %s\n\n", | ||
| 342 | 393 | name.c_str(), | |
| 343 | - info.file_line, | ||
| 344 | - info.function, | ||
| 345 | - *info.function ? ":" : "", | ||
| 394 | + info.function ? info.function : "(unknown function)", | ||
| 395 | + info.file_line ? info.file_line : "(unknown source location)", | ||
| 346 | 396 | info.message); | |
| 347 | - fflush(stderr); | ||
| 348 | 397 | ||
| 398 | + fflush(stderr); | ||
| 349 | 399 | Abort(); | |
| 350 | 400 | } | |
| 351 | 401 | ||
@@ -528,6 +578,9 @@ static void ReportFatalException(Environment* env, | |||
| 528 | 578 | ||
| 529 | 579 | [[noreturn]] void OOMErrorHandler(const char* location, | |
| 530 | 580 | const v8::OOMDetails& details) { | |
| 581 | + // We should never recover from this handler so once it's true it's always | ||
| 582 | + // true. | ||
| 583 | + is_in_oom.store(true); | ||
| 531 | 584 | const char* message = | |
| 532 | 585 | details.is_heap_oom ? "Allocation failed - JavaScript heap out of memory" | |
| 533 | 586 | : "Allocation failed - process out of memory"; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -79,7 +79,17 @@ void GetSockOrPeerName(const v8::FunctionCallbackInfo<v8::Value>& args) { | |||
| 79 | 79 | args.GetReturnValue().Set(err); | |
| 80 | 80 | } | |
| 81 | 81 | ||
| 82 | - void PrintStackTrace(v8::Isolate* isolate, v8::Local<v8::StackTrace> stack); | ||
| 82 | + constexpr int kMaxFrameCountForLogging = 10; | ||
| 83 | + v8::MaybeLocal<v8::StackTrace> GetCurrentStackTrace( | ||
| 84 | + v8::Isolate* isolate, int frame_count = kMaxFrameCountForLogging); | ||
| 85 | + | ||
| 86 | + enum class StackTracePrefix { | ||
| 87 | + kAt, // " at " | ||
| 88 | + kNumber | ||
| 89 | + }; | ||
| 90 | + void PrintStackTrace(v8::Isolate* isolate, | ||
| 91 | + v8::Local<v8::StackTrace> stack, | ||
| 92 | + StackTracePrefix prefix = StackTracePrefix::kAt); | ||
| 83 | 93 | void PrintCaughtException(v8::Isolate* isolate, | |
| 84 | 94 | v8::Local<v8::Context> context, | |
| 85 | 95 | const v8::TryCatch& try_catch); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -560,7 +560,7 @@ v8::TracingController* NodePlatform::GetTracingController() { | |||
| 560 | 560 | Platform::StackTracePrinter NodePlatform::GetStackTracePrinter() { | |
| 561 | 561 | return []() { | |
| 562 | 562 | fprintf(stderr, "\n"); | |
| 563 | - DumpBacktrace(stderr); | ||
| 563 | + DumpNativeBacktrace(stderr); | ||
| 564 | 564 | fflush(stderr); | |
| 565 | 565 | }; | |
| 566 | 566 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,7 +27,7 @@ | |||
| 27 | 27 | constexpr int NODE_REPORT_VERSION = 3; | |
| 28 | 28 | constexpr int NANOS_PER_SEC = 1000 * 1000 * 1000; | |
| 29 | 29 | constexpr double SEC_PER_MICROS = 1e-6; | |
| 30 | - constexpr int MAX_FRAME_COUNT = 10; | ||
| 30 | + constexpr int MAX_FRAME_COUNT = node::kMaxFrameCountForLogging; | ||
| 31 | 31 | ||
| 32 | 32 | namespace node { | |
| 33 | 33 | using node::worker::Worker; | |
@@ -458,14 +458,13 @@ static void PrintEmptyJavaScriptStack(JSONWriter* writer) { | |||
| 458 | 458 | static void PrintJavaScriptStack(JSONWriter* writer, | |
| 459 | 459 | Isolate* isolate, | |
| 460 | 460 | const char* trigger) { | |
| 461 | - // Can not capture the stacktrace when the isolate is in a OOM state or no | ||
| 462 | - // context is entered. | ||
| 463 | - if (!strcmp(trigger, "OOMError") || !isolate->InContext()) { | ||
| 461 | + HandleScope scope(isolate); | ||
| 462 | + Local<v8::StackTrace> stack; | ||
| 463 | + if (!GetCurrentStackTrace(isolate, MAX_FRAME_COUNT).ToLocal(&stack)) { | ||
| 464 | 464 | PrintEmptyJavaScriptStack(writer); | |
| 465 | 465 | return; | |
| 466 | 466 | } | |
| 467 | 467 | ||
| 468 | - HandleScope scope(isolate); | ||
| 469 | 468 | RegisterState state; | |
| 470 | 469 | state.pc = nullptr; | |
| 471 | 470 | state.fp = &state; | |
@@ -476,18 +475,6 @@ static void PrintJavaScriptStack(JSONWriter* writer, | |||
| 476 | 475 | void* samples[MAX_FRAME_COUNT]; | |
| 477 | 476 | isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info); | |
| 478 | 477 | ||
| 479 | - constexpr StackTrace::StackTraceOptions stack_trace_options = | ||
| 480 | - static_cast<StackTrace::StackTraceOptions>( | ||
| 481 | - StackTrace::kDetailed | | ||
| 482 | - StackTrace::kExposeFramesAcrossSecurityOrigins); | ||
| 483 | - Local<StackTrace> stack = StackTrace::CurrentStackTrace( | ||
| 484 | - isolate, MAX_FRAME_COUNT, stack_trace_options); | ||
| 485 | - | ||
| 486 | - if (stack->GetFrameCount() == 0) { | ||
| 487 | - PrintEmptyJavaScriptStack(writer); | ||
| 488 | - return; | ||
| 489 | - } | ||
| 490 | - | ||
| 491 | 478 | writer->json_keyvalue("message", trigger); | |
| 492 | 479 | writer->json_arraystart("stack"); | |
| 493 | 480 | for (int i = 0; i < stack->GetFrameCount(); i++) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -115,7 +115,8 @@ struct AssertionInfo { | |||
| 115 | 115 | }; | |
| 116 | 116 | [[noreturn]] void NODE_EXTERN_PRIVATE Assert(const AssertionInfo& info); | |
| 117 | 117 | [[noreturn]] void NODE_EXTERN_PRIVATE Abort(); | |
| 118 | - void DumpBacktrace(FILE* fp); | ||
| 118 | + void DumpNativeBacktrace(FILE* fp); | ||
| 119 | + void DumpJavaScriptBacktrace(FILE* fp); | ||
| 119 | 120 | ||
| 120 | 121 | // Windows 8+ does not like abort() in Release mode | |
| 121 | 122 | #ifdef _WIN32 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -10,19 +10,22 @@ if (process.argv[2] === 'child') { | |||
| 10 | 10 | const stderr = child.stderr.toString(); | |
| 11 | 11 | ||
| 12 | 12 | assert.strictEqual(child.stdout.toString(), ''); | |
| 13 | - // Stderr will be empty for systems that don't support backtraces. | ||
| 14 | - if (stderr !== '') { | ||
| 15 | - const frames = stderr.trimRight().split('\n').map((s) => s.trim()); | ||
| 13 | + const { nativeStack, jsStack } = common.getPrintedStackTrace(stderr); | ||
| 16 | 14 | ||
| 17 | - if (!frames.every((frame, index) => frame.startsWith(`${index + 1}:`))) { | ||
| 18 | - assert.fail(`Each frame should start with a frame number:\n${stderr}`); | ||
| 19 | - } | ||
| 15 | + if (!nativeStack.every((frame, index) => frame.startsWith(`${index + 1}:`))) { | ||
| 16 | + assert.fail(`Each frame should start with a frame number:\n${stderr}`); | ||
| 17 | + } | ||
| 20 | 18 | ||
| 21 | - if (!common.isWindows) { | ||
| 22 | - const { getBinaryPath } = require('../common/shared-lib-util'); | ||
| 23 | - if (!frames.some((frame) => frame.includes(`[${getBinaryPath()}]`))) { | ||
| 24 | - assert.fail(`Some frames should include the binary name:\n${stderr}`); | ||
| 25 | - } | ||
| 19 | + // For systems that don't support backtraces, the native stack is | ||
| 20 | + // going to be empty. | ||
| 21 | + if (!common.isWindows && nativeStack.length > 0) { | ||
| 22 | + const { getBinaryPath } = require('../common/shared-lib-util'); | ||
| 23 | + if (!nativeStack.some((frame) => frame.includes(`[${getBinaryPath()}]`))) { | ||
| 24 | + assert.fail(`Some native stack frame include the binary name:\n${stderr}`); | ||
| 26 | 25 | } | |
| 27 | 26 | } | |
| 27 | + | ||
| 28 | + if (jsStack.length > 0) { | ||
| 29 | + assert(jsStack.some((frame) => frame.includes(__filename))); | ||
| 30 | + } | ||
| 28 | 31 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -38,10 +38,5 @@ exec(cmdline, function(err, stdout, stderr) { | |||
| 38 | 38 | assert(false, 'this test should fail'); | |
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | - if (err.code !== 134 && err.signal !== 'SIGABRT') { | ||
| 42 | - console.log(stdout); | ||
| 43 | - console.log(stderr); | ||
| 44 | - console.log(err); | ||
| 45 | - assert(false, err); | ||
| 46 | - } | ||
| 41 | + assert(common.nodeProcessAborted(err.code, err.signal)); | ||
| 47 | 42 | }); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -88,6 +88,9 @@ if (process.argv[2] === 'child') { | |||
| 88 | 88 | ||
| 89 | 89 | while (lines.length > 0) { | |
| 90 | 90 | const line = lines.shift().trim(); | |
| 91 | + if (line.length === 0) { | ||
| 92 | + continue; // Skip empty lines. | ||
| 93 | + } | ||
| 91 | 94 | ||
| 92 | 95 | switch (state) { | |
| 93 | 96 | case 'initial': | |
@@ -96,7 +99,7 @@ if (process.argv[2] === 'child') { | |||
| 96 | 99 | break; | |
| 97 | 100 | case 'handle-start': | |
| 98 | 101 | if (/^uv loop at \[.+\] has \d+ open handles in total$/.test(line)) { | |
| 99 | - state = 'assertion-failure'; | ||
| 102 | + state = 'source-line'; | ||
| 100 | 103 | break; | |
| 101 | 104 | } | |
| 102 | 105 | assert.match(line, /^\[.+\] timer( \(active\))?$/); | |
@@ -116,8 +119,12 @@ if (process.argv[2] === 'child') { | |||
| 116 | 119 | } | |
| 117 | 120 | state = 'handle-start'; | |
| 118 | 121 | break; | |
| 122 | + case 'source-line': | ||
| 123 | + assert.match(line, /CheckedUvLoopClose/); | ||
| 124 | + state = 'assertion-failure'; | ||
| 125 | + break; | ||
| 119 | 126 | case 'assertion-failure': | |
| 120 | - assert.match(line, /Assertion .+ failed/); | ||
| 127 | + assert.match(line, /Assertion failed:/); | ||
| 121 | 128 | state = 'done'; | |
| 122 | 129 | break; | |
| 123 | 130 | case 'done': | |
| Back | FazBrowse Home | New Git URL |
0 commit comments