Skip to content

Commit c6db2ac

Browse files
authored
Fix two infinite loops in codegen (#1779)
This PR fixes infinite lops in the reaching definitions and dead storage code. The two added test files are based on ones from Solidity: - https://github.com/ethereum/solidity/blob/develop/test/libsolidity/smtCheckerTests/loops/do_while_break_2.sol - https://github.com/ethereum/solidity/blob/develop/test/libsolidity/smtCheckerTests/loops/do_while_break_2_fail.sol If you add `dbg!(block_no)` between the next two lines and run Solang on either of the test files, you will find that a block is visited repeatedly: https://github.com/hyperledger-solang/solang/blob/279ee911260d74c78acace41d753c5376a7e942e/src/codegen/reaching_definitions.rs#L56-L57 If you fix that infinite loop and add `dbg!(block_no)` between the next two lines, you will again find that a block is visited repeatedly: https://github.com/hyperledger-solang/solang/blob/279ee911260d74c78acace41d753c5376a7e942e/src/codegen/dead_storage.rs#L109-L110 In both cases, the problem appears to be that edges are added to the `blocks_todo` queue unconditionally. My understanding of these algorithms is that they try to iterate to a fixpoint. Hence, I changed each loop so that: - a flag records whether a _state change_ has occurred - at the end of each loop iteration, the edge is added to the queue only if the flag is set Furthermore, I analyzed each loop to try to determine what constitutes a state change. On this point, I invite scrutiny. Nits are welcome on anything, though. Signed-off-by: Samuel Moelius <sam@moeli.us>
1 parent 8eac360 commit c6db2ac

File tree

4 files changed

+75
-9
lines changed

4 files changed

+75
-9
lines changed

src/codegen/dead_storage.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ fn reaching_definitions(cfg: &mut ControlFlowGraph) -> (Vec<Vec<Vec<Transfer>>>,
141141
);
142142

143143
for edge in cfg.blocks[block_no].successors() {
144+
let mut changed = false;
145+
144146
if !block_vars.contains_key(&edge) {
145-
blocks_todo.insert(edge);
146-
block_vars.insert(edge, vec![vars.clone()]);
147+
changed |= block_vars.insert(edge, vec![vars.clone()]).is_none();
147148
} else if block_vars[&edge][0] != vars {
148-
blocks_todo.insert(edge);
149149
let block_vars = block_vars
150150
.get_mut(&edge)
151151
.expect("block vars must contain edge");
@@ -155,20 +155,26 @@ fn reaching_definitions(cfg: &mut ControlFlowGraph) -> (Vec<Vec<Vec<Transfer>>>,
155155
for (incoming_def, storage) in defs {
156156
if !entry.contains_key(incoming_def) {
157157
entry.insert(*incoming_def, storage.clone());
158+
changed = true;
158159
}
159160
}
160161
} else {
161-
block_vars[0].vars.insert(*var_no, defs.clone());
162+
changed |= block_vars[0].vars.insert(*var_no, defs.clone()).is_none();
162163
}
163164
}
164165

165166
// merge storage stores
166167
for store in &vars.stores {
167168
if !block_vars[0].stores.iter().any(|(def, _)| *def == store.0) {
168169
block_vars[0].stores.push(store.clone());
170+
changed = true;
169171
}
170172
}
171173
}
174+
175+
if changed {
176+
blocks_todo.insert(edge);
177+
}
172178
}
173179
}
174180

src/codegen/reaching_definitions.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,42 @@ pub fn find(cfg: &mut ControlFlowGraph) {
6565

6666
for edge in cfg.blocks[block_no].successors() {
6767
if cfg.blocks[edge].defs != vars {
68-
blocks_todo.insert(edge);
68+
let mut changed = false;
69+
6970
// merge incoming set
7071
for (var_no, defs) in &vars {
7172
if let Some(entry) = cfg.blocks[edge].defs.get_mut(var_no) {
7273
for (incoming_def, incoming_modified) in defs {
7374
if let Some(e) = entry.get_mut(incoming_def) {
74-
*e |= *incoming_modified;
75+
if !*e && *incoming_modified {
76+
*e = true;
77+
changed = true;
78+
}
7579
} else {
76-
entry.insert(*incoming_def, *incoming_modified);
80+
changed |=
81+
entry.insert(*incoming_def, *incoming_modified).is_none();
7782
}
7883
}
7984
} else {
80-
cfg.blocks[edge].defs.insert(*var_no, defs.clone());
85+
changed |= cfg.blocks[edge]
86+
.defs
87+
.insert(*var_no, defs.clone())
88+
.is_none();
8189
}
8290

8391
// If a definition from a block executed later reaches this block,
8492
// this is a loop. This is an analysis we use later at the
8593
// common subexpression elimination.
8694
for (incoming_def, _) in defs {
8795
if incoming_def.block_no >= edge {
88-
cfg.blocks[edge].loop_reaching_variables.insert(*var_no);
96+
changed |= cfg.blocks[edge].loop_reaching_variables.insert(*var_no);
8997
}
9098
}
9199
}
100+
101+
if changed {
102+
blocks_todo.insert(edge);
103+
}
92104
}
93105
}
94106
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: --target polkadot --emit cfg
2+
3+
contract C {
4+
// CHECK: C::function::f
5+
function f() public pure {
6+
uint a = 0;
7+
while (true) {
8+
do {
9+
break;
10+
a = 2;
11+
} while (true);
12+
a = 1;
13+
break;
14+
}
15+
assert(a == 1);
16+
}
17+
}
18+
// ====
19+
// SMTEngine: all
20+
// SMTSolvers: z3
21+
// ----
22+
// Warning 5740: (95-100): Unreachable code.
23+
// Warning 5740: (114-118): Unreachable code.
24+
// Info 1391: CHC: 1 verification condition(s) proved safe! Enable the model checker option "show proved safe" to see all of them.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// RUN: --target polkadot --emit cfg
2+
3+
contract C {
4+
// CHECK: C::function::f
5+
function f() public pure {
6+
uint a = 0;
7+
while (true) {
8+
do {
9+
break;
10+
a = 2;
11+
} while (true);
12+
a = 1;
13+
break;
14+
}
15+
assert(a == 2);
16+
}
17+
}
18+
// ====
19+
// SMTEngine: all
20+
// SMTSolvers: z3
21+
// ----
22+
// Warning 5740: (95-100): Unreachable code.
23+
// Warning 5740: (114-118): Unreachable code.
24+
// Warning 6328: (147-161): CHC: Assertion violation happens here.\nCounterexample:\n\na = 1\n\nTransaction trace:\nC.constructor()\nC.f()

0 commit comments

Comments
 (0)