v8/test/debugger/debug/debug-evaluate-repl-mode.js
Simon Zünd f96f93128c Reland "Implement top-level await for REPL mode"
This is a reland of 5bddc0e142

The original CL was speculatively reverted as it was suspected to
cause failures on the non-determinism bot. This was ultimately
confirmed to not be the case, so this CL is safe to reland as-is.

Original change's description:
> Implement top-level await for REPL mode
>
> Design doc: bit.ly/v8-repl-mode
>
> This CL allows the usage of 'await' without wrapping code in an async
> function when using REPL mode in global evaluate. REPL mode evaluate
> is changed to *always* return a Promise. The resolve value of the
> promise is the completion value of the REPL script.
>
> The implementation is based on two existing mechanisms:
>   - Similar to async functions, the content of a REPL script is
>     enclosed in a synthetic 'try' block. Any thrown error
>     is used to reject the Promise of the REPL script.
>
>   - The content of the synthetic 'try' block is also re-written the
>     same way a normal script is. This is, artificial assignments to
>     a ".result" variable are inserted to simulate a completion
>     value. The difference for REPL scripts is, that ".result" is
>     used to resolve the Promise of the REPL script.
>
>   - ".result" is not returned directly but wrapped in an object
>     literal: "{ .repl_result: .result}". This is done to prevent
>     resolved promises from being chained and resolved prematurely:
>
>     > Promse.resolve(42);
>
>     should evaluate to a promise, not 42.
>
> Bug: chromium:1021921
> Change-Id: I00a5aafd9126ca7c97d09cd8787a3aec2821a67f
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1900464
> Reviewed-by: Yang Guo <yangguo@chromium.org>
> Reviewed-by: Leszek Swirski <leszeks@chromium.org>
> Reviewed-by: Toon Verwaest <verwaest@chromium.org>
> Commit-Queue: Simon Zünd <szuend@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#65273}

TBR: yangguo@chromium.org,verwaest@chromium.org
Bug: chromium:1021921
Change-Id: I95c5dc17593161009a533188f91b4cd67234c32f
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1954388
Reviewed-by: Simon Zünd <szuend@chromium.org>
Reviewed-by: Yang Guo <yangguo@chromium.org>
Commit-Queue: Simon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65360}
2019-12-06 10:13:00 +00:00

315 lines
10 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;");
// 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 const
result = await evaluate("const c = 10;");
assertThrows(() => result = evaluate("const c = 11;"),
SyntaxError, "Identifier 'c' has already been declared");
result = await evaluate("c;");
assertEquals(10, result);
// Const vs. const in same script.
assertThrows(() => result = evaluate("const d = 9; const d = 10;"),
SyntaxError, "Identifier 'd' has already been declared");
// Close over const
result = await evaluate("const e = 10; function closure() { return e; }");
result = await evaluate("e;");
assertEquals(10, result);
// Assign to const
assertThrowsAsync(evaluate("e = 11;"),
TypeError, "Assignment to constant variable.");
result = await evaluate("e;");
assertEquals(10, result);
result = await evaluate("closure();");
assertEquals(10, result);
// Assign to const in TDZ
assertThrowsAsync(evaluate("f; const f = 11;"),
ReferenceError, "Cannot access 'f' before initialization");
assertThrowsAsync(evaluate("f = 12;"),
TypeError, "Assignment to constant variable.");
// 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);
// 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());
})().catch(e => {
print(e);
print(e.stack);
%AbortJS("Async test is failing");
});