@memlab/core 1.1.21 → 1.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/__tests__/lib/TestUtils.d.ts +13 -0
  2. package/dist/__tests__/lib/TestUtils.js +39 -0
  3. package/dist/lib/Config.d.ts +10 -1
  4. package/dist/lib/Config.js +21 -2
  5. package/dist/lib/Constant.js +1 -0
  6. package/dist/lib/FileManager.js +1 -1
  7. package/dist/lib/HeapAnalyzer.js +49 -27
  8. package/dist/lib/RunInfoUtils.d.ts +1 -0
  9. package/dist/lib/RunInfoUtils.js +8 -4
  10. package/dist/lib/SerializationHelper.d.ts +18 -0
  11. package/dist/lib/SerializationHelper.js +36 -0
  12. package/dist/lib/Serializer.d.ts +2 -6
  13. package/dist/lib/Serializer.js +12 -3
  14. package/dist/lib/TraceSampler.d.ts +36 -0
  15. package/dist/lib/TraceSampler.js +78 -0
  16. package/dist/lib/Types.d.ts +26 -3
  17. package/dist/lib/Utils.d.ts +8 -0
  18. package/dist/lib/Utils.js +74 -6
  19. package/dist/lib/charts/MemoryBarChart.js +2 -2
  20. package/dist/lib/heap-data/HeapStringNode.js +25 -16
  21. package/dist/lib/leak-filters/LeakFilterRuleList.js +2 -0
  22. package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.d.ts +0 -1
  23. package/dist/lib/leak-filters/rules/FilterDetachedDOMElement.rule.js +22 -9
  24. package/dist/lib/leak-filters/rules/FilterOverSizedNodeAsLeak.rule.js +50 -0
  25. package/dist/lib/leak-filters/rules/FilterUnmountedFiberNode.rule.js +4 -3
  26. package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.d.ts +20 -0
  27. package/dist/lib/leak-filters/rules/FilterXMLHTTPRequest.rule.js +30 -0
  28. package/dist/lib/trace-filters/TraceFilterRuleList.js +2 -0
  29. package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.d.ts +15 -0
  30. package/dist/lib/trace-filters/rules/FilterCppRootsToDetachedDOMTrace.rule.js +44 -0
  31. package/dist/paths/TraceFinder.js +1 -1
  32. package/dist/trace-cluster/TraceBucket.d.ts +1 -1
  33. package/dist/trace-cluster/TraceBucket.js +54 -22
  34. package/package.json +1 -1
@@ -28,6 +28,7 @@ declare function isDOMNodeIncomplete(node: IHeapNode): boolean;
28
28
  declare function isXMLDocumentNode(node: IHeapNode): boolean;
29
29
  declare function isHTMLDocumentNode(node: IHeapNode): boolean;
30
30
  declare function isDOMTextNode(node: IHeapNode): boolean;
31
+ declare function isCppRootsNode(node: IHeapNode): boolean;
31
32
  declare function isRootNode(node: IHeapNode, opt?: AnyOptions): boolean;
32
33
  declare function isDirectPropEdge(edge: IHeapEdge): boolean;
33
34
  declare function isReturnEdge(edge: IHeapEdge): boolean;
@@ -48,6 +49,7 @@ declare function isAlternateNode(node: IHeapNode): boolean;
48
49
  declare function setIsRegularFiberNode(node: IHeapNode): void;
49
50
  declare function isRegularFiberNode(node: IHeapNode): boolean;
50
51
  declare function hasHostRoot(node: IHeapNode): boolean;
52
+ declare function markDetachedFiberNode(node: IHeapNode): boolean;
51
53
  declare type IterateNodeCallback = (node: IHeapNode, snapshot: IHeapSnapshot) => boolean;
52
54
  declare function filterNodesInPlace(idSet: Set<number>, snapshot: IHeapSnapshot, cb: IterateNodeCallback): void;
53
55
  declare function applyToNodes(idSet: Set<number>, snapshot: IHeapSnapshot, cb: (node: IHeapNode, snapshot: IHeapSnapshot) => void, options?: AnyOptions): void;
