Skip to content

Commit b49a7fc

Browse files
committed
loop unwinding in static init of Java enum
Static initialization of Java enums sometimes contains extra code that loops over the array of enum elements. This often prevents initialization of the enumeration and leads to zero coverage. This patch counts the number of enum elements, and explicitely unwinds all loops in <clinit> to this number + 1. This acts as a heuristic to make static inits of enums finish.
1 parent cd4fc7b commit b49a7fc

10 files changed

+204
-19
lines changed

src/cbmc/cbmc_parse_options.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Author: Daniel Kroening, [email protected]
2828
#include <goto-programs/remove_complex.h>
2929
#include <goto-programs/remove_asm.h>
3030
#include <goto-programs/remove_unused_functions.h>
31+
#include <goto-programs/remove_static_init_loops.h>
3132
#include <goto-programs/goto_inline.h>
3233
#include <goto-programs/show_properties.h>
3334
#include <goto-programs/set_properties.h>
@@ -222,6 +223,10 @@ void cbmc_parse_optionst::get_command_line_options(optionst &options)
222223
// all checks supported by goto_check
223224
GOTO_CHECK_PARSE_OPTIONS(cmdline, options);
224225

226+
// unwind loops in java enum static initialization
227+
if(cmdline.isset("java-unwind-enum-static"))
228+
options.set_option("java-unwind-enum-static", true);
229+
225230
// check assertions
226231
if(cmdline.isset("no-assertions"))
227232
options.set_option("assertions", false);
@@ -542,6 +547,9 @@ int cbmc_parse_optionst::doit()
542547
if(set_properties(goto_functions))
543548
return 7; // should contemplate EX_USAGE from sysexits.h
544549

550+
if(options.get_bool_option("java-unwind-enum-static"))
551+
remove_static_init_loops(symbol_table, goto_functions, options);
552+
545553
// do actual BMC
546554
return do_bmc(bmc, goto_functions);
547555
}
@@ -1127,6 +1135,10 @@ void cbmc_parse_optionst::help()
11271135
"Java Bytecode frontend options:\n"
11281136
" --classpath dir/jar set the classpath\n"
11291137
" --main-class class-name set the name of the main class\n"
1138+
// NOLINTNEXTLINE(whitespace/line_length)
1139+
" --java-max-vla-length limit the length of user-code-created arrays\n"
1140+
// NOLINTNEXTLINE(whitespace/line_length)
1141+
" --java-unwind-enum-static try to unwind loops in static initialization of enums\n"
11301142
"\n"
11311143
"Semantic transformations:\n"
11321144
" --nondet-static add nondeterministic initialization of variables with static lifetime\n" // NOLINT(*)

src/cbmc/cbmc_parse_options.h

+6-5
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ class optionst;
3636
"(smt1)(smt2)(fpa)(cvc3)(cvc4)(boolector)(yices)(z3)(opensmt)(mathsat)" \
3737
"(no-sat-preprocessor)" \
3838
"(no-pretty-names)(beautify)" \
39-
"(dimacs)(refine)(max-node-refinement):(refine-arrays)(refine-arithmetic)(aig)" \
40-
"(16)(32)(64)(LP64)(ILP64)(LLP64)(ILP32)(LP32)" \
39+
"(dimacs)(refine)(max-node-refinement):(refine-arrays)(refine-arithmetic)"\
40+
"(aig)(16)(32)(64)(LP64)(ILP64)(LLP64)(ILP32)(LP32)" \
4141
"(little-endian)(big-endian)" \
4242
"(show-goto-functions)(show-loops)" \
4343
"(show-symbol-table)(show-parse-tree)(show-vcc)" \
44-
"(show-claims)(claim):(show-properties)(show-reachable-properties)(property):" \
45-
"(stop-on-fail)(trace)" \
44+
"(show-claims)(claim):(show-properties)(show-reachable-properties)" \
45+
"(property):(stop-on-fail)(trace)" \
4646
"(error-label):(verbosity):(no-library)" \
4747
"(nondet-static)" \
4848
"(version)" \
@@ -54,8 +54,9 @@ class optionst;
5454
"(string-abstraction)(no-arch)(arch):" \
5555
"(round-to-nearest)(round-to-plus-inf)(round-to-minus-inf)(round-to-zero)" \
5656
"(graphml-witness):" \
57+
"(java-max-vla-length):(java-unwind-enum-static)" \
5758
"(localize-faults)(localize-faults-method):" \
58-
"(fixedbv)(floatbv)(all-claims)(all-properties)" // legacy, and will eventually disappear
59+
"(fixedbv)(floatbv)(all-claims)(all-properties)" // legacy, and will eventually disappear // NOLINT(whitespace/line_length)
5960

