Skip to content

Commit 95a99fc

Browse files
committed
Java: make null-pointer and similar checks fatal
These previously used code_assertt, which asserts but then continues -- this changes them to use assert(condition); assume(condition), which ensures subsequent code is not executed in conditions which are in practice impossible. With --java-throw-runtime-exceptions this is irrelevant, as a real exception is raised and the conditional GOTO guarding the throw has the same effect as the assume.
1 parent 06d8bea commit 95a99fc

16 files changed

+221
-36
lines changed

regression/cbmc-java/NullPointer3/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ NullPointer3.class
33
--pointer-check
44
^EXIT=10$
55
^SIGNAL=0$
6-
^.*Throw null: FAILURE$
6+
^.*Null pointer check: FAILURE$
77
^VERIFICATION FAILED$
88
--
99
^warning: ignoring
251 Bytes
Binary file not shown.
216 Bytes
Binary file not shown.
1.36 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
public class Test {
2+
3+
// In each of these cases a guard is repeated -- either an explicit assertion,
4+
// or a guard implied by Java's semantics, e.g. that an array index is in bounds.
5+
// It should be possible to violate the condition the first time, but not the second,
6+
// as the program would have aborted (without --java-throw-runtime-exceptions) or a
7+
// runtime exception thrown (with --java-throw-runtime-exceptions).
8+
9+
public static void testAssertion(boolean condition) {
10+
assert(condition);
11+
assert(condition);
12+
}
13+
14+
public static void testArrayBounds(int[] array, int index) {
15+
if(array == null)
16+
return;
17+
int got = array[index];
18+
got = array[index];
19+
}
20+
21+
public static void testClassCast(boolean unknown) {
22+
A maybe_b = unknown ? new A() : new B();
23+
B definitely_b = (B)maybe_b;
24+
definitely_b = (B)maybe_b;
25+
}
26+
27+
public static void testNullDeref(A maybeNull) {
28+
int got = maybeNull.field;
29+
got = maybeNull.field;
30+
}
31+
32+
public static void testDivByZero(int unknown) {
33+
int div = 1 / unknown;
34+
div = 1 / unknown;
35+
}
36+
37+
public static void testArrayCreation(int maybeNegative) {
38+
int[] arr = new int[maybeNegative];
39+
arr = new int[maybeNegative];
40+
}
41+
42+
}
43+
44+
class A { public int field; }
45+
class B extends A {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
Test.class
3+
--function Test.testArrayBounds
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
array-index-out-of-bounds-low\.1.*: FAILURE$
8+
array-index-out-of-bounds-low\.2.*: SUCCESS$
9+
array-index-out-of-bounds-high\.1.*: FAILURE$
10+
array-index-out-of-bounds-high\.2.*: SUCCESS$
11+
--
12+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--function Test.testArrayCreation
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
array-create-negative-size\.1.*: FAILURE$
8+
array-create-negative-size\.2.*: SUCCESS$
9+
--
10+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--function Test.testAssertion
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
assertion at file Test\.java line 10.*: FAILURE$
8+
assertion at file Test\.java line 11.*: SUCCESS$
9+
--
10+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--function Test.testClassCast
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
bad-dynamic-cast\.1.*: FAILURE$
8+
bad-dynamic-cast\.2.*: SUCCESS$
9+
--
10+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--function Test.testDivByZero
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
integer-divide-by-zero\.1.*: FAILURE$
8+
integer-divide-by-zero\.2.*: SUCCESS$
9+
--
10+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--function Test.testNullDeref
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
testNullDeref:\(LA;\)V\.null-pointer-exception\.1.*: FAILURE$
8+
testNullDeref:\(LA;\)V\.null-pointer-exception\.2.*: SUCCESS$
9+
--
10+
^warning: ignoring

src/goto-instrument/cover.cpp

+14-1
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,21 @@ static void instrument_cover_goals(
261261
{
262262
Forall_goto_program_instructions(i_it, function.body)
263263
{
264+
// Simplify the common case where we have ASSERT(x); ASSUME(x):
264265
if(i_it->is_assert())
265-
i_it->type=goto_program_instruction_typet::ASSUME;
266+
{
267+
auto successor = std::next(i_it);
268+
if(successor != function.body.instructions.end() &&
269+
successor->is_assume() &&
270+
successor->guard == i_it->guard)
271+
{
272+
i_it->make_skip();
273+
}
274+
else
275+
{
276+
i_it->type = goto_program_instruction_typet::ASSUME;
277+
}
278+
}
266279
}
267280
}
268281

src/java_bytecode/java_bytecode_instrument.cpp

+59-34
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Date: June 2017
1111
#include "java_bytecode_instrument.h"
1212

1313
#include <util/arith_tools.h>
14+
#include <util/assert_or_die.h>
1415
#include <util/fresh_symbol.h>
1516
#include <util/std_code.h>
1617
#include <util/std_expr.h>
@@ -82,6 +83,21 @@ const std::vector<std::string> exception_needed_classes = {
8283
"java.lang.NegativeArraySizeException",
8384
"java.lang.NullPointerException"};
8485

86+
/// Get a copy of a source location with additional assertion information
87+
/// \param loc: original source location
88+
/// \param comment: human-readable assertion description
89+
/// \param property_class: assertion property class
90+
static source_locationt with_assertion_description(
91+
const source_locationt &loc,
92+
const irep_idt &comment,
93+
const irep_idt &property_class)
94+
{
95+
source_locationt ret = loc;
96+
ret.set_comment(comment);
97+
ret.set_property_class(property_class);
98+
return ret;
99+
}
100+
85101
/// Creates a class stub for exc_name and generates a
86102
/// conditional GOTO such that exc_name is thrown when
87103
/// cond is met.
@@ -160,11 +176,14 @@ codet java_bytecode_instrumentt::check_arithmetic_exception(
160176
original_loc,
161177
"java.lang.ArithmeticException");
162178

163-
code_assertt ret(binary_relation_exprt(denominator, ID_notequal, zero));
164-
ret.add_source_location()=original_loc;
165-
ret.add_source_location().set_comment("Denominator should be nonzero");
166-
ret.add_source_location().set_property_class("integer-divide-by-zero");
167-
return ret;
179+
source_locationt assertion_loc =
180+
with_assertion_description(
181+
original_loc,
182+
"Denominator should be nonzero",
183+
"integer-divide-by-zero");
184+
185+
return create_assert_or_die(
186+
binary_relation_exprt(denominator, ID_notequal, zero), assertion_loc);
168187
}
169188

170189
/// Checks whether the array access array_struct[idx] is out-of-bounds,
@@ -195,19 +214,21 @@ codet java_bytecode_instrumentt::check_array_access(
195214
"java.lang.ArrayIndexOutOfBoundsException");
196215

197216
code_blockt bounds_checks;
198-
bounds_checks.add(code_assertt(ge_zero));
199-
bounds_checks.operands().back().add_source_location()=original_loc;
200-
bounds_checks.operands().back().add_source_location()
201-
.set_comment("Array index should be >= 0");
202-
bounds_checks.operands().back().add_source_location()
203-
.set_property_class("array-index-out-of-bounds-low");
204-
205-
bounds_checks.add(code_assertt(lt_length));
206-
bounds_checks.operands().back().add_source_location()=original_loc;
207-
bounds_checks.operands().back().add_source_location()
208-
.set_comment("Array index should be < length");
209-
bounds_checks.operands().back().add_source_location()
210-
.set_property_class("array-index-out-of-bounds-high");
217+
218+
bounds_checks.add(
219+
create_assert_or_die(
220+
ge_zero,
221+
with_assertion_description(
222+
original_loc,
223+
"Array index should be >= 0",
224+
"array-index-out-of-bounds-low")));
225+
bounds_checks.add(
226+
create_assert_or_die(
227+
lt_length,
228+
with_assertion_description(
229+
original_loc,
230+
"Array index should be < length",
231+
"array-index-out-of-bounds-high")));
211232

212233
return bounds_checks;
213234
}
@@ -246,11 +267,14 @@ codet java_bytecode_instrumentt::check_class_cast(
246267
}
247268
else
248269
{
249-
code_assertt assert_class(class_cast_check);
250-
assert_class.add_source_location().
251-
set_comment("Dynamic cast check");
252-
assert_class.add_source_location().
253-
set_property_class("bad-dynamic-cast");
270+
codet assert_class =
271+
create_assert_or_die(
272+
class_cast_check,
273+
with_assertion_description(
274+
original_loc,
275+
"Dynamic cast check",
276+
"bad-dynamic-cast"));
277+
254278
check_code=std::move(assert_class);
255279
}
256280

