Skip to content

Commit bc539b5

Browse files
committed
Adds support for concurrency in java programs
This commit enables basic support (creation of threads) for concurrency in java programs using the CAV 13 encoding. To this end, new functions haven been introduced to find and appropriately instrument thread blocks. A thread block is a sequence of codet's surrounded with calls to CProver.startThread:(I)V and CProver.endThread:(I)V. The instrumentation process transforms both functions into the appropriate codet's. This mandates the creation of a new variable per thread, '__CPROVER__thread_id' which is used to store the thread ID. A global counter, '__CPROVER__next_thread_id' is used to keep track of thread id's. Calls to 'CProver.getCurrentThreadID:()I' are also instrumented such that the thread ID of the current thread is returned. Note: instrumentation of thread block's is only enabled when the '--java-threading' command line option is specified.
1 parent 46849e9 commit bc539b5

File tree

4 files changed

+387
-1
lines changed

4 files changed

+387
-1
lines changed

src/java_bytecode/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ SRC = bytecode_info.cpp \
77
jar_file.cpp \
88
java_bytecode_convert_class.cpp \
99
java_bytecode_convert_method.cpp \
10+
java_bytecode_convert_threadblock.cpp \
1011
java_bytecode_instrument.cpp \
1112
java_bytecode_internal_additions.cpp \
1213
java_bytecode_language.cpp \
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
/*******************************************************************\
2+
3+
Module: Java Convert Thread blocks
4+
5+
Author: Kurt Degiogrio, [email protected]
6+
7+
\*******************************************************************/
8+
9+
#include "java_bytecode_convert_threadblock.h"
10+
#include "expr2java.h"
11+
#include "java_types.h"
12+
13+
#include <util/expr_iterator.h>
14+
#include <util/namespace.h>
15+
#include <util/cprover_prefix.h>
16+
#include <util/std_types.h>
17+
#include <util/arith_tools.h>
18+
19+
// Disable linter to allow an std::string constant.
20+
const std::string next_thread_id = CPROVER_PREFIX "_next_thread_id";// NOLINT(*)
21+
const std::string thread_id = CPROVER_PREFIX "_thread_id";// NOLINT(*)
22+
23+
/// Adds a new symbol to the symbol table if it doesn't exist. Otherwise,
24+
/// returns the existing one.
25+
/// /param name: name of the symbol to be generated
26+
/// /param base_name: base name of the symbol to be generated
27+
/// /param type: type of the symbol to be generated
28+
/// /param value: initial value of the symbol to be generated
29+
/// /param is_thread_local: if true this symbol will be set as thread local
30+
/// /param is_static_lifetime: if true this symbol will be set as static
31+
/// /return returns new or existing symbol.
32+
static symbolt add_or_get_symbol(
33+
symbol_tablet &symbol_table,
34+
const irep_idt &name,
35+
const irep_idt &base_name,
36+
const typet &type,
37+
const exprt &value,
38+
const bool is_thread_local,
39+
const bool is_static_lifetime)
40+
{
41+
const symbolt* psymbol = nullptr;
42+
namespacet ns(symbol_table);
43+
ns.lookup(name, psymbol);
44+
if(psymbol != nullptr)
45+
return *psymbol;
46+
symbolt new_symbol;
47+
new_symbol.name = name;
48+
new_symbol.pretty_name = name;
49+
new_symbol.base_name = base_name;
50+
new_symbol.type = type;
51+
new_symbol.value = value;
52+
new_symbol.is_lvalue = true;
53+
new_symbol.is_state_var = true;
54+
new_symbol.is_static_lifetime = is_static_lifetime;
55+
new_symbol.is_thread_local = is_thread_local;
56+
new_symbol.mode = ID_java;
57+
symbol_table.add(new_symbol);
58+
return new_symbol;
59+
}
60+
61+
/// Retrieve the first label identifier. This is used to mark the start of
62+
/// a thread block.
63+
/// /param id: unique thread block identifier
64+
/// /return: fully qualified label identifier
65+
static const std::string get_first_label_id(const std::string &id)
66+
{
67+
return CPROVER_PREFIX "_THREAD_ENTRY_" + id;
68+
}
69+
70+
/// Retrieve the second label identifier. This is used to mark the end of
71+
/// a thread block.
72+
/// /param id: unique thread block identifier
73+
/// /return: fully qualified label identifier
74+
static const std::string get_second_label_id(const std::string &id)
75+
{
76+
return CPROVER_PREFIX "_THREAD_EXIT_" + id;
77+
}
78+
79+
/// Retrieves a thread block identifier from a function call to
80+
/// CProver.startThread:(I)V or CProver.endThread:(I)V
81+
/// /param code: function call to CProver.startThread or CProver.endThread
82+
/// /return: unique thread block identifier
83+
static const std::string get_thread_block_identifier(
84+
const code_function_callt &f_code)
85+
{
86+
PRECONDITION(f_code.arguments().size() == 1);
87+
const exprt &expr = f_code.arguments()[0];
88+
mp_integer lbl_id = binary2integer(expr.op0().get_string(ID_value), false);
89+
return integer2string(lbl_id);
90+
}
91+
92+
/// Transforms the codet stored in in \p f_code, which is a call to function
93+
/// CProver.startThread:(I)V into a block of code as described by the
94+
/// documentation of function convert_thread_block
95+
///
96+
/// The resulting code_blockt is stored in the output parameter \p code.
97+
///
98+
/// \param f_code: function call to CProver.startThread:(I)V
99+
/// \param [out] code: resulting transformation
100+
/// \param symbol_table: a symbol table
101+
static void instrument_start_thread(
102+
const code_function_callt &f_code,
103+
codet &code,
104+
symbol_tablet &symbol_table)
105+
{
106+
PRECONDITION(f_code.arguments().size() == 1);
107+
108+
// Create global variable __CPROVER__next_thread_id. Used as a counter
109+
// in-order to to assign ids to new threads.
110+
const symbolt &next_symbol =
111+
add_or_get_symbol(
112+
symbol_table, next_thread_id, next_thread_id,
113+
java_int_type(),
114+
from_integer(0, java_int_type()), false, true);
115+
116+
// Create thread-local variable __CPROVER__thread_id. Holds the id of
117+
// the thread.
118+
const symbolt &current_symbol =
119+
add_or_get_symbol(
120+
symbol_table, thread_id, thread_id, java_int_type(),
121+
from_integer(0, java_int_type()), true, true);
122+
123+
// Construct appropriate labels.
124+
// Note: java does not have labels so this should be safe.
125+
const std::string &thread_block_id = get_thread_block_identifier(f_code);
126+
const std::string &lbl1 = get_first_label_id(thread_block_id);
127+
const std::string &lbl2 = get_second_label_id(thread_block_id);
128+
129+
// Instrument the following codet's:
130+
//
131+
// A: codet(id=ID_start_thread, destination=__CPROVER_THREAD_ENTRY_<ID>)
132+
// B: codet(id=ID_goto, destination=__CPROVER_THREAD_EXIT_<ID>)
133+
// C: codet(id=ID_label, label=__CPROVER_THREAD_ENTRY_<ID>)
134+
// C.1: codet(id=ID_atomic_begin)
135+
// D: CPROVER__next_thread_id += 1;
136+
// E: __CPROVER__thread_id = __CPROVER__next_thread_id;
137+
// F: codet(id=ID_atomic_end)
138+
139+
codet tmp_a(ID_start_thread);
140+
tmp_a.set(ID_destination, lbl1);
141+
code_gotot tmp_b(lbl2);
142+
code_labelt tmp_c(lbl1);
143+
tmp_c.op0() = codet(ID_skip);
144+
145+
exprt plus(ID_plus, java_int_type());
146+
plus.copy_to_operands(next_symbol.symbol_expr());
147+
plus.copy_to_operands(from_integer(1, java_int_type()));
148+
code_assignt tmp_d(next_symbol.symbol_expr(), plus);
149+
code_assignt tmp_e(current_symbol.symbol_expr(), next_symbol.symbol_expr());
150+
151+
code_blockt block;
152+
block.add(tmp_a);
153+
block.add(tmp_b);
154+
block.add(tmp_c);
155+
block.add(codet(ID_atomic_begin));
156+
block.add(tmp_d);
157+
block.add(tmp_e);
158+
block.add(codet(ID_atomic_end));
159+
block.add_source_location() = f_code.source_location();
160+
161+
code = block;
162+
}
163+
164+
/// Transforms the codet stored in in \p f_code, which is a call to function
165+
/// CProver.endThread:(I)V into a block of code as described by the
166+
/// documentation of the function convert_thread_block.
167+
///
168+
/// The resulting code_blockt is stored in the output parameter \p code.
169+
///
170+
/// \param f_code: function call to CProver.endThread:(I)V
171+
/// \param [out] code: resulting transformation
172+
/// \param symbol_table: a symbol table
173+
static void instrument_endThread(
174+
const code_function_callt &f_code,
175+
codet &code,
176+
symbol_tablet symbol_table)
177+
{
178+
PRECONDITION(f_code.arguments().size() == 1);
179+
180+
// Build id, used to construct appropriate labels.
181+
// Note: java does not have labels so this should be safe
182+
const std::string &thread_block_id = get_thread_block_identifier(f_code);
183+
const std::string &lbl2 = get_second_label_id(thread_block_id);
184+
185+
// Instrument the following code:
186+
//
187+
// A: codet(id=ID_end_thread)
188+
// B: codet(id=ID_label,label= __CPROVER_THREAD_EXIT_<ID>)
189+
codet tmp_a(ID_end_thread);
190+
code_labelt tmp_b(lbl2);
191+
tmp_b.op0() = codet(ID_skip);
192+
193+
code_blockt block;
194+
block.add(tmp_a);
195+
block.add(tmp_b);
196+
block.add_source_location() = code.source_location();
197+
198+
code = block;
199+
}
200+
201+
/// Transforms the codet stored in in \p f_code, which is a call to function
202+
/// CProver.getCurrentThreadID:()I into a code_assignt as described by the
203+
/// documentation of the function convert_thread_block.
204+
///
205+
/// The resulting code_blockt is stored in the output parameter \p code.
206+
///
207+
/// \param f_code: function call to CProver.getCurrentThreadID:()I
208+
/// \param [out] code: resulting transformation
209+
/// \param symbol_table: a symbol table
210+
static void instrument_getCurrentThreadID(
211+
const code_function_callt &f_code,
212+
codet &code,
213+
symbol_tablet symbol_table)
214+
{
215+
PRECONDITION(f_code.arguments().size() == 0);
216+
217+
const symbolt& current_symbol =
218+
add_or_get_symbol(symbol_table,
219+
thread_id,
220+
thread_id,
221+
java_int_type(),
222+
from_integer(0, java_int_type()),
223+
true, true);
224+
225+
code_assignt code_assign(f_code.lhs(), current_symbol.symbol_expr());
226+
code_assign.add_source_location() = f_code.source_location();
227+
228+
code = code_assign;
229+
}
230+
231+
/// Iterate through the symbol table to find and appropriately instrument
232+
/// thread-blocks.
233+
///
234+
/// A thread block is a sequence of codet's surrounded with calls to
235+
/// CProver.startThread:(I)V and CProver.endThread:(I)V. A thread block
236+
/// corresponds to the body of the thread to be created. The only parameter
237+
/// accepted by these functions is an integer used to differentiate between
238+
/// different thread blocks. Function startThread() is transformed into a codet
239+
/// ID_start_thread, which carries a target label in the field 'destination'.
240+
/// Similarly endThread() is transformed into a codet ID_end_thread, which
241+
/// marks the end of the thread body. Both codet's will later be transformed
242+
/// (in goto_convertt) into the goto instructions START_THREAD and END_THREAD.
243+
///
244+
/// Additionally the variable __CPROVER__thread_id (thread local) will store
245+
/// the ID of the new thread. The new id is obtained by incrementing a global
246+
/// variable __CPROVER__next_thread_id.
247+
///
248+
/// The ID of the thread is made accessible to the Java program by having calls
249+
/// to the function 'CProver.getCurrentThreadID()I' replaced by the variable
250+
/// __CPROVER__thread_id. We also perform this substitution in here. The
251+
/// substitution that we perform here assumes that calls to
252+
/// getCurrentThreadID() are ONLY made in the RHS of an expression.
253+
///
254+
/// Example:
255+
///
256+
/// \code
257+
/// CProver.startThread(333)
258+
/// ... // thread body
259+
/// CProver.endThread(333)
260+
/// \endcode
261+
///
262+
/// Is transformed into:
263+
///
264+
/// \code
265+
/// codet(id=ID_start_thread, destination=__CPROVER_THREAD_ENTRY_333)
266+
/// codet(id=ID_goto, destination=__CPROVER_THREAD_EXIT_333)
267+
/// codet(id=ID_label, label=__CPROVER_THREAD_ENTRY_333)
268+
/// codet(id=ID_atomic_begin)
269+
/// __CPROVER__next_thread_id += 1;
270+
/// __CPROVER__thread_id = __CPROVER__next_thread_id;
271+
/// ... // thread body
272+
/// codet(id=ID_end_thread)
273+
/// codet(id=ID_label, label=__CPROVER_THREAD_EXIT_333)
274+
/// \endcode
275+
///
276+
/// Observe that the semantics of ID_start_thread roughly corresponds to: fork
277+
/// the current thread, continue the execution of the current thread in the
278+
/// next line, and continue the execution of the new thread at the destination
279+
/// field of the codet (__CPROVER_THREAD_ENTRY_333).
280+
///
281+
/// Note: the current version assumes that a call to startThread(n), where n is
282+
/// an immediate integer, is in the same scope (nesting block) as a call to
283+
/// endThread(n). Some assertion will fail during symex if this is not the case.
284+
///
285+
/// Note': the ID of the thread is assigned after the thread is created, this
286+
/// 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
288+
/// symex. See https://github.com/diffblue/cbmc/issues/1630/for more details.
289+
///
290+
/// \param symbol_table: a symbol table
291+
void convert_threadblock(symbol_tablet &symbol_table)
292+
{
293+
using instrument_callbackt =
294+
std::function<void(const code_function_callt&, codet&, symbol_tablet&)>;
295+
using expr_replacement_mapt =
296+
std::unordered_map<const exprt, instrument_callbackt, irep_hash>;
297+
298+
namespacet ns(symbol_table);
299+
300+
for(auto entry : symbol_table)
301+
{
302+
expr_replacement_mapt expr_replacement_map;
303+
const symbolt &symbol = entry.second;
304+
305+
// Look for code_function_callt's (without breaking sharing)
306+
// and insert each one into a replacement map along with an associated
307+
// callback that will handle their instrumentation.
308+
for(auto it = symbol.value.depth_begin(), itend = symbol.value.depth_end();
309+
it != itend; ++it)
310+
{
311+
instrument_callbackt cb;
312+
313+
const exprt &expr = *it;
314+
if(expr.id() != ID_code)
315+
continue;
316+
317+
const codet &code = to_code(expr);
318+
if(code.get_statement() != ID_function_call)
319+
continue;
320+
321+
const code_function_callt &f_code = to_code_function_call(code);
322+
const std::string &f_name = expr2java(f_code.function(), ns);
323+
if(f_name == "org.cprover.CProver.startThread:(I)V")
324+
cb = std::bind(instrument_start_thread, std::placeholders::_1,
325+
std::placeholders::_2, std::placeholders::_3);
326+
else if(f_name == "org.cprover.CProver.endThread:(I)V")
327+
cb = std::bind(&instrument_endThread, std::placeholders::_1,
328+
std::placeholders::_2, std::placeholders::_3);
329+
else if(f_name == "org.cprover.CProver.getCurrentThreadID:()I")
330+
cb = std::bind(&instrument_getCurrentThreadID, std::placeholders::_1,
331+
std::placeholders::_2, std::placeholders::_3);
332+
333+
if(cb)
334+
expr_replacement_map.insert({expr, cb});
335+
}
336+
337+
if(!expr_replacement_map.empty())
338+
{
339+
symbolt &w_symbol = symbol_table.get_writeable_ref(entry.first);
340+
// Use expr_replacment_map to look for exprt's that need to be replaced.
341+
// Once found, get a non-const exprt (breaking sharing in the process) and
342+
// call it's associated instrumentation function.
343+
for(auto it = w_symbol.value.depth_begin(),
344+
itend = w_symbol.value.depth_end(); it != itend;)
345+
{
346+
expr_replacement_mapt::iterator m_it = expr_replacement_map.find(*it);
347+
if(m_it != expr_replacement_map.end())
348+
{
349+
codet &code = to_code(it.mutate());
350+
const code_function_callt &f_code = to_code_function_call(code);
351+
m_it->second(f_code, code, symbol_table);
352+
it.next_sibling_or_parent();
353+
354+
expr_replacement_map.erase(m_it);
355+
if(expr_replacement_map.empty())
356+
break;
357+
}
358+
else
359+
++it;
360+
}
361+
}
362+
}
363+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*******************************************************************\
2+
3+
Module: Java Convert Thread blocks
4+
5+
Author: Kurt Degiogrio, [email protected]
6+
7+
\*******************************************************************/
8+
#ifndef CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONVERT_THREADBLOCK_H
9+
#define CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_CONVERT_THREADBLOCK_H
10+
11+
#include <util/symbol_table.h>
12+
13+
void convert_threadblock(symbol_tablet &symbol_table);
14+
15+
#endif

0 commit comments

Comments
 (0)