Skip to content

Commit da5bbe4

Browse files
full_slicer: do not remove ASSUME false
An ASSUME false has no successors in the control-flow graph, so no instruction is control dependent on it. The full slicer therefore removed it unless it was the slicing criterion, which made the instructions that follow it reachable on paths that it blocked, resulting in spurious counterexamples. For example, each failing Java assertion is encoded as ASSERT false followed by ASSUME false, so that with --full-slice any later assertion was reported as failing with the values that violate the first one. Loops unwound by goto-instrument --unwind (without unwinding assertions) and function bodies generated with --generate-function-body-options assume-false end paths the same way; in the latter case the slicer removed the call of the function. Always retain ASSUME false instructions. Their control dependencies, and the calls of the function that contains them, are then retained as well. On jbmc/regression/jbmc-strings with --full-slice this fixes 6 tests and does not cause any new failures. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 37a17ad commit da5bbe4

9 files changed

Lines changed: 119 additions & 0 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
<groupId>org.cprover.regression</groupId>
7+
<artifactId>regression.jbmc.full-slice-assume-false</artifactId>
8+
<version>1.0-SNAPSHOT</version>
9+
10+
<parent>
11+
<groupId>org.cprover.regression</groupId>
12+
<artifactId>regression.jbmc</artifactId>
13+
<version>1.0-SNAPSHOT</version>
14+
</parent>
15+
16+
<build>
17+
<plugins>
18+
<plugin>
19+
<artifactId>maven-jar-plugin</artifactId>
20+
<executions>
21+
<execution>
22+
<id>default-jar</id>
23+
<phase>none</phase>
24+
</execution>
25+
</executions>
26+
</plugin>
27+
</plugins>
28+
</build>
29+
30+
</project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
public class Test {
2+
public static void check(int x) {
3+
assert x != 7;
4+
// Only reachable if the first assertion holds
5+
assert x != 7;
6+
}
7+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
Test
3+
-cp target/classes --function Test.check --full-slice
4+
^\[java::Test\.check:\(I\)V\.assertion\.1\] line 3 .*: FAILURE$
5+
^\[java::Test\.check:\(I\)V\.assertion\.2\] line 5 .*: SUCCESS$
6+
^EXIT=10$
7+
^SIGNAL=0$
8+
^VERIFICATION FAILED$
9+
--
10+
--
11+
A failing Java assertion ends the path with ASSUME false. The full slicer must
12+
keep it, else the second assertion becomes reachable with x == 7.

‎jbmc/regression/jbmc/pom.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
<module>finally7</module>
159159
<module>float1</module>
160160
<module>FloatToIntConversion</module>
161+
<module>full-slice-assume-false</module>
161162
<module>function1</module>
162163
<module>function2</module>
163164
<module>function3</module>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
int nondet_int();
2+
3+
int main()
4+
{
5+
int i = nondet_int();
6+
#ifdef __GNUC__
7+
if(i == 2)
8+
{
9+
__CPROVER_assert(i < 100, "before the assumption");
10+
// __builtin_types_compatible_p yields a proper "bool" typed constant,
11+
// which makes this an ASSUME false
12+
__CPROVER_assume(__builtin_types_compatible_p(double, float));
13+
}
14+
#else
15+
__CPROVER_assume(i != 2);
16+
#endif
17+
__CPROVER_assert(i != 2, "only reachable with i != 2");
18+
return 0;
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--full-slice
4+
^\[main.assertion.1\] line \d+ before the assumption: SUCCESS$
5+
^\[main.assertion.2\] line \d+ only reachable with i != 2: SUCCESS$
6+
^VERIFICATION SUCCESSFUL$
7+
^EXIT=0$
8+
^SIGNAL=0$
9+
--
10+
--
11+
The full slicer must not remove an ASSUME false: it has no successors in the
12+
control-flow graph, so that no instruction is control dependent on it, but
13+
removing it makes the second assertion reachable with i == 2.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
int nondet_int();
2+
void stop(void);
3+
4+
int main()
5+
{
6+
int i = nondet_int();
7+
if(i == 2)
8+
{
9+
__CPROVER_assert(i < 100, "before the call");
10+
// The body of stop is generated using --generate-function-body, it is
11+
// just ASSUME false.
12+
stop();
13+
}
14+
__CPROVER_assert(i != 2, "only reachable with i != 2");
15+
return 0;
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
main.c
3+
--generate-function-body stop --generate-function-body-options assume-false --full-slice
4+
^\[main.assertion.1\] line \d+ before the call: SUCCESS$
5+
^\[main.assertion.2\] line \d+ only reachable with i != 2: SUCCESS$
6+
^VERIFICATION SUCCESSFUL$
7+
^EXIT=0$
8+
^SIGNAL=0$
9+
--
10+
--
11+
The full slicer must keep the call of a function that does not return, here
12+
because its body is ASSUME false: removing the call makes the second assertion
13+
reachable with i == 2.

‎src/goto-instrument/full_slicer.cpp‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,14 @@ static bool implicit(goto_programt::const_targett target)
239239
if(statement==ID_array_copy)
240240
return true;
241241

242+
// An ASSUME false ends all paths through it. As it has no successors in
243+
// the CFG, no instruction is control dependent on it, and neither are the
244+
// instructions following a call of a function that does not return because
245+
// of it. Removing it would, however, make those instructions reachable on
246+
// the paths that it blocks.
247+
if(target->is_assume() && target->condition() == false)
248+
return true;
249+
242250
if(!target->is_assign())
243251
return false;
244252

0 commit comments

Comments
 (0)