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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: async({ foo33 = 1 }) is parsed without errors #13392

Closed
1 task
nicolo-ribaudo opened this issue May 29, 2021 · 13 comments 路 Fixed by #13410
Closed
1 task

[Bug]: async({ foo33 = 1 }) is parsed without errors #13392

nicolo-ribaudo opened this issue May 29, 2021 · 13 comments 路 Fixed by #13410
Assignees
Labels
i: bug outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser Spec: Async Functions

Comments

@nicolo-ribaudo
Copy link
Member

馃捇

  • Would you like to work on a fix?

How are you using Babel?

Programmatic API (babel.transform, babel.parse)

Input code

async({ foo33 = 1 });

Configuration file name

No response

Configuration

Default parser config

Current and expected behavior

foo33 = 1 is parsed as an ObjectProperty whose value is an AssignmentPattern. It should throw, since objects cannot have property initializers with =.

Environment

Babel 7.14.4

Possible solution

No response

Additional context

Reported at https://twitter.com/buzyescayadev/status/1398526884955058181

@tony-go
Copy link
Contributor

tony-go commented May 31, 2021

Hey @nicolo-ribaudo

As the issue #13399 was just taken.

Maybe I could give a try on this one ?

@JLHwung
Copy link
Contributor

JLHwung commented May 31, 2021

Another related issue:

// should throw duplicate proto keys
async({ __proto__: x, __proto__: y })
Spoiler, expand when you need help!

Babel tracks the pattern foo33 = 1 in refExpressionsErrors.shorthandAssign:

if (refExpressionErrors.shorthandAssign === -1) {
refExpressionErrors.shorthandAssign = this.state.start;
}

However, this error is not thrown as it should have, because when we don't see an arrow after async (),

if (state.maybeAsyncArrow) {
this.expressionScope.exit();
}

we lost the access to refExpressionErrors, which is created when parsing every call expression arguments:

possibleAsyncArrow ? new ExpressionErrors() : undefined,

We can create the refExpressionErrors here

node.arguments = this.parseCallExpressionArguments(
tt.parenR,
state.maybeAsyncArrow,
base.type === "Import",
base.type !== "Super",
node,
);

and pass down the refExpressionErros to parseCallExpressionArguments. After we are sure async() is a call expression, we should check refExpressionErrors like we did elsewhere. An example is parsing left hand-side expression before =:

this.checkExpressionErrors(refExpressionErrors, true);

@tony-go
Copy link
Contributor

tony-go commented Jun 1, 2021

Hi 馃憢 guys

I'm hacking around the parser and try to understand the flow for our bug case.

Thanks a lot @JLHwung for your pointers, it helps a lot.

I'll keep you informed.

@existentialism
Copy link
Member

@tony-go hit us up on slack if you have any questions!

@tony-go
Copy link
Contributor

tony-go commented Jun 2, 2021

Hey @JLHwung I tried async({ __proto__: x, __proto__: y }) with the repl from my pull request.

It seems to work there, but in the parser test I add a case and I got:

Expected non-recoverable error message: Redefinition of __proto__ property. (1:22). But instead parsing recovered from errors: [
      {
        "loc": {
          "line": 1,
          "column": 22
        },
        "pos": 22,
        "code": "BABEL_PARSER_SYNTAX_ERROR",
        "reasonCode": "DuplicateProto"
      }
    ]

Do you have an idea about why ? (I'll dig it anyway)

@nicolo-ribaudo
Copy link
Member Author

It's because we already have the tracking logic for non-async arrow functions, and you are making the parser use the same existing logic also for async arrows.

@JLHwung
Copy link
Contributor

JLHwung commented Jun 2, 2021

Hey @JLHwung I tried async({ __proto__: x, __proto__: y }) with the repl from my pull request.

It seems to work there, but in the parser test I add a case and I got:

Expected non-recoverable error message: Redefinition of __proto__ property. (1:22). But instead parsing recovered from errors: [
      {
        "loc": {
          "line": 1,
          "column": 22
        },
        "pos": 22,
        "code": "BABEL_PARSER_SYNTAX_ERROR",
        "reasonCode": "DuplicateProto"
      }
    ]

Do you have an idea about why ? (I'll dig it anyway)

It should throw, which means you have fixed this issue. The error comes from the fact we are enabling errorRecovery for all the parser tests so instead of an error is thrown, Babel parser will recovery from the errors, and record the errors to the AST.

You can try removing options.json and re-run the test, the fixture runner will automatically generate output.json.

@tony-go
Copy link
Contributor

tony-go commented Jun 2, 2021

Thanks a lot guys 馃憦

I just pushed the new test case ^^

I probably understand that our update allows to catch this error, my misunderstanding was more on the fact in one case we use:

  • this.checkExpressionErrors that'll throw
  • this.raise (If I understand well 馃槅 )

Maybe do you guys have an idea ?

@nicolo-ribaudo
Copy link
Member Author

Usually use use this.raise, which immediately reports the error. However, when parsing async ({ foo33 = 1 }) there is a problem: when we find =, we don't know yet if it's an error or not. async ({ foo33 = 1 }) => {} is valid, while async ({ foo33 = 1 }); is not. For this reason, instead of using .raise we record them in ExpressionErrors and then, when we are sure that it's not an async function, we throw the errors we recorded.

@tony-go
Copy link
Contributor

tony-go commented Jun 3, 2021

Hi @nicolo-ribaudo 馃憢

Hope you're doing right ^^

I fully understand why we use a ref (ExpressionErrors) to store the potential error and raise it only if it's necessary.

But at the end of the day this.checkExpressionErrors will:

if (shorthandAssign >= 0) {
  this.unexpected(shorthandAssign); // will throw something
}
if (doubleProto >= 0) {
  this.raise(doubleProto, Errors.DuplicateProto); // will return something
}

I think that the REPL handle both of them similarly. But it seems ambiguous for me. But I probably miss something, let me know in case.

Note: So sorry if my questions are a bit boring 馃檹 - I'm just tryin to better understand the code base. Thanks a lot for your time ^^

@nicolo-ribaudo
Copy link
Member Author

Your question is a really good one, actually 馃槈

@babel/parser has an errorReovery option (which is enabled by default in tests) that makes it push some errors to an ast.errors array. We don't push all of these errors because it's not always easy to build a Babel AST after some invalid syntax.

For example, we could parse ({ foo = 2 }); as if it was ({ foo: 2 }); (so we could recover by replacing = with :), but then what would we do for ({ foo: { x: 3 } = {} });?

  1. When it would be hard to create a valid AST that probably reflects the user intention, we still throw (this.unexpected())
  2. When it's easy to still create an AST, we use this.raise and continue parsing.

In general, "2" is more similar to linting errors (valid syntax but which is disallowed by the spec via Early Errors), while "1" are actual syntax errors (tokens that aren't allowed in that position).

@tony-go
Copy link
Contributor

tony-go commented Jun 3, 2021

Thanks a lot for this level of detail @nicolo-ribaudo ^^

@nicolo-ribaudo
Copy link
Member Author

Thank you for the PR!

@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 Sep 3, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 3, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
i: bug outdated A closed issue/PR that is archived due to age. Recommended to make a new issue pkg: parser Spec: Async Functions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants