Skip to content

Commit 6e085d2

Browse files
authored
Merge pull request #3540 from diffblue/forejtv/fixed-strings
Add a switch for fixed strings on input
2 parents 0bb4242 + 0ad4ec1 commit 6e085d2

File tree

10 files changed

+183
-44
lines changed

10 files changed

+183
-44
lines changed
Binary file not shown.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public class Test {
2+
3+
static void checkStringInputValue(String s) {
4+
if (s.equals("fo")) {
5+
assert(false);
6+
} else if (s.equals("foo")) {
7+
assert(false);
8+
} else if (s.length() == 3) {
9+
assert(false);
10+
} else if (s.equals("barr")) {
11+
assert(false);
12+
} else if (s.equals("")) {
13+
assert(false);
14+
}
15+
assert(false);
16+
}
17+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
Test.class
3+
--verbosity 10 --string-input-value fo --string-input-value barr --function Test.checkStringInputValue --string-input-value "" --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
assertion.1.*FAILURE
7+
assertion.2.*SUCCESS
8+
assertion.3.*SUCCESS
9+
assertion.4.*FAILURE
10+
assertion.5.*FAILURE
11+
assertion.6.*SUCCESS
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
Test.class
3+
--verbosity 10 --function Test.checkStringInputValue --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
assertion.1.*FAILURE
7+
assertion.2.*FAILURE
8+
assertion.3.*FAILURE
9+
assertion.4.*FAILURE
10+
assertion.5.*FAILURE
11+
assertion.6.*FAILURE

jbmc/src/java_bytecode/java_entry_point.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,21 @@ static void java_static_lifetime_init(
131131
code_blockt &code_block=to_code_block(to_code(initialize_symbol.value));
132132
object_factory_parameters.function_id = initialize_symbol.name;
133133

134+
// If there are strings given using --string-input-value, we need to add
135+
// them to the symbol table now, so that they appear in __CPROVER_initialize
136+
// and we can refer to them later when we initialize input values.
137+
for(const auto &val : object_factory_parameters.string_input_values)
138+
{
139+
exprt my_literal(ID_java_string_literal);
140+
my_literal.set(ID_value, val);
141+
// We ignore the return value of the following call, we just need to
142+
// make sure the string is in the symbol table.
143+
get_or_create_string_literal_symbol(
144+
my_literal,
145+
symbol_table,
146+
string_refinement_enabled);
147+
}
148+
134149
// We need to zero out all static variables, or nondet-initialize if they're
135150
// external. Iterate over a copy of the symtab, as its iterators are
136151
// invalidated by object_factory:

jbmc/src/java_bytecode/java_object_factory.cpp

Lines changed: 101 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ Author: Daniel Kroening, kroening@kroening.com
1111
#include <util/expr_initializer.h>
1212
#include <util/fresh_symbol.h>
1313
#include <util/nondet_bool.h>
14+
#include <util/nondet.h>
1415
#include <util/pointer_offset_size.h>
1516

1617
#include <goto-programs/class_identifier.h>
1718
#include <goto-programs/goto_functions.h>
1819

1920
#include "generic_parameter_specialization_map_keys.h"
2021
#include "java_root_class.h"
22+
#include "java_string_literals.h"
2123

2224
class java_object_factoryt
2325
{
@@ -526,9 +528,11 @@ static mp_integer max_value(const typet &type)
526528
UNREACHABLE;
527529
}
528530

529-
/// Check if a structure is a nondeterministic String structure, and if it is
530-
/// initialize its length and data fields.
531-
/// \param struct_expr [out]: struct that we may initialize
531+
/// Initialise length and data fields for a nondeterministic String structure.
532+
///
533+
/// If the structure is not a nondeterministic structure, the call results in
534+
/// a precondition violation.
535+
/// \param struct_expr [out]: struct that we initialize
532536
/// \param code: block to add pre-requisite declarations (e.g. to allocate a
533537
/// data array)
534538
/// \param min_nondet_string_length: minimum length of strings to initialize
@@ -538,9 +542,6 @@ static mp_integer max_value(const typet &type)
538542
/// \param symbol_table: the symbol table
539543
/// \param printable: if true, the nondet string must consist of printable
540544
/// characters only
541-
/// \return true if struct_expr's length and data fields have been set up,
542-
/// false if we took no action because struct_expr wasn't a CharSequence
543-
/// or didn't have the necessary fields.
544545
///
545546
/// The code produced is of the form:
546547
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -564,7 +565,7 @@ static mp_integer max_value(const typet &type)
564565
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
565566
/// Unit tests in `unit/java_bytecode/java_object_factory/` ensure
566567
/// it is the case.
567-
bool initialize_nondet_string_fields(
568+
void initialize_nondet_string_fields(
568569
struct_exprt &struct_expr,
569570
code_blockt &code,
570571
const std::size_t &min_nondet_string_length,
@@ -574,11 +575,9 @@ bool initialize_nondet_string_fields(
574575
symbol_table_baset &symbol_table,
575576
bool printable)
576577
{
577-
if(!java_string_library_preprocesst::implements_java_char_sequence(
578-
struct_expr.type()))
579-
{
580-
return false;
581-
}
578+
PRECONDITION(
579+
java_string_library_preprocesst
580+
::implements_java_char_sequence(struct_expr.type()));
582581

583582
namespacet ns(symbol_table);
584583

@@ -589,8 +588,8 @@ bool initialize_nondet_string_fields(
589588
// (typically when string refinement is not activated), `struct_type`
590589
// just contains the standard Object fields (or may have some other model
591590
// entirely), and in particular has no length and data fields.
592-
if(!struct_type.has_component("length") || !struct_type.has_component("data"))
593-
return false;
591+
PRECONDITION(
592+
struct_type.has_component("length") && struct_type.has_component("data"));
594593

595594
// We allow type StringBuffer and StringBuilder to be initialized
596595
// in the same way has String, because they have the same structure and
@@ -657,8 +656,6 @@ bool initialize_nondet_string_fields(
657656
add_character_set_constraint(
658657
array_pointer, length_expr, " -~", symbol_table, loc, code);
659658
}
660-
661-
return true;
662659
}
663660

664661
/// Initializes a pointer \p expr of type \p pointer_type to a primitive-typed
@@ -943,6 +940,33 @@ symbol_exprt java_object_factoryt::gen_nondet_subtype_pointer_init(
943940
return new_symbol.symbol_expr();
944941
}
945942

943+
/// Creates an alternate_casest vector in which each item contains an
944+
/// assignment of a string from \p string_input_values (or more precisely the
945+
/// literal symbol corresponding to the string) to \p expr.
946+
/// \param expr:
947+
/// Struct-typed lvalue expression to which we want to assign the strings.
948+
/// \param string_input_values:
949+
/// Strings to assign.
950+
/// \param symbol_table:
951+
/// The symbol table in which we'll lool up literal symbol
952+
/// \return A vector that can be passed to generate_nondet_switch
953+
alternate_casest get_string_input_values_code(
954+
const exprt &expr,
955+
const std::list<std::string> &string_input_values,
956+
symbol_table_baset &symbol_table)
957+
{
958+
alternate_casest cases;
959+
for(const auto &val : string_input_values)
960+
{
961+
exprt name_literal(ID_java_string_literal);
962+
name_literal.set(ID_value, val);
963+
const symbol_exprt s =
964+
get_or_create_string_literal_symbol(name_literal, symbol_table, true);
965+
cases.push_back(code_assignt(expr, s));
966+
}
967+
return cases;
968+
}
969+
946970
/// Initializes an object tree rooted at `expr`, allocating child objects as
947971
/// necessary and nondet-initializes their members, or if MUST_UPDATE_IN_PLACE
948972
/// is set, re-initializes already-allocated objects.
@@ -1009,31 +1033,67 @@ void java_object_factoryt::gen_nondet_struct_init(
10091033
{
10101034
class_identifier = struct_tag;
10111035

1012-
// Add an initial all-zero write. Most of the fields of this will be
1013-
// overwritten, but it helps to have a whole-structure write that analysis
1014-
// passes can easily recognise leaves no uninitialised state behind.
1015-
1016-
// This code mirrors the `remove_java_new` pass:
1017-
auto initial_object = zero_initializer(struct_type, source_locationt(), ns);
1018-
CHECK_RETURN(initial_object.has_value());
1019-
irep_idt qualified_clsid = "java::" + id2string(class_identifier);
1020-
set_class_identifier(
1021-
to_struct_expr(*initial_object), ns, struct_tag_typet(qualified_clsid));
1022-
1023-
// If the initialised type is a special-cased String type (one with length
1024-
// and data fields introduced by string-library preprocessing), initialise
1025-
// those fields with nondet values:
1026-
skip_special_string_fields = initialize_nondet_string_fields(
1027-
to_struct_expr(*initial_object),
1028-
assignments,
1029-
object_factory_parameters.min_nondet_string_length,
1030-
object_factory_parameters.max_nondet_string_length,
1031-
loc,
1032-
object_factory_parameters.function_id,
1033-
symbol_table,
1034-
object_factory_parameters.string_printable);
1036+
const bool is_char_sequence =
1037+
java_string_library_preprocesst
1038+
::implements_java_char_sequence(struct_type);
1039+
const bool has_length_and_data =
1040+
struct_type.has_component("length") && struct_type.has_component("data");
1041+
const bool has_string_input_values =
1042+
!object_factory_parameters.string_input_values.empty();
1043+
1044+
if(is_char_sequence && has_length_and_data && has_string_input_values)
1045+
{ // We're dealing with a string and we should set fixed input values.
1046+
skip_special_string_fields = true;
1047+
1048+
// We create a switch statement where each case is an assignment
1049+
// of one of the fixed input strings to the input variable in question
1050+
1051+
const alternate_casest cases =
1052+
get_string_input_values_code(
1053+
expr,
1054+
object_factory_parameters.string_input_values,
1055+
symbol_table);
1056+
assignments.add(generate_nondet_switch(
1057+
id2string(object_factory_parameters.function_id),
1058+
cases,
1059+
java_int_type(),
1060+
ID_java,
1061+
loc,
1062+
symbol_table));
1063+
}
1064+
else
1065+
{
1066+
// Add an initial all-zero write. Most of the fields of this will be
1067+
// overwritten, but it helps to have a whole-structure write that analysis
1068+
// passes can easily recognise leaves no uninitialised state behind.
1069+
1070+
// This code mirrors the `remove_java_new` pass:
1071+
auto initial_object =
1072+
zero_initializer(struct_type, source_locationt(), ns);
1073+
CHECK_RETURN(initial_object.has_value());
1074+
const irep_idt qualified_clsid = "java::" + id2string(class_identifier);
1075+
set_class_identifier(
1076+
to_struct_expr(*initial_object), ns, struct_tag_typet(qualified_clsid));
1077+
1078+
// If the initialised type is a special-cased String type (one with length
1079+
// and data fields introduced by string-library preprocessing), initialise
1080+
// those fields with nondet values
1081+
if(is_char_sequence && has_length_and_data)
1082+
{ // We're dealing with a string
1083+
skip_special_string_fields = true;
1084+
initialize_nondet_string_fields(
1085+
to_struct_expr(*initial_object),
1086+
assignments,
1087+
object_factory_parameters.min_nondet_string_length,
1088+
object_factory_parameters.max_nondet_string_length,
1089+
loc,
1090+
object_factory_parameters.function_id,
1091+
symbol_table,
1092+
object_factory_parameters.string_printable);
1093+
}
10351094

1036-
assignments.add(code_assignt(expr, *initial_object));
1095+
assignments.add(code_assignt(expr, *initial_object));
1096+
}
10371097
}
10381098

10391099
for(const auto &component : components)
@@ -1061,8 +1121,7 @@ void java_object_factoryt::gen_nondet_struct_init(
10611121
}
10621122
else if(skip_special_string_fields && (name == "length" || name == "data"))
10631123
{
1064-
// In this case these were set up by initialise_nondet_string_fields
1065-
// above.
1124+
// In this case these were set up above.
10661125
continue;
10671126
}
10681127
else

jbmc/src/jbmc/jbmc_parse_options.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,25 @@ void jbmc_parse_optionst::get_command_line_options(optionst &options)
250250
options.set_option("refine-strings", false);
251251

252252
if(cmdline.isset("string-printable"))
253+
{
254+
if(cmdline.isset("no-refine-strings"))
255+
{
256+
warning() << "--string-printable ignored due to --no-refine-strings"
257+
<< eom;
258+
}
253259
options.set_option("string-printable", true);
260+
}
254261

255-
if(cmdline.isset("no-refine-strings") && cmdline.isset("string-printable"))
262+
if(cmdline.isset("string-input-value"))
256263
{
257-
warning() << "--string-printable ignored due to --no-refine-strings" << eom;
264+
if(cmdline.isset("no-refine-strings"))
265+
{
266+
warning() << "--string-input-value ignored due to --no-refine-strings"
267+
<< eom;
268+
}
269+
options.set_option(
270+
"string-input-value",
271+
cmdline.get_values("string-input-value"));
258272
}
259273

260274
if(

src/solvers/refinement/string_refinement.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,17 @@ Author: Alberto Griggio, alberto.griggio@gmail.com
3333
#define OPT_STRING_REFINEMENT \
3434
"(no-refine-strings)" \
3535
"(string-printable)" \
36+
"(string-input-value):" \
3637
"(string-non-empty)" \
3738
"(max-nondet-string-length):"
3839

3940
#define HELP_STRING_REFINEMENT \
4041
" --no-refine-strings turn off string refinement\n" \
4142
" --string-printable restrict to printable strings (experimental)\n" /* NOLINT(*) */ \
4243
" --string-non-empty restrict to non-empty strings (experimental)\n" /* NOLINT(*) */ \
44+
" --string-printable st restrict non-null strings to a fixed value st;\n" /* NOLINT(*) */ \
45+
" the switch can be used multiple times to give\n" /* NOLINT(*) */ \
46+
" several strings\n" /* NOLINT(*) */ \
4347
" --max-nondet-string-length n bound the length of nondet (e.g. input) strings\n" /* NOLINT(*) */
4448

4549
// The integration of the string solver into CBMC is incomplete. Therefore,

src/util/object_factory_parameters.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ void object_factory_parameterst::set(const optionst &options)
3737
{
3838
string_printable = options.get_bool_option("string-printable");
3939
}
40+
if(options.is_set("string-input-value"))
41+
{
42+
string_input_values = options.get_list_option("string-input-value");
43+
}
4044
if(options.is_set("min-nondet-string-length"))
4145
{
4246
min_nondet_string_length =

src/util/object_factory_parameters.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Author: Daniel Kroening, kroening@kroening.com
1111

1212
#include <cstdint>
1313
#include <limits>
14+
#include <list>
1415

1516
#include <util/irep.h>
1617

@@ -69,6 +70,9 @@ struct object_factory_parameterst
6970
/// Force string content to be ASCII printable characters when set to true.
7071
bool string_printable = false;
7172

73+
/// Force one of finitely many explicitly given input strings
74+
std::list<std::string> string_input_values;
75+
7276
/// Function id, used as a prefix for identifiers of temporaries
7377
irep_idt function_id;
7478

0 commit comments

Comments
 (0)