Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix nested classes reference private fields #11405

Merged
merged 5 commits into from Apr 14, 2020

Conversation

jridgewell
Copy link
Member

Q                       A
Fixed Issues?
Patch: Bug Fix? 👍
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

We failed to properly handle nested private field, when only a single private field was redeclared. Example

When we hit a nested class that redeclares a field, we skip the nested class. And when we finally traverse that nested class, we'll only have the privates that are declared by it. We've lost the ancestor's private fields!

@nicolo-ribaudo nicolo-ribaudo added PR: Bug Fix 🐛 A type of pull request used for our changelog categories Spec: Class Fields labels Apr 11, 2020
const body = path.get("body");
const loose = isLoose(this.file, feature);
const privateNamesMap = new Map(
privateNamesStack[privateNamesStack.length - 1],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since nodes can be re-queued and skipped, it's not guaranteed that they are visited in-order.

Instead of a stack, I think we could use a WeakMap from class nodes to privateNamesMap, and get the parent class using path.findParent(p => p.isClass()).

@@ -30,6 +30,8 @@ export { FEATURES, injectInitialization };
const version = pkg.version.split(".").reduce((v, x) => v * 1e5 + +x, 0);
const versionKey = "@babel/plugin-class-features/version";

const privateNamesStack = [new Map()];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using a WeakSet, please put this in the createClassFeaturePluign function so that when the plugin finishes running it can be garbage-collected, even if the AST was still retained.

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Apr 11, 2020

When we hit a nested class that redeclares a field, we skip the nested class.

I was wondering if it would be easier to fix it there. The Class visitor in privateNameVisitor could become something like this:

  Class(path) {
    const { privateNamesMap } = this;
    const body = path.get("body.body");

    // First, evaluate the things in the outer scope.
    path.traverse(privateNameInnerVisitor, this);

    const visiblePrivateNames = new Map(privateNamesMap);
    for (const prop of body) {
      if (prop.isPrivate()) visiblePrivateNames.delete(prop.node.key.id.name);
    }

    path.traverse(privateNameVisitor, {
      ...this,
      privateNamesMap: visiblePrivateNames,
      ignoreUndeclared: true,
    });
    path.skip();
  },

@jridgewell
Copy link
Member Author

I was wondering if it would be easier to fix it there. The Class visitor in privateNameVisitor could become something like this:

I was trying to avoid processing the class multiple times. But this is easier to do, given the issues with out-of-order requeueing. Updated.

@@ -0,0 +1,15 @@
class Foo {
Copy link
Contributor

@JLHwung JLHwung Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a case on redeclared private names with computed key reference? The following case should throw since #foo is shadowed.

class Foo {
  #foo = 1;

  test() {
    class Nested {
      #foo = 2;
      [this.#foo] = 3;
    }

    new Nested;
  }
}
(new Foo).test();

Given that it is not directly related to this PR, we can fix it in a separate one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we wait till the other PR to add the test case? It's going to be much harder to fix.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I have a fix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I meant we can add the new case and the fixes in a separate PR, my bad. As long as you come up with a fix, that's awesome! 😄

// This class redeclares some private field. We need to process the outer
// environment with access to all the outer privates, then we can process
// the inner environment with only the still-visible outer privates.
path.traverse(privateNameNestedVisitor, this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JLHwung Maybe it would be enough to remove this line to fix it?

Copy link
Contributor

@JLHwung JLHwung Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so but I haven't tried. The nested private name is still allowed as long as it does not access outer class private names. I think we shall whitelist computed element names for proper resolving behaviour.

class Foo {
  #foo = 1;

  test() {
    class Nested {
      #foo = 2;
      bar = this.#foo;
    }

    new Nested;
  }
}
(new Foo).test();

Copy link
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@nicolo-ribaudo nicolo-ribaudo merged commit 9b48a8e into babel:master Apr 14, 2020
@jridgewell jridgewell deleted the nested-private-redeclare branch April 15, 2020 03:33
jridgewell added a commit to jridgewell/babel that referenced this pull request Apr 15, 2020
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail.

Follow up to babel#11405.
jridgewell added a commit that referenced this pull request Apr 15, 2020
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail.

Follow up to #11405.
jridgewell added a commit to jridgewell/babel that referenced this pull request Apr 15, 2020
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail.

Follow up to babel#11405.
nicolo-ribaudo pushed a commit to jridgewell/babel that referenced this pull request Apr 20, 2020
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail.

Follow up to babel#11405.
nicolo-ribaudo pushed a commit that referenced this pull request Apr 20, 2020
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail.

Follow up to #11405.
@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jul 15, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Bug Fix 🐛 A type of pull request used for our changelog categories Spec: Class Fields
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants