Skip to content

Commit 34b2b02

Browse files
Merge pull request diffblue#1939 from romainbrenguier/bugfix/builtin-string-insert-eval
Fix StringBuilder.insert and String.substring [TG-2459]
2 parents 9d9fafa + 84186ff commit 34b2b02

16 files changed

+259
-76
lines changed
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
2+
public class Test {
3+
public void check (int i) {
4+
if(i == 0)
5+
{
6+
// Arrange
7+
StringBuilder s = new StringBuilder("bar");
8+
9+
// Act
10+
s = org.cprover.CProverString.insert(s, 0, "foo");
11+
12+
// Should succeed
13+
assert s.toString().equals("foobar");
14+
15+
// Should fail
16+
assert !s.toString().equals("foobar");
17+
}
18+
if(i == 1)
19+
{
20+
// Arrange
21+
StringBuilder s = new StringBuilder("bar");
22+
23+
// Act
24+
s = org.cprover.CProverString.insert(s, -10, "foo");
25+
26+
// Should succeed
27+
assert s.toString().equals("foobar");
28+
29+
// Should fail
30+
assert !s.toString().equals("foobar");
31+
}
32+
if(i == 2)
33+
{
34+
// Arrange
35+
StringBuilder s = new StringBuilder("bar");
36+
37+
// Act
38+
s = org.cprover.CProverString.insert(s, 10, "foo");
39+
40+
// Should succeed
41+
assert s.toString().equals("barfoo");
42+
43+
// Should fail
44+
assert !s.toString().equals("barfoo");
45+
}
46+
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
Test.class
3+
--refine-strings --function Test.check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
assertion at file Test.java line 13 .*: SUCCESS
7+
assertion at file Test.java line 16 .*: FAILURE
8+
assertion at file Test.java line 27 .*: SUCCESS
9+
assertion at file Test.java line 30 .*: FAILURE
10+
assertion at file Test.java line 41 .*: SUCCESS
11+
assertion at file Test.java line 44 .*: FAILURE
12+
--
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// This test uses CProverString so should be compiled with
2+
// javac Test.java ../cprover/CProverString.java
3+
import org.cprover.CProverString;
4+
5+
class Test {
6+
public void testSuccess(String s, String t, int start, int end) {
7+
// Filter
8+
if (s == null || t == null)
9+
return;
10+
11+
// Act
12+
StringBuilder sb = new StringBuilder(s);
13+
String result = CProverString.append(sb, t, start, end).toString();
14+
15+
// Arrange
16+
StringBuilder reference = new StringBuilder(s);
17+
if(start < 0)
18+
start = 0;
19+
for (int i = start; i < end && i < t.length(); i++)
20+
reference.append(org.cprover.CProverString.charAt(t, i));
21+
22+
// Assert
23+
assert result.equals(reference.toString());
24+
}
25+
26+
public void testFail(int i) {
27+
// Arrange
28+
StringBuilder sb = new StringBuilder("foo");
29+
30+
// Act
31+
CProverString.append(sb, "bar", 0, 1);
32+
CProverString.append(sb, "bar", 0, 4);
33+
CProverString.append(sb, "foo", -1, 0);
34+
CProverString.append(sb, "foo", -10, -1);
35+
CProverString.append(sb, "bar", -10, 25);
36+
37+
// Assert
38+
assert !sb.toString().equals("foobbarbar");
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
Test.class
3+
--refine-strings --string-max-length 100 --string-max-input-length 4 --verbosity 10 --unwind 5 Test.class --function Test.testSuccess
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
assertion at file Test.java line 23 .*: SUCCESS
7+
--
8+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
Test.class
3+
--refine-strings --string-max-length 10000 --verbosity 10 Test.class --function Test.testFail
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
assertion at file Test.java line 38 .*: FAILURE
7+
--
8+
^warning: ignoring
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// This test uses CProverString so should be compiled with
2+
// javac Test.java ../cprover/CProverString.java
3+
4+
class Test {
5+
public void testSuccess(String s, int start, int end) {
6+
// Filter
7+
if (s == null)
8+
return;
9+
10+
// Act
11+
String sub = org.cprover.CProverString.substring(s, start, end);
12+
13+
// Arrange
14+
StringBuilder reference = new StringBuilder();
15+
if(start < 0)
16+
start = 0;
17+
for (int i = start; i < end && i < s.length(); i++)
18+
reference.append(org.cprover.CProverString.charAt(s, i));
19+
20+
// Assert
21+
assert sub.equals(reference.toString());
22+
}
23+
24+
public void testFail(int i) {
25+
// Arrange
26+
String[] s = new String[5];
27+
28+
// Act
29+
s[0] = org.cprover.CProverString.substring("foo", 0, 1);
30+
s[1] = org.cprover.CProverString.substring("foo", 0, 4);
31+
s[2] = org.cprover.CProverString.substring("foo", -1, 0);
32+
s[3] = org.cprover.CProverString.substring("foo", -10, -1);
33+
s[4] = org.cprover.CProverString.substring("foo", -10, 25);
34+
35+
// Assert
36+
if(i == 0)
37+
assert !s[0].equals("f");
38+
if(i == 1)
39+
assert !s[1].equals("foo");
40+
if(i == 2)
41+
assert !s[2].equals("");
42+
if(i == 3)
43+
assert !s[3].equals("");
44+
if(i == 4)
45+
assert !s[4].equals("foo");
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CORE
2+
Test.class
3+
--refine-strings --unwind 10 --string-max-input-length 6 --function Test.testSuccess
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
assertion at file Test.java line 21.*: SUCCESS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
Test.class
3+
--refine-strings --unwind 10 --string-max-input-length 6 --function Test.testFail
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
assertion at file Test.java line 37 .*: FAILURE
7+
assertion at file Test.java line 39 .*: FAILURE
8+
assertion at file Test.java line 41 .*: FAILURE
9+
assertion at file Test.java line 43 .*: FAILURE
10+
assertion at file Test.java line 45 .*: FAILURE

src/solvers/refinement/string_constraint_generator.h

+3
Original file line numberDiff line numberDiff line change
@@ -439,4 +439,7 @@ utf16_constant_array_to_java(const array_exprt &arr, std::size_t length);
439439
/// \return expression representing the minimum of two expressions
440440
exprt minimum(const exprt &a, const exprt &b);
441441

442+
/// \return expression representing the maximum of two expressions
443+
exprt maximum(const exprt &a, const exprt &b);
444+
442445
#endif

src/solvers/refinement/string_constraint_generator_concat.cpp

+34-35
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,16 @@ Author: Romain Brenguier, [email protected]
1414
#include <solvers/refinement/string_constraint_generator.h>
1515

1616
/// Add axioms enforcing that `res` is the concatenation of `s1` with
17-
/// the substring of `s2` starting at index `start_index` and ending
18-
/// at index `end_index`.
19-
///
20-
/// If `start_index >= end_index`, the value returned is `s1`.
21-
/// If `end_index > |s2|` and/or `start_index < 0`, the appended string will
22-
/// be of length `end_index - start_index` and padded with non-deterministic
23-
/// values.
17+
/// the substring of `s2` starting at index `start_index'` and ending
18+
/// at index `end_index'`.
19+
/// Where start_index' is max(0, start_index) and end_index' is
20+
/// max(min(end_index, s2.length), start_index')
2421
///
2522
/// These axioms are:
26-
/// 1. \f$end\_index > start\_index \Rightarrow |res| = |s_1| + end\_index -
27-
/// start\_index
28-
/// \f$
29-
/// 2. \f$end\_index \le start\_index \Rightarrow res = s_1 \f$
30-
/// 3. \f$\forall i<|s_1|. res[i]=s_1[i] \f$
31-
/// 4. \f$\forall i< end\_index - start\_index.\ res[i+|s_1|]
32-
/// = s_2[start\_index+i]\f$
23+
/// 1. \f$|res| = |s_1| + end\_index' - start\_index' \f$
24+
/// 2. \f$\forall i<|s_1|. res[i]=s_1[i] \f$
25+
/// 3. \f$\forall i< end\_index' - start\_index'.\ res[i+|s_1|]
26+
/// = s_2[start\_index'+i]\f$
3327
///
3428
/// \param res: an array of characters expression
3529
/// \param s1: an array of characters expression
@@ -44,28 +38,33 @@ exprt string_constraint_generatort::add_axioms_for_concat_substr(
4438
const exprt &start_index,
4539
const exprt &end_index)
4640
{
47-
binary_relation_exprt prem(end_index, ID_gt, start_index);
48-
49-
exprt res_length=plus_exprt_with_overflow_check(
50-
s1.length(), minus_exprt(end_index, start_index));
51-
implies_exprt a1(prem, equal_exprt(res.length(), res_length));
52-
lemmas.push_back(a1);
53-
54-
implies_exprt a2(not_exprt(prem), equal_exprt(res.length(), s1.length()));
55-
lemmas.push_back(a2);
41+
const typet &index_type = start_index.type();
42+
const exprt start1 = maximum(start_index, from_integer(0, index_type));
43+
const exprt end1 = maximum(minimum(end_index, s2.length()), start1);
44+
45+
// Axiom 1.
46+
lemmas.push_back([&] { // NOLINT
47+
const plus_exprt res_length(s1.length(), minus_exprt(end1, start1));
48+
return equal_exprt(res.length(), res_length);
49+
}());
50+
51+
// Axiom 2.
52+
constraints.push_back([&] { // NOLINT
53+
const symbol_exprt idx =
54+
fresh_univ_index("QA_index_concat", res.length().type());
55+
return string_constraintt(idx, s1.length(), equal_exprt(s1[idx], res[idx]));
56+
}());
57+
58+
// Axiom 3.
59+
constraints.push_back([&] { // NOLINT
60+
const symbol_exprt idx2 =
61+
fresh_univ_index("QA_index_concat2", res.length().type());
62+
const equal_exprt res_eq(
63+
res[plus_exprt(idx2, s1.length())], s2[plus_exprt(start1, idx2)]);
64+
return string_constraintt(idx2, minus_exprt(end1, start1), res_eq);
65+
}());
5666

57-
symbol_exprt idx=fresh_univ_index("QA_index_concat", res.length().type());
58-
string_constraintt a3(idx, s1.length(), equal_exprt(s1[idx], res[idx]));
59-
constraints.push_back(a3);
60-
61-
symbol_exprt idx2=fresh_univ_index("QA_index_concat2", res.length().type());
62-
equal_exprt res_eq(
63-
res[plus_exprt(idx2, s1.length())], s2[plus_exprt(start_index, idx2)]);
64-
string_constraintt a4(idx2, minus_exprt(end_index, start_index), res_eq);
65-
constraints.push_back(a4);
66-
67-
// We should have a enum type for the possible error codes
68-
return from_integer(0, res.length().type());
67+
return from_integer(0, get_return_code_type());
6968
}
7069

7170
/// Add axioms enforcing that `res` is the concatenation of `s1` with

src/solvers/refinement/string_constraint_generator_main.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -667,3 +667,8 @@ exprt minimum(const exprt &a, const exprt &b)
667667
{
668668
return if_exprt(binary_relation_exprt(a, ID_le, b), a, b);
669669
}
670+
671+
exprt maximum(const exprt &a, const exprt &b)
672+
{
673+
return if_exprt(binary_relation_exprt(a, ID_le, b), b, a);
674+
}

src/solvers/refinement/string_constraint_generator_transformation.cpp

+16-25
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,14 @@ exprt string_constraint_generatort::add_axioms_for_substring(
9898
}
9999

100100
/// Add axioms ensuring that `res` corresponds to the substring of `str`
101-
/// between indexes `start` and `end`.
101+
/// between indexes `start' = max(start, 0)` and
102+
/// `end' = max(min(end, |str|), start')`.
102103
///
103104
/// These axioms are:
104-
/// 1. \f$ {\tt start} < {\tt end} \Rightarrow
105-
/// |{\tt res}| = {\tt end} - {\tt start} \f$
106-
/// 2. \f$ {\tt start} \ge {\tt end} \Rightarrow |{\tt res}| = 0 \f$
107-
/// 3. \f$ |{\tt str}| > {\tt end} \f$
108-
/// 4. \f$ \forall i<|{\tt str}|.\ {\tt res}[i]={\tt str}[{\tt start}+i] \f$
109-
/// \todo Should return code different from 0 if `|str| <= |end|` instead of
110-
/// adding constraint 3.
105+
/// 1. \f$ |{\tt res}| = end' - start' \f$
106+
/// 2. \f$ \forall i<|{\tt res}|.\ {\tt res}[i]={\tt str}[{\tt start'}+i] \f$
107+
/// \todo Should return code different from 0 if `start' != start` or
108+
/// `end' != end`
111109
/// \param res: array of characters expression
112110
/// \param str: array of characters expression
113111
/// \param start: integer expression
@@ -123,26 +121,19 @@ exprt string_constraint_generatort::add_axioms_for_substring(
123121
PRECONDITION(start.type()==index_type);
124122
PRECONDITION(end.type()==index_type);
125123

126-
// We add axioms:
124+
const exprt start1 = maximum(start, from_integer(0, start.type()));
125+
const exprt end1 = maximum(minimum(end, str.length()), start1);
127126

128-
implies_exprt a1(
129-
binary_relation_exprt(start, ID_lt, end),
130-
res.axiom_for_has_length(minus_exprt(end, start)));
131-
lemmas.push_back(a1);
127+
// Axiom 1.
128+
lemmas.push_back(equal_exprt(res.length(), minus_exprt(end1, start1)));
132129

133-
exprt is_empty=res.axiom_for_has_length(from_integer(0, index_type));
134-
implies_exprt a2(binary_relation_exprt(start, ID_ge, end), is_empty);
135-
lemmas.push_back(a2);
130+
// Axiom 2.
131+
constraints.push_back([&] { // NOLINT
132+
const symbol_exprt idx = fresh_univ_index("QA_index_substring", index_type);
133+
return string_constraintt(
134+
idx, res.length(), equal_exprt(res[idx], str[plus_exprt(start1, idx)]));
135+
}());
136136

137-
// Warning: check what to do if the string is not long enough
138-
lemmas.push_back(str.axiom_for_length_ge(end));
139-
140-
symbol_exprt idx=fresh_univ_index("QA_index_substring", index_type);
141-
string_constraintt a4(idx,
142-
res.length(),
143-
equal_exprt(res[idx],
144-
str[plus_exprt(start, idx)]));
145-
constraints.push_back(a4);
146137
return from_integer(0, signedbv_typet(32));
147138
}
148139

0 commit comments

Comments
 (0)