v8/test/debugger/debug/debug-evaluate-repl-mode.js
Luis Fernando Pardo Sixtos 0acdf36510 Adding support for const redeclaration on REPL mode.
This change adds support for `const` redeclaration on REPL mode with
the semantincs recommended in the design doc:

1) REPL scripts should not be able to reassign bindings to `const`
   variables.

2) Re-declaring `const` variables of page scripts is not allowed in
   REPL scripts.

3) Re-declearing `const` variables is not allowed in the same REPL
   script.

4) `const` re-declaration is allowed across separate REPL scripts.

5) Old references to previously declared variables get updated with the
   new value, even those references from within optimized functions.

Design doc: https://goo.gle/devtools-const-repl

Bug: chromium:1076427
Change-Id: Ic73d2ae7fcfbfc1f5b58f61e0c3c69e9c4d85d77
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2865721
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Mythri Alle <mythria@chromium.org>
Commit-Queue: Luis Fernando Pardo Sixtos <lpardosixtos@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#74510}
2021-05-11 16:47:04 +00:00

417 lines
14 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.
Debug = debug.Debug
const evaluate = Debug.evaluateGlobalREPL;
const evaluateNonREPL = (source) => Debug.evaluateGlobal(source, false).value();
(async () => {
// Declare let and get value
let result;
result = await evaluate("let x = 7;");
result = await evaluate("x;");
assertEquals(7, result);
// Re-declare in the same script after declaration in another script.
assertThrows(() => evaluate("let x = 8; let x = 9;"));
result = await evaluate("x;");
assertEquals(7, result);
// Re-declare let as let
assertDoesNotThrow(async () => result = await evaluate("let x = 8;"));
result = await evaluate("x;");
assertEquals(8, result);
await evaluate("let x = 8;");
// Close over let. Inner function is only pre-parsed.
result = await evaluate("function getter() { return x; }");
assertEquals(undefined, result);
result = await evaluate("getter();");
assertEquals(8, result);
result = await evaluate("x = 9;");
assertEquals(9, result);
result = await evaluate("x;");
assertEquals(9, result);
result = await evaluate("getter();");
assertEquals(9, result);
// Modifies the original x; does not create a new one/shadow.
result = await evaluate("let x = 10;");
assertEquals(undefined, result);
result = await evaluate("x;");
assertEquals(10, result);
result = await evaluate("getter();");
assertEquals(10, result);
await evaluate("let x = 10");
// Check store from an inner scope.
result = await evaluate("{ let z; x = 11; } x;");
assertEquals(11, result);
// Check re-declare from an inner scope does nothing.
result = await evaluate("{ let z; let x = 12; } x;");
assertEquals(11, result);
assertThrowsAsync(evaluate("{ let qq = 10; } qq;"),
ReferenceError, "qq is not defined");
// Re-declare in the same script (no previous declaration).
assertThrows(() => result = evaluate("let y = 7; let y = 8;"),
SyntaxError, "Identifier 'y' has already been declared");
// Check TDZ; use before initialization.
// Do not check exact error message, it depends on the path taken through
// the IC machinery and changes sometimes, causing the test to be flaky.
assertThrowsAsync(evaluate("a; let a = 7;"), ReferenceError);
assertThrowsAsync(evaluate("a;"), ReferenceError);
// This is different to non-REPL mode, which throws the kUndefined error here.
assertThrowsAsync(evaluate("a = 7;"),
ReferenceError, "Cannot access 'a' before initialization");
result = await evaluate("let a = 8;");
assertEquals(undefined, result);
result = await evaluate("a;")
assertEquals(8, result);
// Check TDZ; store before initialization.
assertThrowsAsync(evaluate("b = 10; let b;"),
ReferenceError, "Cannot access 'b' before initialization");
// Check that b is still broken.
assertThrowsAsync(evaluate("b = 10; let b;"),
ReferenceError, "Cannot access 'b' before initialization");
// Check that b is still broken when the let defines a value.
assertThrowsAsync(evaluate("b = 10; let b = 7;"),
ReferenceError, "Cannot access 'b' before initialization");
result = await evaluate("let b = 11;");
assertEquals(undefined, result);
// We fixed 'b'!
result = await evaluate("b;");
assertEquals(11, result);
// Check that class works the same. Internally there is no difference between
// class and let so we don't test as extensively as for let.
result = evaluate("class K {};");
assertDoesNotThrow(() => result = evaluate("class K {};"));
// many tests for normal/repl script interactions.
// tests with let x = await
// result = evaluate("toString;");
// Declare const and get value
result = await evaluate("const c = 7;");
result = await evaluate("c;");
assertEquals(7, result);
// Re-declare in the same script after declaration in another script.
assertThrows(() => evaluate("let c = 8; let c = 9;"));
result = await evaluate("c;");
assertEquals(7, result);
// Re-declare const as const
result = await evaluate("const c = 8;");
result = await evaluate("c;");
assertEquals(8, result);
// Assign to const
assertThrowsAsync(evaluate("c = 11;"),
TypeError, "Assignment to constant variable.");
result = await evaluate("c;");
assertEquals(8, result);
await evaluate("const c = 8;");
// Close over const. Inner function is only pre-parsed.
result = await evaluate("function getter() { return c; }");
assertEquals(undefined, result);
result = await evaluate("getter();");
assertEquals(8, result);
// Modifies the original c; does not create a new one/shadow.
result = await evaluate("const c = 10;");
assertEquals(undefined, result);
result = await evaluate("c;");
assertEquals(10, result);
result = await evaluate("getter();");
assertEquals(10, result);
await evaluate("const c = 10");
// Check store from an inner scope throws error.
assertThrowsAsync(evaluate("{ let z; c = 11; };"),
TypeError, "Assignment to constant variable.");
result = await evaluate("c;");
assertEquals(10, result);
// Check re-declare from an inner scope does nothing.
result = await evaluate("{ let z; const c = 12; } c;");
assertEquals(10, result);
assertThrowsAsync(evaluate("{ const qq = 10; } qq;"),
ReferenceError, "qq is not defined");
// Const vs. const in same script.
assertThrows(() => result = evaluate("const d = 9; const d = 10;"),
SyntaxError, "Identifier 'd' has already been declared");
// Check TDZ; const use before initialization.
assertThrowsAsync(evaluate("e; const e = 7;"), ReferenceError);
assertThrowsAsync(evaluate("e;"), ReferenceError);
result = await evaluate("const e = 8;");
assertEquals(undefined, result);
result = await evaluate("e;")
assertEquals(8, result);
// f is marked as constant in TDZ
assertThrowsAsync(evaluate("f = 10; const f = 7;"),
TypeError, ("Assignment to constant variable."));
result = await evaluate("const f = 11;");
assertEquals(undefined, result);
// We fixed 'f'!
result = await evaluate("f;");
assertEquals(11, result);
// Re-declare let as const
evaluate("let z = 10;");
assertThrows(() => result = evaluate("const z = 9;"),
SyntaxError, "Identifier 'z' has already been declared");
result = await evaluate("z;");
assertEquals(10, result)
// Re-declare const as let
result = await evaluate("const g = 12;");
assertThrows(() => result = evaluate("let g = 13;"),
SyntaxError, "Identifier 'g' has already been declared");
result = await evaluate("g;");
assertEquals(12, result);
// Let vs. const in the same script
assertThrows(() => result = evaluate("let h = 13; const h = 14;"),
SyntaxError, "Identifier 'h' has already been declared");
assertThrows(() => result = evaluate("const i = 13; let i = 14;"),
SyntaxError, "Identifier 'i' has already been declared");
// Configurable properties of the global object can be re-declared as let.
result = await evaluate(`Object.defineProperty(globalThis, 'j', {
value: 1,
configurable: true
});`);
result = await evaluate("j;");
assertEquals(1, result);
result = await evaluate("let j = 2;");
result = await evaluate("j;");
assertEquals(2, result);
// Non-configurable properties of the global object (also created by plain old
// top-level var declarations) cannot be re-declared as let.
result = await evaluate(`Object.defineProperty(globalThis, 'k', {
value: 1,
configurable: false
});`);
result = await evaluate("k;");
assertEquals(1, result);
assertThrows(() => result = evaluate("let k = 2;"),
SyntaxError, "Identifier 'k' has already been declared");
result = await evaluate("k;");
assertEquals(1, result);
// ... Except if you do it in the same script.
result = await evaluate(`Object.defineProperty(globalThis, 'k2', {
value: 1,
configurable: false
});
let k2 = 2;`);
result = await evaluate("k2;");
assertEquals(2, result);
result = await evaluate("globalThis.k2;");
assertEquals(1, result);
// But if the property is configurable then both versions are allowed.
result = await evaluate(`Object.defineProperty(globalThis, 'k3', {
value: 1,
configurable: true
});`);
result = await evaluate("k3;");
assertEquals(1, result);
result = await evaluate("let k3 = 2;");
result = await evaluate("k3;");
assertEquals(2, result);
result = await evaluate("globalThis.k3;");
assertEquals(1, result);
result = await evaluate(`Object.defineProperty(globalThis, 'k4', {
value: 1,
configurable: true
});
let k4 = 2;`);
result = await evaluate("k4;");
assertEquals(2, result);
result = await evaluate("globalThis.k4;");
assertEquals(1, result);
// Check var followed by let in the same script.
assertThrows(() => result = evaluate("var k5 = 1; let k5 = 2;"),
SyntaxError, "Identifier 'k5' has already been declared");
// In different scripts.
result = await evaluate("var k6 = 1;");
assertThrows(() => result = evaluate("let k6 = 2;"),
SyntaxError, "Identifier 'k6' has already been declared");
// Check let followed by var in the same script.
assertThrows(() => result = evaluate("let k7 = 1; var k7 = 2;"),
SyntaxError, "Identifier 'k7' has already been declared");
// In different scripts.
result = evaluate("let k8 = 1;");
assertThrows(() => result = evaluate("var k8 = 2;"),
SyntaxError, "Identifier 'k8' has already been declared");
// Check var followed by var in the same script.
result = await evaluate("var k9 = 1; var k9 = 2;");
result = await evaluate("k9;");
assertEquals(2, result);
// In different scripts.
result = await evaluate("var k10 = 1;");
result = await evaluate("var k10 = 2;");
result = await evaluate("k10;");
assertEquals(2, result);
result = await evaluate("globalThis.k10;");
assertEquals(2, result);
// typeof should not throw for undeclared variables
result = await evaluate("typeof k11");
assertEquals("undefined", result);
// Non-configurable properties of the global object (also created by plain old
// top-level var declarations) cannot be re-declared as const.
result = await evaluate(`Object.defineProperty(globalThis, 'k12', {
value: 1,
configurable: false
});`);
result = await evaluate("k12;");
assertEquals(1, result);
assertThrows(() => result = evaluate("const k12 = 2;"),
SyntaxError, "Identifier 'k12' has already been declared");
result = await evaluate("k12;");
assertEquals(1, result);
// ... Except if you do it in the same script.
result = await evaluate(`Object.defineProperty(globalThis, 'k13', {
value: 1,
configurable: false
});
const k13 = 2;`);
result = await evaluate("k13;");
assertEquals(2, result);
result = await evaluate("globalThis.k13;");
assertEquals(1, result);
// But if the property is configurable then both versions are allowed.
result = await evaluate(`Object.defineProperty(globalThis, 'k14', {
value: 1,
configurable: true
});`);
result = await evaluate("k14;");
assertEquals(1, result);
result = await evaluate("const k14 = 2;");
result = await evaluate("k14;");
assertEquals(2, result);
result = await evaluate("globalThis.k14;");
assertEquals(1, result);
result = await evaluate(`Object.defineProperty(globalThis, 'k15', {
value: 1,
configurable: true
});
const k15 = 2;`);
result = await evaluate("k15;");
assertEquals(2, result);
result = await evaluate("globalThis.k15;");
assertEquals(1, result);
// Test lets with names on the object prototype e.g. toString to make sure
// it only works for own properties.
// result = evaluate("let valueOf;");
// REPL vs. non-REPL scripts
// We can still read let values cross-mode.
result = evaluateNonREPL("let l1 = 1; let l2 = 2; let l3 = 3;");
result = await evaluate("l1;");
assertEquals(1, result);
// But we can't re-declare page script lets in a REPL script. We might want to
// later.
assertThrows(() => result = evaluate("let l1 = 2;"),
SyntaxError, "Identifier 'l1' has already been declared");
assertThrows(() => result = evaluate("var l2 = 3;"),
SyntaxError, "Identifier 'l2' has already been declared");
assertThrows(() => result = evaluate("const l3 = 4;"),
SyntaxError, "Identifier 'l3' has already been declared");
// Re-declaring var across modes works.
result = evaluateNonREPL("var l4 = 1; const l5 = 1;");
result = await evaluate("var l4 = 2;");
result = await evaluate("l4;");
assertEquals(2, result);
// Const doesn't.
assertThrows(() => result = evaluate("const l5 = 2;"),
SyntaxError, "Identifier 'l5' has already been declared") ;
result = await evaluate("l5;");
assertEquals(1, result);
// Now REPL followed by non-REPL
result = await evaluate("let l6 = 1; const l7 = 2; let l8 = 3;");
result = evaluateNonREPL("l7;");
assertEquals(2, result);
result = evaluateNonREPL("l6;");
assertEquals(1, result);
// Check that the pattern of `l9; let l9;` does not throw for REPL scripts.
// If REPL scripts behaved the same as normal scripts, this case would
// re-introduce the hole in 'l9's script context slot, causing the IC and feedback
// to 'lie' about the current state.
result = await evaluate("let l9;");
result = await evaluate("l9; let l9;"),
assertEquals(undefined, await evaluate('l9;'));
// Check that binding and re-declaring a function via let works.
result = evaluate("let fn1 = function() { return 21; }");
assertEquals(21, fn1());
result = evaluate("let fn1 = function() { return 42; }");
assertEquals(42, fn1());
// Check that lazily parsed functions that bind a REPL-let variable work.
evaluate("let l10 = 21;");
evaluate("let l10 = 42; function fn2() { return l10; }");
evaluate("let l10 = 'foo';");
assertEquals("foo", fn2());
// Check that binding and re-declaring a function via const works.
result = evaluate("const fn3 = function() { return 21; }");
assertEquals(21, fn3());
result = evaluate("const fn3 = function() { return 42; }");
assertEquals(42, fn3());
// Check that lazily parsed functions that bind a REPL-const variable work.
evaluate("const l11 = 21;");
evaluate("const l11 = 42; function fn4() { return l11; }");
evaluate("const l11 = 'foo1';");
assertEquals("foo1", fn4());
})().catch(e => {
print(e);
print(e.stack);
%AbortJS("Async test is failing");
});