Skip to content

Commit 9937676

Browse files
committed
Added new cmd option to jbmc, 'java-threading'
Setting this option will enable support for analysing multi-threaded java programs. For now, this means that a thread-safe version of clinit_wrapper is generated. This was made opt-in instead of opt-out as the inclusion of thread-safe clinit wrapper has negative implications for performance. Commit also adds appropriate regression tests that use this flag to test the resulting modifications to the clinit_wrapper
1 parent 1a15f38 commit 9937676

File tree

10 files changed

+209
-54
lines changed

10 files changed

+209
-54
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
static_init.class
3+
--function static_init.main --java-threading
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
static_init.class
3+
--function static_init.main --java-threading
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CORE
2+
static_init_order.class
3+
--function static_init_order.test1 --trace --java-threading
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CORE
2+
static_init_order.class
3+
--function static_init_order.test2 --java-threading
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^VERIFICATION FAILED$
7+
--

src/java_bytecode/java_bytecode_language.cpp

+12-2
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ void java_bytecode_languaget::get_language_options(const cmdlinet &cmd)
6969
else
7070
lazy_methods_mode=LAZY_METHODS_MODE_EAGER;
7171

72+
if(cmd.isset("java-threading"))
73+
threading_support = true;
74+
else
75+
threading_support = false;
76+
7277
if(cmd.isset("java-throw-runtime-exceptions"))
7378
{
7479
throw_runtime_exceptions = true;
@@ -701,7 +706,8 @@ bool java_bytecode_languaget::typecheck(
701706
// For each class that will require a static initializer wrapper, create a
702707
// function named package.classname::clinit_wrapper, and a corresponding
703708
// global tracking whether it has run or not:
704-
create_static_initializer_wrappers(symbol_table, synthetic_methods);
709+
create_static_initializer_wrappers(
710+
symbol_table, synthetic_methods, threading_support);
705711

706712
// Now incrementally elaborate methods
707713
// that are reachable from this entry point.
@@ -961,7 +967,11 @@ bool java_bytecode_languaget::convert_single_method(
961967
switch(synthetic_method_it->second)
962968
{
963969
case synthetic_method_typet::STATIC_INITIALIZER_WRAPPER:
964-
symbol.value = get_clinit_wrapper_body(function_id, symbol_table);
970+
if(threading_support)
971+
symbol.value = get_thread_safe_clinit_wrapper_body(
972+
function_id, symbol_table);
973+
else
974+
symbol.value = get_clinit_wrapper_body(function_id, symbol_table);
965975
break;
966976
case synthetic_method_typet::STUB_CLASS_STATIC_INITIALIZER:
967977
symbol.value =

src/java_bytecode/java_bytecode_language.h

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ class java_bytecode_languaget:public languaget
159159
irep_idt main_class;
160160
std::vector<irep_idt> main_jar_classes;
161161
java_class_loadert java_class_loader;
162+
bool threading_support;
162163
bool assume_inputs_non_null; // assume inputs variables to be non-null
163164
object_factory_parameterst object_factory_parameters;
164165
size_t max_user_array_length; // max size for user code created arrays

src/java_bytecode/java_static_initializers.cpp

+158-51
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Author: Chris Smowton, [email protected]
1717
#include <util/arith_tools.h>
1818

1919
/// The three states in which a `<clinit>` method for a class can be before,
20-
/// after, and during static class initialization.
20+
/// after, and during static class initialization. These states are only used
21+
/// when the thread safe version of the clinit wrapper is generated.
2122
///
2223
/// According to the JVM Spec document (section 5.5), the JVM needs to
2324
/// maintain, for every class initializer, a state indicating whether the
@@ -33,7 +34,9 @@ Author: Chris Smowton, [email protected]
3334
/// class is ready to be used, or it has errored"
3435
///
3536
/// The last state corresponds to a fusion of the two original states "ready
36-
/// for use" and "initialization error".
37+
/// for use" and "initialization error". The basis for fusing these states is
38+
/// that for simplification reasons both implementations of the clinit wrapper
39+
/// do not handle exceptions, hence the error state is not possible.
3740
enum class clinit_statest
3841
{
3942
NOT_INIT,
@@ -98,6 +101,15 @@ static symbolt add_new_symbol(
98101
return new_symbol;
99102
}
100103

104+
/// Get name of the static-initialization-already-done global variable for a
105+
/// given class.
106+
/// \param class_name: class symbol name
107+
/// \return static initializer wrapper-already run global name
108+
static irep_idt clinit_already_run_variable_name(const irep_idt &class_name)
109+
{
110+
return id2string(class_name) + "::clinit_already_run";
111+
}
112+
101113
/// Get name of the real static initializer for a given class. Doesn't check
102114
/// if a static initializer actually exists.
103115
/// \param class_name: class symbol name
@@ -165,6 +177,40 @@ gen_clinit_eqexpr(const exprt &expr, const clinit_statest state)
165177
return equal_exprt(expr, init_s);
166178
}
167179

180+
/// Generates codet that iterates through the base types of the class specified
181+
/// by class_name, C, and recursively adds calls to their clinit wrapper.
182+
/// Finally a call to the clinint wrapper of C is made.
183+
/// \param symbol_table: symbol table
184+
/// \param class_name: name of the class to generate clinit wrapper calls for
185+
/// \param [out] init_body: appended with calls to clinit wrapper
186+
static void clinit_wrapper_do_recursive_calls(
187+
const symbol_tablet &symbol_table,
188+
const irep_idt &class_name,
189+
code_blockt &init_body)
190+
{
191+
const symbolt &class_symbol = symbol_table.lookup_ref(class_name);
192+
for(const auto &base : to_class_type(class_symbol.type).bases())
193+
{
194+
const auto base_name = to_symbol_type(base.type()).get_identifier();
195+
irep_idt base_init_routine = clinit_wrapper_name(base_name);
196+
auto findit = symbol_table.symbols.find(base_init_routine);
197+
if(findit == symbol_table.symbols.end())
198+
continue;
199+
code_function_callt call_base;
200+
call_base.function() = findit->second.symbol_expr();
201+
init_body.move_to_operands(call_base);
202+
}
203+
204+
const irep_idt &real_clinit_name = clinit_function_name(class_name);
205+
auto find_sym_it = symbol_table.symbols.find(real_clinit_name);
206+
if(find_sym_it != symbol_table.symbols.end())
207+
{
208+
code_function_callt call_real_init;
209+
call_real_init.function() = find_sym_it->second.symbol_expr();
210+
init_body.move_to_operands(call_real_init);
211+
}
212+
}
213+
168214
/// Checks whether a static initializer wrapper is needed for a given class,
169215
/// i.e. if the given class or any superclass has a static initializer.
170216
/// \param class_name: class symbol name
@@ -199,32 +245,51 @@ static bool needs_clinit_wrapper(
199245
/// \param synthetic_methods: synthetic method type map. The new clinit wrapper
200246
/// symbol will be recorded, such that we get a callback to produce its body
201247
/// if and when required.
248+
/// \param thread_safe: if true state variables required to make the
249+
/// clinit_wrapper thread safe will be created.
202250
static void create_clinit_wrapper_symbols(
203251
const irep_idt &class_name,
204252
symbol_tablet &symbol_table,
205-
synthetic_methods_mapt &synthetic_methods)
253+
synthetic_methods_mapt &synthetic_methods,
254+
const bool thread_safe)
206255
{
207-
exprt not_init_value = from_integer(
208-
static_cast<int>(clinit_statest::NOT_INIT), clinit_states_type);
256+
if(thread_safe)
257+
{
258+
exprt not_init_value = from_integer(
259+
static_cast<int>(clinit_statest::NOT_INIT), clinit_states_type);
209260

210-
// Create two global static synthetic "fields" for the class "id"
211-
// these two variables hold the state of the class initialization algorithm
212-
// across calls to the clinit_wrapper
213-
add_new_symbol(
214-
symbol_table,
215-
clinit_state_var_name(class_name),
216-
clinit_states_type,
217-
not_init_value,
218-
false,
219-
true);
261+
// Create two global static synthetic "fields" for the class "id"
262+
// these two variables hold the state of the class initialization algorithm
263+
// across calls to the clinit_wrapper
264+
add_new_symbol(
265+
symbol_table,
266+
clinit_state_var_name(class_name),
267+
clinit_states_type,
268+
not_init_value,
269+
false,
270+
true);
220271

221-
add_new_symbol(
222-
symbol_table,
223-
clinit_thread_local_state_var_name(class_name),
224-
clinit_states_type,
225-
not_init_value,
226-
true,
227-
true);
272+
add_new_symbol(
273+
symbol_table,
274+
clinit_thread_local_state_var_name(class_name),
275+
clinit_states_type,
276+
not_init_value,
277+
true,
278+
true);
279+
}
280+
else
281+
{
282+
const irep_idt &already_run_name =
283+
clinit_already_run_variable_name(class_name);
284+
285+
add_new_symbol(
286+
symbol_table,
287+
already_run_name,
288+
bool_typet(),
289+
false_exprt(),
290+
false,
291+
true);
292+
}
228293

229294
// Create symbol for the "clinit_wrapper"
230295
symbolt wrapper_method_symbol;
@@ -254,6 +319,8 @@ static void create_clinit_wrapper_symbols(
254319
"clinit wrapper");
255320
}
256321

322+
/// Thread safe version of the static initialiser.
323+
///
257324
/// Produces the static initialiser wrapper body for the given function. This
258325
/// static initializer implements (a simplification of) the algorithm defined
259326
/// in Section 5.5 of the JVM Specs. This function, or wrapper, checks whether
@@ -319,7 +386,7 @@ static void create_clinit_wrapper_symbols(
319386
/// name created by `create_clinit_wrapper_symbols`)
320387
/// \param symbol_table: global symbol table
321388
/// \return the body of the static initialiser wrapper
322-
codet get_clinit_wrapper_body(
389+
codet get_thread_safe_clinit_wrapper_body(
323390
const irep_idt &function_id,
324391
symbol_table_baset &symbol_table)
325392
{
@@ -470,34 +537,11 @@ codet get_clinit_wrapper_body(
470537
//
471538
// java::C::<clinit>();
472539
//
473-
code_blockt init_body;
474540
{
475-
// iterate through the base types and add recursive calls to the
476-
// clinit_wrapper()
477-
const symbolt &class_symbol = symbol_table.lookup_ref(class_name);
478-
for(const auto &base : to_class_type(class_symbol.type).bases())
479-
{
480-
const auto base_name = to_symbol_type(base.type()).get_identifier();
481-
irep_idt base_init_routine = clinit_wrapper_name(base_name);
482-
auto findit = symbol_table.symbols.find(base_init_routine);
483-
if(findit == symbol_table.symbols.end())
484-
continue;
485-
code_function_callt call_base;
486-
call_base.function() = findit->second.symbol_expr();
487-
init_body.move_to_operands(call_base);
488-
}
489-
490-
// call java::C::<clinit>(), if the class has one static initializer
491-
const irep_idt &real_clinit_name = clinit_function_name(class_name);
492-
auto find_sym_it = symbol_table.symbols.find(real_clinit_name);
493-
if(find_sym_it != symbol_table.symbols.end())
494-
{
495-
code_function_callt call_real_init;
496-
call_real_init.function() = find_sym_it->second.symbol_expr();
497-
init_body.move_to_operands(call_real_init);
498-
}
541+
code_blockt init_body;
542+
clinit_wrapper_do_recursive_calls(symbol_table, class_name, init_body);
543+
function_body.append(init_body);
499544
}
500-
function_body.append(init_body);
501545

502546
// ATOMIC_START
503547
// C::__CPROVER_PREFIX_clinit_state = INIT_COMPLETE;
@@ -516,14 +560,77 @@ codet get_clinit_wrapper_body(
516560
return function_body;
517561
}
518562

563+
/// Produces the static initialiser wrapper body for the given function.
564+
/// Note: this version of the clinit wrapper is not thread safe.
565+
/// \param function_id: clinit wrapper function id (the wrapper_method_symbol
566+
/// name created by `create_clinit_wrapper_symbols`)
567+
/// \param symbol_table: global symbol table
568+
/// \return the body of the static initialiser wrapper/
569+
codet get_clinit_wrapper_body(
570+
const irep_idt &function_id,
571+
symbol_table_baset &symbol_table)
572+
{
573+
// Assume that class C extends class C' and implements interfaces
574+
// I1, ..., In. We now create the following function (possibly recursively
575+
// creating the clinit_wrapper functions for C' and I1, ..., In):
576+
//
577+
// java::C::clinit_wrapper()
578+
// {
579+
// if (java::C::clinit_already_run == false)
580+
// {
581+
// java::C::clinit_already_run = true; // before recursive calls!
582+
//
583+
// java::C'::clinit_wrapper();
584+
// java::I1::clinit_wrapper();
585+
// java::I2::clinit_wrapper();
586+
// // ...
587+
// java::In::clinit_wrapper();
588+
//
589+
// java::C::<clinit>();
590+
// }
591+
// }
592+
const symbolt &wrapper_method_symbol = symbol_table.lookup_ref(function_id);
593+
irep_idt class_name = wrapper_method_symbol.type.get(ID_C_class);
594+
INVARIANT(
595+
!class_name.empty(), "wrapper function should be annotated with its class");
596+
597+
const symbolt &already_run_symbol =
598+
symbol_table.lookup_ref(clinit_already_run_variable_name(class_name));
599+
600+
equal_exprt check_already_run(
601+
already_run_symbol.symbol_expr(),
602+
false_exprt());
603+
604+
// the entire body of the function is an if-then-else
605+
code_ifthenelset wrapper_body;
606+
607+
// add the condition to the if
608+
wrapper_body.cond() = check_already_run;
609+
610+
// add the "already-run = false" statement
611+
code_blockt init_body;
612+
code_assignt set_already_run(already_run_symbol.symbol_expr(), true_exprt());
613+
init_body.move_to_operands(set_already_run);
614+
615+
clinit_wrapper_do_recursive_calls(symbol_table, class_name, init_body);
616+
617+
wrapper_body.then_case() = init_body;
618+
619+
return wrapper_body;
620+
}
621+
622+
519623
/// Create static initializer wrappers for all classes that need them.
520624
/// \param symbol_table: global symbol table
521625
/// \param synthetic_methods: synthetic methods map. Will be extended noting
522626
/// that any wrapper belongs to this code, and so `get_clinit_wrapper_body`
523627
/// should be used to produce the method body when required.
628+
/// \param thread_safe: if true state variables required to make the
629+
/// clinit_wrapper thread safe will be created.
524630
void create_static_initializer_wrappers(
525631
symbol_tablet &symbol_table,
526-
synthetic_methods_mapt &synthetic_methods)
632+
synthetic_methods_mapt &synthetic_methods,
633+
const bool thread_safe)
527634
{
528635
// Top-sort the class hierarchy, such that we visit parents before children,
529636
// and can so identify parents that need static initialisation by whether we
@@ -539,7 +646,7 @@ void create_static_initializer_wrappers(
539646
if(needs_clinit_wrapper(class_identifier, symbol_table))
540647
{
541648
create_clinit_wrapper_symbols(
542-
class_identifier, symbol_table, synthetic_methods);
649+
class_identifier, symbol_table, synthetic_methods, thread_safe);
543650
}
544651
}
545652
}

src/java_bytecode/java_static_initializers.h

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ bool is_clinit_wrapper_function(const irep_idt &function_id);
2424

2525
void create_static_initializer_wrappers(
2626
symbol_tablet &symbol_table,
27-
synthetic_methods_mapt &synthetic_methods);
27+
synthetic_methods_mapt &synthetic_methods,
28+
const bool thread_safe);
29+
30+
codet get_thread_safe_clinit_wrapper_body(
31+
const irep_idt &function_id,
32+
symbol_table_baset &symbol_table);
2833

2934
codet get_clinit_wrapper_body(
3035
const irep_idt &function_id,

src/jbmc/jbmc_parse_options.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1047,6 +1047,7 @@ void jbmc_parse_optionst::help()
10471047
JAVA_BYTECODE_LANGUAGE_OPTIONS_HELP
10481048
// This one is handled by jbmc_parse_options not by the Java frontend,
10491049
// hence its presence here:
1050+
" --java-threading enable experimental support for java multi-threading\n"// NOLINT(*)
10501051
" --java-unwind-enum-static try to unwind loops in static initialization of enums\n" // NOLINT(*)
10511052
// Currently only supported in the JBMC frontend:
10521053
" --symex-driven-lazy-loading only load functions when first entered by symbolic execution\n" // NOLINT(*)

0 commit comments

Comments
 (0)