fix(ivy): incorrectly generating shared pure function between null and object literal (#35481)

In #33705 we made it so that we generate pure functions for object/array literals in order to avoid having them be shared across elements/views. The problem this introduced is that further down the line the `ContantPool` uses the generated literal in order to figure out whether to share an existing factory or to create a new one. `ConstantPool` determines whether to share a factory by creating a key from the AST node and using it to look it up in the factory cache, however the key generation function didn't handle function invocations and replaced them with `null`. This means that the key for `{foo: pureFunction0(...)}` and `{foo: null}` are the same.

These changes rework the logic so that instead of generating a `null` key
for function invocations, we generate a variable called `<unknown>` which
shouldn't be able to collide with anything.

Fixes #35298.

PR Close #35481
This commit is contained in:
crisbeto
2020-02-16 12:49:23 +01:00
committed by Miško Hevery
parent 9228d7f15d
commit 22786c8e88
3 changed files with 226 additions and 3 deletions

View File

@ -3128,6 +3128,159 @@ describe('compiler compliance', () => {
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
it('should not share pure functions between null and object literals', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: \`
<div [dir]="{foo: null}"></div>
<div [dir]="{foo: {}}"></div>
\`
})
export class MyApp {}
@NgModule({declarations: [MyApp]})
export class MyModule {}
`
}
};
const MyAppDeclaration = `
const $c0$ = function () { return { foo: null }; };
const $c1$ = function () { return {}; };
const $c2$ = function (a0) { return { foo: a0 }; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["ng-component"]],
decls: 2,
vars: 6,
consts: [[${AttributeMarker.Bindings}, "dir"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
$r3$.ɵɵelement(1, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$)));
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
it('should not share pure functions between null and array literals', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: \`
<div [dir]="{foo: null}"></div>
<div [dir]="{foo: []}"></div>
\`
})
export class MyApp {}
@NgModule({declarations: [MyApp]})
export class MyModule {}
`
}
};
const MyAppDeclaration = `
const $c0$ = function () { return { foo: null }; };
const $c1$ = function () { return []; };
const $c2$ = function (a0) { return { foo: a0 }; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["ng-component"]],
decls: 2,
vars: 6,
consts: [[${AttributeMarker.Bindings}, "dir"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
$r3$.ɵɵelement(1, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$)));
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
it('should not share pure functions between null and function calls', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
template: \`
<div [dir]="{foo: null}"></div>
<div [dir]="{foo: getFoo()}"></div>
\`
})
export class MyApp {
getFoo() {
return 'foo!';
}
}
@NgModule({declarations: [MyApp]})
export class MyModule {}
`
}
};
const MyAppDeclaration = `
const $c0$ = function () { return { foo: null }; };
const $c1$ = function (a0) { return { foo: a0 }; };
MyApp.ɵcmp = $r3$.ɵɵdefineComponent({
type: MyApp,
selectors: [["ng-component"]],
decls: 2,
vars: 5,
consts: [[${AttributeMarker.Bindings}, "dir"]],
template: function MyApp_Template(rf, ctx) {
if (rf & 1) {
$r3$.ɵɵelement(0, "div", 0);
$r3$.ɵɵelement(1, "div", 0);
}
if (rf & 2) {
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
$r3$.ɵɵadvance(1);
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(3, $c1$, ctx.getFoo()));
}
},
encapsulation: 2
});
`;
const result = compile(files, angularFiles);
expectEmit(result.source, MyAppDeclaration, 'Invalid component definition');
});
});
describe('inherited base classes', () => {