stack-traces.js 13.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//     * Neither the name of Google Inc. nor the names of its
//       contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

function testMethodNameInference() {
  function Foo() { }
  Foo.prototype.bar = function () { FAIL; };
  (new Foo).bar();
}

function testNested() {
  function one() {
    function two() {
      function three() {
        FAIL;
      }
      three();
    }
    two();
  }
  one();
}

function testArrayNative() {
  [1, 2, 3].map(function () { FAIL; });
}

function testImplicitConversion() {
  function Nirk() { }
  Nirk.prototype.valueOf = function () { FAIL; };
  return 1 + (new Nirk);
}

function testEval() {
  eval("function Doo() { FAIL; }; Doo();");
}

function testNestedEval() {
  var x = "FAIL";
  eval("function Outer() { eval('function Inner() { eval(x); }'); Inner(); }; Outer();");
}

66
function testEvalWithSourceURL() {
67
  eval("function Doo() { FAIL; }; Doo();\n//# sourceURL=res://name");
68 69 70 71
}

function testNestedEvalWithSourceURL() {
  var x = "FAIL";
72
  var innerEval = 'function Inner() { eval(x); }\n//@ sourceURL=res://inner-eval';
73
  eval("function Outer() { eval(innerEval); Inner(); }; Outer();\n//# sourceURL=res://outer-eval");
74 75
}

76 77 78 79 80 81 82 83 84 85
function testValue() {
  Number.prototype.causeError = function () { FAIL; };
  (1).causeError();
}

function testConstructor() {
  function Plonk() { FAIL; }
  new Plonk();
}

86 87 88 89 90 91 92 93 94 95 96
function testRenamedMethod() {
  function a$b$c$d() { return FAIL; }
  function Wookie() { }
  Wookie.prototype.d = a$b$c$d;
  (new Wookie).d();
}

function testAnonymousMethod() {
  (function () { FAIL }).call([1, 2, 3]);
}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
function testFunctionName() {
  function gen(name, counter) {
    var f = function foo() {
      if (counter === 0) {
        FAIL;
      }
      gen(name, counter - 1)();
    }
    if (counter === 4) {
      Object.defineProperty(f, 'name', {get: function(){ throw 239; }});
    } else if (counter == 3) {
      Object.defineProperty(f, 'name', {value: 'boo' + '_' + counter});
    } else {
      Object.defineProperty(f, 'name', {writable: true});
      if (counter === 2)
        f.name = 42;
      else
        f.name = name + '_' + counter;
    }
    return f;
  }
  gen('foo', 4)();
}

function testFunctionInferredName() {
  var f = function() {
    FAIL;
  }
  f();
}

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
function CustomError(message, stripPoint) {
  this.message = message;
  Error.captureStackTrace(this, stripPoint);
}

CustomError.prototype.toString = function () {
  return "CustomError: " + this.message;
};

function testDefaultCustomError() {
  throw new CustomError("hep-hey", undefined);
}

function testStrippedCustomError() {
  throw new CustomError("hep-hey", CustomError);
}

145 146 147 148 149 150 151 152 153 154 155 156
MyObj = function() { FAIL; }

MyObjCreator = function() {}

MyObjCreator.prototype.Create = function() {
  return new MyObj();
}

function testClassNames() {
  (new MyObjCreator).Create();
}

157 158
// Utility function for testing that the expected strings occur
// in the stack trace produced when running the given function.
159
function testTrace(name, fun, expected, unexpected) {
160 161 162 163 164
  var threw = false;
  try {
    fun();
  } catch (e) {
    for (var i = 0; i < expected.length; i++) {
165
      assertTrue(e.stack.indexOf(expected[i]) != -1,
166
                 name + " doesn't contain expected[" + i + "] stack = " + e.stack);
167
    }
168 169
    if (unexpected) {
      for (var i = 0; i < unexpected.length; i++) {
170 171
        assertEquals(e.stack.indexOf(unexpected[i]), -1,
                     name + " contains unexpected[" + i + "]");
172 173
      }
    }
174 175
    threw = true;
  }
176
  assertTrue(threw, name + " didn't throw");
177 178 179 180 181 182 183 184
}

// Test that the error constructor is not shown in the trace
function testCallerCensorship() {
  var threw = false;
  try {
    FAIL;
  } catch (e) {
185 186
    assertEquals(-1, e.stack.indexOf('at new ReferenceError'),
                 "CallerCensorship contained new ReferenceError");
187 188
    threw = true;
  }
189
  assertTrue(threw, "CallerCensorship didn't throw");
190 191 192 193 194 195 196 197 198 199 200 201
}

