Skip to content

Commit db8d660

Browse files
jgardn3rcirras
authored andcommitted
Fix cfg exceptions around empty statement lists
1 parent 53f7d67 commit db8d660

File tree

3 files changed

+151
-1
lines changed

3 files changed

+151
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Exceptions from empty structures (e.g., `if`) in `LoopExecutingAtMostOnce` and `RedundantJump`.
13+
1014
## [1.14.1] - 2025-03-05
1115

1216
### Fixed

delphi-frontend/src/main/java/au/com/integradev/delphi/cfg/api/Branch.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
*/
1919
package au.com.integradev.delphi.cfg.api;
2020

21+
import java.util.Collections;
22+
import java.util.HashSet;
2123
import java.util.Set;
2224

2325
/** A block where the control flow is dictated by a boolean condition, e.g., {@code if} */
@@ -38,6 +40,9 @@ public interface Branch extends Block, Terminated {
3840

3941
@Override
4042
default Set<Block> getSuccessors() {
41-
return Set.of(getTrueBlock(), getFalseBlock());
43+
Set<Block> successors = new HashSet<>();
44+
successors.add(getTrueBlock());
45+
successors.add(getFalseBlock());
46+
return Collections.unmodifiableSet(successors);
4247
}
4348
}

delphi-frontend/src/test/java/au/com/integradev/delphi/cfg/ControlFlowGraphTest.java

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,28 @@ void testIfAnd() {
317317
block(element(NameReferenceNode.class, "Foo")).succeedsTo(0)));
318318
}
319319

