Skip to content

Commit 7b5fb34

Browse files
committed
Fixed universal constraints that break invariants.
In preparation for checking the invariant of universal constraints, all violations have been fixed. Additionally, an `indexOf` axiom had an off-by-one error and the contains regression test was missing a `--string-max-length`.
1 parent 727f316 commit 7b5fb34

File tree

6 files changed

+83
-192
lines changed

6 files changed

+83
-192
lines changed
Binary file not shown.

regression/strings-smoke-tests/java_contains/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
test_contains.class
3-
--refine-strings
3+
--refine-strings --string-max-length 100
44
^EXIT=10$
55
^SIGNAL=0$
66
^\[.*assertion.1\].* line 8.* SUCCESS$

src/solvers/refinement/string_constraint_generator_indexof.cpp

Lines changed: 48 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Author: Romain Brenguier, [email protected]
1515
#include <solvers/refinement/string_constraint_generator.h>
1616

1717
/// Add axioms stating that the returned value is the index within str of the
18-
/// first occurence of c starting the search at from_index, or -1 if no such
18+
/// first occurrence of c starting the search at from_index, or -1 if no such
1919
/// character occurs at or after position from_index.
2020
/// \param str: a string expression
2121
/// \param c: an expression representing a character
@@ -69,7 +69,7 @@ exprt string_constraint_generatort::add_axioms_for_index_of(
6969
}
7070

