fix(ivy): detect frozen flyweight objects in definitions and unfreeze (#25755)

defineComponent() and friends can return a flyweight EMPTY object for
specific fields when they contain no data. InheritDefinitionFeature
was attempting to write into these flyweight objects, which have been
protected with Object.freeze().

This commit adds detection to InheritDefinitionFeature to identify the
frozen objects.

PR Close #25755
This commit is contained in:
Alex Rickabaugh 2018-08-30 15:25:32 -07:00 committed by Misko Hevery
parent 2c66523222
commit a417b2b419
3 changed files with 60 additions and 2 deletions

View File

@ -17,8 +17,8 @@ import {Type} from '../type';
import {BaseDef, ComponentDefFeature, ComponentDefInternal, ComponentQuery, ComponentTemplate, ComponentType, DirectiveDefFeature, DirectiveDefInternal, DirectiveType, DirectiveTypesOrFactory, PipeDefInternal, PipeType, PipeTypesOrFactory} from './interfaces/definition';
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
const EMPTY: {} = {};
const EMPTY_ARRAY: any[] = [];
export const EMPTY: {} = {};
export const EMPTY_ARRAY: any[] = [];
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
Object.freeze(EMPTY);
Object.freeze(EMPTY_ARRAY);

View File

@ -8,6 +8,7 @@
import {Type} from '../../type';
import {fillProperties} from '../../util/property';
import {EMPTY, EMPTY_ARRAY} from '../definition';
import {ComponentDefInternal, ComponentTemplate, DirectiveDefFeature, DirectiveDefInternal, RenderFlags} from '../interfaces/definition';
@ -47,6 +48,16 @@ export function InheritDefinitionFeature(
}
const baseDef = (superType as any).ngBaseDef;
// Some fields in the definition may be empty, if there were no values to put in them that
// would've justified object creation. Unwrap them if necessary.
if (baseDef || superDef) {
const writeableDef = definition as any;
writeableDef.inputs = maybeUnwrapEmpty(definition.inputs);
writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs);
writeableDef.outputs = maybeUnwrapEmpty(definition.outputs);
}
if (baseDef) {
// Merge inputs and outputs
fillProperties(definition.inputs, baseDef.inputs);
@ -162,3 +173,15 @@ export function InheritDefinitionFeature(
superType = Object.getPrototypeOf(superType);
}
}
function maybeUnwrapEmpty<T>(value: T[]): T[];
function maybeUnwrapEmpty<T>(value: T): T;
function maybeUnwrapEmpty(value: any): any {
if (value === EMPTY) {
return {};
} else if (Array.isArray(value) && value === EMPTY_ARRAY) {
return [];
} else {
return value;
}
}

View File

@ -7,6 +7,7 @@
*/
import {EventEmitter, Output} from '../../src/core';
import {EMPTY} from '../../src/render3/definition';
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
import {ComponentDefInternal, DirectiveDefInternal, RenderFlags, defineBase, defineComponent, defineDirective} from '../../src/render3/index';
@ -127,6 +128,40 @@ describe('InheritDefinitionFeature', () => {
});
});
it('should detect EMPTY inputs and outputs', () => {
class SuperDirective {
static ngDirectiveDef = defineDirective({
inputs: {
testIn: 'testIn',
},
outputs: {
testOut: 'testOut',
},
type: SuperDirective,
selectors: [['', 'superDir', '']],
factory: () => new SuperDirective(),
});
}
class SubDirective extends SuperDirective {
static ngDirectiveDef = defineDirective({
type: SubDirective,
selectors: [['', 'subDir', '']],
factory: () => new SubDirective(),
features: [InheritDefinitionFeature]
});
}
const subDef = SubDirective.ngDirectiveDef as DirectiveDefInternal<any>;
expect(subDef.inputs).toEqual({
testIn: 'testIn',
});
expect(subDef.outputs).toEqual({
testOut: 'testOut',
});
});
it('should inherit inputs from ngBaseDefs along the way', () => {
class Class5 {