Skip to content

Commit c0ee316

Browse files
committed
JBMC: Support for synchronized methods
This commit adds support for synchronized methods by instrumenting all methods marked with the synchronization flag with calls to 'monitorenter' and 'monitorexit'. These two methods are located in the java models library and implement a critical section. To this end the following changes are made: 1. New irep_id, 'is_synchronized', to represent the synchronized keyword and appropriate changes to 'java_byecode_convert_method.cpp' to set this flag when a synchronized method is encountered. 2. Setting of the 'is_static' flag when the method in question is static. 3. Functions to find and instrument synchronized methods. Specifically, calls to "java::java.lang.Object.monitorenter:(Ljava/lang/Object;)V" and "java::java.lang.Object.monitorexit:(Ljava/lang/Object;)V" are respectively instrumented at the start and end of a synchronized method . Note, the former is instrumented at every point where the execution of the synchronized methods terminates. Specifically out of order return statements and exceptions. Static synchronized methods are not supported yet as the synchronization semantics for static methods is different (locking on the class instead the of the object). Upon encountering a static synchronized method the current implementation will ignore the synchronized flag. (showing a warning in the process). This may obviously result in superfluous interleavings. Note: instrumentation of synchronized methods is only triggered if the '--java-threading' command line option is specified. Note': instrumentation of synchronized methods requires the use of the java core models library as the locking mechanism is implemented in the model 'java.long.Object'.
1 parent 7efa7bf commit c0ee316

6 files changed

+277
-11
lines changed

jbmc/src/java_bytecode/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ SRC = bytecode_info.cpp \
88
jar_file.cpp \
99
java_bytecode_convert_class.cpp \
1010
java_bytecode_convert_method.cpp \
11-
java_bytecode_convert_threadblock.cpp \
11+
java_bytecode_concurrency_instrumentation.cpp \
1212
java_bytecode_instrument.cpp \
1313
java_bytecode_internal_additions.cpp \
1414
java_bytecode_language.cpp \

jbmc/src/java_bytecode/java_bytecode_convert_threadblock.cpp renamed to jbmc/src/java_bytecode/java_bytecode_concurrency_instrumentation.cpp

+259-6
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ Author: Kurt Degiogrio, [email protected]
66
77
\*******************************************************************/
88

9-
#include "java_bytecode_convert_threadblock.h"
9+
#include "java_bytecode_concurrency_instrumentation.h"
1010
#include "expr2java.h"
1111
#include "java_types.h"
1212

1313
#include <util/expr_iterator.h>
1414
#include <util/namespace.h>
1515
#include <util/cprover_prefix.h>
1616
#include <util/std_types.h>
17+
#include <util/fresh_symbol.h>
1718
#include <util/arith_tools.h>
1819