7171
/// Add axioms stating that the returned value is the index within haystack of
72-
/// the first occurence of needle starting the search at from_index, or -1 if
72+
/// the first occurrence of needle starting the search at from_index, or -1 if
7373
/// needle does not occur at or after position from_index.
7474
/// \param haystack: a string expression
7575
/// \param needle: a string expression
@@ -92,7 +92,7 @@ exprt string_constraint_generatort::add_axioms_for_index_of_string(
9292
// contains ==> haystack[n+offset]=needle[n]
9393
// a4 : forall n:[from_index,offset[.
9494
// contains ==> (exists m:[0,|needle|[. haystack[m+n] != needle[m]])
95-
// a5: forall n:[from_index,|haystack|-|needle|[.
95+
// a5: forall n:[from_index,|haystack|-|needle|].
9696
// !contains ==> (exists m:[0,|needle|[. haystack[m+n] != needle[m])
9797

9898
implies_exprt a1(
@@ -116,70 +116,35 @@ exprt string_constraint_generatort::add_axioms_for_index_of_string(
116116
equal_exprt(haystack[plus_exprt(qvar, offset)], needle[qvar]));
117117
axioms.push_back(a3);
118118

119-
if(!is_constant_string(needle))
120-
{
121-
// string_not contains_constraintt are formulas of the form:
122-
// forall x in [lb,ub[. p(x) => exists y in [lb,ub[. s1[x+y] != s2[y]
123-
string_not_contains_constraintt a4(
124-
from_index,
125-
offset,
126-
contains,
127-
from_integer(0, index_type),
128-
needle.length(),
129-
haystack,
130-
needle);
131-
axioms.push_back(a4);
132-
133-
string_not_contains_constraintt a5(
134-
from_index,
119+
// string_not contains_constraintt are formulas of the form:
120+
// forall x in [lb,ub[. p(x) => exists y in [lb,ub[. s1[x+y] != s2[y]
121+
string_not_contains_constraintt a4(
122+
from_index,
123+
offset,
124+
contains,
125+
from_integer(0, index_type),
126+
needle.length(),
127+
haystack,
128+
needle);
129+
axioms.push_back(a4);
130+
131+
string_not_contains_constraintt a5(
132+
from_index,
133+
plus_exprt( // Add 1 for inclusive upper bound.
135134
minus_exprt(haystack.length(), needle.length()),
136-
not_exprt(contains),
137-
from_integer(0, index_type),
138-
needle.length(),
139-
haystack,
140-
needle);
141-
axioms.push_back(a5);
142-
}
143-
else
144-
{
145-
// Unfold the existential quantifier as a disjunction in case of a constant
146-
// a4 && a5 <=> a6:
147-
// forall n:[from_index,|haystack|-|needle|].
148-
// !contains || n < offset ==>
149-
// haystack[n] != needle[0] || ... ||
150-
// haystack[n+|needle|-1] != needle[|needle|-1]
151-
symbol_exprt qvar2=fresh_univ_index("QA_index_of_string_2", index_type);
152-
mp_integer sub_length;
153-
INVARIANT(
154-
!to_integer(needle.length(), sub_length),
155-
string_refinement_invariantt("a constant string must have constant "
156-
"length"));
157-
exprt::operandst disjuncts;
158-
for(mp_integer offset=0; offset<sub_length; ++offset)
159-
{
160-
exprt expr_offset=from_integer(offset, index_type);
161-
plus_exprt shifted(expr_offset, qvar2);
162-
disjuncts.push_back(
163-
not_exprt(equal_exprt(haystack[shifted], needle[expr_offset])));
164-
}
165-
166-
or_exprt premise(
167-
not_exprt(contains), binary_relation_exprt(qvar2, ID_lt, offset));
168-
minus_exprt length_diff(haystack.length(), needle.length());
169-
string_constraintt a6(
170-
qvar2,
171-
from_index,
172-
plus_exprt(from_integer(1, index_type), length_diff),
173-
premise,
174-
disjunction(disjuncts));
175-
axioms.push_back(a6);
176-
}
135+
from_integer(1, index_type)),
136+
not_exprt(contains),
137+
from_integer(0, index_type),
138+
needle.length(),
139+
haystack,
140+
needle);
141+
axioms.push_back(a5);
177142

178143
return offset;
179144
}
180145

181146
/// Add axioms stating that the returned value is the index within haystack of
182-
/// the last occurence of needle starting the search backward at from_index (ie
147+
/// the last occurrence of needle starting the search backward at from_index (ie
183148
/// the index is smaller or equal to from_index), or -1 if needle does not occur
184149
/// before from_index.
185150
/// \param haystack: a string expression
@@ -235,62 +200,25 @@ exprt string_constraint_generatort::add_axioms_for_last_index_of_string(
235200
from_index,
236201
length_diff);
237202

238-
if(!is_constant_string(needle))
239-
{
240-
string_not_contains_constraintt a4(
241-
plus_exprt(offset, from_integer(1, index_type)),
242-
plus_exprt(end_index, from_integer(1, index_type)),
243-
contains,
244-
from_integer(0, index_type),
245-
needle.length(),
246-
haystack,
247-
needle);
248-
axioms.push_back(a4);
249-
250-
string_not_contains_constraintt a5(
251-
from_integer(0, index_type),
252-
plus_exprt(end_index, from_integer(1, index_type)),
253-
not_exprt(contains),
254-
from_integer(0, index_type),
255-
needle.length(),
256-
haystack,
257-
needle);
258-
axioms.push_back(a5);
259-
}
260-
else
261-
{
262-
// Unfold the existential quantifier as a disjunction in case of a constant
263-
// a4 && a5 <=> a6:
264-
// forall n:[0, min(from_index, |haystack| - |needle|)].
265-
// !contains || (n > offset) ==>
266-
// haystack[n] != needle[0] || ... ||
267-
// haystack[n+|needle|-1] != needle[|needle|-1]
268-
symbol_exprt qvar2=fresh_univ_index("QA_index_of_string_2", index_type);
269-
mp_integer sub_length;
270-
INVARIANT(
271-
!to_integer(needle.length(), sub_length),
272-
string_refinement_invariantt("a constant string must have constant "
273-
"length"));
274-
exprt::operandst disjuncts;
275-
for(mp_integer offset=0; offset<sub_length; ++offset)
276-
{
277-
exprt expr_offset=from_integer(offset, index_type);
278-
plus_exprt shifted(expr_offset, qvar2);
279-
disjuncts.push_back(
280-
not_exprt(equal_exprt(haystack[shifted], needle[expr_offset])));
281-
}
282-
283-
or_exprt premise(
284-
not_exprt(contains), binary_relation_exprt(qvar2, ID_gt, offset));
285-
286-
string_constraintt a6(
287-
qvar2,
288-
from_integer(0, index_type),
289-
plus_exprt(from_integer(1, index_type), end_index),
290-
premise,
291-
disjunction(disjuncts));
292-
axioms.push_back(a6);
293-
}
203+
string_not_contains_constraintt a4(
204+
plus_exprt(offset, from_integer(1, index_type)),
205+
plus_exprt(end_index, from_integer(1, index_type)),
206+
contains,
207+
from_integer(0, index_type),
208+
needle.length(),
209+
haystack,
210+
needle);
211+
axioms.push_back(a4);
212+
213+
string_not_contains_constraintt a5(
214+
from_integer(0, index_type),
215+
plus_exprt(end_index, from_integer(1, index_type)),
216+
not_exprt(contains),
217+
from_integer(0, index_type),
218+
needle.length(),
219+
haystack,
220+
needle);
221+
axioms.push_back(a5);
294222

295223
return offset;
296224
}
@@ -333,7 +261,7 @@ exprt string_constraint_generatort::add_axioms_for_index_of(
333261
}
334262

