diff --git a/test/mjsunit/mjsunit-assertion-error.js b/test/mjsunit/mjsunit-assertion-error.js new file mode 100644 index 0000000000..08c6ff619d --- /dev/null +++ b/test/mjsunit/mjsunit-assertion-error.js @@ -0,0 +1,102 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +let fileName = 'mjsunit-assertion-error.js'; + +function addDefaultFrames(frameExpectations) { + // The last frame contains the error instantiation. + frameExpectations.unshift('assertTrue.*mjsunit.js'); + // Frist frame is the top-level script. + frameExpectations.push(fileName); +} + +function assertErrorMessage(frameExpectations, error) { + let stack = error.stack.split("\n"); + let title = stack.shift(); + assertContains('MjsUnitAssertionError', title); + addDefaultFrames(frameExpectations); + // Add default frames to the expectations. + assertEquals(frameExpectations.length, stack.length); + for (let i = 0; i < stack.length; i++) { + let frame = stack[i]; + let expectation = frameExpectations[i]; + assertTrue(frame.search(expectation) != -1, + `Frame ${i}: Did not find '${expectation}' in '${frame}'`); + } +} + +// Toplevel +try { + assertTrue(false); +} catch(e) { + assertErrorMessage([], e); +} + +// Single function. +function throwError() { + assertTrue(false); +} +try { + throwError(); + assertUnreachable(); +} catch(e) { + assertErrorMessage(['throwError'], e); +} + +// Nested function. +function outer() { + throwError(); +} +try { + outer(); + assertUnreachable(); +} catch(e) { + assertErrorMessage(['throwError', 'outer'], e); +} + +// Test Array helper nesting +try { + [1].map(throwError); + assertUnreachable(); +} catch(e) { + assertErrorMessage(['throwError', 'Array.map'], e); +} +try { + Array.prototype.map.call([1], throwError); + assertUnreachable(); +} catch(e) { + assertErrorMessage(['throwError', 'Array.map'], e); +} + +// Test eval +try { + eval("assertTrue(false);"); + assertUnreachable(); +} catch(e) { + assertErrorMessage(['eval'], e); +} + +(function testNestedEval() { + try { + eval("assertTrue(false);"); + assertUnreachable(); + } catch(e) { + assertErrorMessage(['eval', 'testNestedEval'], e); + } +})(); + + +(function testConstructor() { + class Failer { + constructor() { + assertTrue(false); + } + } + try { + new Failer(); + assertUnreachable(); + } catch(e) { + assertErrorMessage(['new Failer', 'testConstructor'], e); + } +})(); diff --git a/test/mjsunit/mjsunit.js b/test/mjsunit/mjsunit.js index 70107bb57e..a933056004 100644 --- a/test/mjsunit/mjsunit.js +++ b/test/mjsunit/mjsunit.js @@ -27,17 +27,85 @@ function MjsUnitAssertionError(message) { this.message = message; - // This allows fetching the stack trace using TryCatch::StackTrace. - this.stack = new Error("").stack; + // Temporarily install a custom stack trace formatter and restore the + // previous value. + let prevPrepareStackTrace = Error.prepareStackTrace; + try { + Error.prepareStackTrace = MjsUnitAssertionError.prepareStackTrace; + // This allows fetching the stack trace using TryCatch::StackTrace. + this.stack = new Error("MjsUnitAssertionError").stack; + } finally { + Error.prepareStackTrace = prevPrepareStackTrace; + } } +// Custom V8-specific stack trace formatter that is temporarily installed on +// the Error object. +MjsUnitAssertionError.prepareStackTrace = function(error, stack) { + // Trigger default formatting with recursion. + try { + // Filter-out all but the first mjsunit frame. + let filteredStack = []; + let inMjsunit = true; + for (let i = 0; i < stack.length; i++) { + let frame = stack[i]; + if (inMjsunit) { + let file = frame.getFileName(); + if (!file || !file.endsWith("mjsunit.js")) { + inMjsunit = false; + // Push the last mjsunit frame, typically containing the assertion + // function. + if (i > 0) filteredStack.push(stack[i-1]); + filteredStack.push(stack[i]); + } + continue; + } + filteredStack.push(frame); + } + stack = filteredStack; + + // Infer function names and calculate {max_name_length} + let max_name_length = 0; + stack.forEach(each => { + let name = each.getFunctionName(); + if (name == null) name = ""; + if (each.isEval()) { + name = name; + } else if (each.isConstructor()) { + name = "new " + name; + } else if (each.isNative()) { + name = "native " + name; + } else if (!each.isToplevel()) { + name = each.getTypeName() + "." + name; + } + each.name = name; + max_name_length = Math.max(name.length, max_name_length) + }); + + // Format stack frames. + stack = stack.map(each => { + let frame = " at " + each.name.padEnd(max_name_length); + let fileName = each.getFileName(); + if (each.isEval()) return frame + " " + each.getEvalOrigin(); + frame += " " + (fileName ? fileName : ""); + let line= each.getLineNumber(); + frame += " " + (line ? line : ""); + let column = each.getColumnNumber(); + frame += (column ? ":" + column : ""); + return frame; + }); + return "" + error.message + "\n" + stack.join("\n"); + } catch(e) {}; + return error.stack; +} + + /* * This file is included in all mini jsunit test cases. The test * framework expects lines that signal failed tests to start with * the f-word and ignore all other lines. */ - MjsUnitAssertionError.prototype.toString = function () { return this.message + "\n\nStack: " + this.stack; }; diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status index da39dd9295..e0def4aa9b 100644 --- a/test/mjsunit/mjsunit.status +++ b/test/mjsunit/mjsunit.status @@ -649,4 +649,11 @@ 'whitespaces': [SKIP], }], # variant == wasm_traps +############################################################################## +['no_harness', { + # skip assertion tests since the stack trace is broken if mjsunit is + # included in the snapshot + 'mjsunit-assertion-error' : [SKIP], +}], # no_harness + ] diff --git a/tools/run-deopt-fuzzer.py b/tools/run-deopt-fuzzer.py index 27f5cc7a5a..742d8552ef 100755 --- a/tools/run-deopt-fuzzer.py +++ b/tools/run-deopt-fuzzer.py @@ -405,6 +405,7 @@ def Execute(arch, mode, args, options, suites, workspace): "novfp3": False, "predictable": False, "byteorder": sys.byteorder, + "no_harness": False, } all_tests = [] num_tests = 0 diff --git a/tools/run-tests.py b/tools/run-tests.py index 5ee50c20f1..2123a2015e 100755 --- a/tools/run-tests.py +++ b/tools/run-tests.py @@ -836,6 +836,7 @@ def Execute(arch, mode, args, options, suites): "novfp3": options.novfp3, "predictable": options.predictable, "byteorder": sys.byteorder, + "no_harness": options.no_harness } all_tests = [] num_tests = 0