1920
// Disable linter to allow an std::string constant.
@@ -38,7 +39,7 @@ static symbolt add_or_get_symbol(
3839
const bool is_thread_local,
3940
const bool is_static_lifetime)
4041
{
41-
const symbolt* psymbol = nullptr;
42+
const symbolt *psymbol = nullptr;
4243
namespacet ns(symbol_table);
4344
ns.lookup(name, psymbol);
4445
if(psymbol != nullptr)
@@ -89,6 +90,186 @@ static const std::string get_thread_block_identifier(
8990
return integer2string(lbl_id);
9091
}
9192

93+
/// Creates a monitorenter/monitorexit code_function_callt for
94+
/// the given synchronization object.
95+
/// \param symbol_table: a symbol table
96+
/// \param is_enter: indicates whether we are creating a monitorenter or
97+
/// monitorexit.
98+
/// \param object: expression representing a 'java.Lang.Object'. This object is
99+
/// used to achieve object-level locking by calling monitoroenter/monitorexit.
100+
static codet get_monitor_call(
101+
const symbol_tablet &symbol_table,
102+
bool is_enter,
103+
const exprt &object)
104+
{
105+
const irep_idt symbol =
106+
is_enter ? "java::java.lang.Object.monitorenter:(Ljava/lang/Object;)V"
107+
: "java::java.lang.Object.monitorexit:(Ljava/lang/Object;)V";
108+
109+
auto it = symbol_table.symbols.find(symbol);
110+
111+
// If the 'java::java.lang.Object.monitorenter:(Ljava/lang/Object;)V' or
112+
// 'java::java.lang.Object.monitorexit:(Ljava/lang/Object;)V' method is not
113+
// found symex will rightfully complain as it cannot find the symbol
114+
// associated with the method in question. To avoid this and thereby ensuring
115+
// JBMC works both with and without the models, we replace the aforementioned
116+
// methods with skips when we cannot find them.
117+
//
118+
// Note: this can only happen if the java-core-models library is not loaded.
119+
//
120+
// Note': we explicitly prevent JBMC from stubbing these methods.
121+
if(it == symbol_table.symbols.end())
122+
return code_skipt();
123+
124+
// Otherwise we create a function call
125+
code_function_callt call;
126+
call.function() = symbol_exprt(symbol, it->second.type);
127+
call.lhs().make_nil();
128+
call.arguments().push_back(object);
129+
return call;
130+
}
131+
132+
/// Introduces a monitorexit before every return recursively.
133+
/// Note: this breaks sharing on \p code
134+
/// \param [out] code: current element to modify
135+
/// \param monitorexit: codet to insert before the return
136+
static void monitor_exits(codet &code, const codet &monitorexit)
137+
{
138+
const irep_idt &statement = code.get_statement();
139+
if(statement == ID_return)
140+
{
141+
// Replace the return with a monitor exit plus return block
142+
code_blockt return_block;
143+
return_block.add(monitorexit);
144+
return_block.move_to_operands(code);
145+
code = return_block;
146+
}
147+
else if(
148+
statement == ID_label || statement == ID_block ||
149+
statement == ID_decl_block)
150+
{
151+
// If label or block found, explore the code inside the block
152+
Forall_operands(it, code)
153+
{
154+
codet &sub_code = to_code(*it);
155+
monitor_exits(sub_code, monitorexit);
156+
}
157+
}
158+
}
159+
160+
/// Transforms the codet stored in \c symbol.value. The \p symbol is expected to
161+
/// be a Java synchronized method. Java synchronized methods do not have
162+
/// explicit calls to the instructions monitorenter and monitorexit, the JVM
163+
/// interprets the synchronized flag in a method and uses monitor of the object
164+
/// to implement locking and unlocking. Therefore JBMC has to emulate this same
165+
/// behavior. We insert a call to our model of monitorenter at the beginning of
166+
/// the method and calls to our model of monitorexit at each return instruction.
167+
/// We wrap the entire body of the method with an exception handler that will
168+
/// call our model of monitorexit if the method returns exceptionally.
169+
///
170+
/// Example:
171+
///
172+
/// \code
173+
/// synchronized int amethod(int p)
174+
/// {
175+
/// int x = 0;
176+
/// if(p == 0)
177+
/// return -1;
178+
/// x = p / 10
179+
/// return x
180+
/// }
181+
/// \endcode
182+
///
183+
/// Is transformed into the codet equivalent of:
184+
///
185+
/// \code
186+
/// synchronized int amethod(int p)
187+
/// {
188+
/// try
189+
/// {
190+
/// java::java.lang.Object.monitorenter(this);
191+
/// int x = 0;
192+
/// if(p == 0)
193+
/// {
194+
/// java::java.lang.Object.monitorexit(this);
195+
/// return -1;
196+
/// }
197+
/// java::java.lang.Object.monitorexit(this);
198+
/// return x
199+
/// }
200+
/// catch(java::java.lang.Throwable e)
201+
/// {
202+
/// // catch every exception, including errors!
203+
/// java::java.lang.Object.monitorexit(this);
204+
/// throw e;
205+
/// }
206+
/// }
207+
/// \endcode
208+
///
209+
/// \param symbol_table: a symbol_table
210+
/// \param symbol: writeable symbol hosting code to synchronize
211+
/// \param sync_object: object to use as a lock
212+
static void instrument_synchronized_code(
213+
symbol_tablet &symbol_table,
214+
symbolt &symbol,
215+
const exprt &sync_object)
216+
{
217+
PRECONDITION(!symbol.type.get_bool(ID_is_static));
218+
219+
codet &code = to_code(symbol.value);
220+
221+
// Get the calls to the functions that implement the critical section
222+
codet monitorenter = get_monitor_call(symbol_table, true, sync_object);
223+
codet monitorexit = get_monitor_call(symbol_table, false, sync_object);
224+
225+
// Create a unique catch label and empty throw type (i.e. any)
226+
// and catch-push them at the beginning of the code (i.e. begin try)
227+
code_push_catcht catch_push;
228+
irep_idt handler("pc-synchronized-catch");
229+
irep_idt exception_id;
230+
code_push_catcht::exception_listt &exception_list =
231+
catch_push.exception_list();
232+
exception_list.push_back(
233+
code_push_catcht::exception_list_entryt(exception_id, handler));
234+
235+
// Create a catch-pop to indicate the end of the try block
236+
code_pop_catcht catch_pop;
237+
238+
// Create the finally block with the same label targeted in the catch-push
239+
const symbolt &tmp_symbol = get_fresh_aux_symbol(
240+
java_reference_type(symbol_typet("java::java.lang.Throwable")),
241+
id2string(symbol.name),
242+
"caught-synchronized-exception",
243+
code.source_location(),
244+
ID_java,
245+
symbol_table);
246+
symbol_exprt catch_var(tmp_symbol.name, tmp_symbol.type);
247+
catch_var.set(ID_C_base_name, tmp_symbol.base_name);
248+
code_landingpadt catch_statement(catch_var);
249+
codet catch_instruction = catch_statement;
250+
code_labelt catch_label(handler, code_blockt());
251+
code_blockt &catch_block = to_code_block(catch_label.code());
252+
catch_block.add(catch_instruction);
253+
catch_block.add(monitorexit);
254+
255+
// Re-throw exception
256+
side_effect_expr_throwt throw_expr;
257+
throw_expr.copy_to_operands(catch_var);
258+
catch_block.add(code_expressiont(throw_expr));
259+
260+
// Write a monitorexit before every return
261+
monitor_exits(code, monitorexit);
262+
263+
// Wrap the code into a try finally
264+
code_blockt try_block;
265+
try_block.move_to_operands(monitorenter);
266+
try_block.move_to_operands(catch_push);
267+
try_block.move_to_operands(code);
268+
try_block.move_to_operands(catch_pop);
269+
try_block.move_to_operands(catch_label);
270+
code = try_block;
271+
}
272+
92273
/// Transforms the codet stored in in \p f_code, which is a call to function
93274
/// CProver.startThread:(I)V into a block of code as described by the
94275
/// documentation of function convert_thread_block
@@ -284,20 +465,20 @@ static void instrument_getCurrentThreadID(
284465
///
285466
/// Note': the ID of the thread is assigned after the thread is created, this
286467
/// creates bogus interleavings. Currently, it's not possible to
287-
/// assign the thread ID before the creation of the thead, due to a bug in
468+
/// assign the thread ID before the creation of the thread, due to a bug in
288469
/// symex. See https://github.com/diffblue/cbmc/issues/1630/for more details.
289470
///
290471
/// \param symbol_table: a symbol table
291472
void convert_threadblock(symbol_tablet &symbol_table)
292473
{
293474
using instrument_callbackt =
294-
std::function<void(const code_function_callt&, codet&, symbol_tablet&)>;
475+
std::function<void(const code_function_callt&, codet&, symbol_tablet&)>;
295476
using expr_replacement_mapt =
296-
std::unordered_map<const exprt, instrument_callbackt, irep_hash>;
477+
std::unordered_map<const exprt, instrument_callbackt, irep_hash>;
297478

298479
namespacet ns(symbol_table);
299480

300-
for(auto entry : symbol_table)
481+
for(const auto &entry : symbol_table)
301482
{
302483
expr_replacement_mapt expr_replacement_map;
303484
const symbolt &symbol = entry.second;
@@ -361,3 +542,75 @@ void convert_threadblock(symbol_tablet &symbol_table)
361542
}
362543
}
363544
}
545+
546+
/// Iterate through the symbol table to find and instrument synchronized
547+
/// methods.
548+
///
549+
/// Synchronized methods make it impossible for two invocations of the same
550+
/// method on the same object to interleave. In-order to replicate
551+
/// these semantics a call to
552+
/// "java::java.lang.Object.monitorenter:(Ljava/lang/Object;)V" and
553+
/// "java::java.lang.Object.monitorexit:(Ljava/lang/Object;)V" is instrumented
554+
/// at the start and end of the method. Note, that the former is instrumented
555+
/// at every statement where the execution can exit the method in question.
556+
/// Specifically, out of order return statements and exceptions. The latter
557+
/// is dealt with by instrumenting a try catch block.
558+
///
559+
/// Note: Static synchronized methods are not supported yet as the
560+
/// synchronization semantics for static methods is different (locking on the
561+
/// class instead the of the object). Upon encountering a static synchronized
562+
/// method the current implementation will ignore the synchronized flag.
563+
/// (showing a warning in the process). This may result in superfluous
564+
/// interleavings.
565+
///
566+
/// Note': This method requires the availability of
567+
/// "java::java.lang.Object.monitorenter:(Ljava/lang/Object;)V" and
568+
/// "java::java.lang.Object.monitorexit:(Ljava/lang/Object;)V".
569+
/// (java-library-models). If they are not available code_skipt will inserted
570+
/// instead of calls to the aforementioned methods.
571+
///
572+
/// \param symbol_table: a symbol table
573+
/// \param message_handler: status output
574+
void convert_synchronized_methods(
575+
symbol_tablet &symbol_table,
576+
message_handlert &message_handler)
577+
{
578+
namespacet ns(symbol_table);
579+
for(const auto &entry : symbol_table)
580+
{
581+
const symbolt &symbol = entry.second;
582+
583+
if(symbol.type.id() != ID_code)
584+
continue;
585+
if(symbol.value.is_nil())
586+
continue;
587+
if(!symbol.type.get_bool(ID_is_synchronized))
588+
continue;
589+
590+
if(symbol.type.get_bool(ID_is_static))
591+
{
592+
messaget message(message_handler);
593+
message.warning() << "Java method '" << entry.first
594+
<< "' is static and synchronized."
595+
<< " This is unsupported, the synchronized keyword"
596+
<< " will be ignored."
597+
<< messaget::eom;
598+
continue;
599+
}
600+
601+
// find the object to synchronize on
602+
const irep_idt this_id(id2string(symbol.name) + "::this");
603+
exprt this_expr(this_id);
604+
605+
auto it = symbol_table.symbols.find(this_id);
606+
607+
if(it == symbol_table.symbols.end())
608+
// failed to find object to synchronize on
609+
continue;
610+
611+
// get writeable reference and instrument the method
612+
symbolt &w_symbol = symbol_table.get_writeable_ref(entry.first);
613+
instrument_synchronized_code(
614+
symbol_table, w_symbol, it->second.symbol_expr());
615+
}
616+
}

jbmc/src/java_bytecode/java_bytecode_convert_threadblock.h renamed to jbmc/src/java_bytecode/java_bytecode_concurrency_instrumentation.h

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ Module: Java Convert Thread blocks
55
Author: Kurt Degiogrio, [email protected]
66
77
\*******************************************************************/
8-
#ifndef CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONVERT_THREADBLOCK_H
9-
#define CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONVERT_THREADBLOCK_H
8+
#ifndef CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONCURRENCY_INSTRUMENTATION_H
9+
#define CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONCURRENCY_INSTRUMENTATION_H
1010

1111
#include <util/symbol_table.h>
12+
#include <util/message.h>
1213

1314
void convert_threadblock(symbol_tablet &symbol_table);
15+
void convert_synchronized_methods(
16+
symbol_tablet &symbol_table,
17+
message_handlert &message_handler);
1418

1519
#endif

jbmc/src/java_bytecode/java_bytecode_convert_method.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,11 @@ void java_bytecode_convert_method_lazy(
366366
else
367367
member_type.set_access(ID_default);
368368

369+
if(m.is_synchronized)
370+
member_type.set(ID_is_synchronized, true);
371+
if(m.is_static)
372+
member_type.set(ID_is_static, true);
373+
369374
// do we need to add 'this' as a parameter?
370375
if(!m.is_static)
371376
{

jbmc/src/java_bytecode/java_bytecode_language.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ Author: Daniel Kroening, [email protected]
2222

2323
#include <goto-programs/class_hierarchy.h>
2424

25+
#include "java_bytecode_concurrency_instrumentation.h"
2526
#include "java_bytecode_convert_class.h"
2627
#include "java_bytecode_convert_method.h"
27-
#include "java_bytecode_convert_threadblock.h"
2828
#include "java_bytecode_internal_additions.h"
2929
#include "java_bytecode_instrument.h"
3030
#include "java_bytecode_typecheck.h"
@@ -755,9 +755,12 @@ bool java_bytecode_languaget::typecheck(
755755
bool res = java_bytecode_typecheck(
756756
symbol_table, get_message_handler(), string_refinement_enabled);
757757

758-
// now instrument thread-blocks
758+
// now instrument thread-blocks and synchronized methods.
759759
if(threading_support)
760+
{
760761
convert_threadblock(symbol_table);
762+
convert_synchronized_methods(symbol_table, get_message_handler());
763+
}
761764

762765
return res;
763766
}

src/util/irep_ids.def

+1
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ IREP_ID_TWO(C_is_anonymous, #is_anonymous)
384384
IREP_ID_ONE(is_enum_constant)
385385
IREP_ID_ONE(is_inline)
386386
IREP_ID_ONE(is_extern)
387+
IREP_ID_ONE(is_synchronized)
387388
IREP_ID_ONE(is_global)
388389
IREP_ID_ONE(is_thread_local)
389390
IREP_ID_ONE(is_parameter)

0 commit comments

Comments
 (0)