Files
Igor Sheludko 79cdd69a1c [error] Define Error.stack property as a JavaScript accessor
... instead of the native data property.
This fixes the JS spec violation when reading a data property does not
expect any observable side effects. For example, OrdinaryGetOwnProperty
(see https://tc39.es/ecma262/#sec-ordinarygetownproperty, step 4a).

Differences to previous behaviour:
- Error.stack is defined as a JavaScript accessor property.
- all Error objects get a private "error_stack_symbol" field where
  the captured stack trace is stored (previously it was added only when
  the error was actually thrown which caused unnecessary transitions).
- Error.captureStackTrace(obj) adds public "stack" accessor property
  and private "error_stack_symbol" property to given "obj".
- calling "stack" getter/setter is a no-op in case receiver is not an
  "Error-like" object, i.e. it doesn't have a "error_stack_symbol"
  property and it doesn't have a prototype with such a property (the
  lookup stops at JSProxy or interceptor).
- the "stack" getter walks the prototype chain from receiver until it
  finds a holder with the "error_stack_symbol" property which is then
  used for computing the result.
  This is slightly different from the previous behaviour in case
  receiver's prototype chain contains multiple error objects.
- the "stack" setter walks the prototype chain from receiver until it
  finds a holder with the "error_stack_symbol" property and stores the
  value there.

Drive-by: handle lazy accessor pairs in ObjectVisitorDeepFreezer.

Bug: v8:5962
Change-Id: I109d6fbbbdb9588d13ca86d217efd9a4a11d8447
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4459251
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#87286}
2023-04-26 16:52:47 +00:00

104 lines
3.0 KiB
JavaScript

// Copyright 2019 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.
//
// Flags: --allow-natives-syntax
(function TestErrorObjectsRetainMap() {
const error1 = new Error("foo");
const error2 = new Error("bar");
assertTrue(%HaveSameMap(error1, error2));
// Trigger serialization of the stack-trace.
error1.stack;
assertTrue(%HaveSameMap(error1, error2));
error2.stack;
assertTrue(%HaveSameMap(error1, error2));
})();
(function TestPrepareStackTraceCallback() {
Error.prepareStackTrace = (error, frame) => {
return "custom stack trace No. 42";
};
const error = new Error("foo");
// Check it twice, so both code paths in the accessor are exercised.
assertEquals(error.stack, "custom stack trace No. 42");
assertEquals(error.stack, "custom stack trace No. 42");
})();
(function TestPrepareStackTraceCallbackMessesWithProperty() {
Error.prepareStackTrace = (error, frames) => {
error.stack = "Yes, we can write to this!";
return 42;
};
const error = new Error("foo");
// Check it twice. The first returns the formatting result,
// the second the value of the private symbol.
assertEquals(error.stack, 42);
assertEquals(error.stack, 42);
})();
(function TestPrepareStackTraceCallbackInstallsGetter() {
Error.prepareStackTrace = (error, frames) => {
Object.defineProperty(error, "stack", { get: () => 42 });
return "<formatted stack trace>";
};
const error = new Error("foo");
// Check it twice. The second time the accessor should be used.
assertEquals(error.stack, "<formatted stack trace>");
assertEquals(error.stack, 42);
})();
(function TestPrepareStackTraceCallbackInstallsSetter() {
Error.prepareStackTrace = (error, frames) => {
Object.defineProperty(error, "stack", { get: undefined, set: (x) => {
error[42] = x;
}});
return "<formatted stack trace>";
};
const error = new Error("foo");
// Cause the accessor to get installed.
error.stack;
error.stack = "Who needs stack traces anyway?";
assertEquals(error[42], "Who needs stack traces anyway?");
assertEquals(error.stack, undefined); // No getter.
})();
(function TestFormatStackPropertyInDictionaryMode() {
Error.prepareStackTrace = (error, frames) => {
return "<formatted stack trace>";
};
const error = new Error("foo");
error[%MaxSmi()] = 42;
assertTrue(%HasDictionaryElements(error));
// Check it twice.
assertEquals(error.stack, "<formatted stack trace>");
assertEquals(error.stack, "<formatted stack trace>");
})();
(function TestTransitionToDictionaryModeAfterFormatting() {
Error.prepareStackTrace = (error, frames) => {
return "<formatted stack trace>";
};
const error = new Error("foo");
assertFalse(%HasDictionaryElements(error));
assertEquals(error.stack, "<formatted stack trace>");
error[%MaxSmi()] = 42;
assertTrue(%HasDictionaryElements(error));
assertEquals(error.stack, "<formatted stack trace>");
})();