@@ -283,12 +307,12 @@ codet java_bytecode_instrumentt::check_null_dereference(
283307
equal_expr,
284308
original_loc, "java.lang.NullPointerException");
285309

286-
code_assertt check((not_exprt(equal_expr)));
287-
check.add_source_location()
288-
.set_comment("Throw null");
289-
check.add_source_location()
290-
.set_property_class("null-pointer-exception");
291-
return check;
310+
return create_assert_or_die(
311+
not_exprt(equal_expr),
312+
with_assertion_description(
313+
original_loc,
314+
"Null pointer check",
315+
"null-pointer-exception"));
292316
}
293317

294318
/// Checks whether `length`>=0 and throws NegativeArraySizeException/
@@ -313,11 +337,12 @@ codet java_bytecode_instrumentt::check_array_length(
313337
original_loc,
314338
"java.lang.NegativeArraySizeException");
315339

316-
code_assertt check(ge_zero);
317-
check.add_source_location().set_comment("Array size should be >= 0");
318-
check.add_source_location()
319-
.set_property_class("array-create-negative-size");
320-
return check;
340+
return create_assert_or_die(
341+
ge_zero,
342+
with_assertion_description(
343+
original_loc,
344+
"Array size should be >= 0",
345+
"array-create-negative-size"));
321346
}
322347