320+
@Test
321+
void testEmptyIf() {
322+
test(
323+
"if C then begin end; A;",
324+
checker(
325+
block(element(NameReferenceNode.class, "C"))
326+
.branchesTo(1, 1)
327+
.withTerminator(IfStatementNode.class),
328+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
329+
}
330+
331+
@Test
332+
void testEmptyIfElse() {
333+
test(
334+
"if C then begin end else begin end; A;",
335+
checker(
336+
block(element(NameReferenceNode.class, "C"))
337+
.branchesTo(1, 1)
338+
.withTerminator(IfStatementNode.class),
339+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
340+
}
341+
320342
@Test
321343
void testLocalVarDeclaration() {
322344
test(
@@ -421,6 +443,42 @@ void testCaseStatementElse() {
421443
.succeedsToCases(2, 3, 4)));
422444
}
423445

446+
@Test
447+
void testEmptyCase() {
448+
test(
449+
"case S of end; A;",
450+
checker(
451+
block(element(NameReferenceNode.class, "S"))
452+
.withTerminator(CaseStatementNode.class)
453+
.succeedsToCases(1),
454+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
455+
}
456+
457+
@Test
458+
void testEmptyCaseElse() {
459+
test(
460+
"case S of else end; A;",
461+
checker(
462+
block(element(NameReferenceNode.class, "S"))
463+
.withTerminator(CaseStatementNode.class)
464+
.succeedsToCases(1),
465+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
466+
}
467+
468+
@Test
469+
void testEmptyCaseArm() {
470+
test(
471+
"case S of S1:; S2:; end; A;",
472+
checker(
473+
block(
474+
element(NameReferenceNode.class, "S"),
475+
element(NameReferenceNode.class, "S1"),
476+
element(NameReferenceNode.class, "S2"))
477+
.withTerminator(CaseStatementNode.class)
478+
.succeedsToCases(1, 1),
479+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
480+
}
481+
424482
@Test
425483
void testRepeat() {
426484
test(
@@ -457,6 +515,17 @@ void testRepeatBreak() {
457515
.withTerminator(RepeatStatementNode.class)));
458516
}
459517

518+
@Test
519+
void testEmptyRepeat() {
520+
test(
521+
"repeat until C; A;",
522+
checker(
523+
block(element(NameReferenceNode.class, "C"))
524+
.branchesTo(1, 2)
525+
.withTerminator(RepeatStatementNode.class),
526+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
527+
}
528+
460529
@Test
461530
void testWhile() {
462531
test(
@@ -493,6 +562,17 @@ void testWhileBreak() {
493562
.withTerminator(StatementTerminator.BREAK)));
494563
}
495564

565+
@Test
566+
void testEmptyWhile() {
567+
test(
568+
"while C do; A;",
569+
checker(
570+
block(element(NameReferenceNode.class, "C"))
571+
.branchesTo(2, 1)
572+
.withTerminator(WhileStatementNode.class),
573+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
574+
}
575+
496576
@Test
497577
void testForToVarDecl() {
498578
test(
@@ -585,6 +665,19 @@ void testForToConditionalContinue() {
585665
.withTerminator(ForToStatementNode.class)));
586666
}
587667

668+
@Test
669+
void testEmptyForTo() {
670+
test(
671+
"for I := F to T do; A;",
672+
checker(
673+
block(element(NameReferenceNode.class, "F")).succeedsTo(3),
674+
block(element(NameReferenceNode.class, "T")).succeedsTo(2),
675+
block(element(NameReferenceNode.class, "I"))
676+
.branchesTo(2, 1)
677+
.withTerminator(ForToStatementNode.class),
678+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
679+
}
680+
588681
@Test
589682
void testForInVarDecl() {
590683
test(
@@ -671,6 +764,18 @@ void testForInConditionalContinue() {
671764
.withTerminator(ForInStatementNode.class)));
672765
}
673766

767+
@Test
768+
void testEmptyForIn() {
769+
test(
770+
"for I in C do; A;",
771+
checker(
772+
block(element(NameReferenceNode.class, "C")).succeedsTo(2),
773+
block(element(NameReferenceNode.class, "I"))
774+
.branchesTo(2, 1)
775+
.withTerminator(ForInStatementNode.class),
776+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
777+
}
778+
674779
@Test
675780
void testBreakOutsideOfLoop() {
676781
GraphChecker checker = checker();
@@ -696,6 +801,15 @@ void testWith() {
696801
block(element(NameReferenceNode.class, "Foo")).succeedsTo(0)));
697802
}
698803

804+
@Test
805+
void testEmptyWith() {
806+
test(
807+
"with S do; A;",
808+
checker(
809+
block(element(NameReferenceNode.class, "S")).succeedsTo(1),
810+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
811+
}
812+
699813
@Test
700814
void testTryFinallyNoRaise() {
701815
test(
@@ -1074,6 +1188,25 @@ void testTryExceptReRaise() {
10741188
.succeedsToWithExceptions(0)));
10751189
}
10761190

1191+
@Test
1192+
void testEmptyTryExcept() {
1193+
test(
1194+
"try except end; A",
1195+
checker(
1196+
block(element(TryStatementNode.class)).succeedsTo(1),
1197+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
1198+
}
1199+
1200+
@Test
1201+
void testEmptyTryFinally() {
1202+
test(
1203+
"try finally end; A",
1204+
checker(
1205+
block(element(TryStatementNode.class)).succeedsTo(2),
1206+
block().succeedsToWithExit(1, 0),
1207+
block(element(NameReferenceNode.class, "A")).succeedsTo(0)));
1208+
}
1209+
10771210
@Test
10781211
void testRaiseOutsideTry() {
10791212
test(
@@ -1320,6 +1453,14 @@ void testLabelSeparatesBlock() {
13201453
.jumpsTo(2, 0)));
13211454
}
13221455

1456+
@Test
1457+
void testEmptyLabel() {
1458+
test(
1459+
Map.of("label", List.of("A,B")),
1460+
"A: B: C;",
1461+
checker(block(element(NameReferenceNode.class, "C")).succeedsTo(0)));
1462+
}
1463+
13231464
@Test
13241465
void testAnonymousRoutinesAreIgnored() {
13251466
test(

0 commit comments

Comments
 (0)