Skip to content

Commit d7645fd

Browse files
authored
Merge pull request #3447 from smowton/smowton/feature/java-parse-without-instructions
JCI: don't load method instructions
2 parents 870882c + 8f7f8ae commit d7645fd

File tree

9 files changed

+162
-11
lines changed

9 files changed

+162
-11
lines changed

jbmc/src/java_bytecode/java_bytecode_parser.cpp

+24-8
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ Author: Daniel Kroening, [email protected]
3232
class java_bytecode_parsert:public parsert
3333
{
3434
public:
35-
java_bytecode_parsert()
35+
explicit java_bytecode_parsert(bool skip_instructions)
36+
: skip_instructions(skip_instructions)
3637
{
37-
get_bytecodes();
38+
populate_bytecode_mnemonics_table();
3839
}
3940

4041
virtual bool parse();
@@ -78,6 +79,7 @@ class java_bytecode_parsert:public parsert
7879
};
7980

8081
std::vector<bytecodet> bytecodes;
82+
const bool skip_instructions = false;
8183

8284
pool_entryt &pool_entry(u2 index)
8385
{
@@ -101,8 +103,13 @@ class java_bytecode_parsert:public parsert
101103
return java_type_from_string(id2string(pool_entry(index).s));
102104
}
103105

104-
void get_bytecodes()
106+
void populate_bytecode_mnemonics_table()
105107
{
108+
// This is only useful for rbytecodes, which in turn is only useful to
109+
// parse method instructions.
110+
if(skip_instructions)
111+
return;
112+
106113
// pre-hash the mnemonics, so we do this only once
107114
bytecodes.resize(256);
108115
for(const bytecode_infot *p=bytecode_info; p->mnemonic!=nullptr; p++)
@@ -951,6 +958,9 @@ void java_bytecode_parsert::rfields(classt &parsed_class)
951958
void java_bytecode_parsert::rbytecode(
952959
methodt::instructionst &instructions)
953960
{
961+
INVARIANT(
962+
bytecodes.size() == 256, "bytecode mnemonics should have been populated");
963+
954964
u4 code_length=read_u4();
955965

956966
u4 address;
@@ -1215,7 +1225,7 @@ void java_bytecode_parsert::rmethod_attribute(methodt &method)
12151225

12161226
irep_idt attribute_name=pool_entry(attribute_name_index).s;
12171227

1218-
if(attribute_name=="Code")
1228+
if(attribute_name == "Code" && !skip_instructions)
12191229
{
12201230
UNUSED_u2(max_stack);
12211231
UNUSED_u2(max_locals);
@@ -1861,9 +1871,12 @@ void java_bytecode_parsert::rmethod(classt &parsed_class)
18611871
}
18621872

18631873
optionalt<java_bytecode_parse_treet>
1864-
java_bytecode_parse(std::istream &istream, message_handlert &message_handler)
1874+
java_bytecode_parse(
1875+
std::istream &istream,
1876+
message_handlert &message_handler,
1877+
bool skip_instructions)
18651878
{
1866-
java_bytecode_parsert java_bytecode_parser;
1879+
java_bytecode_parsert java_bytecode_parser(skip_instructions);
18671880
java_bytecode_parser.in=&istream;
18681881
java_bytecode_parser.set_message_handler(message_handler);
18691882

@@ -1878,7 +1891,10 @@ java_bytecode_parse(std::istream &istream, message_handlert &message_handler)
18781891
}
18791892

18801893
optionalt<java_bytecode_parse_treet>
1881-
java_bytecode_parse(const std::string &file, message_handlert &message_handler)
1894+
java_bytecode_parse(
1895+
const std::string &file,
1896+
message_handlert &message_handler,
1897+
bool skip_instructions)
18821898
{
18831899
std::ifstream in(file, std::ios::binary);
18841900

@@ -1890,7 +1906,7 @@ java_bytecode_parse(const std::string &file, message_handlert &message_handler)
18901906
return {};
18911907
}
18921908

1893-
return java_bytecode_parse(in, message_handler);
1909+
return java_bytecode_parse(in, message_handler, skip_instructions);
18941910
}
18951911

18961912
/// Parses the local variable type table of a method. The LVTT holds generic

jbmc/src/java_bytecode/java_bytecode_parser.h

+21-3
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,28 @@ Author: Daniel Kroening, [email protected]
1616

1717
struct java_bytecode_parse_treet;
1818

19+
/// Attempt to parse a Java class from the given file.
20+
/// \param file: file to load from
21+
/// \param msg: handles log messages
22+
/// \param skip_instructions: if true, the loaded class's methods will all be
23+
/// empty. Saves time and memory for consumers that only want signature info.
24+
/// \return parse tree, or empty optionalt on failure
1925
optionalt<java_bytecode_parse_treet>
20-
java_bytecode_parse(const std::string &file, class message_handlert &);
21-
26+
java_bytecode_parse(
27+
const std::string &file,
28+
class message_handlert &msg,
29+
bool skip_instructions = false);
30+
31+
/// Attempt to parse a Java class from the given stream
32+
/// \param stream: stream to load from
33+
/// \param msg: handles log messages
34+
/// \param skip_instructions: if true, the loaded class's methods will all be
35+
/// empty. Saves time and memory for consumers that only want signature info.
36+
/// \return parse tree, or empty optionalt on failure
2237
optionalt<java_bytecode_parse_treet>
23-
java_bytecode_parse(std::istream &, class message_handlert &);
38+
java_bytecode_parse(
39+
std::istream &stream,
40+
class message_handlert &msg,
41+
bool skip_instructions = false);
2442

2543
#endif // CPROVER_JAVA_BYTECODE_JAVA_BYTECODE_PARSER_H

jbmc/unit/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ SRC += java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp \
3636
java_bytecode/java_bytecode_parse_generics/parse_lvtt_generic_local_vars.cpp \
3737
java_bytecode/java_bytecode_parse_lambdas/java_bytecode_convert_class_lambda_method_handles.cpp \
3838
java_bytecode/java_bytecode_parse_lambdas/java_bytecode_parse_lambda_method_table.cpp \
39+
java_bytecode/java_bytecode_parser/parse_class_without_instructions.cpp \
3940
java_bytecode/java_bytecode_parser/parse_java_annotations.cpp \
4041
java_bytecode/java_bytecode_parser/parse_java_attributes.cpp \
4142
java_bytecode/java_bytecode_parser/parse_java_class.cpp \
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
public @interface Annotation { }
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
public class Trivial {
2+
@Annotation
3+
public class Inner {
4+
@Annotation
5+
private int x;
6+
public Inner() { x = 1; }
7+
@Annotation
8+
public void f() { x++; };
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*******************************************************************\
2+
3+
Module: Unit tests to parse a class without its methods' instructions
4+
5+
Author: Diffblue Ltd.
6+
7+
\*******************************************************************/
8+
9+
#include <java-testing-utils/load_java_class.h>
10+
#include <java_bytecode/java_bytecode_parse_tree.h>
11+
#include <java_bytecode/java_bytecode_parser.h>
12+
#include <testing-utils/message.h>
13+
#include <util/message.h>
14+
15+
#include <testing-utils/catch.hpp>
16+
17+
static void check_class_structure(
18+
const java_bytecode_parse_treet::classt &loaded_class)
19+
{
20+
REQUIRE(loaded_class.methods.size() == 2);
21+
REQUIRE(loaded_class.is_public);
22+
REQUIRE(loaded_class.annotations.size() == 1);
23+
REQUIRE(loaded_class.fields.size() == 2);
24+
REQUIRE(loaded_class.super_class == "java.lang.Object");
25+
REQUIRE(loaded_class.is_inner_class);
26+
REQUIRE(loaded_class.outer_class == "Trivial");
27+
28+
const auto &fieldone = *loaded_class.fields.begin();
29+
const auto &fieldtwo = *std::next(loaded_class.fields.begin());
30+
const auto &field_x = fieldone.name == "x" ? fieldone : fieldtwo;
31+
REQUIRE(field_x.name == "x");
32+
REQUIRE(field_x.is_private);
33+
REQUIRE(field_x.annotations.size() == 1);
34+
35+
const auto &methodone = *loaded_class.methods.begin();
36+
const auto &methodtwo = *std::next(loaded_class.methods.begin());
37+
const auto &method_f =
38+
methodone.name == "f" ? methodone : methodtwo;
39+
const auto &method_constructor =
40+
methodone.name == "f" ? methodtwo : methodone;
41+
42+
REQUIRE(method_f.is_public);
43+
REQUIRE(method_f.annotations.size() == 1);
44+
REQUIRE(method_constructor.is_public);
45+
REQUIRE(method_constructor.annotations.size() == 0);
46+
}
47+
48+
SCENARIO(
49+
"java_bytecode_parse_class_without_instructions",
50+
"[core][java_bytecode][java_bytecode_parser]")
51+
{
52+
WHEN("Loading a class without instructions")
53+
{
54+
auto loaded =
55+
java_bytecode_parse(
56+
"./java_bytecode/java_bytecode_parser/Trivial$Inner.class",
57+
null_message_handler,
58+
true);
59+
THEN("Loading should succeed")
60+
{
61+
REQUIRE(loaded);
62+
const auto &loaded_class = loaded->parsed_class;
63+
64+
THEN("It should have the expected structure")
65+
{
66+
check_class_structure(loaded_class);
67+
const auto &methodone = *loaded_class.methods.begin();
68+
const auto &methodtwo = *std::next(loaded_class.methods.begin());
69+
70+
THEN("Neither method should have instructions")
71+
{
72+
REQUIRE(methodone.instructions.size() == 0);
73+
REQUIRE(methodtwo.instructions.size() == 0);
74+
}
75+
}
76+
}
77+
}
78+
79+
WHEN("Loading the same class normally")
80+
{
81+
auto loaded =
82+
java_bytecode_parse(
83+
"./java_bytecode/java_bytecode_parser/Trivial$Inner.class",
84+
null_message_handler,
85+
false);
86+
THEN("Loading should succeed")
87+
{
88+
REQUIRE(loaded);
89+
const auto &loaded_class = loaded->parsed_class;
90+
91+
THEN("It should have the expected structure")
92+
{
93+
check_class_structure(loaded_class);
94+
const auto &methodone = *loaded_class.methods.begin();
95+
const auto &methodtwo = *std::next(loaded_class.methods.begin());
96+
97+
THEN("Both methods should have instructions")
98+
{
99+
REQUIRE(methodone.instructions.size() != 0);
100+
REQUIRE(methodtwo.instructions.size() != 0);
101+
}
102+
}
103+
}
104+
}
105+
}

0 commit comments

Comments
 (0)