335263
/// Add axioms stating that the returned value is the index within str of the
336-
/// last occurence of c starting the search backward at from_index, or -1 if no
264+
/// last occurrence of c starting the search backward at from_index, or -1 if no
337265
/// such character occurs at or before position from_index.
338266
/// \param str: a string expression
339267
/// \param c: an expression representing a character
@@ -373,7 +301,7 @@ exprt string_constraint_generatort::add_axioms_for_last_index_of(
373301
equal_exprt(str[index], c)));
374302
axioms.push_back(a3);
375303

376-
symbol_exprt n=fresh_univ_index("QA_last_index_of", index_type);
304+
symbol_exprt n=fresh_univ_index("QA_last_index_of1", index_type);
377305
string_constraintt a4(
378306
n,
379307
plus_exprt(index, index1),
@@ -382,7 +310,7 @@ exprt string_constraint_generatort::add_axioms_for_last_index_of(
382310
not_exprt(equal_exprt(str[n], c)));
383311
axioms.push_back(a4);
384312

385-
symbol_exprt m=fresh_univ_index("QA_last_index_of", index_type);
313+
symbol_exprt m=fresh_univ_index("QA_last_index_of2", index_type);
386314
string_constraintt a5(
387315
m,
388316
from_index_plus_one,

src/solvers/refinement/string_constraint_generator_main.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ string_exprt string_constraint_generatort::convert_java_string_to_string_exprt(
164164
{
165165
java_content=dereference_exprt(java_content, java_content.type());
166166
}
167-
168167
refined_string_typet type(java_int_type(), java_char_type());
169168

170169
return string_exprt(length, java_content, type);

src/solvers/refinement/string_constraint_generator_testing.cpp

Lines changed: 19 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ exprt string_constraint_generatort::add_axioms_for_contains(
187187
PRECONDITION(f.type()==bool_typet() || f.type().id()==ID_c_bool);
188188
string_exprt s0=get_string_expr(args(f, 2)[0]);
189189
string_exprt s1=get_string_expr(args(f, 2)[1]);
190-
bool constant=is_constant_string(s1);
191190

192191
symbol_exprt contains=fresh_boolean("contains");
193192
const refined_string_typet ref_type=to_refined_string_type(s0.type());
@@ -218,66 +217,24 @@ exprt string_constraint_generatort::add_axioms_for_contains(
218217
equal_exprt(startpos, from_integer(-1, index_type)));
219218
axioms.push_back(a3);
220219

221-
if(constant)
222-
{
223-
// If the string is constant, we can use a more efficient axiom for a4:
224-
// contains ==> AND_{i < |s1|} s1[i] = s0[startpos + i]
225-
mp_integer s1_length;
226-
INVARIANT(
227-
!to_integer(s1.length(), s1_length),
228-
string_refinement_invariantt("a constant string expression must have a "
229-
"constant length"));
230-
exprt::operandst conjuncts;
231-
for(mp_integer i=0; i<s1_length; ++i)
232-
{
233-
exprt expr_i=from_integer(i, index_type);
234-
plus_exprt shifted_i(expr_i, startpos);
235-
conjuncts.push_back(equal_exprt(s1[expr_i], s0[shifted_i]));
236-
}
237-
implies_exprt a4(contains, conjunction(conjuncts));
238-
axioms.push_back(a4);
239-
240-
// The a5 constraint for constant strings translates to:
241-
// !contains ==> |s1| > |s0| ||
242-
// (forall qvar <= |s0| - |s1|.
243-
// !(AND_{i < |s1|} s1[i] == s0[i + qvar])
244-
//
245-
// which we implement as:
246-
// forall qvar <= |s0| - |s1|. (!contains && |s0| >= |s1|)
247-
// ==> !(AND_{i < |s1|} (s1[i] == s0[qvar+i]))
248-
symbol_exprt qvar=fresh_univ_index("QA_contains_constant", index_type);
249-
exprt::operandst conjuncts1;
250-
for(mp_integer i=0; i<s1_length; ++i)
251-
{
252-
exprt expr_i=from_integer(i, index_type);
253-
plus_exprt shifted_i(expr_i, qvar);
254-
conjuncts1.push_back(equal_exprt(s1[expr_i], s0[shifted_i]));
255-
}
256-
257-
string_constraintt a5(
258-
qvar,
259-
plus_exprt(from_integer(1, index_type), length_diff),
260-
and_exprt(not_exprt(contains), s0.axiom_for_is_longer_than(s1)),
261-
not_exprt(conjunction(conjuncts1)));
262-
axioms.push_back(a5);
263-
}
264-
else
265-
{
266-
symbol_exprt qvar=fresh_univ_index("QA_contains", index_type);
267-
exprt qvar_shifted=plus_exprt(qvar, startpos);
268-
string_constraintt a4(
269-
qvar, s1.length(), contains, equal_exprt(s1[qvar], s0[qvar_shifted]));
270-
axioms.push_back(a4);
271-
272-
// We rewrite axiom a4 as:
273-
// forall startpos <= |s0|-|s1|. (!contains && |s0| >= |s1|)
274-
// ==> exists witness < |s1|. s1[witness] != s0[startpos+witness]
275-
string_not_contains_constraintt a5(
276-
from_integer(0, index_type),
277-
plus_exprt(from_integer(1, index_type), length_diff),
278-
and_exprt(not_exprt(contains), s0.axiom_for_is_longer_than(s1)),
279-
from_integer(0, index_type), s1.length(), s0, s1);
280-
axioms.push_back(a5);
281-
}
220+
symbol_exprt qvar=fresh_univ_index("QA_contains", index_type);
221+
exprt qvar_shifted=plus_exprt(qvar, startpos);
222+
string_constraintt a4(
223+
qvar, s1.length(), contains, equal_exprt(s1[qvar], s0[qvar_shifted]));
224+
axioms.push_back(a4);
225+
226+
// We rewrite axiom a4 as:
227+
// forall startpos <= |s0|-|s1|. (!contains && |s0| >= |s1|)
228+
// ==> exists witness < |s1|. s1[witness] != s0[startpos+witness]
229+
string_not_contains_constraintt a5(
230+
from_integer(0, index_type),
231+
plus_exprt(from_integer(1, index_type), length_diff),
232+
and_exprt(not_exprt(contains), s0.axiom_for_is_longer_than(s1)),
233+
from_integer(0, index_type),
234+
s1.length(),
235+
s0,
236+
s1);
237+
axioms.push_back(a5);
238+
282239
return typecast_exprt(contains, f.type());
283240
}

src/solvers/refinement/string_constraint_generator_transformation.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,6 @@ string_exprt string_constraint_generatort::add_axioms_for_to_upper_case(
287287
exprt char_a=constant_char('a', char_type);
288288
exprt char_A=constant_char('A', char_type);
289289
exprt char_z=constant_char('z', char_type);
290-
exprt char_Z=constant_char('Z', char_type);
291290

292291
// TODO: add support for locales using case mapping information
293292
// from the UnicodeData file.
@@ -296,21 +295,29 @@ string_exprt string_constraint_generatort::add_axioms_for_to_upper_case(
296295
// a1 : |res| = |str|
297296
// a2 : forall idx<str.length, 'a'<=str[idx]<='z' => res[idx]=str[idx]+'A'-'a'
298297
// a3 : forall idx<str.length, !('a'<=str[idx]<='z') => res[idx]=str[idx]
298+
// Note that index expressions are only allowed in the body of universal
299+
// axioms, so we use a trivial premise and push our premise into the body.
299300

300301
exprt a1=res.axiom_for_has_same_length_as(str);
301302
axioms.push_back(a1);
302303

303-
symbol_exprt idx=fresh_univ_index("QA_upper_case", index_type);
304+
symbol_exprt idx1=fresh_univ_index("QA_upper_case1", index_type);
304305
exprt is_lower_case=and_exprt(
305-
binary_relation_exprt(char_a, ID_le, str[idx]),
306-
binary_relation_exprt(str[idx], ID_le, char_z));
306+
binary_relation_exprt(char_a, ID_le, str[idx1]),
307+
binary_relation_exprt(str[idx1], ID_le, char_z));
307308
minus_exprt diff(char_A, char_a);
308-
equal_exprt convert(res[idx], plus_exprt(str[idx], diff));
309-
string_constraintt a2(idx, res.length(), is_lower_case, convert);
309+
equal_exprt convert(res[idx1], plus_exprt(str[idx1], diff));
310+
implies_exprt body1(is_lower_case, convert);
311+
string_constraintt a2(idx1, res.length(), body1);
310312
axioms.push_back(a2);
311313

312-
equal_exprt eq(res[idx], str[idx]);
313-
string_constraintt a3(idx, res.length(), not_exprt(is_lower_case), eq);
314+
symbol_exprt idx2=fresh_univ_index("QA_upper_case2", index_type);
315+
exprt is_not_lower_case=not_exprt(and_exprt(
316+
binary_relation_exprt(char_a, ID_le, str[idx2]),
317+
binary_relation_exprt(str[idx2], ID_le, char_z)));
318+
equal_exprt eq(res[idx2], str[idx2]);
319+
implies_exprt body2(is_not_lower_case, eq);
320+
string_constraintt a3(idx2, res.length(), body2);
314321
axioms.push_back(a3);
315322
return res;
316323
}

0 commit comments

Comments
 (0)