Skip to content

Commit 59e6489

Browse files
mattlewis92crisbeto
authored andcommitted
fix(core): Clear lView from IcuIteratorState when stack is empty to prevent memory leak
If a component template contains an icu expression it is being retained until the next change detection cycle for that template. This results in a net retention of only ever a single copy of the given lView but that creates an opportunity for compounding leaks. Change the icu i18n_icu_container_visitor to free the IcuIteratorState retained lView when the stack is empty so that garbage collection can occur when the view is discarded.
1 parent bfcaf17 commit 59e6489

File tree

2 files changed

+25
-2
lines changed

2 files changed

+25
-2
lines changed

packages/core/src/render3/i18n/i18n_icu_container_visitor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {TIcuContainerNode} from '../interfaces/node';
1515
import {RNode} from '../interfaces/renderer_dom';
1616
import {LView, TVIEW} from '../interfaces/view';
1717

18-
interface IcuIteratorState {
18+
export interface IcuIteratorState {
1919
stack: any[];
2020
index: number;
2121
lView?: LView;
@@ -35,7 +35,7 @@ function enterIcu(state: IcuIteratorState, tIcu: TIcu, lView: LView) {
3535
}
3636
}
3737

38-
function icuContainerIteratorNext(state: IcuIteratorState): RNode | null {
38+
export function icuContainerIteratorNext(state: IcuIteratorState): RNode | null {
3939
if (state.index < state.removes!.length) {
4040
const removeOpCode = state.removes![state.index++] as number;
4141
ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number');
@@ -54,6 +54,8 @@ function icuContainerIteratorNext(state: IcuIteratorState): RNode | null {
5454
}
5555
} else {
5656
if (state.stack.length === 0) {
57+
// Clear the lView reference when iteration completes to allow garbage collection
58+
state.lView = undefined;
5759
return null;
5860
} else {
5961
state.removes = state.stack.pop();

packages/core/test/render3/i18n/i18n_spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import {
1111
getTranslationForTemplate,
1212
i18nStartFirstCreatePass,
1313
} from '../../../src/render3/i18n/i18n_parse';
14+
import {
15+
icuContainerIteratorNext,
16+
IcuIteratorState,
17+
} from '../../../src/render3/i18n/i18n_icu_container_visitor';
1418
import {getTIcu} from '../../../src/render3/i18n/i18n_util';
1519
import {TNodeType} from '../../../src/render3/interfaces/node';
1620

@@ -584,6 +588,23 @@ describe('Runtime i18n', () => {
584588
],
585589
});
586590
});
591+
592+
it('should clear lView reference when ICU iteration completes', () => {
593+
const state: IcuIteratorState = {
594+
stack: [],
595+
index: 0,
596+
removes: [] as any,
597+
lView: [] as any, // Mock lView
598+
};
599+
600+
// Call icuContainerIteratorNext with an empty removes array and empty stack
601+
// This simulates the end of iteration
602+
const result = icuContainerIteratorNext(state);
603+
604+
expect(result).toBeNull();
605+
// Verify that lView should be cleared to allow garbage collection
606+
expect(state.lView).toBeUndefined();
607+
});
587608
});
588609

589610
describe(`i18nAttribute`, () => {

0 commit comments

Comments
 (0)