From a417b2b41988f1eb98e9c9574bec591d059cb42b Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Thu, 30 Aug 2018 15:25:32 -0700 Subject: [PATCH] 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 --- packages/core/src/render3/definition.ts | 4 +-- .../features/inherit_definition_feature.ts | 23 ++++++++++++ .../Inherit_definition_feature_spec.ts | 35 +++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 41546a186b..6286ddc9f6 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -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); diff --git a/packages/core/src/render3/features/inherit_definition_feature.ts b/packages/core/src/render3/features/inherit_definition_feature.ts index f28d1bc200..87118d300f 100644 --- a/packages/core/src/render3/features/inherit_definition_feature.ts +++ b/packages/core/src/render3/features/inherit_definition_feature.ts @@ -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(value: T[]): T[]; +function maybeUnwrapEmpty(value: T): T; +function maybeUnwrapEmpty(value: any): any { + if (value === EMPTY) { + return {}; + } else if (Array.isArray(value) && value === EMPTY_ARRAY) { + return []; + } else { + return value; + } +} diff --git a/packages/core/test/render3/Inherit_definition_feature_spec.ts b/packages/core/test/render3/Inherit_definition_feature_spec.ts index 3cdf5fe902..29f234e7ed 100644 --- a/packages/core/test/render3/Inherit_definition_feature_spec.ts +++ b/packages/core/test/render3/Inherit_definition_feature_spec.ts @@ -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; + + expect(subDef.inputs).toEqual({ + testIn: 'testIn', + }); + expect(subDef.outputs).toEqual({ + testOut: 'testOut', + }); + }); + it('should inherit inputs from ngBaseDefs along the way', () => { class Class5 {