Skip to content

Commit

Permalink
Fix tdz checks in transform-block-scoping plugin (#9498)
Browse files Browse the repository at this point in the history
* Better tdz tests

- Use jest's expect.toThrow/expect.not.toThrow
- Add input/output tests

* Fix basic tdz (a = 2; let a)

Fixes #6848

* Make _guessExecutionStatusRelativeTo more robust

* Add tests

* Return less "unkown" execution status

* "function" execution status does not exist

* Fix recursive functions

* Update helper version

* "finally" blocks are always executed

* Typo
  • Loading branch information
nicolo-ribaudo committed Jul 21, 2019
1 parent 9bc9571 commit fced5ce
Show file tree
Hide file tree
Showing 65 changed files with 507 additions and 122 deletions.
31 changes: 18 additions & 13 deletions packages/babel-helpers/src/helpers.js
Expand Up @@ -817,18 +817,6 @@ helpers.taggedTemplateLiteralLoose = helper("7.0.0-beta.0")`
}
`;

helpers.temporalRef = helper("7.0.0-beta.0")`
import undef from "temporalUndefined";
export default function _temporalRef(val, name) {
if (val === undef) {
throw new ReferenceError(name + " is not defined - temporal dead zone");
} else {
return val;
}
}
`;

helpers.readOnlyError = helper("7.0.0-beta.0")`
export default function _readOnlyError(name) {
throw new Error("\\"" + name + "\\" is read-only");
Expand All @@ -842,7 +830,24 @@ helpers.classNameTDZError = helper("7.0.0-beta.0")`
`;

helpers.temporalUndefined = helper("7.0.0-beta.0")`
export default {};
// This function isn't mean to be called, but to be used as a reference.
// We can't use a normal object because it isn't hoisted.
export default function _temporalUndefined() {}
`;

helpers.tdz = helper("7.5.5")`
export default function _tdzError(name) {
throw new ReferenceError(name + " is not defined - temporal dead zone");
}
`;

helpers.temporalRef = helper("7.0.0-beta.0")`
import undef from "temporalUndefined";
import err from "tdz";
export default function _temporalRef(val, name) {
return val === undef ? err(name) : val;
}
`;

helpers.slicedToArray = helper("7.0.0-beta.0")`
Expand Down
18 changes: 11 additions & 7 deletions packages/babel-plugin-transform-block-scoping/src/index.js
Expand Up @@ -16,7 +16,7 @@ export default declare((api, opts) => {
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
}
if (typeof tdzEnabled !== "boolean") {
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
throw new Error(`.tdz must be a boolean, or undefined`);
}

return {
Expand All @@ -33,11 +33,13 @@ export default declare((api, opts) => {

for (let i = 0; i < node.declarations.length; i++) {
const decl = node.declarations[i];
if (decl.init) {
const assign = t.assignmentExpression("=", decl.id, decl.init);
assign._ignoreBlockScopingTDZ = true;
nodes.push(t.expressionStatement(assign));
}
const assign = t.assignmentExpression(
"=",
decl.id,
decl.init || scope.buildUndefinedNode(),
);
assign._ignoreBlockScopingTDZ = true;
nodes.push(t.expressionStatement(assign));
decl.init = this.addHelper("temporalUndefined");
}

Expand Down Expand Up @@ -181,6 +183,8 @@ const letReferenceBlockVisitor = traverse.visitors.merge([
// simply rename the variables.
if (state.loopDepth > 0) {
path.traverse(letReferenceFunctionVisitor, state);
} else {
path.traverse(tdzVisitor, state);
}
return path.skip();
},
Expand Down Expand Up @@ -756,7 +760,7 @@ class BlockScoping {
closurify: false,
loopDepth: 0,
tdzEnabled: this.tdzEnabled,
addHelper: name => this.addHelper(name),
addHelper: name => this.state.addHelper(name),
};

if (isInLoop(this.blockPath)) {
Expand Down
27 changes: 8 additions & 19 deletions packages/babel-plugin-transform-block-scoping/src/tdz.js
@@ -1,12 +1,12 @@
import { types as t } from "@babel/core";
import { types as t, template } from "@babel/core";

function getTDZStatus(refPath, bindingPath) {
const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath);

if (executionStatus === "before") {
return "inside";
} else if (executionStatus === "after") {
return "outside";
} else if (executionStatus === "after") {
return "inside";
} else {
return "maybe";
}
Expand Down Expand Up @@ -41,7 +41,7 @@ export const visitor = {
if (bindingPath.isFunctionDeclaration()) return;

const status = getTDZStatus(path, bindingPath);
if (status === "inside") return;
if (status === "outside") return;

if (status === "maybe") {
const assert = buildTDZAssert(node, state);
Expand All @@ -57,19 +57,8 @@ export const visitor = {
} else {
path.replaceWith(assert);
}
} else if (status === "outside") {
path.replaceWith(
t.throwStatement(
t.inherits(
t.newExpression(t.identifier("ReferenceError"), [
t.stringLiteral(
`${node.name} is not defined - temporal dead zone`,
),
]),
node,
),
),
);
} else if (status === "inside") {
path.replaceWith(template.ast`${state.addHelper("tdz")}("${node.name}")`);
}
},

Expand All @@ -87,14 +76,14 @@ export const visitor = {
const id = ids[name];

if (isReference(id, path.scope, state)) {
nodes.push(buildTDZAssert(id, state));
nodes.push(id);
}
}

if (nodes.length) {
node._ignoreBlockScopingTDZ = true;
nodes.push(node);
path.replaceWithMultiple(nodes.map(t.expressionStatement));
path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n)));
}
},
},
Expand Down
Expand Up @@ -5,6 +5,7 @@ if (x) {

var innerScope = true;
var res = transform(code, {
configFile: false,
plugins: opts.plugins.concat([
function (b) {
var t = b.types;
Expand Down Expand Up @@ -34,3 +35,4 @@ if (x) {
}`;

expect(res.code).toBe(expected);
expect(innerScope).toBe(false);
@@ -1,3 +1,5 @@
f();
expect(() => {
f();

const f = function f() {}
const f = function f() {}
}).toThrow(ReferenceError);
@@ -0,0 +1,3 @@
f();

const f = function f() {}

This file was deleted.

@@ -0,0 +1,3 @@
babelHelpers.tdz("f")();

var f = function f() {};
@@ -1 +1,3 @@
let { b: d } = { d }
expect(() => {
let { b: d } = { d }
}).toThrow(ReferenceError);
@@ -0,0 +1 @@
let { b: d } = { d }

This file was deleted.

@@ -0,0 +1,5 @@
var {
b: d
} = {
d: babelHelpers.tdz("d")
};
@@ -0,0 +1,7 @@
expect(() => {
function f() {
x;
}
let x;
f();
}).not.toThrow();
@@ -0,0 +1,6 @@
function f() {
x;
}

var x;
f();
@@ -0,0 +1,7 @@
expect(() => {
function f() {
x;
}
f();
let x;
}).toThrow(ReferenceError);
@@ -0,0 +1,5 @@
function f() {
x;
}
f();
let x;
@@ -0,0 +1,6 @@
function f() {
babelHelpers.tdz("x");
}

f();
var x;
@@ -0,0 +1,6 @@
expect(() => {
function f() { x }
Math.random() === 2 && f();
let x;
f();
}).not.toThrow();
@@ -0,0 +1,4 @@
function f() { x }
Math.random() === 2 && f();
let x;
f();
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() === 2 && f();
x = void 0;
f();
@@ -0,0 +1,5 @@
function f() { x }
Math.random() === 2 && f();
let x = 3;

expect(x).toBe(3);
@@ -0,0 +1,5 @@
function f() { x }
Math.random() === 2 && f();
let x = 3;

expect(x).toBe(3);
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() === 2 && f();
x = 3;
expect(x).toBe(3);
@@ -0,0 +1,29 @@
// "random" :)
let random = (i => {
const vals = [0, 0, 1, 1];
return () => vals[i++];
})(0);

expect(() => {
function f() { x }
random() && f();
let x;
}).not.toThrow();

expect(() => {
function f() { x }
random() || f();
let x;
}).toThrow(ReferenceError);

expect(() => {
function f() { x }
random() && f();
let x;
}).toThrow(ReferenceError);

expect(() => {
function f() { x }
random() || f();
let x;
}).not.toThrow();
@@ -0,0 +1,3 @@
function f() { x }
Math.random() && f();
let x;
@@ -0,0 +1,9 @@
var x = babelHelpers.temporalUndefined;

function f() {
babelHelpers.temporalRef(x, "x");
}

Math.random() && f();
x = void 0;
void 0;
@@ -0,0 +1,17 @@
expect(() => {
function f() {
return function() { x };
}
let g = f();
let x;
g();
}).not.toThrow();

expect(() => {
function f() {
return function() { x };
}
let g = f();
g();
let x;
}).toThrow(ReferenceError);
@@ -0,0 +1,5 @@
function f() {
return function() { x };
}
f();
let x;
@@ -0,0 +1,11 @@
var x = babelHelpers.temporalUndefined;

function f() {
return function () {
babelHelpers.temporalRef(x, "x");
};
}

f();
x = void 0;
void 0;
@@ -0,0 +1,9 @@
expect(() => {
function f(i) {
if (i) f(i - 1);
x;
}

let x;
f(3);
}).not.toThrow();
@@ -0,0 +1,7 @@
function f(i) {
if (i) f(i - 1);
x;
}

let x;
f(3);
@@ -0,0 +1,7 @@
function f(i) {
if (i) f(i - 1);
x;
}

var x;
f(3);

0 comments on commit fced5ce

Please sign in to comment.