Skip to content

Commit 3f718ba

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 d87c2db commit 3f718ba

15 files changed

+182
-40
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

+11-1
Original file line numberDiff line numberDiff line change
@@ -261,8 +261,18 @@ 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+
successor->make_skip();
273+
}
274+
i_it->type = goto_program_instruction_typet::ASSUME;
275+
}
266276
}
267277
}
268278

src/java_bytecode/java_bytecode_instrument.cpp

+33-34
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,12 @@ codet java_bytecode_instrumentt::check_arithmetic_exception(
160160
original_loc,
161161
"java.lang.ArithmeticException");
162162

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;
163+
source_locationt assertion_loc = original_loc;
164+
assertion_loc.set_comment("Denominator should be nonzero");
165+
assertion_loc.set_property_class("integer-divide-by-zero");
166+
167+
return create_fatal_assertion(
168+
binary_relation_exprt(denominator, ID_notequal, zero), assertion_loc);
168169
}
169170

170171
/// Checks whether the array access array_struct[idx] is out-of-bounds,
@@ -195,19 +196,17 @@ codet java_bytecode_instrumentt::check_array_access(
195196
"java.lang.ArrayIndexOutOfBoundsException");
196197

197198
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");
199+
200+
source_locationt low_check_loc = original_loc;
201+
low_check_loc.set_comment("Array index should be >= 0");
202+
low_check_loc.set_property_class("array-index-out-of-bounds-low");
203+
204+
source_locationt high_check_loc = original_loc;
205+
high_check_loc.set_comment("Array index should be < length");
206+
high_check_loc.set_property_class("array-index-out-of-bounds-high");
207+
208+
bounds_checks.add(create_fatal_assertion(ge_zero, low_check_loc));
209+
bounds_checks.add(create_fatal_assertion(lt_length, high_check_loc));
211210

212211
return bounds_checks;
213212
}
@@ -246,11 +245,12 @@ codet java_bytecode_instrumentt::check_class_cast(
246245
}
247246
else
248247
{
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");
248+
source_locationt check_loc = original_loc;
249+
check_loc.set_comment("Dynamic cast check");
250+
check_loc.set_property_class("bad-dynamic-cast");
251+
252+
codet assert_class = create_fatal_assertion(class_cast_check, check_loc);
253+
254254
check_code=std::move(assert_class);
255255
}
256256

@@ -283,12 +283,11 @@ codet java_bytecode_instrumentt::check_null_dereference(
283283
equal_expr,
284284
original_loc, "java.lang.NullPointerException");
285285

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;
286+
source_locationt check_loc = original_loc;
287+
check_loc.set_comment("Null pointer check");
288+
check_loc.set_property_class("null-pointer-exception");
289+
290+
return create_fatal_assertion(not_exprt(equal_expr), check_loc);
292291
}
293292

294293
/// Checks whether `length`>=0 and throws NegativeArraySizeException/
@@ -313,11 +312,11 @@ codet java_bytecode_instrumentt::check_array_length(
313312
original_loc,
314313
"java.lang.NegativeArraySizeException");
315314

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;
315+
source_locationt check_loc;
316+
check_loc.set_comment("Array size should be >= 0");
317+
check_loc.set_property_class("array-create-negative-size");
318+
319+
return create_fatal_assertion(ge_zero, check_loc);
321320
}
322321

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

src/util/std_code.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,15 @@ void code_blockt::append(const code_blockt &extra_block)
107107
add(to_code(operand));
108108
}
109109
}
110+
111+
code_blockt create_fatal_assertion(
112+
const exprt &condition, const source_locationt &loc)
113+
{
114+
code_blockt result;
115+
result.copy_to_operands(code_assertt(condition));
116+
result.copy_to_operands(code_assumet(condition));
117+
for(auto &op : result.operands())
118+
op.add_source_location() = loc;
119+
result.add_source_location() = loc;
120+
return result;
121+
}

src/util/std_code.h

+18-4
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,7 @@ inline code_deadt &to_code_dead(codet &code)
350350
return static_cast<code_deadt &>(code);
351351
}
352352

353-
/*! \brief An assumption
354-
*/
353+
/// An assumption, which must hold in subsequent code.
355354
class code_assumet:public codet
356355
{
357356
public:
@@ -396,8 +395,8 @@ inline code_assumet &to_code_assume(codet &code)
396395
return static_cast<code_assumet &>(code);
397396
}
398397

399-
/*! \brief An assertion
400-
*/
398+
/// A non-fatal assertion, which checks a condition then permits execution to
399+
/// continue.
401400
class code_assertt:public codet
402401
{
403402
public:
@@ -442,6 +441,21 @@ inline code_assertt &to_code_assert(codet &code)
442441
return static_cast<code_assertt &>(code);
443442
}
444443

444+
/// Create a fatal assertion, which checks a condition and then halts if it does
445+
/// not hold. Equivalent to `ASSERT(condition); ASSUME(condition)`.
446+
///
447+
/// Source level assertions should probably use this, whilst checks that are
448+
/// normally non-fatal at runtime, such as integer overflows, should use
449+
/// code_assertt by itself.
450+
/// \param condition: condition to assert
451+
/// \param source_location: source location to attach to the generated code;
452+
/// conventionally this should have `comment` and `property_class` fields set
453+
/// to indicate the nature of the assertion.
454+
/// \return A code block that asserts a condition then aborts if it does not
455+
/// hold.
456+
code_blockt create_fatal_assertion(
457+
const exprt &condition, const source_locationt &source_location);
458+
445459
/*! \brief An if-then-else
446460
*/
447461
class code_ifthenelset:public codet

0 commit comments

Comments
 (0)