| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a08cdea commit 30cfe0a
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -237,6 +237,7 @@ void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) { | |||
| 237 | 237 | auto* fatal_error_cb = s.fatal_error_callback ? | |
| 238 | 238 | s.fatal_error_callback : OnFatalError; | |
| 239 | 239 | isolate->SetFatalErrorHandler(fatal_error_cb); | |
| 240 | + isolate->SetOOMErrorHandler(OOMErrorHandler); | ||
| 240 | 241 | ||
| 241 | 242 | if ((s.flags & SHOULD_NOT_SET_PREPARE_STACK_TRACE_CALLBACK) == 0) { | |
| 242 | 243 | auto* prepare_stack_trace_cb = s.prepare_stack_trace_callback ? | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -459,6 +459,36 @@ void OnFatalError(const char* location, const char* message) { | |||
| 459 | 459 | ABORT(); | |
| 460 | 460 | } | |
| 461 | 461 | ||
| 462 | + void OOMErrorHandler(const char* location, bool is_heap_oom) { | ||
| 463 | + const char* message = | ||
| 464 | + is_heap_oom ? "Allocation failed - JavaScript heap out of memory" | ||
| 465 | + : "Allocation failed - process out of memory"; | ||
| 466 | + if (location) { | ||
| 467 | + FPrintF(stderr, "FATAL ERROR: %s %s\n", location, message); | ||
| 468 | + } else { | ||
| 469 | + FPrintF(stderr, "FATAL ERROR: %s\n", message); | ||
| 470 | + } | ||
| 471 | + | ||
| 472 | + Isolate* isolate = Isolate::TryGetCurrent(); | ||
| 473 | + Environment* env = nullptr; | ||
| 474 | + if (isolate != nullptr) { | ||
| 475 | + env = Environment::GetCurrent(isolate); | ||
| 476 | + } | ||
| 477 | + bool report_on_fatalerror; | ||
| 478 | + { | ||
| 479 | + Mutex::ScopedLock lock(node::per_process::cli_options_mutex); | ||
| 480 | + report_on_fatalerror = per_process::cli_options->report_on_fatalerror; | ||
| 481 | + } | ||
| 482 | + | ||
| 483 | + if (report_on_fatalerror) { | ||
| 484 | + report::TriggerNodeReport( | ||
| 485 | + isolate, env, message, "OOMError", "", Local<Object>()); | ||
| 486 | + } | ||
| 487 | + | ||
| 488 | + fflush(stderr); | ||
| 489 | + ABORT(); | ||
| 490 | + } | ||
| 491 | + | ||
| 462 | 492 | v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings( | |
| 463 | 493 | v8::Local<v8::Context> context, | |
| 464 | 494 | v8::Local<v8::Value> source, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -21,6 +21,7 @@ void AppendExceptionLine(Environment* env, | |||
| 21 | 21 | ||
| 22 | 22 | [[noreturn]] void FatalError(const char* location, const char* message); | |
| 23 | 23 | void OnFatalError(const char* location, const char* message); | |
| 24 | + void OOMErrorHandler(const char* location, bool is_heap_oom); | ||
| 24 | 25 | ||
| 25 | 26 | // Helpers to construct errors similar to the ones provided by | |
| 26 | 27 | // lib/internal/errors.js. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,6 +26,7 @@ | |||
| 26 | 26 | constexpr int NODE_REPORT_VERSION = 2; | |
| 27 | 27 | constexpr int NANOS_PER_SEC = 1000 * 1000 * 1000; | |
| 28 | 28 | constexpr double SEC_PER_MICROS = 1e-6; | |
| 29 | + constexpr int MAX_FRAME_COUNT = 10; | ||
| 29 | 30 | ||
| 30 | 31 | namespace node { | |
| 31 | 32 | namespace report { | |
@@ -43,6 +44,10 @@ using v8::Maybe; | |||
| 43 | 44 | using v8::MaybeLocal; | |
| 44 | 45 | using v8::Nothing; | |
| 45 | 46 | using v8::Object; | |
| 47 | + using v8::RegisterState; | ||
| 48 | + using v8::SampleInfo; | ||
| 49 | + using v8::StackFrame; | ||
| 50 | + using v8::StackTrace; | ||
| 46 | 51 | using v8::String; | |
| 47 | 52 | using v8::TryCatch; | |
| 48 | 53 | using v8::V8; | |
@@ -62,6 +67,9 @@ static void PrintJavaScriptErrorStack(JSONWriter* writer, | |||
| 62 | 67 | Isolate* isolate, | |
| 63 | 68 | Local<Value> error, | |
| 64 | 69 | const char* trigger); | |
| 70 | + static void PrintJavaScriptStack(JSONWriter* writer, | ||
| 71 | + Isolate* isolate, | ||
| 72 | + const char* trigger); | ||
| 65 | 73 | static void PrintJavaScriptErrorProperties(JSONWriter* writer, | |
| 66 | 74 | Isolate* isolate, | |
| 67 | 75 | Local<Value> error); | |
@@ -269,8 +277,6 @@ static void WriteNodeReport(Isolate* isolate, | |||
| 269 | 277 | // Report summary JavaScript error stack backtrace | |
| 270 | 278 | PrintJavaScriptErrorStack(&writer, isolate, error, trigger); | |
| 271 | 279 | ||
| 272 | - // Report summary JavaScript error properties backtrace | ||
| 273 | - PrintJavaScriptErrorProperties(&writer, isolate, error); | ||
| 274 | 280 | writer.json_objectend(); // the end of 'javascriptStack' | |
| 275 | 281 | ||
| 276 | 282 | // Report V8 Heap and Garbage Collector information | |
@@ -317,7 +323,7 @@ static void WriteNodeReport(Isolate* isolate, | |||
| 317 | 323 | env, | |
| 318 | 324 | "Worker thread subreport", | |
| 319 | 325 | trigger, | |
| 320 | - Local<Object>(), | ||
| 326 | + Local<Value>(), | ||
| 321 | 327 | os); | |
| 322 | 328 | ||
| 323 | 329 | Mutex::ScopedLock lock(workers_mutex); | |
@@ -534,19 +540,80 @@ static Maybe<std::string> ErrorToString(Isolate* isolate, | |||
| 534 | 540 | return Just<>(std::string(*sv, sv.length())); | |
| 535 | 541 | } | |
| 536 | 542 | ||
| 543 | + static void PrintEmptyJavaScriptStack(JSONWriter* writer) { | ||
| 544 | + writer->json_keyvalue("message", "No stack."); | ||
| 545 | + writer->json_arraystart("stack"); | ||
| 546 | + writer->json_element("Unavailable."); | ||
| 547 | + writer->json_arrayend(); | ||
| 548 | + | ||
| 549 | + writer->json_objectstart("errorProperties"); | ||
| 550 | + writer->json_objectend(); | ||
| 551 | + } | ||
| 552 | + | ||
| 553 | + // Do our best to report the JavaScript stack without calling into JavaScript. | ||
| 554 | + static void PrintJavaScriptStack(JSONWriter* writer, | ||
| 555 | + Isolate* isolate, | ||
| 556 | + const char* trigger) { | ||
| 557 | + // Can not capture the stacktrace when the isolate is in a OOM state. | ||
| 558 | + if (!strcmp(trigger, "OOMError")) { | ||
| 559 | + PrintEmptyJavaScriptStack(writer); | ||
| 560 | + return; | ||
| 561 | + } | ||
| 562 | + | ||
| 563 | + HandleScope scope(isolate); | ||
| 564 | + RegisterState state; | ||
| 565 | + state.pc = nullptr; | ||
| 566 | + state.fp = &state; | ||
| 567 | + state.sp = &state; | ||
| 568 | + | ||
| 569 | + // in-out params | ||
| 570 | + SampleInfo info; | ||
| 571 | + void* samples[MAX_FRAME_COUNT]; | ||
| 572 | + isolate->GetStackSample(state, samples, MAX_FRAME_COUNT, &info); | ||
| 573 | + | ||
| 574 | + Local<StackTrace> stack = StackTrace::CurrentStackTrace( | ||
| 575 | + isolate, MAX_FRAME_COUNT, StackTrace::kDetailed); | ||
| 576 | + | ||
| 577 | + if (stack->GetFrameCount() == 0) { | ||
| 578 | + PrintEmptyJavaScriptStack(writer); | ||
| 579 | + return; | ||
| 580 | + } | ||
| 581 | + | ||
| 582 | + writer->json_keyvalue("message", trigger); | ||
| 583 | + writer->json_arraystart("stack"); | ||
| 584 | + for (int i = 0; i < stack->GetFrameCount(); i++) { | ||
| 585 | + Local<StackFrame> frame = stack->GetFrame(isolate, i); | ||
| 586 | + | ||
| 587 | + Utf8Value function_name(isolate, frame->GetFunctionName()); | ||
| 588 | + Utf8Value script_name(isolate, frame->GetScriptName()); | ||
| 589 | + const int line_number = frame->GetLineNumber(); | ||
| 590 | + const int column = frame->GetColumn(); | ||
| 591 | + | ||
| 592 | + std::string stack_line = SPrintF( | ||
| 593 | + "at %s (%s:%d:%d)", *function_name, *script_name, line_number, column); | ||
| 594 | + writer->json_element(stack_line); | ||
| 595 | + } | ||
| 596 | + writer->json_arrayend(); | ||
| 597 | + writer->json_objectstart("errorProperties"); | ||
| 598 | + writer->json_objectend(); | ||
| 599 | + } | ||
| 600 | + | ||
| 537 | 601 | // Report the JavaScript stack. | |
| 538 | 602 | static void PrintJavaScriptErrorStack(JSONWriter* writer, | |
| 539 | 603 | Isolate* isolate, | |
| 540 | 604 | Local<Value> error, | |
| 541 | 605 | const char* trigger) { | |
| 606 | + if (error.IsEmpty()) { | ||
| 607 | + return PrintJavaScriptStack(writer, isolate, trigger); | ||
| 608 | + } | ||
| 609 | + | ||
| 542 | 610 | TryCatch try_catch(isolate); | |
| 543 | 611 | HandleScope scope(isolate); | |
| 544 | 612 | Local<Context> context = isolate->GetCurrentContext(); | |
| 545 | 613 | std::string ss = ""; | |
| 546 | - if ((!strcmp(trigger, "FatalError")) || | ||
| 547 | - (!strcmp(trigger, "Signal")) || | ||
| 548 | - (!ErrorToString(isolate, context, error).To(&ss))) { | ||
| 549 | - ss = "No stack.\nUnavailable.\n"; | ||
| 614 | + if (!ErrorToString(isolate, context, error).To(&ss)) { | ||
| 615 | + PrintEmptyJavaScriptStack(writer); | ||
| 616 | + return; | ||
| 550 | 617 | } | |
| 551 | 618 | ||
| 552 | 619 | int line = ss.find('\n'); | |
@@ -569,6 +636,9 @@ static void PrintJavaScriptErrorStack(JSONWriter* writer, | |||
| 569 | 636 | } | |
| 570 | 637 | writer->json_arrayend(); | |
| 571 | 638 | } | |
| 639 | + | ||
| 640 | + // Report summary JavaScript error properties backtrace | ||
| 641 | + PrintJavaScriptErrorProperties(writer, isolate, error); | ||
| 572 | 642 | } | |
| 573 | 643 | ||
| 574 | 644 | // Report a native stack backtrace | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,23 @@ | |||
| 1 | + #include <node.h> | ||
| 2 | + #include <v8.h> | ||
| 3 | + | ||
| 4 | + using v8::FunctionCallbackInfo; | ||
| 5 | + using v8::Isolate; | ||
| 6 | + using v8::Local; | ||
| 7 | + using v8::MaybeLocal; | ||
| 8 | + using v8::Object; | ||
| 9 | + using v8::Value; | ||
| 10 | + | ||
| 11 | + void TriggerFatalError(const FunctionCallbackInfo<Value>& args) { | ||
| 12 | + Isolate* isolate = args.GetIsolate(); | ||
| 13 | + | ||
| 14 | + // Trigger a v8 ApiCheck failure. | ||
| 15 | + MaybeLocal<Value> value; | ||
| 16 | + value.ToLocalChecked(); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + void init(Local<Object> exports) { | ||
| 20 | + NODE_SET_METHOD(exports, "triggerFatalError", TriggerFatalError); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + NODE_MODULE(NODE_GYP_MODULE_NAME, init) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,9 @@ | |||
| 1 | + { | ||
| 2 | + 'targets': [ | ||
| 3 | + { | ||
| 4 | + 'target_name': 'binding', | ||
| 5 | + 'sources': [ 'binding.cc' ], | ||
| 6 | + 'includes': ['../common.gypi'], | ||
| 7 | + } | ||
| 8 | + ] | ||
| 9 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,52 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + const common = require('../../common'); | ||
| 4 | + const assert = require('assert'); | ||
| 5 | + const path = require('path'); | ||
| 6 | + const spawnSync = require('child_process').spawnSync; | ||
| 7 | + const helper = require('../../common/report.js'); | ||
| 8 | + const tmpdir = require('../../common/tmpdir'); | ||
| 9 | + | ||
| 10 | + const binding = path.resolve(__dirname, `./build/${common.buildType}/binding`); | ||
| 11 | + | ||
| 12 | + if (process.argv[2] === 'child') { | ||
| 13 | + (function childMain() { | ||
| 14 | + const addon = require(binding); | ||
| 15 | + addon.triggerFatalError(); | ||
| 16 | + })(); | ||
| 17 | + return; | ||
| 18 | + } | ||
| 19 | + | ||
| 20 | + const ARGS = [ | ||
| 21 | + __filename, | ||
| 22 | + 'child', | ||
| 23 | + ]; | ||
| 24 | + | ||
| 25 | + { | ||
| 26 | + // Verify that --report-on-fatalerror is respected when set. | ||
| 27 | + tmpdir.refresh(); | ||
| 28 | + const args = ['--report-on-fatalerror', ...ARGS]; | ||
| 29 | + const child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | ||
| 30 | + assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | ||
| 31 | + | ||
| 32 | + const reports = helper.findReports(child.pid, tmpdir.path); | ||
| 33 | + assert.strictEqual(reports.length, 1); | ||
| 34 | + | ||
| 35 | + const report = reports[0]; | ||
| 36 | + helper.validate(report); | ||
| 37 | + | ||
| 38 | + const content = require(report); | ||
| 39 | + assert.strictEqual(content.header.trigger, 'FatalError'); | ||
| 40 | + | ||
| 41 | + // Check that the javascript stack is present. | ||
| 42 | + assert.strictEqual(content.javascriptStack.stack.findIndex((frame) => frame.match('childMain')), 0); | ||
| 43 | + } | ||
| 44 | + | ||
| 45 | + { | ||
| 46 | + // Verify that --report-on-fatalerror is respected when not set. | ||
| 47 | + const args = ARGS; | ||
| 48 | + const child = spawnSync(process.execPath, args, { cwd: tmpdir.path }); | ||
| 49 | + assert.notStrictEqual(child.status, 0, 'Process exited unexpectedly'); | ||
| 50 | + const reports = helper.findReports(child.pid, tmpdir.path); | ||
| 51 | + assert.strictEqual(reports.length, 0); | ||
| 52 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,10 +44,12 @@ const ARGS = [ | |||
| 44 | 44 | const report = reports[0]; | |
| 45 | 45 | helper.validate(report); | |
| 46 | 46 | ||
| 47 | + const content = require(report); | ||
| 47 | 48 | // Errors occur in a context where env is not available, so thread ID is | |
| 48 | 49 | // unknown. Assert this, to verify that the underlying env-less situation is | |
| 49 | 50 | // actually reached. | |
| 50 | - assert.strictEqual(require(report).header.threadId, null); | ||
| 51 | + assert.strictEqual(content.header.threadId, null); | ||
| 52 | + assert.strictEqual(content.header.trigger, 'OOMError'); | ||
| 51 | 53 | } | |
| 52 | 54 | ||
| 53 | 55 | { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments