f96f93128c
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}
315 lines
10 KiB
JavaScript
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");
|
|
});
|