// Test that the explicit constructor call is shown in the trace
function testUnintendedCallerCensorship() {
  var threw = false;
  try {
    new ReferenceError({
      toString: function () {
        FAIL;
      }
    });
  } catch (e) {
202 203
    assertTrue(e.stack.indexOf('at new ReferenceError') != -1,
               "UnintendedCallerCensorship didn't contain new ReferenceError");
204 205
    threw = true;
  }
206
  assertTrue(threw, "UnintendedCallerCensorship didn't throw");
207 208 209 210 211 212 213 214 215
}

// If an error occurs while the stack trace is being formatted it should
// be handled gracefully.
function testErrorsDuringFormatting() {
  function Nasty() { }
  Nasty.prototype.foo = function () { throw new RangeError(); };
  var n = new Nasty();
  n.__defineGetter__('constructor', function () { CONS_FAIL; });
216
  assertThrows(()=>n.foo(), RangeError);
217 218 219
  // Now we can't even format the message saying that we couldn't format
  // the stack frame.  Put that in your pipe and smoke it!
  ReferenceError.prototype.toString = function () { NESTED_FAIL; };
220
  assertThrows(()=>n.foo(), RangeError);
221 222 223
}


224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
// Poisonous object that throws a reference error if attempted converted to
// a primitive values.
var thrower = { valueOf: function() { FAIL; },
                toString: function() { FAIL; } };

// Tests that a native constructor function is included in the
// stack trace.
function testTraceNativeConstructor(nativeFunc) {
  var nativeFuncName = nativeFunc.name;
  try {
    new nativeFunc(thrower);
    assertUnreachable(nativeFuncName);
  } catch (e) {
    assertTrue(e.stack.indexOf(nativeFuncName) >= 0, nativeFuncName);
  }
}

// Tests that a native conversion function is included in the
// stack trace.
function testTraceNativeConversion(nativeFunc) {
  var nativeFuncName = nativeFunc.name;
  try {
    nativeFunc(thrower);
    assertUnreachable(nativeFuncName);
  } catch (e) {
    assertTrue(e.stack.indexOf(nativeFuncName) >= 0, nativeFuncName);
  }
}


function testOmittedBuiltin(throwing, omitted) {
255
  var reached = false;
256 257
  try {
    throwing();
258
    reached = true;
259 260
  } catch (e) {
    assertTrue(e.stack.indexOf(omitted) < 0, omitted);
261 262
  } finally {
    assertFalse(reached);
263 264 265 266
  }
}


267
testTrace("testArrayNative", testArrayNative, ["Array.map"]);
268 269 270 271 272
testTrace("testNested", testNested, ["at one", "at two", "at three"]);
testTrace("testMethodNameInference", testMethodNameInference, ["at Foo.bar"]);
testTrace("testImplicitConversion", testImplicitConversion, ["at Nirk.valueOf"]);
testTrace("testEval", testEval, ["at Doo (eval at testEval"]);
testTrace("testNestedEval", testNestedEval, ["eval at Inner (eval at Outer"]);
273 274 275 276 277
testTrace("testEvalWithSourceURL", testEvalWithSourceURL,
    [ "at Doo (res://name:1:18)" ]);
testTrace("testNestedEvalWithSourceURL", testNestedEvalWithSourceURL,
    [" at Inner (res://inner-eval:1:20)",
     " at Outer (res://outer-eval:1:37)"]);
278 279 280 281
testTrace("testValue", testValue, ["at Number.causeError"]);
testTrace("testConstructor", testConstructor, ["new Plonk"]);
testTrace("testRenamedMethod", testRenamedMethod, ["Wookie.a$b$c$d [as d]"]);
testTrace("testAnonymousMethod", testAnonymousMethod, ["Array.<anonymous>"]);
282 283 284
testTrace("testFunctionName", testFunctionName,
    [" at foo_0 ", " at foo_1", " at foo ", " at boo_3 ", " at foo "]);
testTrace("testFunctionInferredName", testFunctionInferredName, [" at f "]);
285 286
testTrace("testDefaultCustomError", testDefaultCustomError,
    ["hep-hey", "new CustomError"],
287
    ["collectStackTrace"]);
288 289
testTrace("testStrippedCustomError", testStrippedCustomError, ["hep-hey"],
    ["new CustomError", "collectStackTrace"]);
290 291
testTrace("testClassNames", testClassNames,
          ["new MyObj", "MyObjCreator.Create"], ["as Create"]);
292 293 294
testCallerCensorship();
testUnintendedCallerCensorship();
testErrorsDuringFormatting();
295 296 297 298 299 300 301 302 303 304 305 306 307

testTraceNativeConversion(String);  // Does ToString on argument.
testTraceNativeConversion(RegExp);  // Does ToString on argument.

testTraceNativeConstructor(String);  // Does ToString on argument.
testTraceNativeConstructor(RegExp);  // Does ToString on argument.

// Omitted because QuickSort has builtins object as receiver, and is non-native
// builtin.
testOmittedBuiltin(function(){ [thrower, 2].sort(function (a,b) {
                                                     (b < a) - (a < b); });
                   }, "QuickSort");

