Skip to content

Commit bcbb6fa

Browse files
authored
Merge pull request #1162 from jasigal/fix/invalid-string-constraint-transformations#779
Fixed invalid transformations on string constraints and added invariant checking.
2 parents f798fef + c306d56 commit bcbb6fa

10 files changed

+346
-248
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/doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1473,7 +1473,7 @@ FORMULA_TRANSPARENT = YES
14731473
# The default value is: NO.
14741474
# This tag requires that the tag GENERATE_HTML is set to YES.
14751475

1476-
USE_MATHJAX = NO
1476+
USE_MATHJAX = YES
14771477

14781478
# When MathJax is enabled you can set the default output format to be used for
14791479
# the MathJax output. See the MathJax site (see:

src/solvers/refinement/string_constraint.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,30 @@ Author: Romain Brenguier, [email protected]
2424
#include <solvers/refinement/bv_refinement.h>
2525
#include <solvers/refinement/string_refinement_invariant.h>
2626
#include <util/refined_string_type.h>
27+
#include <langapi/language_util.h>
28+
29+
/*! \brief Universally quantified string constraint
30+
31+
This represents a universally quantified string constraint as laid out in
32+
DOI: 10.1007/978-3-319-03077-7. The paper seems to specify a universal
33+
constraint as follows.
34+
35+
A universal constraint is of the form \f$\forall n. L(n) \rightarrow
36+
P(n, s_0,\ldots, s_k)\f$ where
37+
38+
1. \f$L(n)\f$ does not contain string indices [possibly not required, but
39+
implied by examples]
40+
2. \f$\forall n. L(n) \rightarrow P'\left(s_0[f_0(n)],\ldots, s_k[f_k(n)]
41+
\right)\f$, i.e. when focusing on one specific string, all indices are
42+
the same [stated in a roundabout manner]
43+
3. Each \f$f\f$ is linear and therefore has an inverse [explicitly stated]
44+
4. \f$L(n)\f$ and \f$P(n, s_0,\ldots, s_k)\f$ may contain other (free)
45+
variables, but in \f$P\f$, \f$n\f$ can only occur as an argument to an
46+
\f$f\f$ [explicitly stated, implied]
47+
48+
We extend this slightly by restricting n to be in a specific range, but this
49+
is another implication which can be pushed in to \f$L(n)\f$.
50+
*/
2751

2852
class string_constraintt: public exprt
2953
{
@@ -114,6 +138,15 @@ extern inline string_constraintt &to_string_constraint(exprt &expr)
114138
return static_cast<string_constraintt &>(expr);
115139
}
116140

141+
inline static std::string from_expr(
142+
const namespacet &ns,
143+
const irep_idt &identifier,
144+
const string_constraintt &expr)
145+
{
146+
return from_expr(ns, identifier, expr.premise())+" => "+
147+
from_expr(ns, identifier, expr.body());
148+
}
149+
117150
class string_not_contains_constraintt: public exprt
118151
{
119152
public:

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
}

0 commit comments

Comments
 (0)