Skip to content

Commit 6af4d4e

Browse files
committed
Add null check on virtual function calls after its subexpressions
For example, for "x->y . f()" we should `assert(x != null); assert(x->y != null);`, not the other way around.
1 parent c752d9d commit 6af4d4e

File tree

13 files changed

+190
-3
lines changed

13 files changed

+190
-3
lines changed
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
public class Test {
2+
3+
public static void main(int argc) {
4+
5+
A a = argc == 1 ? new A() : null;
6+
a.field.virtualmethod();
7+
8+
}
9+
10+
}
11+
12+
class A {
13+
14+
public B field;
15+
16+
public A() {
17+
field = new B();
18+
}
19+
20+
}
21+
22+
class B {
23+
24+
public void virtualmethod() { }
25+
26+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CORE
2+
Test.class
3+
--function Test.main
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
\[java::Test\.main:\(I\)V\.null-pointer-exception\.3\] line 6.*SUCCESS
7+
\[java::Test\.main:\(I\)V\.null-pointer-exception\.2\] line 6.*FAILURE
8+
^VERIFICATION FAILED$
9+
--
10+
^warning: ignoring
11+
--
12+
This is testing the construct "something->field . some_method()"
13+
That requires a null check against "something" (for the deref to read 'field')
14+
and another one against "something->field" (for the instance method call).
15+
If they're made in the correct order (check "something" first) then one of the
16+
null checks cannot fail in this particular scenario; if they're made in the
17+
wrong order ("something->field" first) then they can both fail.

jbmc/src/java_bytecode/java_bytecode_instrument.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ void java_bytecode_instrumentt::instrument_code(codet &code)
445445
const java_method_typet &function_type =
446446
to_java_method_type(code_function_call.function().type());
447447

448+
for(const auto &arg : code_function_call.arguments())
449+
add_expr_instrumentation(block, arg);
450+
448451
// Check for a null this-argument of a virtual call:
449452
if(function_type.has_this())
450453
{
@@ -453,9 +456,6 @@ void java_bytecode_instrumentt::instrument_code(codet &code)
453456
code_function_call.source_location()));
454457
}
455458

456-
for(const auto &arg : code_function_call.arguments())
457-
add_expr_instrumentation(block, arg);
458-
459459
prepend_instrumentation(code, block);
460460
}
461461

jbmc/unit/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ SRC += java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp \
1919
java_bytecode/java_bytecode_convert_method/convert_initalizers.cpp \
2020
java_bytecode/java_bytecode_convert_method/convert_invoke_dynamic.cpp \
2121
java_bytecode/java_bytecode_convert_method/convert_method.cpp \
22+
java_bytecode/java_bytecode_instrument/virtual_call_null_checks.cpp \
2223
java_bytecode/java_bytecode_parse_generics/parse_bounded_generic_inner_classes.cpp \
2324
java_bytecode/java_bytecode_parse_generics/parse_derived_generic_class.cpp \
2425
java_bytecode/java_bytecode_parse_generics/parse_functions_with_generics.cpp \
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
public class VirtualCallNullChecks {
2+
3+
public static void main() {
4+
5+
A a = new A();
6+
a.field.virtualmethod();
7+
8+
}
9+
10+
}
11+
12+
class A {
13+
14+
public B field;
15+
16+
public A() {
17+
field = new B();
18+
}
19+
20+
}
21+
22+
class B {
23+
24+
public void virtualmethod() { }
25+
26+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
testing-utils
2+
java-testing-utils
3+
analyses
4+
goto-programs
5+
java_bytecode
6+
util
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*******************************************************************\
2+
3+
Module: Unit test to check Java virtual calls via a pointer
4+
yield a correct sequence of not-null assumptions.
5+
6+
Author: Diffblue Limited.
7+
8+
\*******************************************************************/
9+
10+
#include <testing-utils/catch.hpp>
11+
#include <testing-utils/message.h>
12+
#include <java-testing-utils/load_java_class.h>
13+
14+
#include <analyses/local_safe_pointers.h>
15+
#include <goto-programs/goto_convert_functions.h>
16+
#include <java_bytecode/java_bytecode_language.h>
17+
#include <util/expr_iterator.h>
18+
19+
// We're expecting a call "something->field . B.virtualmethod()":
20+
static bool is_expected_virtualmethod_call(
21+
const goto_programt::instructiont &instruction)
22+
{
23+
if(instruction.type != FUNCTION_CALL)
24+
return false;
25+
const auto &virtual_call = to_code_function_call(instruction.code);
26+
const auto &called_function = virtual_call.function();
27+
if(called_function.id() != ID_virtual_function)
28+
return false;
29+
if(called_function.get(ID_identifier) != "java::B.virtualmethod:()V")
30+
return false;
31+
if(virtual_call.arguments().size() != 1)
32+
return false;
33+
const auto &this_argument = virtual_call.arguments()[0];
34+
if(this_argument.id() != ID_member)
35+
return false;
36+
if(this_argument.op0().id() != ID_dereference)
37+
return false;
38+
39+
return true;
40+
}
41+
42+
SCENARIO(
43+
"java_bytecode_virtual_call_null_checks",
44+
"[core][java_bytecode][java_bytecode_instrument]")
45+
{
46+
GIVEN("A class that makes a virtual call via a pointer")
47+
{
48+
symbol_tablet symbol_table = load_java_class(
49+
"VirtualCallNullChecks", "./java_bytecode/java_bytecode_instrument");
50+
51+
WHEN("The virtual call is converted")
52+
{
53+
namespacet ns(symbol_table);
54+
goto_functionst goto_functions;
55+
goto_convert(symbol_table, goto_functions, null_message_handler);
56+
57+
const auto &main_function =
58+
goto_functions.function_map.at("java::VirtualCallNullChecks.main:()V");
59+
60+
THEN("The loaded function should call B.virtualmethod via a pointer")
61+
{
62+
// This just checks that the class actually makes the expected call,
63+
// i.e. that it hasn't been optimised away or similar.
64+
std::size_t found_virtualmethod_calls =
65+
std::count_if(
66+
main_function.body.instructions.begin(),
67+
main_function.body.instructions.end(),
68+
is_expected_virtualmethod_call);
69+
70+
REQUIRE(found_virtualmethod_calls == 1);
71+
}
72+
THEN("All pointer usages should be safe")
73+
{
74+
// This analysis checks that any usage of a pointer is preceded by an
75+
// assumption that it is non-null
76+
// (e.g. assume(x != nullptr); y = x->...)
77+
local_safe_pointerst safe_pointers(ns);
78+
safe_pointers(main_function.body);
79+
80+
for(auto instrit = main_function.body.instructions.begin(),
81+
instrend = main_function.body.instructions.end();
82+
instrit != instrend; ++instrit)
83+
{
84+
for(auto it = instrit->code.depth_begin(),
85+
itend = instrit->code.depth_end();
86+
it != itend; ++it)
87+
{
88+
if(it->id() == ID_dereference)
89+
{
90+
const auto &deref = to_dereference_expr(*it);
91+
REQUIRE(
92+
safe_pointers.is_safe_dereference(deref, instrit));
93+
}
94+
}
95+
96+
for(auto it = instrit->guard.depth_begin(),
97+
itend = instrit->guard.depth_end();
98+
it != itend; ++it)
99+
{
100+
if(it->id() == ID_dereference)
101+
{
102+
const auto &deref = to_dereference_expr(*it);
103+
REQUIRE(
104+
safe_pointers.is_safe_dereference(deref, instrit));
105+
}
106+
}
107+
}
108+
}
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)