152
return;
153
}
154
>
const leakingObjsSet = new Set(leakingObjects.map(o => o.value));
lifecycle.ts
155
>
156
>
// Remove all objects that are a child of other leaking objects. Assumes there are no cycles.
157
>
uncoveredLeakingObjs = leakingObjects.filter(l => {
158
>
return !(l.parent && leakingObjsSet.has(l.parent));
159
>
});
160
>
161
>
if (uncoveredLeakingObjs.length === 0) {
162
throw new Error('There are cyclic diposable chains!');
163
}
164
}
166
>
if (!uncoveredLeakingObjs) {
167
return undefined;
168
}
170
>
function getStackTracePath(leaking: DisposableInfo): string[] {
171
>
function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) {
172
>
while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) {
173
>
array.shift();
174
>
}
175
>
}
176
>
177
>
const lines = leaking.source!.split('\n').map(p => p.trim().replace('at ', '')).filter(l => l !== '');
178
>
removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]);
179
>
return lines.reverse();
180
>
}
181
>
182
>
const stackTraceStarts = new SetMap<string, DisposableInfo>();
183
>
for (const leaking of uncoveredLeakingObjs) {
184
>
const stackTracePath = getStackTracePath(leaking);
185
>
for (let i = 0; i <= stackTracePath.length; i++) {
186
>
stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking);
187
>
}
188
>
}
189
>
190
>
// Put earlier leaks first
191
>
uncoveredLeakingObjs.sort(compareBy(l => l.idx, numberComparator));
192
>
193
>
let message = '';
194
>
195
>
let i = 0;
196
>
for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) {
197
>
i++;
198
>
const stackTracePath = getStackTracePath(leaking);
199
>
const stackTraceFormattedLines = [];
200
>
201
>
for (let i = 0; i < stackTracePath.length; i++) {
202
>
let line = stackTracePath[i];
203
>
const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n'));
204
>
line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`;
205
>
206
>
const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n'));
207
>
const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v);
208
>
delete continuations[stackTracePath[i]];
209
>
for (const [cont, set] of Object.entries(continuations)) {
210
if (set) {
211
stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
212
}
213
}
215
>
stackTraceFormattedLines.unshift(line);
216
>
}
217
>
218
>
message += `\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`;
219
>
}
220
>
221
>
if (uncoveredLeakingObjs.length > maxReported) {
222
message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`;
223
}
225
>
return { leaks: uncoveredLeakingObjs, details: message };
226
}
227
}