323348
/// Checks whether `expr` requires instrumentation, and if so adds it

src/util/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
SRC = arith_tools.cpp \
22
array_name.cpp \
3+
assert_or_die.cpp \
34
base_type.cpp \
45
bv_arithmetic.cpp \
56
byte_operators.cpp \

src/util/assert_or_die.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
#include "assert_or_die.h"
3+
4+
/// Cprover's code_assertt is non-fatal -- we check the condition and continue
5+
/// to execute the target program. By contrast code_assumet requires that the
6+
/// condition hold for execution to continue. This method therefore creates an
7+
/// "assert or die" construct, as C's `assert` function for example, that
8+
/// checks the condition and then halts if it does not hold.
9+
/// Source level assertions should probably use this, whilst checks that are
10+
/// normally non-fatal at runtime, such as integer overflows, should use
11+
/// code_assertt by itself.
12+
/// \param condition: condition to assert
13+
/// \return code of the form `assert(condition); assume(condition);`
14+
codet create_assert_or_die(const exprt &condition, const source_locationt &loc)
15+
{
16+
code_blockt result;
17+
result.copy_to_operands(code_assertt(condition));
18+
result.copy_to_operands(code_assumet(condition));
19+
for(auto &op : result.operands())
20+
op.add_source_location() = loc;
21+
result.add_source_location() = loc;
22+
return result;
23+
}

src/util/assert_or_die.h

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*******************************************************************\
2+
3+
Module: Assert or die
4+
5+
Author: Diffblue Ltd
6+
7+
\*******************************************************************/
8+
9+
#ifndef CPROVER_UTIL_ASSERT_OR_DIE_H
10+
#define CPROVER_UTIL_ASSERT_OR_DIE_H
11+
12+
#include "std_code.h"
13+
14+
codet create_assert_or_die(const exprt &condition, const source_locationt &loc);
15+
16+
#endif // CPROVER_UTIL_ASSERT_OR_DIE_H

0 commit comments

Comments
 (0)