Skip to content

Commit 3fbf5f9

Browse files
[vm, service] Fix stack overflow in heap_snapshot.dart's data representation.
Although the old code does not involve deeply nested invocations, it does create one local variable per node, which requires a very large frame. Instead, flatten the tree into a list and re-inflate the tree interpretatively. Change-Id: I9bbb8f0fe100f1d69daa14a92aa69412c668472b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135444 Reviewed-by: Ben Konyi <bkonyi@google.com>
1 parent 43bbf73 commit 3fbf5f9

1 file changed

Lines changed: 50 additions & 57 deletions

File tree

runtime/observatory/bin/heap_snapshot.dart

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -74,50 +74,36 @@ Future<SnapshotGraph> load(String uri) async {
7474
return reader.done;
7575
}
7676

77-
String makeJson(root) {
78-
// 'root' can be arbitrarily deep, so we need to use an explicit stack
79-
// instead of the call stack. We also must avoid using a JSON expression to
80-
// avoid stack overflow in the JS engine parser.
81-
final ids = new Map<dynamic, int>();
82-
final sb = new StringBuffer();
83-
final worklist = new List<dynamic>();
84-
worklist.add(root);
85-
86-
var id = 0;
87-
while (id < worklist.length) {
88-
var object = worklist[id];
89-
ids[object] = id++;
90-
worklist.addAll(object.children);
77+
String makeData(dynamic root) {
78+
// 'root' can be arbitrarily deep, so we can't directly represent it as a
79+
// JSON tree, which cause a stack overflow here encoding it and in the JS
80+
// engine decoding it. Instead we flatten the tree into a list of tuples with
81+
// a parent pointer and re-inflate it in JS.
82+
final indices = <dynamic, int>{};
83+
final preorder = <dynamic>[];
84+
preorder.add(root);
85+
86+
for (var index = 0; index < preorder.length; index++) {
87+
final object = preorder[index];
88+
preorder.addAll(object.children);
9189
}
9290

93-
while (!worklist.isEmpty) {
94-
var object = worklist.removeLast();
95-
var id = ids[object];
96-
97-
sb.write('var node');
98-
sb.write(id.toString());
99-
sb.write(' = {"name":"');
100-
sb.write(object.description);
101-
sb.write('","type":"');
102-
sb.write(object.klass.name);
103-
sb.write('","size":');
104-
sb.write(object.retainedSize.toString());
105-
sb.write(',"children":[');
106-
107-
bool first = true;
108-
for (var child in object.children) {
109-
if (!first) {
110-
sb.write(',');
111-
}
112-
first = false;
113-
sb.write('node');
114-
sb.write(ids[child].toString());
115-
}
91+
final flattened = <dynamic>[];
92+
for (var index = 0; index < preorder.length; index++) {
93+
final object = preorder[index];
94+
indices[object] = index;
11695

117-
sb.write(']};\n');
96+
flattened.add(object.description);
97+
flattened.add(object.klass.name);
98+
flattened.add(object.retainedSize);
99+
if (index == 0) {
100+
flattened.add(null);
101+
} else {
102+
flattened.add(indices[object.parent] as int);
103+
}
118104
}
119105

120-
return sb.toString();
106+
return json.encode(flattened);
121107
}
122108

123109
var css = '''
@@ -362,28 +348,35 @@ function showDominatorTree(v) {
362348
content.appendChild(topTile);
363349
}
364350
365-
function linkParents(root) {
351+
function inflateData(flattened) {
366352
// 'root' can be arbitrarily deep, so we need to use an explicit stack
367353
// instead of the call stack.
368-
let worklist = new Array();
369-
worklist.push(root);
370-
371-
while (worklist.length > 0) {
372-
let node = worklist.pop();
373-
node.children.forEach(function (child) {
374-
child["parent"] = node;
375-
worklist.push(child);
376-
});
354+
let nodes = new Array();
355+
let i = 0;
356+
while (i < flattened.length) {
357+
let node = {
358+
"name": flattened[i++],
359+
"type": flattened[i++],
360+
"size": flattened[i++],
361+
"children": [],
362+
"parent": null
363+
};
364+
nodes.push(node);
365+
366+
let parentIndex = flattened[i++];
367+
if (parentIndex != null) {
368+
let parent = nodes[parentIndex];
369+
parent.children.push(node);
370+
node.parent = parent;
371+
}
377372
}
373+
374+
return nodes[0];
378375
}
379376
380-
var root = null;
381-
(function() {
382-
__JSON__
383-
root = node0;
384-
})();
377+
var root = __DATA__;
378+
root = inflateData(root);
385379
386-
linkParents(root);
387380
showDominatorTree(root);
388381
''';
389382

@@ -422,7 +415,7 @@ main(List<String> args) async {
422415
final dir = await Directory.systemTemp.createTemp('heap-snapshot');
423416
final path = dir.path + '/merged-dominator.html';
424417
final file = await File(path).create();
425-
final tree = makeJson(snapshot.mergedRoot);
426-
await file.writeAsString(html.replaceAll('__JSON__', tree));
418+
final tree = makeData(snapshot.mergedRoot);
419+
await file.writeAsString(html.replaceAll('__DATA__', tree));
427420
print('Wrote file://' + path);
428421
}

0 commit comments

Comments
 (0)