@@ -75,6 +77,8 @@ declare function isObjectNode(node: IHeapNode): boolean;
75
77
  declare function isPlainJSObjectNode(node: IHeapNode): boolean;
76
78
  declare function pathHasDetachedHTMLNode(path: LeakTracePathItem): boolean;
77
79
  declare function pathHasEdgeWithIndex(path: LeakTracePathItem, idx: number): boolean;
80
+ declare function pathHasEdgeWithName(path: LeakTracePathItem, edgeName: string): boolean;
81
+ declare function pathHasNodeOrEdgeWithName(path: LeakTracePathItem, name: Optional<string>): boolean;
78
82
  declare function getLastNodeId(path: LeakTracePathItem): number;
79
83
  declare function getReadablePercent(num: number): string;
80
84
  declare function getReadableBytes(bytes: Optional<number>): string;
@@ -183,6 +187,7 @@ declare const _default: {
183
187
  hasReactEdges: typeof hasReactEdges;
184
188
  isAlternateNode: typeof isAlternateNode;
185
189
  isBlinkRootNode: typeof isBlinkRootNode;
190
+ isCppRootsNode: typeof isCppRootsNode;
186
191
  isDOMInternalNode: typeof isDOMInternalNode;
187
192
  isDOMNodeIncomplete: typeof isDOMNodeIncomplete;
188
193
  isDOMTextNode: typeof isDOMTextNode;
@@ -225,11 +230,14 @@ declare const _default: {
225
230
  mapToObject: typeof mapToObject;
226
231
  markAllDetachedFiberNode: typeof markAllDetachedFiberNode;
227
232
  markAlternateFiberNode: typeof markAlternateFiberNode;
233
+ markDetachedFiberNode: typeof markDetachedFiberNode;
228
234
  memCache: Record<string, any>;
229
235
  normalizeBaseUrl: typeof normalizeBaseUrl;
230
236
  objectToMap: typeof objectToMap;
231
237
  pathHasDetachedHTMLNode: typeof pathHasDetachedHTMLNode;
232
238
  pathHasEdgeWithIndex: typeof pathHasEdgeWithIndex;
239
+ pathHasEdgeWithName: typeof pathHasEdgeWithName;
240
+ pathHasNodeOrEdgeWithName: typeof pathHasNodeOrEdgeWithName;
233
241
  repeat: typeof repeat;
234
242
  resolveFilePath: typeof resolveFilePath;
235
243
  resolveSnapshotFilePath: typeof resolveSnapshotFilePath;
package/dist/lib/Utils.js CHANGED
@@ -126,7 +126,7 @@ function isFiberNode(node) {
126
126
  return name === 'FiberNode' || name === 'Detached FiberNode';
127
127
  }
128
128
  // quickly check the detachedness field
129
- // need to call hasHostRoot(node) before this function
129
+ // need to call markDetachedFiberNode(node) before this function
130
130
  // does not traverse and check the existance of HostRoot
131
131
  // NOTE: Doesn't work for FiberNode without detachedness field
132
132
  function isDetachedFiberNode(node) {
@@ -271,6 +271,10 @@ function isHTMLDocumentNode(node) {
271
271
  function isDOMTextNode(node) {
272
272
  return node.type === 'native' && node.name === 'Text';
273
273
  }
274
+ // check if this is a [C++ roots] (synthetic) node
275
+ function isCppRootsNode(node) {
276
+ return node.name === 'C++ roots' && node.type === 'synthetic';
277
+ }
274
278
  function isRootNode(node, opt = {}) {
275
279
  if (!node) {
276
280
  return false;
@@ -493,13 +497,42 @@ function hasHostRoot(node) {
493
497
  }
494
498
  cur = getReactFiberNode(cur, 'return');
495
499
  }
500
+ return false;
501
+ }
502
+ // The Fiber tree starts with a special type of Fiber node (HostRoot).
503
+ // return true if the node is a mounted Fiber node
504
+ function markDetachedFiberNode(node) {
505
+ if (node && node.is_detached) {
506
+ return false;
507
+ }
508
+ let cur = node;
509
+ const visitedIds = new Set();
510
+ const visitedNodes = new Set();
511
+ while (cur && isFiberNode(cur)) {
512
+ if (cur.id == null || visitedIds.has(cur.id)) {
513
+ break;
514
+ }
515
+ visitedNodes.add(cur);
516
+ // if a Fiber node whose dominator is neither root nor
517
+ // another Fiber node, then consider it as detached Fiber node
518
+ if (cur.dominatorNode && cur.dominatorNode.id !== 1) {
519
+ if (!isFiberNode(cur.dominatorNode)) {
520
+ cur.markAsDetached();
521
+ }
522
+ }
523
+ visitedIds.add(cur.id);
524
+ if (isHostRoot(cur)) {
525
+ return true;
526
+ }
527
+ cur = getReactFiberNode(cur, 'return');
528
+ }
496
529
  for (const visitedNode of visitedNodes) {
497
530
  visitedNode.markAsDetached();
498
531
  }
499
532
  return false;
500
533
  }
501
534
  function filterNodesInPlace(idSet, snapshot, cb) {
502
- const ids = Array.from(idSet.keys());
535
+ const ids = Array.from(idSet);
503
536
  for (const id of ids) {
504
537
  const node = snapshot.getNodeById(id);
505
538
  if (node && !cb(node, snapshot)) {
@@ -508,7 +541,7 @@ function filterNodesInPlace(idSet, snapshot, cb) {
508
541
  }
509
542
  }
510
543
  function applyToNodes(idSet, snapshot, cb, options = {}) {
511
- let ids = Array.from(idSet.keys());
544
+ let ids = Array.from(idSet);
512
545
  if (options.shuffle) {
513
546
  ids.sort(() => Math.random() - 0.5);
514
547
  }
@@ -951,6 +984,33 @@ function pathHasEdgeWithIndex(path, idx) {
951
984
  }
952
985
  return false;
953
986
  }
987
+ function pathHasEdgeWithName(path, edgeName) {
988
+ let p = path;
989
+ while (p) {
990
+ if (p.edge && p.edge.name_or_index === edgeName) {
991
+ return true;
992
+ }
993
+ p = p.next;
994
+ }
995
+ return false;
996
+ }
997
+ function pathHasNodeOrEdgeWithName(path, name) {
998
+ if (name == null) {
999
+ return true;
1000
+ }
1001
+ name = name.toLowerCase();
1002
+ let p = path;
1003
+ while (p) {
1004
+ if (p.edge && `${p.edge.name_or_index}`.toLowerCase().includes(name)) {
1005
+ return true;
1006
+ }
1007
+ if (p.node && `${p.node.name}`.toLowerCase().includes(name)) {
1008
+ return true;
1009
+ }
1010
+ p = p.next;
1011
+ }
1012
+ return false;
1013
+ }
954
1014
  function getLastNodeId(path) {
955
1015
  if (!path) {
956
1016
  return -1;
@@ -1502,8 +1562,9 @@ function dumpSnapshot(file, snapshot) {
1502
1562
  function markAllDetachedFiberNode(snapshot) {
1503
1563
  Console_1.default.overwrite('marking all detached Fiber nodes...');
1504
1564
  snapshot.nodes.forEach(node => {
1505
- // hasHostRoot checks and marks detached Fiber Nodes
1506
- isFiberNode(node) && !hasHostRoot(node);
1565
+ if (isFiberNode(node)) {
1566
+ markDetachedFiberNode(node);
1567
+ }
1507
1568
  });
1508
1569
  }
1509
1570
  function markAlternateFiberNode(snapshot) {
@@ -1694,11 +1755,14 @@ function runShell(command, options = {}) {
1694
1755
  execOptions.shell = '/bin/bash';
1695
1756
  }
1696
1757
  let ret = null;
1758
+ if (Config_1.default.verbose || Config_1.default.isContinuousTest) {
1759
+ Console_1.default.lowLevel(`running shell command: ${command}`);
1760
+ }
1697
1761
  try {
1698
1762
  ret = child_process_1.default.execSync(command, execOptions);
1699
1763
  }
1700
1764
  catch (ex) {
1701
- if (Config_1.default.verbose) {
1765
+ if (Config_1.default.verbose || Config_1.default.isContinuousTest) {
1702
1766
  if (ex instanceof Error) {
1703
1767
  Console_1.default.lowLevel(ex.message);
1704
1768
  Console_1.default.lowLevel((_c = ex.stack) !== null && _c !== void 0 ? _c : '');
@@ -1814,6 +1878,7 @@ exports.default = {
1814
1878
  hasReactEdges,
1815
1879
  isAlternateNode,
1816
1880
  isBlinkRootNode,
1881
+ isCppRootsNode,
1817
1882
  isDOMInternalNode,
1818
1883
  isDOMNodeIncomplete,
1819
1884
  isDOMTextNode,
@@ -1856,11 +1921,14 @@ exports.default = {
1856
1921
  mapToObject,
1857
1922
  markAllDetachedFiberNode,
1858
1923
  markAlternateFiberNode,
1924
+ markDetachedFiberNode,
1859
1925
  memCache,
1860
1926
  normalizeBaseUrl,
1861
1927
  objectToMap,
1862
1928
  pathHasDetachedHTMLNode,
1863
1929
  pathHasEdgeWithIndex,
1930
+ pathHasEdgeWithName,
1931
+ pathHasNodeOrEdgeWithName,
1864
1932
  repeat,
1865
1933
  resolveFilePath,
1866
1934
  resolveSnapshotFilePath,
@@ -97,7 +97,7 @@ class MemoryBarChart {
97
97
  }
98
98
  loadPlotData(options = {}) {
99
99
  // plot data for a single run
100
- if (!options.controlWorkDirs && !options.treatmentWorkDir) {
100
+ if (!options.controlWorkDirs && !options.treatmentWorkDirs) {
101
101
  return this.loadPlotDataFromWorkDir(options);
102
102
  }
103
103
  // plot data for control and test run
@@ -105,7 +105,7 @@ class MemoryBarChart {
105
105
  workDir: options.controlWorkDirs && options.controlWorkDirs[0],
106
106
  });
107
107
  const testPlotData = this.loadPlotDataFromWorkDir({
108
- workDir: options.treatmentWorkDir,
108
+ workDir: options.treatmentWorkDirs && options.treatmentWorkDirs[0],
109
109
  });
110
110
  // merge plot data
111
111
  return this.mergePlotData([controlPlotData, testPlotData]);
@@ -21,25 +21,34 @@ class HeapStringNode extends HeapNode_1.default {
21
21
  }
22
22
  get stringValue() {
23
23
  var _a, _b, _c;
24
- const type = this.type;
25
- if (type === 'concatenated string') {
26
- const firstNode = (_a = this.getReferenceNode('first')) === null || _a === void 0 ? void 0 : _a.toStringNode();
27
- const secondNode = (_b = this.getReferenceNode('second')) === null || _b === void 0 ? void 0 : _b.toStringNode();
28
- if (firstNode == null || secondNode == null) {
29
- throw (0, HeapUtils_1.throwError)(new Error('broken concatenated string'));
24
+ const stack = [this];
25
+ let ret = '';
26
+ while (stack.length > 0) {
27
+ const node = stack.pop();
28
+ const type = node.type;
29
+ if (type === 'concatenated string') {
30
+ const firstNode = (_a = node.getReferenceNode('first')) === null || _a === void 0 ? void 0 : _a.toStringNode();
31
+ const secondNode = (_b = node.getReferenceNode('second')) === null || _b === void 0 ? void 0 : _b.toStringNode();
32
+ if (firstNode == null || secondNode == null) {
33
+ throw (0, HeapUtils_1.throwError)(new Error('broken concatenated string'));
34
+ }
35
+ stack.push(secondNode);
36
+ stack.push(firstNode);
37
+ continue;
30
38
  }
31
- return firstNode.stringValue + secondNode.stringValue;
32
- }
33
- if (type === 'sliced string') {
34
- const parentNode = (_c = this.getReferenceNode('parent')) === null || _c === void 0 ? void 0 : _c.toStringNode();
35
- if (parentNode == null) {
36
- throw (0, HeapUtils_1.throwError)(new Error('broken sliced string'));
39
+ if (type === 'sliced string') {
40
+ const parentNode = (_c = node.getReferenceNode('parent')) === null || _c === void 0 ? void 0 : _c.toStringNode();
41
+ if (parentNode == null) {
42
+ throw (0, HeapUtils_1.throwError)(new Error('broken sliced string'));
43
+ }
44
+ // sliced string in heap snapshot doesn't include
45
+ // the start index and the end index, so this may be inaccurate
46
+ ret += `<sliced string of @${parentNode.id}>`;
47
+ continue;
37
48
  }
38
- // sliced string in heap snapshot doesn't include
39
- // the start index and the end index, so this may be inaccurate
40
- return parentNode.stringValue;
49
+ ret += node.name;
41
50
  }
42
- return this.name;
51
+ return ret;
43
52
  }
44
53
  getJSONifyableObject() {
45
54
  const rep = super.getJSONifyableObject();
@@ -21,6 +21,7 @@ const FilterOverSizedNodeAsLeak_rule_1 = require("./rules/FilterOverSizedNodeAsL
21
21
  const FilterStackTraceFrame_rule_1 = require("./rules/FilterStackTraceFrame.rule");
22
22
  const FilterTrivialNode_rule_1 = require("./rules/FilterTrivialNode.rule");
23
23
  const FilterUnmountedFiberNode_rule_1 = require("./rules/FilterUnmountedFiberNode.rule");
24
+ const FilterXMLHTTPRequest_rule_1 = require("./rules/FilterXMLHTTPRequest.rule");
24
25
  const list = [
25
26
  new FilterByExternalFilter_rule_1.FilterByExternalFilterRule(),
26
27
  new FilterTrivialNode_rule_1.FilterTrivialNodeRule(),
@@ -29,5 +30,6 @@ const list = [
29
30
  new FilterUnmountedFiberNode_rule_1.FilterUnmountedFiberNodeRule(),
30
31
  new FilterDetachedDOMElement_rule_1.FilterDetachedDOMElementRule(),
31
32
  new FilterStackTraceFrame_rule_1.FilterStackTraceFrameRule(),
33
+ new FilterXMLHTTPRequest_rule_1.FilterXMLHTTPRequestRule(),
32
34
  ];
33
35
  exports.default = (0, InternalValueSetter_1.setInternalValue)(list, __filename, Constant_1.default.internalDir);
@@ -15,6 +15,5 @@ import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
15
15
  */
16
16
  export declare class FilterDetachedDOMElementRule implements ILeakObjectFilterRule {
17
17
  filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
18
- protected checkDetachedFiberNode(config: MemLabConfig, node: IHeapNode): boolean;
19
18
  }
20
19
  //# sourceMappingURL=FilterDetachedDOMElement.rule.d.ts.map
@@ -23,18 +23,31 @@ class FilterDetachedDOMElementRule {
23
23
  const isDetached = Utils_1.default.isDetachedDOMNode(node, {
24
24
  ignoreInternalNode: true,
25
25
  });
26
- if (isDetached) {
26
+ if (isDetached &&
27
+ !isDominatedByEdgeName(node, 'stateNode') &&
28
+ !isDetachedDOMNodeDominatedByDehydratedMemoizedState(node)) {
27
29
  return BaseLeakFilter_rule_1.LeakDecision.LEAK;
28
30
  }
29
31
  return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
30
32
  }
31
- checkDetachedFiberNode(config, node) {
32
- if (!config.detectFiberNodeLeak ||
33
- !Utils_1.default.isFiberNode(node) ||
34
- Utils_1.default.hasHostRoot(node)) {
35
- return false;
36
- }
37
- return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
38
- }
39
33
  }
40
34
  exports.FilterDetachedDOMElementRule = FilterDetachedDOMElementRule;
35
+ function isDominatedByEdgeName(node, edgeNameOrIndex) {
36
+ var _a;
37
+ const referrerNode = node.getAnyReferrerNode(edgeNameOrIndex);
38
+ if (referrerNode == null) {
39
+ return false;
40
+ }
41
+ return referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id);
42
+ }
43
+ // check if the input is a detached DOM node dominated by a 'dehydrated'
44
+ // edge from a memoizedState. In this case, the node is not a memory leak
45
+ function isDetachedDOMNodeDominatedByDehydratedMemoizedState(node) {
46
+ var _a;
47
+ const referrerNode = node.getAnyReferrerNode('dehydrated', 'property');
48
+ if (referrerNode == null) {
49
+ return false;
50
+ }
51
+ return (referrerNode.id === ((_a = node.dominatorNode) === null || _a === void 0 ? void 0 : _a.id) &&
52
+ isDominatedByEdgeName(referrerNode, 'memoizedState'));
53
+ }
@@ -8,8 +8,13 @@
8
8
  * @format
9
9
  * @oncall web_perf_infra
10
10
  */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
11
14
  Object.defineProperty(exports, "__esModule", { value: true });
12
15
  exports.FilterOverSizedNodeAsLeakRule = void 0;
16
+ const Utils_1 = __importDefault(require("../../Utils"));
17
+ const Config_1 = require("../../Config");
13
18
  const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
14
19
  /**
15
20
  * trivial nodes are not reported as memory leaks
@@ -17,6 +22,10 @@ const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
17
22
  class FilterOverSizedNodeAsLeakRule {
18
23
  filter(config, node) {
19
24
  if (config.oversizeObjectAsLeak) {
25
+ // TODO: add support to skip this check
26
+ if (!isHeapNodeUsefulForLeakTraceDiffing(config, node)) {
27
+ return BaseLeakFilter_rule_1.LeakDecision.NOT_LEAK;
28
+ }
20
29
  return node.retainedSize > config.oversizeThreshold
21
30
  ? BaseLeakFilter_rule_1.LeakDecision.LEAK
22
31
  : BaseLeakFilter_rule_1.LeakDecision.NOT_LEAK;
@@ -25,3 +34,44 @@ class FilterOverSizedNodeAsLeakRule {
25
34
  }
26
35
  }
27
36
  exports.FilterOverSizedNodeAsLeakRule = FilterOverSizedNodeAsLeakRule;
37
+ function isHeapNodeUsefulForLeakTraceDiffing(config, node) {
38
+ if (config.traceAllObjectsMode === Config_1.TraceObjectMode.Default) {
39
+ return true;
40
+ }
41
+ const name = node.name;
42
+ if (node.type !== 'object') {
43
+ return false;
44
+ }
45
+ if (name.startsWith('system / ')) {
46
+ return false;
47
+ }
48
+ if (Utils_1.default.isFiberNode(node) && !Utils_1.default.isDetachedFiberNode(node)) {
49
+ return false;
50
+ }
51
+ if (Utils_1.default.isDOMNodeIncomplete(node) && !Utils_1.default.isDetachedDOMNode(node)) {
52
+ return false;
53
+ }
54
+ if (node.getAnyReferrer('__proto__') != null) {
55
+ return false;
56
+ }
57
+ if (node.getAnyReferrer('prototype') != null) {
58
+ return false;
59
+ }
60
+ // react internal objects
61
+ if (node.getAnyReferrer('dependencies') != null) {
62
+ return false;
63
+ }
64
+ if (node.getAnyReferrer('memoizedState') != null) {
65
+ return false;
66
+ }
67
+ if (node.getAnyReferrer('next') != null) {
68
+ return false;
69
+ }
70
+ if (node.getAnyReferrer('deps') != null) {
71
+ return false;
72
+ }
73
+ if (node.getReference('baseQueue') != null) {
74
+ return false;
75
+ }
76
+ return true;
77
+ }
@@ -26,9 +26,10 @@ class FilterUnmountedFiberNodeRule {
26
26
  return BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
27
27
  }
28
28
  checkDetachedFiberNode(config, node) {
29
- if (!config.detectFiberNodeLeak ||
30
- !Utils_1.default.isFiberNode(node) ||
31
- Utils_1.default.hasHostRoot(node)) {
29
+ if (!config.detectFiberNodeLeak || !Utils_1.default.isFiberNode(node)) {
30
+ return false;
31
+ }
32
+ if (!Utils_1.default.isDetachedFiberNode(node)) {
32
33
  return false;
33
34
  }
34
35
  return !Utils_1.default.isNodeDominatedByDeletionsArray(node);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall web_perf_infra
9
+ */
10
+ import type { MemLabConfig } from '../../Config';
11
+ import type { IHeapNode } from '../../Types';
12
+ import { ILeakObjectFilterRule, LeakDecision } from '../BaseLeakFilter.rule';
13
+ /**
14
+ * mark XMLHTTPRequest with status ok as memory leaks
15
+ */
16
+ export declare class FilterXMLHTTPRequestRule implements ILeakObjectFilterRule {
17
+ filter(_config: MemLabConfig, node: IHeapNode): LeakDecision;
18
+ protected checkFinishedXMLHTTPRequest(node: IHeapNode): boolean;
19
+ }
20
+ //# sourceMappingURL=FilterXMLHTTPRequest.rule.d.ts.map
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @format
9
+ * @oncall web_perf_infra
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.FilterXMLHTTPRequestRule = void 0;
13
+ const BaseLeakFilter_rule_1 = require("../BaseLeakFilter.rule");
14
+ /**
15
+ * mark XMLHTTPRequest with status ok as memory leaks
16
+ */
17
+ class FilterXMLHTTPRequestRule {
18
+ filter(_config, node) {
19
+ return this.checkFinishedXMLHTTPRequest(node)
20
+ ? BaseLeakFilter_rule_1.LeakDecision.LEAK
21
+ : BaseLeakFilter_rule_1.LeakDecision.MAYBE_LEAK;
22
+ }
23
+ checkFinishedXMLHTTPRequest(node) {
24
+ if (node.name !== 'XMLHttpRequest' || node.type !== 'native') {
25
+ return false;
26
+ }
27
+ return (node.findAnyReference((edge) => edge.toNode.name === '{"status":"ok"}') != null);
28
+ }
29
+ }
30
+ exports.FilterXMLHTTPRequestRule = FilterXMLHTTPRequestRule;
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const Constant_1 = __importDefault(require("../Constant"));
16
16
  const InternalValueSetter_1 = require("../InternalValueSetter");
17
17
  const FilterAttachedDOMToDetachedDOMTrace_rule_1 = require("./rules/FilterAttachedDOMToDetachedDOMTrace.rule");
18
+ const FilterCppRootsToDetachedDOMTrace_rule_1 = require("./rules/FilterCppRootsToDetachedDOMTrace.rule");
18
19
  const FilterDOMNodeChainTrace_rule_1 = require("./rules/FilterDOMNodeChainTrace.rule");
19
20
  const FilterHermesTrace_rule_1 = require("./rules/FilterHermesTrace.rule");
20
21
  const FilterInternalNodeTrace_rule_1 = require("./rules/FilterInternalNodeTrace.rule");
@@ -29,5 +30,6 @@ const list = [
29
30
  new FilterPendingActivitiesTrace_rule_1.FilterPendingActivitiesTraceRule(),
30
31
  new FilterDOMNodeChainTrace_rule_1.FilterDOMNodeChainTraceRule(),
31
32
  new FilterAttachedDOMToDetachedDOMTrace_rule_1.FilterAttachedDOMToDetachedDOMTraceRule(),
33
+ new FilterCppRootsToDetachedDOMTrace_rule_1.FilterCppRootsToDetachedDOMTraceRule(),
32
34
  ];
33
35
  exports.default = (0, InternalValueSetter_1.setInternalValue)(list, __filename, Constant_1.default.internalDir);
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @format
8
+ * @oncall web_perf_infra
9
+ */
10
+ import type { LeakTracePathItem } from '../../Types';
11
+ import { ILeakTraceFilterRule, LeakTraceFilterOptions, TraceDecision } from '../BaseTraceFilter.rule';
12
+ export declare class FilterCppRootsToDetachedDOMTraceRule implements ILeakTraceFilterRule {
13
+ filter(p: LeakTracePathItem, options?: LeakTraceFilterOptions): TraceDecision;
14
+ }
15
+ //# sourceMappingURL=FilterCppRootsToDetachedDOMTrace.rule.d.ts.map
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ *
8
+ * @format
9
+ * @oncall web_perf_infra
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.FilterCppRootsToDetachedDOMTraceRule = void 0;
16
+ const Config_1 = __importDefault(require("../../Config"));
17
+ const Utils_1 = __importDefault(require("../../Utils"));
18
+ const BaseTraceFilter_rule_1 = require("../BaseTraceFilter.rule");
19
+ class FilterCppRootsToDetachedDOMTraceRule {
20
+ filter(p, options = {}) {
21
+ var _a;
22
+ const curConfig = (_a = options.config) !== null && _a !== void 0 ? _a : Config_1.default;
23
+ // if the path contains edges from [C++ roots] to detached DOM elements
24
+ if (curConfig.hideBrowserLeak && hasCppRootsToDetachedDOMNode(p)) {
25
+ return BaseTraceFilter_rule_1.TraceDecision.NOT_INSIGHTFUL;
26
+ }
27
+ return BaseTraceFilter_rule_1.TraceDecision.MAYBE_INSIGHTFUL;
28
+ }
29
+ }
30
+ exports.FilterCppRootsToDetachedDOMTraceRule = FilterCppRootsToDetachedDOMTraceRule;
31
+ function hasCppRootsToDetachedDOMNode(path) {
32
+ let p = path;
33
+ // all the reference chain consists of DOM elements/nodes
34
+ while (p && p.node) {
35
+ if (Utils_1.default.isCppRootsNode(p.node) &&
36
+ p.next &&
37
+ p.next.node &&
38
+ Utils_1.default.isDOMNodeIncomplete(p.next.node)) {
39
+ return true;
40
+ }
41
+ p = p.next;
42
+ }
43
+ return false;
44
+ }
@@ -450,7 +450,7 @@ class TraceFinder {
450
450
  return Config_1.default.edgeNameGreyList.has(String(edge.name_or_index));
451
451
  }
452
452
  isLessPreferableNode(node) {
453
- return Config_1.default.nodeNameGreyList.has(node.name);
453
+ return Config_1.default.nodeNameGreyList.has(node.name) || Utils_1.default.isCppRootsNode(node);
454
454
  }
455
455
  // each edge is indexed by fromNode's ID, toNode's ID, edge name, and edge type
456
456
  getEdgeKey(edge) {
@@ -37,7 +37,7 @@ export default class NormalizedTrace {
37
37
  private static buildTraceToPathMap;
38
38
  private static pushLeakPathToCluster;
39
39
  private static initEmptyCluster;
40
- static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], treatmentPaths: LeakTracePathItem[], treatmentSnapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb, option?: {
40
+ static clusterControlTreatmentPaths(leakPathsFromControlRuns: LeakTracePathItem[][], controlSnapshots: IHeapSnapshot[], leakPathsFromTreatmentRuns: LeakTracePathItem[][], treatmentSnapshots: IHeapSnapshot[], aggregateDominatorMetrics: AggregateNodeCb, option?: {
41
41
  strategy?: IClusterStrategy;
42
42
  }): ControlTreatmentClusterResult;
43
43
  static generateUnClassifiedClusters(paths: LeakTracePathItem[], snapshot: IHeapSnapshot, aggregateDominatorMetrics: AggregateNodeCb): TraceCluster[];