Skip to content

Commit 362c2c3

Browse files
committed
Return nodes that get moved after a finally are put directly in the try-complex.
This solves a lot of layout issues, as there is no need to link back into the containing cluster. Additionally, it is more correct, as the eturn happens outside the original cluster and does not have ry semantics.
1 parent 6c3569d commit 362c2c3

File tree

3 files changed

+28
-23
lines changed

3 files changed

+28
-23
lines changed

src/control-flow/cfg-defs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export class BlockHandler {
8989
private gotos: Array<{ label: string; node: string }> = [];
9090
/**
9191
* All the returns encountered so far.
92-
*
92+
*
9393
* This is needed for `finally` clauses in exception handling,
9494
* as the return is moved/duplicated to the end of the finally clause.
9595
* This means that when processing returns, we expect to get a new set
@@ -112,7 +112,7 @@ export class BlockHandler {
112112
}
113113

114114
public forEachReturn(callback: (returnNode: string) => string) {
115-
this.returns = this.returns.map(callback)
115+
this.returns = this.returns.map(callback);
116116
}
117117

118118
public processGotos(callback: (gotoNode: string, labelNode: string) => void) {

src/control-flow/cfg-python.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class CFGBuilder {
4343
const endNode = this.addNode("RETURN", "implicit return");
4444
// `entry` will be non-null for any valid code
4545
if (entry) this.addEdge(startNode, entry);
46-
if (exit) this.addEdge(exit, endNode)
46+
if (exit) this.addEdge(exit, endNode);
4747
this.entry = startNode;
4848
}
4949
return { graph: this.graph, entry: this.entry };
@@ -68,10 +68,10 @@ export class CFGBuilder {
6868
this.activeClusters.pop();
6969
}
7070

71-
private withCluster<T>(type: ClusterType, fn: () => T): T {
71+
private withCluster<T>(type: ClusterType, fn: (cluster: Cluster) => T): T {
7272
const cluster = this.startCluster(type);
7373
try {
74-
return fn();
74+
return fn(cluster);
7575
} finally {
7676
this.endCluster(cluster);
7777
}
@@ -89,11 +89,12 @@ export class CFGBuilder {
8989
return id;
9090
}
9191

92-
private cloneNode(node: string): string {
92+
private cloneNode(node: string, overrides?: { cluster: Cluster }): string {
9393
const id = `node${this.nodeId++}`;
9494
const originalAttrs = this.graph.getNodeAttributes(node);
9595
const nodeAttrs = structuredClone(originalAttrs);
9696
nodeAttrs.cluster = originalAttrs.cluster;
97+
Object.assign(nodeAttrs, overrides);
9798
this.graph.addNode(id, nodeAttrs);
9899
return id;
99100
}
@@ -208,7 +209,7 @@ export class CFGBuilder {
208209

209210
const bodySyntax = this.getSyntax(match, "try-body");
210211
const finallySyntax = this.getSyntax(match, "finally-body");
211-
return this.withCluster("try-complex", () => {
212+
return this.withCluster("try-complex", (tryComplexCluster) => {
212213
const bodyBlock = this.withCluster("try", () =>
213214
getBlock(bodySyntax),
214215
) as BasicBlock;
@@ -222,10 +223,16 @@ export class CFGBuilder {
222223
// so that we can link them.
223224
const duplicateFinallyBlock = getBlock(finallySyntax) as BasicBlock;
224225
// We also clone the return node, to place it _after_ the finally block
225-
const returnNodeClone = this.cloneNode(returnNode);
226+
// We also override the cluster node, pulling it up to the `try-complex`,
227+
// as the return is neither in a `try`, `except`, or `finally` context.
228+
const returnNodeClone = this.cloneNode(returnNode, {
229+
cluster: tryComplexCluster,
230+
});
226231

227-
if (duplicateFinallyBlock.entry) this.addEdge(returnNode, duplicateFinallyBlock.entry);
228-
if (duplicateFinallyBlock.exit) this.addEdge(duplicateFinallyBlock.exit, returnNodeClone)
232+
if (duplicateFinallyBlock.entry)
233+
this.addEdge(returnNode, duplicateFinallyBlock.entry);
234+
if (duplicateFinallyBlock.exit)
235+
this.addEdge(duplicateFinallyBlock.exit, returnNodeClone);
229236

230237
// We return the cloned return node as the new return node, in case we're nested
231238
// in a scope that will process it.
@@ -240,13 +247,13 @@ export class CFGBuilder {
240247
return finallyBlock;
241248
});
242249

243-
if (bodyBlock.exit && finallyBlock?.entry) this.addEdge(bodyBlock.exit, finallyBlock.entry);
250+
if (bodyBlock.exit && finallyBlock?.entry)
251+
this.addEdge(bodyBlock.exit, finallyBlock.entry);
244252

245253
return blockHandler.update({
246254
entry: bodyBlock.entry,
247255
exit: finallyBlock?.exit ?? bodyBlock.exit,
248256
});
249-
250257
});
251258
}
252259
private processWithStatement(withSyntax: Parser.SyntaxNode): BasicBlock {
@@ -448,7 +455,10 @@ export class CFGBuilder {
448455
return match;
449456
}
450457

451-
private getSyntax(match: Parser.QueryMatch, name: string): Parser.SyntaxNode | undefined {
458+
private getSyntax(
459+
match: Parser.QueryMatch,
460+
name: string,
461+
): Parser.SyntaxNode | undefined {
452462
return this.getSyntaxMany(match, name)[0];
453463
}
454464

src/control-flow/render.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ function buildHierarchy(cfg: CFG): Hierarchy {
7777
};
7878
}
7979

80-
8180
// cfg.graph.forEachNode((node, { cluster }) => {
8281
// console.log(node, cluster?.id ?? "toplevel");
8382
// });
@@ -122,7 +121,7 @@ function renderNode(graph: CFGGraph, node: string, verbose: boolean): string {
122121
if (verbose) {
123122
label = `${node} ${graph.getNodeAttributes(node).type} ${graph.getNodeAttributes(node).code}`;
124123

125-
const clusterAttrs = graph.getNodeAttribute(node, "cluster")
124+
const clusterAttrs = graph.getNodeAttribute(node, "cluster");
126125
label = `${clusterAttrs?.id} ${clusterAttrs?.type}\n${label}`;
127126
}
128127
let shape = "box";
@@ -209,11 +208,9 @@ function renderSubgraphs(
209208
verbose: boolean,
210209
topGraph: CFGGraph,
211210
) {
212-
let dotContent = ""
213-
if (hierarchy.cluster?.type !== "try") {
214-
dotContent += `subgraph cluster_${hierarchy.cluster?.id ?? "toplevel"} {\n`;
215-
if (hierarchy.cluster) dotContent += clusterStyle(hierarchy.cluster);
216-
}
211+
let dotContent = "";
212+
dotContent += `subgraph cluster_${hierarchy.cluster?.id ?? "toplevel"} {\n`;
213+
if (hierarchy.cluster) dotContent += clusterStyle(hierarchy.cluster);
217214
hierarchy.graph.forEachNode((node) => {
218215
dotContent += renderNode(topGraph, node, verbose);
219216
});
@@ -223,9 +220,7 @@ function renderSubgraphs(
223220
hierarchy.graph.forEachEdge((edge, _attributes, source, target) => {
224221
dotContent += renderEdge(edge, source, target, topGraph);
225222
});
226-
if (hierarchy.cluster?.type !== "try") {
227-
dotContent += "\n}";
228-
}
223+
dotContent += "\n}";
229224
return dotContent;
230225
}
231226

0 commit comments

Comments
 (0)