308
var reached = false;
309
var error = new Error();
310
error.toString = function() { reached = true; };
311
error.stack;
312
assertFalse(reached);
313

314
reached = false;
315
error = new Error();
316 317
Array.prototype.push = function(x) { reached = true; };
Array.prototype.join = function(x) { reached = true; };
318
error.stack;
319
assertFalse(reached);
320 321 322 323 324 325 326 327 328 329 330 331

var fired = false;
error = new Error({ toString: function() { fired = true; } });
assertTrue(fired);
error.stack;
assertTrue(fired);

// Check that throwing exception in a custom stack trace formatting function
// does not lead to recursion.
Error.prepareStackTrace = function() { throw new Error("abc"); };
var message;
try {
332 333 334 335 336
  try {
    throw new Error();
  } catch (e) {
    e.stack;
  }
337 338 339 340 341 342 343 344
} catch (e) {
  message = e.message;
}

assertEquals("abc", message);

// Test that modifying Error.prepareStackTrace by itself works.
Error.prepareStackTrace = function() { Error.prepareStackTrace = "custom"; };
345
new Error().stack;
346 347

assertEquals("custom", Error.prepareStackTrace);
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367

// Check that the formatted stack trace can be set to undefined.
error = new Error();
error.stack = undefined;
assertEquals(undefined, error.stack);

// Check that the stack trace accessors are not forcibly set.
var my_error = {};
Object.freeze(my_error);
assertThrows(function() { Error.captureStackTrace(my_error); });

my_error = {};
Object.preventExtensions(my_error);
assertThrows(function() { Error.captureStackTrace(my_error); });

var fake_error = {};
my_error = new Error();
var stolen_getter = Object.getOwnPropertyDescriptor(my_error, 'stack').get;
Object.defineProperty(fake_error, 'stack', { get: stolen_getter });
assertEquals(undefined, fake_error.stack);
jgruber's avatar
jgruber committed
368 369 370 371 372 373 374 375 376 377 378 379 380 381

// Check that overwriting the stack property during stack trace formatting
// does not crash.
error = new Error();
error.__defineGetter__("name", function() { error.stack = "abc"; });
assertEquals("abc", error.stack);

error = new Error();
error.__defineGetter__("name", function() { delete error.stack; });
assertEquals(undefined, error.stack);

// Check that repeated trace collection does not crash.
error = new Error();
Error.captureStackTrace(error);
382

383 384 385 386 387 388 389
// Check property descriptor.
var o = {};
Error.captureStackTrace(o);
assertEquals([], Object.keys(o));
var desc = Object.getOwnPropertyDescriptor(o, "stack");
assertFalse(desc.enumerable);
assertTrue(desc.configurable);
390
assertTrue(desc.writable);
391

392 393
// Check that exceptions thrown within prepareStackTrace throws an exception.
Error.prepareStackTrace = function(e, frames) { throw 42; }
394
assertThrows(() => new Error().stack);
395 396 397

// Check that we don't crash when CaptureSimpleStackTrace returns undefined.
var o = {};
398
var oldStackTraceLimit = Error.stackTraceLimit;
399 400
Error.stackTraceLimit = "not a number";
Error.captureStackTrace(o);
401
Error.stackTraceLimit = oldStackTraceLimit;
402 403 404 405 406 407 408 409 410 411 412

// Check that we don't crash when a callsite's function's script is empty.
Error.prepareStackTrace = function(e, frames) {
  assertEquals(undefined, frames[0].getEvalOrigin());
}
try {
  DataView();
  assertUnreachable();
} catch (e) {
  assertEquals(undefined, e.stack);
}
jgruber's avatar
jgruber committed
413

414 415 416
// Check that a tight recursion in prepareStackTrace throws when accessing
// stack. Trying again without a custom formatting function formats correctly.
var err = new Error("abc");
jgruber's avatar
jgruber committed
417 418
Error.prepareStackTrace = () => Error.prepareStackTrace();
try {
419 420
  err.stack;
  assertUnreachable();
jgruber's avatar
jgruber committed
421
} catch (e) {
422
  err = e;
jgruber's avatar
jgruber committed
423
}
424 425 426 427 428 429 430 431 432 433 434 435 436 437

Error.prepareStackTrace = undefined;
assertTrue(
    err.stack.indexOf("RangeError: Maximum call stack size exceeded") != -1);
assertTrue(err.stack.indexOf("prepareStackTrace") != -1);

// Check that the callsite constructor throws.

Error.prepareStackTrace = (e,s) => s;
var constructor = new Error().stack[0].constructor;

assertThrows(() => constructor.call());
assertThrows(() => constructor.call(
    null, {}, () => undefined, {valueOf() { return 0 }}, false));