X Tutup
The Wayback Machine - https://web.archive.org/web/20220131125108/https://github.com/angular/angular/issues/44069
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

Optional chaining of method calls may generate invalid instructions #44069

Closed
ties-s opened this issue Nov 4, 2021 · 7 comments
Closed

Optional chaining of method calls may generate invalid instructions #44069

ties-s opened this issue Nov 4, 2021 · 7 comments

Comments

@ties-s
Copy link

@ties-s ties-s commented Nov 4, 2021

Which @angular/* package(s) are the source of the bug?

forms

Is this a regression?

Yes

Description

When passing a form control value to a function that is end the end of an optional chain, the value is null.

Expected (works in v12), console:
Thu Nov 04 2021 22:42:39 GMT+0100 (CET) – Thu Nov 04 2021 22:42:39 GMT+0100 (CET)

Actual in v13:
Thu Nov 04 2021 22:42:39 GMT+0100 (CET) – null

This problem does not occur when using ! instead of ? on the form value, or when using the value outside a function that is part of an optional chain.

Please provide a link to a minimal reproduction of the bug

No response

Please provide the exception or error you saw

// app.component.ts
import { Component } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';

@Component({
	selector: 'app-root',
	templateUrl: './app.component.html',
	styleUrls: ['./app.component.scss']
})
export class AppComponent {
	form = this.fb.group({
		test: [new Date(), Validators.required]
	})

	constructor(private fb: FormBuilder) {}

	public a?= {
		fn(_: any) {
			return true;
		}
	}

	test(e: any) {
		console.log(this.form.get('test')?.value, e);
	}
}

// app.component.html
<div [formGroup]="form">
	<button class="show" (click)="a?.fn(test(form.get('test')?.value))">SHOW</button>
</div>

Please provide the environment you discovered this bug in

Angular CLI: 13.0.1
Node: 16.11.1
Package Manager: npm 8.1.2
OS: darwin arm64

Angular: 13.0.0
... animations, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1300.1
@angular-devkit/build-angular   13.0.1
@angular-devkit/core            13.0.1
@angular-devkit/schematics      13.0.1
@angular/cli                    13.0.1
@schematics/angular             13.0.1
rxjs                            6.6.7
typescript                      4.4.4

Anything else?

Checked exact same sample in latest Angular 12, no problems there.

@ties-s
Copy link
Author

@ties-s ties-s commented Nov 4, 2021

@petebacondarwin
Copy link
Member

@petebacondarwin petebacondarwin commented Nov 5, 2021

I think that this is indeed a bug in how the Angular expression is being converted to TS in the ivy handler.

The following expression

a?.fn(test(form.get('test')?.value))

is converted to

let tmp_b_0;
ctx.a == null ? null : ctx.a.fn(ctx.test((tmp_b_0 = tmp_b_0) == null ? null : tmp_b_0.value));

while the following expression

test(form.get('test')?.value)

is converted to

ctx.test((tmp_b_0 = ctx.form.get("test")) == null ? null : tmp_b_0.value);

Note that in the first case we have:

ctx.test((tmp_b_0 = tmp_b_0) == null ? null : tmp_b_0.value)

while in the second:

ctx.test((tmp_b_0 = ctx.form.get("test")) == null ? null : tmp_b_0.value);

So in the first case tmp_b_0 is always undefined...

@ngbot ngbot bot added this to the Backlog milestone Nov 5, 2021
@ngbot ngbot bot added this to the Backlog milestone Nov 5, 2021
@ngbot ngbot bot added this to the Backlog milestone Nov 5, 2021
@ties-s
Copy link
Author

@ties-s ties-s commented Nov 5, 2021

Yeah I noticed that too. So this is a bug. A pretty big one for my code base.

@JoostK
Copy link
Member

@JoostK JoostK commented Nov 5, 2021

I have a hunch this got broken in #42882. I'll be looking at a fix.

@JoostK JoostK self-assigned this Nov 5, 2021
@JoostK JoostK changed the title Missing form value in template when used as argument Angular 13 Optional chaining of method calls may generate invalid instructions Nov 5, 2021
JoostK added a commit to JoostK/angular that referenced this issue Nov 5, 2021
When a safe method call such as `person?.getName()` is used, the
compiler would generate invalid code if the argument list also contained
a safe access. For example, the following code:

```
person?.getName(config?.get('title').enabled)
```

would generate

```
let tmp;
ctx.person == null ? null : ctx.person.getName((tmp = tmp) == null ?
null : tmp.enabled)
```

Notice how the call to `config.get('title')` has completely disappeared,
with `(tmp = tmp)` having taken its place.

The issue occurred due to how the argument list would be converted
from expression AST to output AST twice. First, the outer safe method
call would first convert its arguments list. This resulted in a
temporary being allocated for `ctx.config.get('title')`, which was
stored in the internal `_resultMap`. Only after the argument list has
been converted would the outer safe method call realize that it should
be guarded by a safe access of `person`, entering the
`convertSafeAccess` procedure to convert itself. This would convert the
argument list once again, but this time the `_resultMap` would already
contain the temporary `tmp` for `config?.get('title')`. Consequently,
the safe method in the argument list would be emitted as `tmp`.

This commit fixes the issue by ensuring that nodes are only converted
once.

Closes angular#44069
JoostK added a commit to JoostK/angular that referenced this issue Nov 5, 2021
When a safe method call such as `person?.getName()` is used, the
compiler would generate invalid code if the argument list also contained
a safe method call. For example, the following code:

```
person?.getName(config?.get('title').enabled)
```

would generate

```
let tmp;
ctx.person == null ? null : ctx.person.getName((tmp = tmp) == null ?
null : tmp.enabled)
```

Notice how the call to `config.get('title')` has completely disappeared,
with `(tmp = tmp)` having taken its place.

The issue occurred due to how the argument list would be converted
from expression AST to output AST twice. First, the outer safe method
call would first convert its arguments list. This resulted in a
temporary being allocated for `config.get('title')`, which was stored in
the internal `_resultMap`. Only after the argument list has been
converted would the outer safe method call realize that it should be
guarded by a safe access of `person`, entering the `convertSafeAccess`
procedure to convert itself. This would convert the argument list once
again, but this time the `_resultMap` would already contain the
temporary `tmp` for `config?.get('title')`. Consequently, the safe
method in the argument list would be emitted as `tmp`.

This commit fixes the issue by ensuring that nodes are only converted
once.

Closes angular#44069
JoostK added a commit to JoostK/angular that referenced this issue Nov 6, 2021
When a safe method call such as `person?.getName()` is used, the
compiler would generate invalid code if the argument list also contained
a safe method call. For example, the following code:

```
person?.getName(config?.get('title').enabled)
```

would generate

```
let tmp;
ctx.person == null ? null : ctx.person.getName((tmp = tmp) == null ?
null : tmp.enabled)
```

Notice how the call to `config.get('title')` has completely disappeared,
with `(tmp = tmp)` having taken its place.

The issue occurred due to how the argument list would be converted
from expression AST to output AST twice. First, the outer safe method
call would first convert its arguments list. This resulted in a
temporary being allocated for `config.get('title')`, which was stored in
the internal `_resultMap`. Only after the argument list has been
converted would the outer safe method call realize that it should be
guarded by a safe access of `person`, entering the `convertSafeAccess`
procedure to convert itself. This would convert the argument list once
again, but this time the `_resultMap` would already contain the
temporary `tmp` for `config?.get('title')`. Consequently, the safe
method in the argument list would be emitted as `tmp`.

This commit fixes the issue by ensuring that nodes are only converted
once.

Closes angular#44069
atscott added a commit that referenced this issue Nov 8, 2021
When a safe method call such as `person?.getName()` is used, the
compiler would generate invalid code if the argument list also contained
a safe method call. For example, the following code:

```
person?.getName(config?.get('title').enabled)
```

would generate

```
let tmp;
ctx.person == null ? null : ctx.person.getName((tmp = tmp) == null ?
null : tmp.enabled)
```

Notice how the call to `config.get('title')` has completely disappeared,
with `(tmp = tmp)` having taken its place.

The issue occurred due to how the argument list would be converted
from expression AST to output AST twice. First, the outer safe method
call would first convert its arguments list. This resulted in a
temporary being allocated for `config.get('title')`, which was stored in
the internal `_resultMap`. Only after the argument list has been
converted would the outer safe method call realize that it should be
guarded by a safe access of `person`, entering the `convertSafeAccess`
procedure to convert itself. This would convert the argument list once
again, but this time the `_resultMap` would already contain the
temporary `tmp` for `config?.get('title')`. Consequently, the safe
method in the argument list would be emitted as `tmp`.

This commit fixes the issue by ensuring that nodes are only converted
once.

Closes #44069

PR Close #44088
@atscott atscott closed this in b249e24 Nov 8, 2021
@ties-s
Copy link
Author

@ties-s ties-s commented Nov 9, 2021

Thank for the quick fix! Any info on when a new release will be cut?

@JoostK
Copy link
Member

@JoostK JoostK commented Nov 9, 2021

Typically on Wednesdays (US timezone)

@angular-automatic-lock-bot
Copy link

@angular-automatic-lock-bot angular-automatic-lock-bot bot commented Dec 10, 2021

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Dec 10, 2021
dimakuba added a commit to dimakuba/angular that referenced this issue Dec 28, 2021
…4088)

When a safe method call such as `person?.getName()` is used, the
compiler would generate invalid code if the argument list also contained
a safe method call. For example, the following code:

```
person?.getName(config?.get('title').enabled)
```

would generate

```
let tmp;
ctx.person == null ? null : ctx.person.getName((tmp = tmp) == null ?
null : tmp.enabled)
```

Notice how the call to `config.get('title')` has completely disappeared,
with `(tmp = tmp)` having taken its place.

The issue occurred due to how the argument list would be converted
from expression AST to output AST twice. First, the outer safe method
call would first convert its arguments list. This resulted in a
temporary being allocated for `config.get('title')`, which was stored in
the internal `_resultMap`. Only after the argument list has been
converted would the outer safe method call realize that it should be
guarded by a safe access of `person`, entering the `convertSafeAccess`
procedure to convert itself. This would convert the argument list once
again, but this time the `_resultMap` would already contain the
temporary `tmp` for `config?.get('title')`. Consequently, the safe
method in the argument list would be emitted as `tmp`.

This commit fixes the issue by ensuring that nodes are only converted
once.

Closes angular#44069

PR Close angular#44088
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
3 participants
X Tutup