6061
class cbmc_parse_optionst:
6162
public parse_options_baset,

src/goto-programs/Makefile

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ SRC = goto_convert.cpp goto_convert_function_call.cpp \
1717
goto_trace.cpp xml_goto_trace.cpp vcd_goto_trace.cpp \
1818
graphml_witness.cpp remove_virtual_functions.cpp \
1919
class_hierarchy.cpp show_goto_functions.cpp get_goto_model.cpp \
20-
slice_global_inits.cpp goto_inline_class.cpp class_identifier.cpp
20+
slice_global_inits.cpp goto_inline_class.cpp class_identifier.cpp \
21+
remove_static_init_loops.cpp
2122

2223
INCLUDES= -I ..
2324

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*******************************************************************\
2+
3+
Module: Unwind loops in static initializers
4+
5+
Author: Daniel Kroening, [email protected]
6+
7+
\*******************************************************************/
8+
9+
#include <util/message.h>
10+
#include <util/string2int.h>
11+
12+
#include "remove_static_init_loops.h"
13+
14+
class remove_static_init_loopst
15+
{
16+
public:
17+
explicit remove_static_init_loopst(
18+
const symbol_tablet &_symbol_table):
19+
symbol_table(_symbol_table)
20+
{}
21+
22+
void unwind_enum_static(
23+
const goto_functionst &goto_functions,
24+
optionst &options);
25+
protected:
26+
const symbol_tablet &symbol_table;
27+
};
28+
29+
/*******************************************************************\
30+
31+
Function: unwind_enum_static
32+
33+
Inputs: goto_functions and options
34+
35+
Outputs: side effect is adding <clinit> loops to unwindset
36+
37+
Purpose: unwind static initialization loops of Java enums as far as
38+
the enum has elements, thus flattening them completely
39+
40+
\*******************************************************************/
41+
42+
void remove_static_init_loopst::unwind_enum_static(
43+
const goto_functionst &goto_functions,
44+
optionst &options)
45+
{
46+
size_t unwind_max=0;
47+
forall_goto_functions(f, goto_functions)
48+
{
49+
auto &p=f->second.body;
50+
for(const auto &ins : p.instructions)
51+
{
52+
// is this a loop?
53+
if(ins.is_backwards_goto())
54+
{
55+
size_t loop_id=ins.loop_number;
56+
const std::string java_clinit="<clinit>:()V";
57+
const std::string &fname=id2string(ins.function);
58+
size_t class_prefix_length=fname.find_last_of('.');
59+
assert(
60+
class_prefix_length!=std::string::npos &&
61+
"could not identify class name");
62+
const std::string &classname=fname.substr(0, class_prefix_length);
63+
const symbolt &class_symbol=symbol_table.lookup(classname);
64+
const class_typet &class_type=to_class_type(class_symbol.type);
65+
size_t unwinds=class_type.get_size_t(ID_java_enum_static_unwind);
66+
67+
unwind_max=std::max(unwind_max, unwinds);
68+
if(fname.length()>java_clinit.length())
69+
{
70+
// extend unwindset with unwinds for <clinit> of enum
71+
if(fname.compare(
72+
fname.length()-java_clinit.length(),
73+
java_clinit.length(),
74+
java_clinit)==0 && unwinds>0)
75+
{
76+
const std::string &set=options.get_option("unwindset");
77+
std::string newset;
78+
if(set!="")
79+
newset=",";
80+
newset+=
81+
id2string(ins.function)+"."+std::to_string(loop_id)+":"+
82+
std::to_string(unwinds);
83+
options.set_option("unwindset", set+newset);
84+
}
85+
}
86+
}
87+
}
88+
}
89+
const std::string &vla_length=options.get_option("java-max-vla-length");
90+
if(!vla_length.empty())
91+
{
92+
size_t max_vla_length=safe_string2unsigned(vla_length);
93+
if(max_vla_length<unwind_max)
94+
throw "cannot unwind <clinit> due to insufficient max vla length";
95+
}
96+
}
97+
98+
/*******************************************************************\
99+
100+
Function: remove_static_init_loops
101+
102+
Inputs: symbol table, goto_functions and options
103+
104+
Outputs: side effect is adding <clinit> loops to unwindset
105+
106+
Purpose: this is the entry point for the removal of loops in static
107+
initialization code of Java enums
108+
109+
\*******************************************************************/
110+
111+
void remove_static_init_loops(
112+
const symbol_tablet &symbol_table,
113+
const goto_functionst &goto_functions,
114+
optionst &options)
115+
{
116+
remove_static_init_loopst remove_loops(symbol_table);
117+
remove_loops.unwind_enum_static(goto_functions, options);
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*******************************************************************\
2+
3+
Module: Unwind loops in static initializers
4+
5+
Author: Daniel Kroening, [email protected]
6+
7+
\*******************************************************************/
8+
9+
#include <goto-programs/goto_functions.h>
10+
11+
#include <util/options.h>
12+
#include <util/symbol_table.h>
13+
14+
#ifndef CPROVER_GOTO_PROGRAMS_REMOVE_STATIC_INIT_LOOPS_H
15+
#define CPROVER_GOTO_PROGRAMS_REMOVE_STATIC_INIT_LOOPS_H
16+
17+
void remove_static_init_loops(
18+
const symbol_tablet &,
19+
const goto_functionst &,
20+
optionst &);
21+
22+
#endif // CPROVER_GOTO_PROGRAMS_REMOVE_STATIC_INIT_LOOPS_H

src/java_bytecode/java_bytecode_convert_class.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ void java_bytecode_convert_classt::convert(const classt &c)
7777

7878
class_type.set_tag(c.name);
7979
class_type.set(ID_base_name, c.name);
80+
if(c.is_enum)
81+
class_type.set(
82+
ID_java_enum_static_unwind,
83+
std::to_string(c.enum_elements+1));
8084

8185
if(!c.extends.empty())
8286
{

src/java_bytecode/java_bytecode_parse_tree.cpp

+12-7
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ void java_bytecode_parse_treet::classt::swap(
3434
{
3535
other.name.swap(name);
3636
other.extends.swap(extends);
37+
other.is_enum=is_enum;
38+
other.enum_elements=enum_elements;
3739
std::swap(other.is_abstract, is_abstract);
3840
other.implements.swap(implements);
3941
other.fields.swap(fields);
@@ -61,10 +63,7 @@ void java_bytecode_parse_treet::output(std::ostream &out) const
6163
for(class_refst::const_iterator it=class_refs.begin();
6264
it!=class_refs.end();
6365
it++)
64-
{
6566
out << " " << *it << '\n';
66-
}
67-
6867
}
6968

7069
/*******************************************************************\
@@ -88,7 +87,8 @@ void java_bytecode_parse_treet::classt::output(std::ostream &out) const
8887
}
8988

9089
out << "class " << name;
91-
if(!extends.empty()) out << " extends " << extends;
90+
if(!extends.empty())
91+
out << " extends " << extends;
9292
out << " {" << '\n';
9393

9494
for(fieldst::const_iterator
@@ -139,7 +139,10 @@ void java_bytecode_parse_treet::annotationt::output(std::ostream &out) const
139139
bool first=true;
140140
for(const auto &element_value_pair : element_value_pairs)
141141
{
142-
if(first) first=false; else out << ", ";
142+
if(first)
143+
first=false;
144+
else
145+
out << ", ";
143146
element_value_pair.output(out);
144147
}
145148

@@ -159,7 +162,8 @@ Function: java_bytecode_parse_treet::annotationt::element_value_pairt::output
159162
160163
\*******************************************************************/
161164

162-
void java_bytecode_parse_treet::annotationt::element_value_pairt::output(std::ostream &out) const
165+
void java_bytecode_parse_treet::annotationt::element_value_pairt::output(
166+
std::ostream &out) const
163167
{
164168
symbol_tablet symbol_table;
165169
namespacet ns(symbol_table);
@@ -233,7 +237,8 @@ void java_bytecode_parse_treet::methodt::output(std::ostream &out) const
233237
for(std::vector<exprt>::const_iterator
234238
a_it=i.args.begin(); a_it!=i.args.end(); a_it++)
235239
{
236-
if(a_it!=i.args.begin()) out << ',';
240+
if(a_it!=i.args.begin())
241+
out << ',';
237242
#if 0
238243
out << ' ' << from_expr(*a_it);
239244
#else

src/java_bytecode/java_bytecode_parse_tree.h

+17-5
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,20 @@ class java_bytecode_parse_treet
123123
class stack_map_table_entryt
124124
{
125125
public:
126-
enum stack_frame_type { SAME, SAME_LOCALS_ONE_STACK, SAME_LOCALS_ONE_STACK_EXTENDED,
127-
CHOP, SAME_EXTENDED, APPEND, FULL};
126+
enum stack_frame_type
127+
{
128+
SAME, SAME_LOCALS_ONE_STACK, SAME_LOCALS_ONE_STACK_EXTENDED,
129+
CHOP, SAME_EXTENDED, APPEND, FULL
130+
};
128131
stack_frame_type type;
129132
size_t offset_delta;
130133
size_t chops;
131134
size_t appends;
132135

133-
typedef std::vector<verification_type_infot> local_verification_type_infot;
134-
typedef std::vector<verification_type_infot> stack_verification_type_infot;
136+
typedef std::vector<verification_type_infot>
137+
local_verification_type_infot;
138+
typedef std::vector<verification_type_infot>
139+
stack_verification_type_infot;
135140

136141
local_verification_type_infot locals;
137142
stack_verification_type_infot stack;
@@ -142,7 +147,10 @@ class java_bytecode_parse_treet
142147

143148
virtual void output(std::ostream &out) const;
144149

145-
inline methodt():is_native(false), is_abstract(false), is_synchronized(false)
150+
inline methodt():
151+
is_native(false),
152+
is_abstract(false),
153+
is_synchronized(false)
146154
{
147155
}
148156
};
@@ -151,13 +159,17 @@ class java_bytecode_parse_treet
151159
{
152160
public:
153161
virtual void output(std::ostream &out) const;
162+
bool is_enum;
154163
};
155164

156165
class classt
157166
{
158167
public:
159168
irep_idt name, extends;
160169
bool is_abstract;
170+
bool is_enum;
171+
size_t enum_elements=0;
172+
161173
typedef std::list<irep_idt> implementst;
162174
implementst implements;
163175

src/java_bytecode/java_bytecode_parser.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ Function: java_bytecode_parsert::rClassFile
264264
#define ACC_ABSTRACT 0x0400
265265
#define ACC_STRICT 0x0800
266266
#define ACC_SYNTHETIC 0x1000
267+
#define ACC_ENUM 0x4000
267268

268269
#ifdef _MSC_VER
269270
#define UNUSED
@@ -295,10 +296,11 @@ void java_bytecode_parsert::rClassFile()
295296

296297
classt &parsed_class=parse_tree.parsed_class;
297298

298-
u2 UNUSED access_flags=read_u2();
299+
u2 access_flags=read_u2();
299300
u2 this_class=read_u2();
300301
u2 super_class=read_u2();
301302

303+
parsed_class.is_enum=(access_flags&ACC_ENUM)!=0;
302304
parsed_class.name=
303305
constant(this_class).type().get(ID_C_base_name);
304306

@@ -310,6 +312,12 @@ void java_bytecode_parsert::rClassFile()
310312
rfields(parsed_class);
311313
rmethods(parsed_class);
312314

315+
// count elements of enum
316+
if(parsed_class.is_enum)
317+
for(fieldt &field : parse_tree.parsed_class.fields)
318+
if(field.is_enum)
319+
parse_tree.parsed_class.enum_elements++;
320+
313321
u2 attributes_count=read_u2();
314322

315323
for(std::size_t j=0; j<attributes_count; j++)
@@ -698,6 +706,7 @@ void java_bytecode_parsert::rfields(classt &parsed_class)
698706
field.name=pool_entry(name_index).s;
699707
field.is_static=(access_flags&ACC_STATIC)!=0;
700708
field.is_final=(access_flags&ACC_FINAL)!=0;
709+
field.is_enum=(access_flags&ACC_ENUM)!=0;
701710
field.signature=id2string(pool_entry(descriptor_index).s);
702711

703712
for(std::size_t j=0; j<attributes_count; j++)

src/util/irep_ids.txt

+1
Original file line numberDiff line numberDiff line change
@@ -736,3 +736,4 @@ bswap
736736
java_bytecode_index
737737
java_instanceof
738738
java_super_method_call
739+
java_enum_static_unwind

0 commit comments

Comments
 (0)