Skip to content

Commit 086c266

Browse files
authored
Merge pull request #2651 from smowton/smowton/feature/unroll-enum-clone-loops
Unroll enumeration values() loops
2 parents f69b244 + 4f158a3 commit 086c266

25 files changed

+164
-70
lines changed
Binary file not shown.
Binary file not shown.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.diffblue.regression;
2+
3+
public class Foo {
4+
public int foo(MyEnum e) {
5+
if (e == null) return 0;
6+
switch (e) {
7+
case A:
8+
return 1;
9+
case B:
10+
return 2;
11+
case C:
12+
return 3;
13+
}
14+
return 5;
15+
}
16+
}
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.diffblue.regression;
2+
3+
enum MyEnum {
4+
A, B, C, D;
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
com/diffblue/regression/Foo.class
3+
--trace --cover location --depth 1024 --java-max-vla-length 16 --refine-strings --max-nondet-string-length 20 --string-printable --unwind 10 --function "com.diffblue.regression.Foo.foo" --java-unwind-enum-static
4+
line 8.*SATISFIED
5+
line 10.*SATISFIED
6+
line 12.*SATISFIED
7+
line 14.*SATISFIED
8+
^EXIT=0$
9+
^SIGNAL=0$
10+
--
Binary file not shown.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.diffblue.regression;
2+
public class EnumIter {
3+
int f() {
4+
MyEnum[] a = MyEnum.values();
5+
int num = a[2].ordinal() + a[3].ordinal();
6+
return num ;
7+
}
8+
}
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.diffblue.regression;
2+
3+
enum MyEnum {
4+
A, B, C, D;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
com/diffblue/regression/EnumIter.class
3+
--trace --cover location --depth 1024 --java-max-vla-length 16 --refine-strings --max-nondet-string-length 20 --string-printable --unwind 2 --function "com.diffblue.regression.EnumIter.f" --java-unwind-enum-static
4+
\d+ of \d+ covered \(100\.0%\)
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
Binary file not shown.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.diffblue.regression;
2+
public class EnumIter {
3+
String f() {
4+
MyEnum[] a = MyEnum.values();
5+
return a[2].name() + a[3].name();
6+
}
7+
}
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.diffblue.regression;
2+
3+
enum MyEnum {
4+
A, B, C, D;
5+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CORE symex-driven-lazy-loading-expected-failure
2+
com/diffblue/regression/EnumIter.class
3+
--trace --cover location --depth 1024 --java-max-vla-length 16 --refine-strings --max-nondet-string-length 20 --string-printable --unwind 2 --function "com.diffblue.regression.EnumIter.f" --java-unwind-enum-static
4+
\d+ of \d+ covered \(100\.0%\)
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--

jbmc/src/java_bytecode/java_bytecode_convert_method.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,9 +1959,12 @@ codet java_bytecode_convert_methodt::convert_switch(
19591959
mp_integer number;
19601960
bool ret = to_integer(to_constant_expr(*a_it), number);
19611961
DATA_INVARIANT(!ret, "case label expected to be integer");
1962+
// The switch case does not contain any code, it just branches via a GOTO
1963+
// to the jump target of the tableswitch/lookupswitch case at
1964+
// hand. Therefore we consider this code to belong to the source bytecode
1965+
// instruction and not the target instruction.
19621966
code_case.code() = code_gotot(label(integer2string(number)));
1963-
code_case.code().add_source_location() =
1964-
address_map.at(integer2unsigned(number)).source->source_location;
1967+
code_case.code().add_source_location() = location;
19651968

19661969
if(a_it == args.begin())
19671970
code_case.set_default();

jbmc/src/java_bytecode/java_enum_static_init_unwind_handler.cpp

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,47 @@ Author: Chris Smowton, [email protected]
1414
#include <util/invariant.h>
1515
#include <util/suffix.h>
1616

17+
/// Check if we may be in a function that loops over the cases of an
18+
/// enumeration (note we return a candidate function that matches a pattern;
19+
/// our caller must verify it really belongs to an enumeration).
20+
/// At the moment we know of two cases that definitely do so:
21+
/// * An enumeration type's static initialiser
22+
/// * The array[reference].clone() method when called from an enumeration type's
23+
/// 'values()' method
24+
/// \param context: the current call stack
25+
/// \return the name of an enclosing function that may be defined on the
26+
/// relevant enum type, or an empty string if we don't find one.
27+
static irep_idt
28+
find_enum_function_on_stack(const goto_symex_statet::call_stackt &context)
29+
{
30+
static irep_idt reference_array_clone_id =
31+
"java::array[reference].clone:()Ljava/lang/Object;";
32+
33+
PRECONDITION(!context.empty());
34+
const irep_idt &current_function = context.back().function_identifier;
35+
36+
if(context.size() >= 2 && current_function == reference_array_clone_id)
37+
{
38+
const irep_idt &clone_caller =
39+
context.at(context.size() - 2).function_identifier;
40+
if(id2string(clone_caller).find(".values:()[L") != std::string::npos)
41+
return clone_caller;
42+
else
43+
return irep_idt();
44+
}
45+
else if(has_suffix(id2string(current_function), ".<clinit>:()V"))
46+
return current_function;
47+
else
48+
return irep_idt();
49+
}
50+
1751
/// Unwind handler that special-cases the clinit (static initializer) functions
18-
/// of enumeration classes. When java_bytecode_convert_classt has annotated them
52+
/// of enumeration classes, and VALUES array cloning in their values() methods.
53+
/// When java_bytecode_convert_classt has annotated them
1954
/// with a size of the enumeration type, this forces unwinding of any loop in
2055
/// the static initializer to at least that many iterations, with intent to
21-
/// permit population of the enumeration's value array.
22-
/// \param function_id: function the loop is in
56+
/// permit population / copying of the enumeration's value array.
57+
/// \param context: call stack when the loop back-edge was taken
2358
/// \param loop_number: ordinal number of the loop (ignored)
2459
/// \param unwind_count: iteration count that is about to begin
2560
/// \param [out] unwind_max: may be set to an advisory (unenforced) maximum when
@@ -29,17 +64,17 @@ Author: Chris Smowton, [email protected]
2964
/// unwind_count is <= the enumeration size, or unknown (defer / no decision)
3065
/// otherwise.
3166
tvt java_enum_static_init_unwind_handler(
32-
const irep_idt &function_id,
67+
const goto_symex_statet::call_stackt &context,
3368
unsigned loop_number,
3469
unsigned unwind_count,
3570
unsigned &unwind_max,
3671
const symbol_tablet &symbol_table)
3772
{
38-
const std::string &function_str = id2string(function_id);
39-
if(!has_suffix(function_str, ".<clinit>:()V"))
73+
const irep_idt enum_function_id = find_enum_function_on_stack(context);
74+
if(enum_function_id.empty())
4075
return tvt::unknown();
4176

42-
const symbolt &function_symbol = symbol_table.lookup_ref(function_str);
77+
const symbolt &function_symbol = symbol_table.lookup_ref(enum_function_id);
4378
irep_idt class_id = function_symbol.type.get(ID_C_class);
4479
INVARIANT(
4580
!class_id.empty(), "functions should have their defining class annotated");

jbmc/src/java_bytecode/java_enum_static_init_unwind_handler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ Author: Chris Smowton, [email protected]
1212
#ifndef CPROVER_JAVA_BYTECODE_JAVA_ENUM_STATIC_INIT_UNWIND_HANDLER_H
1313
#define CPROVER_JAVA_BYTECODE_JAVA_ENUM_STATIC_INIT_UNWIND_HANDLER_H
1414

15+
#include <goto-symex/goto_symex_state.h>
16+
1517
#include <util/symbol_table.h>
1618
#include <util/threeval.h>
1719

1820
tvt java_enum_static_init_unwind_handler(
19-
const irep_idt &function_id,
21+
const goto_symex_statet::call_stackt &context,
2022
unsigned loop_number,
2123
unsigned unwind_count,
2224
unsigned &unwind_max,

jbmc/src/jbmc/jbmc_parse_options.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -479,16 +479,12 @@ int jbmc_parse_optionst::doit()
479479
configure_bmc = [](bmct &bmc, const symbol_tablet &symbol_table) {
480480
bmc.add_loop_unwind_handler(
481481
[&symbol_table](
482-
const irep_idt &function_id,
482+
const goto_symex_statet::call_stackt &context,
483483
unsigned loop_number,
484484
unsigned unwind,
485485
unsigned &max_unwind) {
486486
return java_enum_static_init_unwind_handler(
487-
function_id,
488-
loop_number,
489-
unwind,
490-
max_unwind,
491-
symbol_table);
487+
context, loop_number, unwind, max_unwind, symbol_table);
492488
});
493489
};
494490
}

src/cbmc/symex_bmc.cpp

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ void symex_bmct::merge_goto(
106106

107107
bool symex_bmct::get_unwind(
108108
const symex_targett::sourcet &source,
109+
const goto_symex_statet::call_stackt &context,
109110
unsigned unwind)
110111
{
111112
const irep_idt id=goto_programt::loop_id(*source.pc);
@@ -116,11 +117,7 @@ bool symex_bmct::get_unwind(
116117
for(auto handler : loop_unwind_handlers)
117118
{
118119
abort_unwind_decision =
119-
handler(
120-
source.pc->function,
121-
source.pc->loop_number,
122-
unwind,
123-
this_loop_limit);
120+
handler(context, source.pc->loop_number, unwind, this_loop_limit);
124121
if(abort_unwind_decision.is_known())
125122
break;
126123
}

src/cbmc/symex_bmc.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@ class symex_bmct: public goto_symext
3535
// To show progress
3636
source_locationt last_source_location;
3737

38-
/// Loop unwind handlers take the function ID and loop number, the unwind
39-
/// count so far, and an out-parameter specifying an advisory maximum, which
40-
/// they may set. If set the advisory maximum is set it is *only* used to
41-
/// print useful information for the user (e.g. "unwinding iteration N, max
42-
/// M"), and is not enforced. They return true to halt unwinding, false to
43-
/// authorise unwinding, or Unknown to indicate they have no opinion.
38+
/// Loop unwind handlers take the call stack, loop number, the unwind count so
39+
/// far, and an out-parameter specifying an advisory maximum, which they may
40+
/// set. If set the advisory maximum is set it is *only* used to print useful
41+
/// information for the user (e.g. "unwinding iteration N, max M"), and is not
42+
/// enforced. They return true to halt unwinding, false to authorise
43+
/// unwinding, or Unknown to indicate they have no opinion.
4444
typedef
45-
std::function<tvt(const irep_idt &, unsigned, unsigned, unsigned &)>
45+
std::function<tvt(
46+
const goto_symex_statet::call_stackt &, unsigned, unsigned, unsigned &)>
4647
loop_unwind_handlert;
4748

4849
/// Recursion unwind handlers take the function ID, the unwind count so far,
@@ -105,6 +106,7 @@ class symex_bmct: public goto_symext
105106
// for loop unwinding
106107
virtual bool get_unwind(
107108
const symex_targett::sourcet &source,
109+
const goto_symex_statet::call_stackt &context,
108110
unsigned unwind);
109111

110112
virtual bool get_unwind_recursion(

src/goto-programs/interpreter_evaluate.cpp

Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Author: Daniel Kroening, [email protected]
1414
#include <util/fixedbv.h>
1515
#include <util/ieee_float.h>
1616
#include <util/pointer_offset_size.h>
17+
#include <util/simplify_expr.h>
1718
#include <util/string_container.h>
1819

1920
#include <langapi/language_util.h>
@@ -894,52 +895,38 @@ void interpretert::evaluate(
894895
mp_integer address=evaluate_address(
895896
expr,
896897
true); // fail quietly
897-
if(address.is_zero() && expr.id()==ID_index)
898+
if(address.is_zero())
898899
{
899-
// Try reading from a constant array:
900-
mp_vectort idx;
901-
evaluate(expr.op1(), idx);
902-
if(idx.size()==1)
900+
exprt simplified;
901+
// In case of being an indexed access, try to evaluate the index, then
902+
// simplify.
903+
if(expr.id() == ID_index)
903904
{
904-
mp_integer read_from_index=idx[0];
905-
if(expr.op0().id()==ID_array)
905+
exprt evaluated_index = expr;
906+
mp_vectort idx;
907+
evaluate(expr.op1(), idx);
908+
if(idx.size() == 1)
906909
{
907-
const auto &ops=expr.op0().operands();
908-
DATA_INVARIANT(read_from_index.is_long(), "index is too large");
909-
if(read_from_index>=0 && read_from_index<ops.size())
910-
{
911-
evaluate(ops[read_from_index.to_long()], dest);
912-
if(dest.size()!=0)
913-
return;
914-
}
915-
}
916-
else if(expr.op0().id() == ID_array_list)
917-
{
918-
// This sort of construct comes from boolbv_get, but doesn't seem
919-
// to have an exprt yet. Its operands are a list of key-value pairs.
920-
const auto &ops=expr.op0().operands();
921-
DATA_INVARIANT(
922-
ops.size()%2==0,
923-
"array-list has odd number of operands");
924-
for(size_t listidx=0; listidx!=ops.size(); listidx+=2)
925-
{
926-
mp_vectort elem_idx;
927-
evaluate(ops[listidx], elem_idx);
928-
CHECK_RETURN(elem_idx.size()==1);
929-
if(elem_idx[0]==read_from_index)
930-
{
931-
evaluate(ops[listidx+1], dest);
932-
if(dest.size()!=0)
933-
return;
934-
else
935-
break;
936-
}
937-
}
938-
// If we fall out the end of this loop then the constant array-list
939-
// didn't define an element matching the index we're looking for.
910+
evaluated_index.op1() =
911+
constant_exprt(integer2string(idx[0]), expr.op1().type());
940912
}
913+
simplified = simplify_expr(evaluated_index, ns);
914+
}
915+
else
916+
{
917+
// Try reading from a constant -- simplify_expr has all the relevant
918+
// cases (index-of-constant-array, member-of-constant-struct and so on)
919+
// Note we complain of a problem even if simplify did *something* but
920+
// still left us with an unresolved index, member, etc.
921+
simplified = simplify_expr(expr, ns);
922+
}
923+
if(simplified.id() == expr.id())
924+
evaluate_address(expr); // Evaluate again to print error message.
925+
else
926+
{
927+
evaluate(simplified, dest);
928+
return;
941929
}
942-
evaluate_address(expr); // Evaluate again to print error message.
943930
}
944931
else if(!address.is_zero())
945932
{

src/goto-symex/goto_symex.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ class goto_symext
328328
// with false we continue.
329329
virtual bool get_unwind(
330330
const symex_targett::sourcet &source,
331+
const goto_symex_statet::call_stackt &context,
331332
unsigned unwind);
332333

333334
virtual void loop_bound_exceeded(statet &, const exprt &guard);

src/goto-symex/symex_goto.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ void goto_symext::symex_goto(statet &state)
8686
unwind++;
8787

8888
// continue unwinding?
89-
if(get_unwind(state.source, unwind))
89+
if(get_unwind(state.source, state.call_stack(), unwind))
9090
{
9191
// no!
9292
loop_bound_exceeded(state, new_guard);
@@ -540,6 +540,7 @@ void goto_symext::loop_bound_exceeded(
540540

541541
bool goto_symext::get_unwind(
542542
const symex_targett::sourcet &source,
543+
const goto_symex_statet::call_stackt &context,
543544
unsigned unwind)
544545
{
545546
// by default, we keep going

0 commit comments

Comments
 (0)