Skip to content

Commit 36468a3

Browse files
committed
Add ControlFlowGraph and implementation
1 parent 987eff3 commit 36468a3

26 files changed

+4301
-0
lines changed
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Sonar Delphi Plugin
3+
* Copyright (C) 2025 Integrated Application Development
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
18+
*/
19+
package au.com.integradev.delphi.cfg;
20+
21+
import au.com.integradev.delphi.cfg.api.Block;
22+
import au.com.integradev.delphi.cfg.api.ControlFlowGraph;
23+
import au.com.integradev.delphi.cfg.block.BlockImpl;
24+
import au.com.integradev.delphi.cfg.block.ProtoBlock;
25+
import au.com.integradev.delphi.cfg.block.ProtoBlockFactory;
26+
import com.google.common.collect.Lists;
27+
import java.util.ArrayDeque;
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.Deque;
31+
import java.util.HashMap;
32+
import java.util.LinkedHashMap;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.Map.Entry;
36+
import java.util.Objects;
37+
import java.util.Optional;
38+
import java.util.Set;
39+
import java.util.stream.Collectors;
40+
import java.util.stream.Stream;
41+
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
42+
import org.sonar.plugins.communitydelphi.api.ast.GotoStatementNode;
43+
import org.sonar.plugins.communitydelphi.api.ast.LabelStatementNode;
44+
import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode;
45+
import org.sonar.plugins.communitydelphi.api.type.Type;
46+
47+
public class ControlFlowGraphBuilder {
48+
private final List<ProtoBlock> blocks = new ArrayList<>();
49+
private final Deque<ProtoBlock> exitBlocks = new ArrayDeque<>();
50+
private final Deque<ProtoBlock> breakTargets = new ArrayDeque<>();
51+
private final Deque<ProtoBlock> continueTargets = new ArrayDeque<>();
52+
private final Map<String, ProtoBlock> labelTargets = new HashMap<>();
53+
private final Map<String, List<UnresolvedLabel>> unresolvedLabels = new HashMap<>();
54+
private final Deque<TryContext> tryContexts = new ArrayDeque<>();
55+
56+
private ProtoBlock currentBlock;
57+
58+
public ControlFlowGraphBuilder() {
59+
ProtoBlock exitBlock = ProtoBlockFactory.exitBlock();
60+
addBlock(exitBlock);
61+
exitBlocks.add(exitBlock);
62+
addBlockBefore(exitBlock);
63+
}
64+
65+
public ControlFlowGraph build() {
66+
Map<ProtoBlock, Block> map = new LinkedHashMap<>();
67+
for (ProtoBlock block : blocks) {
68+
map.put(block, block.createBlock());
69+
}
70+
for (ProtoBlock block : blocks) {
71+
block.updateBlockData(map);
72+
}
73+
74+
ControlFlowGraphImpl cfg =
75+
new ControlFlowGraphImpl(
76+
map.get(currentBlock), map.get(exitBlocks.peek()), new ArrayList<>(map.values()));
77+
cfg.prune();
78+
79+
populatePredecessors(cfg);
80+
populateIds(cfg);
81+
82+
return cfg;
83+
}
84+
85+
private static void populatePredecessors(ControlFlowGraph cfg) {
86+
for (Block block : cfg.getBlocks()) {
87+
for (Block successor : block.getSuccessors()) {
88+
((BlockImpl) successor).addPredecessor(block);
89+
}
90+
}
91+
}
92+
93+
private static void populateIds(ControlFlowGraph cfg) {
94+
List<Block> blocks = Lists.reverse(cfg.getBlocks());
95+
for (int blockId = 0; blockId < blocks.size(); blockId++) {
96+
((BlockImpl) blocks.get(blockId)).setId(blockId);
97+
}
98+
}
99+
100+
public ProtoBlock getExitBlock() {
101+
return exitBlocks.peek();
102+
}
103+
104+
public ProtoBlock getBreakTarget() {
105+
return breakTargets.peek();
106+
}
107+
108+
public ProtoBlock getContinueTarget() {
109+
return continueTargets.peek();
110+
}
111+
112+
public void pushLoopContext(ProtoBlock continueTarget, ProtoBlock breakTarget) {
113+
continueTargets.push(continueTarget);
114+
breakTargets.push(breakTarget);
115+
}
116+
117+
public void popLoopContext() {
118+
breakTargets.pop();
119+
continueTargets.pop();
120+
}
121+
122+
public void pushExitBlock(ProtoBlock target) {
123+
exitBlocks.push(target);
124+
}
125+
126+
public void popExitBlock() {
127+
exitBlocks.pop();
128+
}
129+
130+
private static class UnresolvedLabel {
131+
ProtoBlock nextBlock;
132+
ProtoBlock block;
133+
DelphiNode node;
134+
}
135+
136+
public void addLabel(LabelStatementNode labelNode) {
137+
NameReferenceNode labelName = labelNode.getNameReference();
138+
String label = labelName.getImage();
139+
140+
labelTargets.put(label, currentBlock);
141+
if (!unresolvedLabels.containsKey(label)) {
142+
return;
143+
}
144+
145+
// When "resolving" label, all the previously unresolved targets must be updated
146+
for (UnresolvedLabel unresolvedLabel : unresolvedLabels.get(label)) {
147+
unresolvedLabel.block.update(
148+
ProtoBlockFactory.jump(unresolvedLabel.node, currentBlock, unresolvedLabel.nextBlock));
149+
}
150+
}
151+
152+
public void addGoto(GotoStatementNode gotoNode) {
153+
NameReferenceNode labelNode = gotoNode.getNameReference();
154+
String label = labelNode.getImage();
155+
if (labelTargets.containsKey(label)) {
156+
addBlock(ProtoBlockFactory.jump(gotoNode, labelTargets.get(label), currentBlock));
157+
return;
158+
}
159+
160+
// When labels are used before they are processed they become `unresolved`
161+
addBlockBeforeCurrent();
162+
unresolvedLabels.putIfAbsent(label, new ArrayList<>());
163+
UnresolvedLabel unresolvedLabel = new UnresolvedLabel();
164+
unresolvedLabel.nextBlock = currentBlock;
165+
unresolvedLabel.block = addBlockBeforeCurrent();
166+
unresolvedLabel.node = gotoNode;
167+
unresolvedLabels.get(label).add(unresolvedLabel);
168+
169+
addElement(labelNode);
170+
}
171+
172+
private static class TryContext {
173+
LinkedHashMap<Type, ProtoBlock> catches = new LinkedHashMap<>();
174+
ProtoBlock elseBlock;
175+
}
176+
177+
public void pushTryFinallyContext() {
178+
tryContexts.push(new TryContext());
179+
}
180+
181+
public void pushTryExceptContext(List<Entry<Type, ProtoBlock>> catches, ProtoBlock elseBlock) {
182+
TryContext tryContext = new TryContext();
183+
tryContext.catches = new LinkedHashMap<>();
184+
catches.forEach(entry -> tryContext.catches.put(entry.getKey(), entry.getValue()));
185+
tryContext.elseBlock = elseBlock;
186+
tryContexts.push(tryContext);
187+
}
188+
189+
public boolean inTryContext() {
190+
return !tryContexts.isEmpty();
191+
}
192+
193+
public ProtoBlock getCatchTarget(Type exceptionType) {
194+
if (tryContexts.isEmpty()) {
195+
return getExitBlock();
196+
}
197+
TryContext tryContext = tryContexts.peek();
198+
return tryContext.catches.keySet().stream()
199+
.filter(catchType -> isCompatibleType(exceptionType, catchType))
200+
.findFirst()
201+
.map(tryContext.catches::get)
202+
.or(() -> Optional.ofNullable(tryContext.elseBlock))
203+
.orElse(getExitBlock());
204+
}
205+
206+
private static boolean isCompatibleType(Type exceptionType, Type catchType) {
207+
return exceptionType.is(catchType) || exceptionType.isDescendantOf(catchType);
208+
}
209+
210+
public Set<ProtoBlock> getAllCatchTargets() {
211+
if (tryContexts.isEmpty()) {
212+
return Collections.emptySet();
213+
}
214+
TryContext context = tryContexts.peek();
215+
Stream<ProtoBlock> elseOrExit =
216+
Stream.of(Optional.ofNullable(context.elseBlock).orElse(getExitBlock()));
217+
return Stream.concat(context.catches.values().stream(), elseOrExit)
218+
.filter(Objects::nonNull)
219+
.collect(Collectors.toSet());
220+
}
221+
222+
public void popTryContext() {
223+
tryContexts.pop();
224+
}
225+
226+
public ProtoBlock getCurrentBlock() {
227+
return currentBlock;
228+
}
229+
230+
public void setCurrentBlock(ProtoBlock currentBlock) {
231+
this.currentBlock = currentBlock;
232+
}
233+
234+
public void addElement(DelphiNode element) {
235+
currentBlock.addElement(element);
236+
}
237+
238+
public ProtoBlock addBlockBeforeCurrent() {
239+
addBlockBefore(currentBlock);
240+
return currentBlock;
241+
}
242+
243+
public void addBlockBefore(ProtoBlock successor) {
244+
addBlock(ProtoBlockFactory.linear(successor));
245+
}
246+
247+
public void addBlock(ProtoBlock block) {
248+
blocks.add(block);
249+
currentBlock = block;
250+
}
251+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Sonar Delphi Plugin
3+
* Copyright (C) 2025 Integrated Application Development
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
18+
*/
19+
package au.com.integradev.delphi.cfg;
20+
21+
import au.com.integradev.delphi.cfg.api.Block;
22+
import au.com.integradev.delphi.cfg.api.ControlFlowGraph;
23+
import au.com.integradev.delphi.cfg.api.Terminated;
24+
import au.com.integradev.delphi.cfg.block.BlockImpl;
25+
import java.util.Optional;
26+
import java.util.stream.IntStream;
27+
import org.sonar.plugins.communitydelphi.api.ast.BinaryExpressionNode;
28+
import org.sonar.plugins.communitydelphi.api.ast.DelphiNode;
29+
30+
public final class ControlFlowGraphDebug {
31+
private static final int MAX_NODE_TYPE_NAME = 30;
32+
33+
private ControlFlowGraphDebug() {
34+
// Utility class
35+
}
36+
37+
public static String toString(ControlFlowGraph cfg) {
38+
StringBuilder buffer = new StringBuilder();
39+
buffer.append("Starts at ");
40+
buffer.append(getBlockString(cfg.getEntryBlock()));
41+
buffer.append('\n');
42+
buffer.append('\n');
43+
for (Block block : cfg.getBlocks()) {
44+
buffer.append(toString(block));
45+
}
46+
return buffer.toString();
47+
}
48+
49+
public static String toString(Block block) {
50+
StringBuilder buffer = new StringBuilder();
51+
buffer.append(getBlockString(block));
52+
53+
IntStream.range(0, block.getElements().size())
54+
.forEach(index -> appendElement(buffer, index, block.getElements().get(index)));
55+
56+
getAs(block, Terminated.class)
57+
.ifPresent(
58+
successors -> {
59+
buffer.append("\nT:\t");
60+
appendKind(buffer, successors.getTerminator());
61+
buffer.append(successors.getTerminator().getImage());
62+
});
63+
64+
buffer.append(getAs(block, BlockImpl.class).orElseThrow().getDescription());
65+
buffer.append("\n\n");
66+
return buffer.toString();
67+
}
68+
69+
private static void appendKind(StringBuilder buffer, DelphiNode node) {
70+
String name = node.getClass().getSimpleName();
71+
if (node instanceof BinaryExpressionNode) {
72+
name += " " + ((BinaryExpressionNode) node).getOperator();
73+
}
74+
buffer.append(String.format("%-" + MAX_NODE_TYPE_NAME + "s\t", name));
75+
}
76+
77+
private static void appendElement(StringBuilder buffer, int index, DelphiNode node) {
78+
buffer.append('\n');
79+
buffer.append(index);
80+
buffer.append(":\t");
81+
appendKind(buffer, node);
82+
buffer.append(node.getImage());
83+
}
84+
85+
private static String getBlockString(Block block) {
86+
return "B" + ((BlockImpl) block).getId();
87+
}
88+
89+
private static <T> Optional<T> getAs(Block block, Class<T> clazz) {
90+
if (clazz.isInstance(block)) {
91+
return Optional.of(clazz.cast(block));
92+
}
93+
return Optional.empty();
94+
}
95+
}

0 commit comments

Comments
 (0)