Skip to content

Commit

Permalink
fix: Deleting super property should throw (#15223)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicol貌 Ribaudo <nicolo.ribaudo@gmail.com>
Fixes #15169
  • Loading branch information
SuperSodaSea committed Dec 5, 2022
1 parent 00783f5 commit 2bba4a5
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 5 deletions.
14 changes: 14 additions & 0 deletions packages/babel-helper-member-expression-to-functions/src/index.ts
Expand Up @@ -247,6 +247,11 @@ const handle = {
} else if (parentIsCall) {
// `(a?.#b)()` to `(a == null ? void 0 : a.#b.bind(a))()`
member.replaceWith(this.boundGet(member));
} else if (
this.delete &&
parentPath.isUnaryExpression({ operator: "delete" })
) {
parentPath.replaceWith(this.delete(member));
} else {
member.replaceWith(this.get(member));
}
Expand Down Expand Up @@ -491,6 +496,12 @@ const handle = {
return;
}

// delete MEMBER -> _delete(MEMBER)
if (this.delete && parentPath.isUnaryExpression({ operator: "delete" })) {
parentPath.replaceWith(this.delete(member));
return;
}

// for (MEMBER of ARR)
// for (MEMBER in ARR)
// { KEY: MEMBER } = OBJ -> { KEY: _destructureSet(MEMBER) } = OBJ
Expand Down Expand Up @@ -562,6 +573,9 @@ export interface Handler<State> {
member: Member,
args: t.OptionalCallExpression["arguments"],
): t.Expression;
// TODO(Babel 8): Consider making this required, since `.get` doesn't
// really work as a fallback for `.delete`
delete?(this: HandlerState<State> & State, member: Member): t.Expression;
}

export interface HandlerState<State = {}> extends Handler<State> {
Expand Down
1 change: 1 addition & 0 deletions packages/babel-helper-replace-supers/package.json
Expand Up @@ -17,6 +17,7 @@
"@babel/helper-environment-visitor": "workspace:^",
"@babel/helper-member-expression-to-functions": "workspace:^",
"@babel/helper-optimise-call-expression": "workspace:^",
"@babel/template": "workspace:^",
"@babel/traverse": "workspace:^",
"@babel/types": "workspace:^"
},
Expand Down
34 changes: 29 additions & 5 deletions packages/babel-helper-replace-supers/src/index.ts
@@ -1,9 +1,11 @@
import type { NodePath, Scope } from "@babel/traverse";
import traverse from "@babel/traverse";
import type { File } from "@babel/core";
import environmentVisitor from "@babel/helper-environment-visitor";
import memberExpressionToFunctions from "@babel/helper-member-expression-to-functions";
import type { HandlerState } from "@babel/helper-member-expression-to-functions";
import optimiseCall from "@babel/helper-optimise-call-expression";
import environmentVisitor from "@babel/helper-environment-visitor";
import template from "@babel/template";
import traverse from "@babel/traverse";
import type { NodePath, Scope } from "@babel/traverse";
import {
assignmentExpression,
booleanLiteral,
Expand All @@ -16,7 +18,6 @@ import {
thisExpression,
} from "@babel/types";
import type * as t from "@babel/types";
import type { File } from "@babel/core";

// TODO (Babel 8): Don't export this.
export {
Expand Down Expand Up @@ -107,7 +108,13 @@ type SuperMember = NodePath<
interface SpecHandler
extends Pick<
Handler,
"get" | "set" | "destructureSet" | "call" | "optionalCall" | "memoise"
| "memoise"
| "get"
| "set"
| "destructureSet"
| "call"
| "optionalCall"
| "delete"
> {
_get(
this: Handler & SpecHandler,
Expand Down Expand Up @@ -240,6 +247,23 @@ const specHandlers: SpecHandler = {
true,
);
},

delete(this: Handler & SpecHandler, superMember: SuperMember) {
if (superMember.node.computed) {
return sequenceExpression([
callExpression(this.file.addHelper("toPropertyKey"), [
cloneNode(superMember.node.property),
]),
template.expression.ast`
function () { throw new ReferenceError("'delete super[expr]' is invalid"); }()
`,
]);
} else {
return template.expression.ast`
function () { throw new ReferenceError("'delete super.prop' is invalid"); }()
`;
}
},
};

const looseHandlers = {
Expand Down
@@ -0,0 +1,15 @@
expect(() => {
new class { y = delete super.x; };
}).toThrow(ReferenceError);

expect(() => {
new class { y = delete super[0]; };
}).toThrow(ReferenceError);

expect(() => {
class X1 { static y = delete super.x; }
}).toThrow(ReferenceError);

expect(() => {
class X2 { static y = delete super[0]; }
}).toThrow(ReferenceError);
@@ -0,0 +1,7 @@
new class { y = delete super.x; };

new class { y = delete super[0]; };

class X1 { static y = delete super.x; }

class X2 { static y = delete super[0]; }
@@ -0,0 +1,38 @@
new ( /*#__PURE__*/function () {
"use strict";

function _class2() {
babelHelpers.classCallCheck(this, _class2);
babelHelpers.defineProperty(this, "y", function () {
throw new ReferenceError("'delete super.prop' is invalid");
}());
}
return babelHelpers.createClass(_class2);
}())();
new ( /*#__PURE__*/function () {
"use strict";

function _class4() {
babelHelpers.classCallCheck(this, _class4);
babelHelpers.defineProperty(this, "y", (babelHelpers.toPropertyKey(0), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}()));
}
return babelHelpers.createClass(_class4);
}())();
var X1 = /*#__PURE__*/babelHelpers.createClass(function X1() {
"use strict";

babelHelpers.classCallCheck(this, X1);
});
babelHelpers.defineProperty(X1, "y", function () {
throw new ReferenceError("'delete super.prop' is invalid");
}());
var X2 = /*#__PURE__*/babelHelpers.createClass(function X2() {
"use strict";

babelHelpers.classCallCheck(this, X2);
});
babelHelpers.defineProperty(X2, "y", (babelHelpers.toPropertyKey(0), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}()));
@@ -0,0 +1,23 @@
// [Symbol.toPrimitive] must be called if exist
var counter = 0;
expect(() => {
(new class {
f() {
delete super[{
[Symbol.toPrimitive]: function() { ++counter; return 0; },
}];
}
}).f();
}).toThrow(ReferenceError);
expect(counter).toBe(1);

// [Symbol.toPrimitive] must return a primitive value
expect(() => {
(new class {
f() {
delete super[{
[Symbol.toPrimitive]: function() { return {}; },
}];
}
}).f();
}).toThrow(TypeError);
@@ -0,0 +1,18 @@
// [Symbol.toPrimitive] must be called if exist
var counter = 0;
(new class {
f() {
delete super[{
[Symbol.toPrimitive]: function() { ++counter; return 0; },
}];
}
}).f();

// [Symbol.toPrimitive] must return a primitive value
(new class {
f() {
delete super[{
[Symbol.toPrimitive]: function() { return {}; },
}];
}
}).f();
@@ -0,0 +1,45 @@
// [Symbol.toPrimitive] must be called if exist
var counter = 0;
new ( /*#__PURE__*/function () {
"use strict";

function _class() {
babelHelpers.classCallCheck(this, _class);
}
babelHelpers.createClass(_class, [{
key: "f",
value: function f() {
babelHelpers.toPropertyKey({
[Symbol.toPrimitive]: function () {
++counter;
return 0;
}
}), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}();
}
}]);
return _class;
}())().f();

