7a2c371383
This CL demonstrates minimum valuable addition to existing debug evaluate without side effects mechanism. With this CL user can evaluate expressions like: [a,b] // create any kind of temporary array literals [a,b].reduce((x,y) => x + y, 0); // use reduce method [1,2,3].fill(2); // change temporary arrays The core idea: any change of the object created during evaluation without side effects is side effect free. As soon as we try to store this temporary object to object existed before evaluation we will terminate execution. Implementation: - track all objects allocated during evaluation and mark them as temporary, - patch all bytecodes which change objects. A little more details (including performance analysis): [1]. [1] https://docs.google.com/document/d/10qqAtZADspPnpYa6SEdYRxrddfKIZJIzbLtGpsZQkRo/edit# Bug: v8:7588 Change-Id: I69f7b96e1ebd7ad0022219e8213211c7be72a111 Reviewed-on: https://chromium-review.googlesource.com/972615 Commit-Queue: Aleksey Kozyatinskiy <kozyatinskiy@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#52370}
236 lines
8.4 KiB
JavaScript
236 lines
8.4 KiB
JavaScript
// 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.
|
|
|
|
Debug = debug.Debug
|
|
|
|
var exception = null;
|
|
var object_with_symbol_key = {[Symbol("a")]: 1};
|
|
var object_with_callbacks = { toString: () => "string", valueOf: () => 3};
|
|
var symbol_for_a = Symbol.for("a");
|
|
var typed_array = new Uint8Array([1, 2, 3]);
|
|
var array_buffer = new ArrayBuffer(3);
|
|
var data_view = new DataView(new ArrayBuffer(8), 0, 8);
|
|
var array = [1,2,3];
|
|
|
|
function listener(event, exec_state, event_data, data) {
|
|
if (event != Debug.DebugEvent.Break) return;
|
|
try {
|
|
function success(expectation, source) {
|
|
var result = exec_state.frame(0).evaluate(source, true).value();
|
|
if (expectation !== undefined) assertEquals(expectation, result);
|
|
}
|
|
function fail(source) {
|
|
assertThrows(() => exec_state.frame(0).evaluate(source, true),
|
|
EvalError);
|
|
}
|
|
|
|
// Test some Object functions.
|
|
success({}, `new Object()`);
|
|
success({p : 3}, `Object.create({}, { p: { value: 3 } })`);
|
|
success("[[\"a\",1],[\"b\",2]]",
|
|
`JSON.stringify(Object.entries({a:1, b:2}))`);
|
|
success({value: 1, writable: true, enumerable: true, configurable: true},
|
|
`Object.getOwnPropertyDescriptor({a: 1}, "a")`);
|
|
success("{\"a\":{\"value\":1,\"writable\":true," +
|
|
"\"enumerable\":true,\"configurable\":true}}",
|
|
`JSON.stringify(Object.getOwnPropertyDescriptors({a: 1}))`);
|
|
success(["a"], `Object.getOwnPropertyNames({a: 1})`);
|
|
success(undefined, `Object.getOwnPropertySymbols(object_with_symbol_key)`);
|
|
success({}, `Object.getPrototypeOf(Object.create({}))`);
|
|
success(true, `Object.is(Object, Object)`);
|
|
success(true, `Object.isExtensible({})`);
|
|
success(false, `Object.isFrozen({})`);
|
|
success(false, `Object.isSealed({})`);
|
|
success([1, 2], `Object.values({a:1, b:2})`);
|
|
|
|
fail(`Object.assign({}, {})`);
|
|
fail(`Object.defineProperties({}, [{p:{value:3}}])`);
|
|
fail(`Object.defineProperty({}, {p:{value:3}})`);
|
|
fail(`Object.freeze({})`);
|
|
fail(`Object.preventExtensions({})`);
|
|
fail(`Object.seal({})`);
|
|
fail(`Object.setPrototypeOf({}, {})`);
|
|
|
|
// Test some Object.prototype functions.
|
|
success(true, `({a:1}).hasOwnProperty("a")`);
|
|
success(true, `Object.prototype.isPrototypeOf({})`);
|
|
success(true, `({a:1}).propertyIsEnumerable("a")`);
|
|
success("[object Object]", `({a:1}).toString()`);
|
|
success("string", `(object_with_callbacks).toString()`);
|
|
success(3, `(object_with_callbacks).valueOf()`);
|
|
|
|
// Test Array functions.
|
|
success(true, `Array.isArray([1, 2, 3])`);
|
|
success([], `new Array()`);
|
|
success([undefined, undefined], `new Array(2)`);
|
|
success([1, 2], `new Array(1, 2)`);
|
|
fail(`Array.from([1, 2, 3])`);
|
|
fail(`Array.of(1, 2, 3)`);
|
|
var function_param = [
|
|
"forEach", "every", "some", "reduce", "reduceRight", "find", "filter",
|
|
"map", "findIndex"
|
|
];
|
|
var fails = ["toString", "join", "toLocaleString", "pop", "push", "reverse",
|
|
"shift", "unshift", "splice", "sort", "copyWithin"];
|
|
for (f of Object.getOwnPropertyNames(Array.prototype)) {
|
|
if (typeof Array.prototype[f] === "function") {
|
|
if (fails.includes(f)) {
|
|
if (function_param.includes(f)) {
|
|
fail(`[1, 2, 3].${f}(()=>{});`);
|
|
} else {
|
|
fail(`[1, 2, 3].${f}();`);
|
|
}
|
|
} else if (function_param.includes(f)) {
|
|
exec_state.frame(0).evaluate(`[1, 2, 3].${f}(()=>{});`, true);
|
|
} else {
|
|
exec_state.frame(0).evaluate(`[1, 2, 3].${f}();`, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
success([1,1,1], '[1,2,3].fill(1)');
|
|
fail(`array.fill(1)`);
|
|
|
|
// Test ArrayBuffer functions.
|
|
success(3, `array_buffer.byteLength`);
|
|
success(2, `array_buffer.slice(1, 3).byteLength`);
|
|
success(true, `ArrayBuffer.isView(typed_array)`);
|
|
|
|
// Test DataView functions.
|
|
success(undefined, `new DataView(array_buffer, 1, 2)`);
|
|
success(undefined, `data_view.buffer`);
|
|
success(undefined, `data_view.byteLength`);
|
|
success(undefined, `data_view.byteOffset`);
|
|
for (f of Object.getOwnPropertyNames(DataView.prototype)) {
|
|
if (typeof data_view[f] === 'function') {
|
|
if (f.startsWith('getBig')) {
|
|
success(0n, `data_view.${f}()`);
|
|
} else if (f.startsWith('get')) {
|
|
success(0, `data_view.${f}()`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test TypedArray functions.
|
|
success({}, `new Uint8Array()`);
|
|
success({0: 0, 1: 0}, `new Uint8Array(2)`);
|
|
success({0: 1, 1: 2, 2: 3}, `new Uint8Array(typed_array)`);
|
|
success(true, `!!typed_array.buffer`);
|
|
success(0, `typed_array.byteOffset`);
|
|
success(3, `typed_array.byteLength`);
|
|
fail(`Uint8Array.of(1, 2)`);
|
|
function_param = [
|
|
"forEach", "every", "some", "reduce", "reduceRight", "find", "filter",
|
|
"map", "findIndex"
|
|
];
|
|
fails = ["toString", "join", "toLocaleString", "reverse", "sort",
|
|
"copyWithin", "fill", "set"];
|
|
var typed_proto_proto = Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array()));
|
|
for (f of Object.getOwnPropertyNames(typed_proto_proto)) {
|
|
if (typeof typed_array[f] === "function" && f !== "constructor") {
|
|
if (fails.includes(f)) {
|
|
if (function_param.includes(f)) {
|
|
fail(`typed_array.${f}(()=>{});`);
|
|
} else {
|
|
fail(`typed_array.${f}();`);
|
|
}
|
|
} else if (function_param.includes(f)) {
|
|
exec_state.frame(0).evaluate(`typed_array.${f}(()=>{});`, true);
|
|
} else {
|
|
exec_state.frame(0).evaluate(`typed_array.${f}();`, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test Math functions.
|
|
for (f of Object.getOwnPropertyNames(Math)) {
|
|
if (typeof Math[f] === "function") {
|
|
var result = exec_state.frame(0).evaluate(
|
|
`Math.${f}(0.5, -0.5);`, true).value();
|
|
if (f != "random") assertEquals(Math[f](0.5, -0.5), result);
|
|
}
|
|
}
|
|
|
|
// Test Number functions.
|
|
success(new Number(0), `new Number()`);
|
|
for (f of Object.getOwnPropertyNames(Number)) {
|
|
if (typeof Number[f] === "function") {
|
|
success(Number[f](0.5), `Number.${f}(0.5);`);
|
|
}
|
|
}
|
|
for (f of Object.getOwnPropertyNames(Number.prototype)) {
|
|
if (typeof Number.prototype[f] === "function") {
|
|
if (f == "toLocaleString") continue;
|
|
success(Number(0.5)[f](5), `Number(0.5).${f}(5);`);
|
|
}
|
|
}
|
|
|
|
// Test String functions.
|
|
success(new String(), `new String()`);
|
|
success(" ", "String.fromCodePoint(0x20)");
|
|
success(" ", "String.fromCharCode(0x20)");
|
|
for (f of Object.getOwnPropertyNames(String.prototype)) {
|
|
if (typeof String.prototype[f] === "function") {
|
|
// Do not expect locale-specific or regexp-related functions to work.
|
|
// {Lower,Upper}Case (Locale-specific or not) do not work either
|
|
// if Intl is enabled.
|
|
if (f.indexOf("locale") >= 0) continue;
|
|
if (f.indexOf("Locale") >= 0) continue;
|
|
if (typeof Intl !== 'undefined') {
|
|
if (f == "toUpperCase") continue;
|
|
if (f == "toLowerCase") continue;
|
|
}
|
|
if (f == "normalize") continue;
|
|
if (f == "match") continue;
|
|
if (f == "search") continue;
|
|
if (f == "split" || f == "replace") {
|
|
fail(`'abcd'.${f}(2)`);
|
|
continue;
|
|
}
|
|
success("abcd"[f](2), `"abcd".${f}(2);`);
|
|
}
|
|
}
|
|
fail("'abCd'.toLocaleLowerCase()");
|
|
fail("'abcd'.toLocaleUpperCase()");
|
|
if (typeof Intl !== 'undefined') {
|
|
fail("'abCd'.toLowerCase()");
|
|
fail("'abcd'.toUpperCase()");
|
|
}
|
|
fail("'abcd'.match(/a/)");
|
|
fail("'abcd'.replace(/a/)");
|
|
fail("'abcd'.search(/a/)");
|
|
fail("'abcd'.split(/a/)");
|
|
|
|
// Test RegExp functions.
|
|
fail(`/a/.compile()`);
|
|
fail(`/a/.exec('abc')`);
|
|
fail(`/a/.test('abc')`);
|
|
fail(`/a/.toString()`);
|
|
|
|
// Test JSON functions.
|
|
success('{"abc":[1,2]}', "JSON.stringify(JSON.parse('{\"abc\":[1,2]}'))");
|
|
|
|
// Test Symbol functions.
|
|
success(undefined, `Symbol("a")`);
|
|
fail(`Symbol.for("a")`); // Symbol.for can be observed via Symbol.keyFor.
|
|
success("a", `Symbol.keyFor(symbol_for_a)`);
|
|
success("Symbol(a)", `symbol_for_a.valueOf().toString()`);
|
|
success("Symbol(a)", `symbol_for_a[Symbol.toPrimitive]().toString()`);
|
|
} catch (e) {
|
|
exception = e;
|
|
print(e, e.stack);
|
|
};
|
|
};
|
|
|
|
// Add the debug event listener.
|
|
Debug.setListener(listener);
|
|
|
|
function f() {
|
|
debugger;
|
|
};
|
|
|
|
f();
|
|
|
|
assertNull(exception);
|