-
Notifications
You must be signed in to change notification settings - Fork 274
String solver: stop relying on array size #4626
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
78d92f8
eb44428
504d6ea
82e6d68
fddf30a
eab5e53
91598f6
07f622c
fb8c1d1
e17c2e7
1645ffa
b630e78
1f869ef
2e6c4f1
d87ea4e
cb0cee4
7ff979e
3a6a8e9
981f2d2
ab66476
0b72c16
4dbadb0
f6b324f
5035425
1db3059
58bf455
b1c9eb1
536253b
7678ef1
cd61e81
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
CORE | ||
test.class | ||
--function test.main --trace | ||
^EXIT=10$ | ||
^SIGNAL=0$ | ||
^\[.*assertion.1\].* line 6.* FAILURE$ | ||
abc_constarray=\{ 'a', 'b', 'c' \} | ||
-- | ||
-- | ||
Check that the string literal used in the constructor is properly concretized | ||
in the trace even if it is not part of the equation. | ||
This is necessary for test generation. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
public class test | ||
{ | ||
public static void main() | ||
{ | ||
String x = new String("abc"); | ||
assert false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,25 +21,48 @@ operator()(const irep_idt &prefix, const typet &type) | |
return result; | ||
} | ||
|
||
exprt array_poolt::get_length(const array_string_exprt &s) const | ||
exprt array_poolt::get_or_create_length(const array_string_exprt &s) | ||
{ | ||
if(s.length() == infinity_exprt(s.length().type())) | ||
if(const auto &if_expr = expr_try_dynamic_cast<if_exprt>((exprt)s)) | ||
{ | ||
auto it = length_of_array.find(s); | ||
if(it != length_of_array.end()) | ||
return it->second; | ||
return if_exprt{ | ||
if_expr->cond(), | ||
get_or_create_length(to_array_string_expr(if_expr->true_case())), | ||
get_or_create_length(to_array_string_expr(if_expr->false_case()))}; | ||
} | ||
return s.length(); | ||
|
||
auto emplace_result = | ||
length_of_array.emplace(s, symbol_exprt(s.length_type())); | ||
if(emplace_result.second) | ||
{ | ||
emplace_result.first->second = | ||
fresh_symbol("string_length", s.length_type()); | ||
} | ||
|
||
return emplace_result.first->second; | ||
} | ||
|
||
optionalt<exprt> | ||
array_poolt::get_length_if_exists(const array_string_exprt &s) const | ||
{ | ||
auto find_result = length_of_array.find(s); | ||
if(find_result != length_of_array.end()) | ||
return find_result->second; | ||
return {}; | ||
} | ||
|
||
array_string_exprt | ||
array_poolt::fresh_string(const typet &index_type, const typet &char_type) | ||
{ | ||
symbol_exprt length = fresh_symbol("string_length", index_type); | ||
array_typet array_type(char_type, length); | ||
array_typet array_type{char_type, infinity_exprt(index_type)}; | ||
symbol_exprt content = fresh_symbol("string_content", array_type); | ||
array_string_exprt str = to_array_string_expr(content); | ||
created_strings_value.insert(str); | ||
arrays_of_pointers.emplace( | ||
address_of_exprt(index_exprt(str, from_integer(0, index_type))), str); | ||
|
||
// add length to length_of_array map | ||
get_or_create_length(str); | ||
|
||
return str; | ||
} | ||
|
||
|
@@ -87,40 +110,73 @@ array_string_exprt array_poolt::make_char_array_for_char_pointer( | |
to_array_string_expr(fresh_symbol(symbol_name, char_array_type)); | ||
const auto insert_result = | ||
arrays_of_pointers.insert({char_pointer, array_sym}); | ||
|
||
// add length to length_of_array map | ||
get_or_create_length(array_sym); | ||
|
||
return to_array_string_expr(insert_result.first->second); | ||
} | ||
|
||
/// Given an array_string_exprt, get the size of the underlying array. If that | ||
/// size is undefined, create a new symbol for the size. | ||
/// Then add an entry from `array_expr` to that size in the `length_of_array` | ||
/// map. | ||
/// | ||
/// This function should only be used at the creation of the | ||
/// `array_string_exprt`s, as it is the only place where we can reliably refer | ||
/// to the size in the type of the array. | ||
static void attempt_assign_length_from_type( | ||
const array_string_exprt &array_expr, | ||
std::unordered_map<array_string_exprt, exprt, irep_hash> &length_of_array, | ||
symbol_generatort &symbol_generator) | ||
{ | ||
DATA_INVARIANT( | ||
array_expr.id() != ID_if, | ||
"attempt_assign_length_from_type does not handle if exprts"); | ||
// This invariant seems always true, but I don't know why. | ||
// If we find a case where this is violated, try calling | ||
// attempt_assign_length_from_type on the true and false cases. | ||
const exprt &size_from_type = to_array_type(array_expr.type()).size(); | ||
const exprt &size_to_assign = | ||
size_from_type != infinity_exprt(size_from_type.type()) | ||
? size_from_type | ||
: symbol_generator("string_length", array_expr.length_type()); | ||
|
||
const auto emplace_result = | ||
length_of_array.emplace(array_expr, size_to_assign); | ||
INVARIANT( | ||
emplace_result.second, | ||
"attempt_assign_length_from_type should only be called when no entry" | ||
"for the array_string_exprt exists in the length_of_array map"); | ||
} | ||
|
||
void array_poolt::insert( | ||
const exprt &pointer_expr, | ||
array_string_exprt &array_expr) | ||
const array_string_exprt &array_expr) | ||
{ | ||
const exprt &length = array_expr.length(); | ||
if(length == infinity_exprt(length.type())) | ||
{ | ||
auto pair = length_of_array.insert( | ||
std::make_pair(array_expr, fresh_symbol("string_length", length.type()))); | ||
array_expr.length() = pair.first->second; | ||
} | ||
|
||
const auto it_bool = | ||
arrays_of_pointers.insert(std::make_pair(pointer_expr, array_expr)); | ||
created_strings_value.insert(array_expr); | ||
|
||
INVARIANT( | ||
it_bool.second, "should not associate two arrays to the same pointer"); | ||
|
||
attempt_assign_length_from_type(array_expr, length_of_array, fresh_symbol); | ||
} | ||
|
||
const std::set<array_string_exprt> &array_poolt::created_strings() const | ||
/// Return a map mapping all array_string_exprt of the array_pool to their | ||
/// length. | ||
const std::unordered_map<array_string_exprt, exprt, irep_hash> & | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 exposing the whole map here seems unnecessary since we are only using the first component. An alternative would be to define a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll leave it like this for now so that the PR doesn't blow up. |
||
array_poolt::created_strings() const | ||
{ | ||
return created_strings_value; | ||
return length_of_array; | ||
} | ||
|
||
const array_string_exprt & | ||
array_poolt::find(const exprt &pointer, const exprt &length) | ||
array_string_exprt array_poolt::find(const exprt &pointer, const exprt &length) | ||
{ | ||
const array_typet array_type(pointer.type().subtype(), length); | ||
const array_string_exprt array = | ||
make_char_array_for_char_pointer(pointer, array_type); | ||
return *created_strings_value.insert(array).first; | ||
return array; | ||
} | ||
|
||
array_string_exprt of_argument(array_poolt &array_pool, const exprt &arg) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,17 +53,31 @@ class array_poolt final | |
return arrays_of_pointers; | ||
} | ||
|
||
/// Associate an actual finite length to infinite arrays | ||
/// Get the length of an array_string_exprt from the array_pool. If the length | ||
/// does not yet exist, create a new symbol and add it to the pool. | ||
/// | ||
/// If the array_string_exprt is an `if` expression, the true and false parts | ||
/// are handled independently (and recursively). That is, no new length symbol | ||
/// is added to the pool for `if` expressions, but symbols may be added for | ||
/// each of the parts. | ||
/// \param s: array expression representing a string | ||
/// \return expression for the length of `s` | ||
exprt get_length(const array_string_exprt &s) const; | ||
exprt get_or_create_length(const array_string_exprt &s); | ||
|
||
void insert(const exprt &pointer_expr, array_string_exprt &array); | ||
/// As opposed to get_length(), do not create a new symbol if the length | ||
/// of the array_string_exprt does not have one in the array_pool, but instead | ||
/// return an empty optional. | ||
/// \param s: array expression representing a string | ||
/// \return expression for the length of `s`, or empty optional | ||
optionalt<exprt> get_length_if_exists(const array_string_exprt &s) const; | ||
|
||
void insert(const exprt &pointer_expr, const array_string_exprt &array); | ||
|
||
/// Creates a new array if the pointer is not pointing to an array | ||
const array_string_exprt &find(const exprt &pointer, const exprt &length); | ||
array_string_exprt find(const exprt &pointer, const exprt &length); | ||
|
||
const std::set<array_string_exprt> &created_strings() const; | ||
const std::unordered_map<array_string_exprt, exprt, irep_hash> & | ||
created_strings() const; | ||
|
||
/// Construct a string expression whose length and content are new variables | ||
/// \param index_type: type used to index characters of the strings | ||
|
@@ -74,18 +88,19 @@ class array_poolt final | |
|
||
private: | ||
/// Associate arrays to char pointers | ||
/// Invariant: Each array_string_exprt in this map has a corresponding entry | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where is this enforced? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Improved documentation. Now states:
|
||
/// in length_of_array. | ||
/// This is enforced whenever we add an element to | ||
/// `arrays_of_pointers`, i.e. fresh_string() | ||
/// and make_char_array_for_char_pointer(). | ||
std::unordered_map<exprt, array_string_exprt, irep_hash> arrays_of_pointers; | ||
|
||
/// Associate length to arrays of infinite size | ||
std::unordered_map<array_string_exprt, symbol_exprt, irep_hash> | ||
length_of_array; | ||
std::unordered_map<array_string_exprt, exprt, irep_hash> length_of_array; | ||
|
||
/// Generate fresh symbols | ||
symbol_generatort &fresh_symbol; | ||
|
||
/// Strings created in the pool | ||
std::set<array_string_exprt> created_strings_value; | ||
|
||
array_string_exprt make_char_array_for_char_pointer( | ||
const exprt &char_pointer, | ||
const typet &char_array_type); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this allocates a symbol even if
s
already has a length, I'd suggest inserting a dummy and then replacing it with afresh_symbol
if appropriateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in fixup commit