// [Symbol.toPrimitive] must return a primitive value
new ( /*#__PURE__*/function () {
"use strict";

function _class2() {
babelHelpers.classCallCheck(this, _class2);
}
babelHelpers.createClass(_class2, [{
key: "f",
value: function f() {
babelHelpers.toPropertyKey({
[Symbol.toPrimitive]: function () {
return {};
}
}), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}();
}
}]);
return _class2;
}())().f();
@@ -0,0 +1,27 @@
expect(() => {
(new class {
f() { delete super.x; }
}).f();
}).toThrow(ReferenceError);

expect(() => {
(new class {
f() { delete super[0]; }
}).f();
}).toThrow(ReferenceError);

// [expr] should be evaluated
var counter = 0;
expect(() => {
(new class {
f() { delete super[++counter]; }
}).f();
}).toThrow(ReferenceError);
expect(counter).toBe(1);

// TypeError before ReferenceError
expect(() => {
(new class {
f() { delete super[0()]; }
}).f();
}).toThrow(TypeError);
@@ -0,0 +1,18 @@
(new class {
f() { delete super.x; }
}).f();

(new class {
f() { delete super[0]; }
}).f();

// [expr] should be evaluated
var counter = 0;
(new class {
f() { delete super[++counter]; }
}).f();

// TypeError before ReferenceError
(new class {
f() { delete super[0()]; }
}).f();
@@ -0,0 +1,69 @@
new ( /*#__PURE__*/function () {
"use strict";

function _class() {
babelHelpers.classCallCheck(this, _class);
}
babelHelpers.createClass(_class, [{
key: "f",
value: function f() {
(function () {
throw new ReferenceError("'delete super.prop' is invalid");
})();
}
}]);
return _class;
}())().f();
new ( /*#__PURE__*/function () {
"use strict";

function _class2() {
babelHelpers.classCallCheck(this, _class2);
}
babelHelpers.createClass(_class2, [{
key: "f",
value: function f() {
babelHelpers.toPropertyKey(0), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}();
}
}]);
return _class2;
}())().f();

// [expr] should be evaluated
var counter = 0;
new ( /*#__PURE__*/function () {
"use strict";

function _class3() {
babelHelpers.classCallCheck(this, _class3);
}
babelHelpers.createClass(_class3, [{
key: "f",
value: function f() {
babelHelpers.toPropertyKey(++counter), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}();
}
}]);
return _class3;
}())().f();

// TypeError before ReferenceError
new ( /*#__PURE__*/function () {
"use strict";

function _class4() {
babelHelpers.classCallCheck(this, _class4);
}
babelHelpers.createClass(_class4, [{
key: "f",
value: function f() {
babelHelpers.toPropertyKey(0()), function () {
throw new ReferenceError("'delete super[expr]' is invalid");
}();
}
}]);
return _class4;
}())().f();
1 change: 1 addition & 0 deletions yarn.lock
Expand Up @@ -886,6 +886,7 @@ __metadata:
"@babel/helper-environment-visitor": "workspace:^"
"@babel/helper-member-expression-to-functions": "workspace:^"
"@babel/helper-optimise-call-expression": "workspace:^"
"@babel/template": "workspace:^"
"@babel/traverse": "workspace:^"
"@babel/types": "workspace:^"
languageName: unknown
Expand Down

0 comments on commit 2bba4a5

Please sign in to comment.