summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/theory/strings/core_solver.cpp43
-rw-r--r--src/theory/strings/core_solver.h5
-rw-r--r--test/regress/CMakeLists.txt1
-rw-r--r--test/regress/regress0/strings/issue4820.smt215
4 files changed, 46 insertions, 18 deletions
diff --git a/src/theory/strings/core_solver.cpp b/src/theory/strings/core_solver.cpp
index 062bfe12f..438a559b8 100644
--- a/src/theory/strings/core_solver.cpp
+++ b/src/theory/strings/core_solver.cpp
@@ -839,8 +839,12 @@ size_t CoreSolver::getSufficientNonEmptyOverlap(Node c, Node d, bool isRev)
return p2 == std::string::npos ? p : (p > p2 + 1 ? p2 + 1 : p);
}
-Node CoreSolver::getDecomposeConclusion(
- Node x, Node l, bool isRev, SkolemCache* skc, std::vector<Node>& newSkolems)
+Node CoreSolver::getDecomposeConclusion(Node x,
+ Node l,
+ bool isRev,
+ bool addLenConc,
+ SkolemCache* skc,
+ std::vector<Node>& newSkolems)
{
Assert(l.getType().isInteger());
NodeManager* nm = NodeManager::currentNM();
@@ -850,7 +854,7 @@ Node CoreSolver::getDecomposeConclusion(
Node sk2 = skc->mkSkolemCached(x, n, SkolemCache::SK_SUFFIX_REM, "dc_spt2");
newSkolems.push_back(sk2);
Node conc = x.eqNode(nm->mkNode(STRING_CONCAT, sk1, sk2));
- if (options::stringLenConc())
+ if (addLenConc)
{
Node lc = nm->mkNode(STRING_LENGTH, isRev ? sk2 : sk1).eqNode(l);
conc = nm->mkNode(AND, conc, lc);
@@ -2081,8 +2085,8 @@ void CoreSolver::processDeq(Node ni, Node nj)
// len(x)>=1 => x = k1 ++ k2 ^ len(k1) = 1
SkolemCache* skc = d_termReg.getSkolemCache();
std::vector<Node> newSkolems;
- Node conc =
- getDecomposeConclusion(nck, d_one, false, skc, newSkolems);
+ Node conc = getDecomposeConclusion(
+ nck, d_one, false, options::stringLenConc(), skc, newSkolems);
Assert(newSkolems.size() == 2);
if (options::stringLenConc())
{
@@ -2108,8 +2112,8 @@ void CoreSolver::processDeq(Node ni, Node nj)
// are both non-constants. We split them into parts that have the same
// lengths.
//
- // len(x) >= len(y) => x = k1 ++ k2 ^ len(k1) = len(y)
- // len(y) >= len(x) => y = k3 ++ k4 ^ len(k3) = len(x)
+ // len(x) > len(y) => x = k1 ++ k2 ^ len(k1) = len(y)
+ // len(y) > len(x) => y = k3 ++ k4 ^ len(k3) = len(x)
Trace("strings-solve")
<< "Non-Simple Case 1 : add lemmas " << std::endl;
SkolemCache* skc = d_termReg.getSkolemCache();
@@ -2120,19 +2124,24 @@ void CoreSolver::processDeq(Node ni, Node nj)
Node uy = r == 0 ? y : x;
Node uxLen = nm->mkNode(STRING_LENGTH, ux);
Node uyLen = nm->mkNode(STRING_LENGTH, uy);
+ // We always request the length constraint in the conclusion here
+ // because the skolem needs to have length `uyLen`. If we only assert
+ // that the skolem's length is greater or equal to one, we can end up
+ // in a loop:
+ //
+ // 1. Split: x = k1 ++ k2 ^ len(k1) >= 1
+ // 2. Assume: k2 = ""
+ // 3. Deduce: x = k1
+ //
+ // After step 3, `k1` is marked congruent because `x` is the older
+ // variable. So we get `x` in the normal form again.
std::vector<Node> newSkolems;
- Node conc = getDecomposeConclusion(ux, uyLen, false, skc, newSkolems);
+ Node conc =
+ getDecomposeConclusion(ux, uyLen, false, true, skc, newSkolems);
Assert(newSkolems.size() == 2);
- if (options::stringLenConc())
- {
- d_termReg.registerTermAtomic(newSkolems[0], LENGTH_IGNORE);
- }
- else
- {
- d_termReg.registerTermAtomic(newSkolems[0], LENGTH_GEQ_ONE);
- }
+ d_termReg.registerTermAtomic(newSkolems[1], LENGTH_GEQ_ONE);
std::vector<Node> antecLen;
- antecLen.push_back(nm->mkNode(GEQ, uxLen, uyLen));
+ antecLen.push_back(nm->mkNode(GT, uxLen, uyLen));
d_im.sendInference(
{}, antecLen, conc, Inference::DEQ_DISL_STRINGS_SPLIT, true);
}
diff --git a/src/theory/strings/core_solver.h b/src/theory/strings/core_solver.h
index 2ee61e759..da364e243 100644
--- a/src/theory/strings/core_solver.h
+++ b/src/theory/strings/core_solver.h
@@ -260,11 +260,13 @@ class CoreSolver
* a conjunction of splitting string x into pieces based on length l, e.g.:
* x = k_1 ++ k_2
* where k_1 (resp. k_2) is a skolem corresponding to a substring of x of
- * length l if isRev is false (resp. true).
+ * length l if isRev is false (resp. true). The function optionally adds a
+ * length constraint len(k_1) = l (resp. len(k_2) = l).
*
* @param x The string term
* @param l The length term
* @param isRev Whether the equation is in a reverse direction
+ * @param addLenConc Whether to add the length constraint
* @param skc The skolem cache (to allocate fresh variables if necessary)
* @param newSkolems The vector to add new variables to
* @return The conclusion of the inference.
@@ -272,6 +274,7 @@ class CoreSolver
static Node getDecomposeConclusion(Node x,
Node l,
bool isRev,
+ bool addLenConc,
SkolemCache* skc,
std::vector<Node>& newSkolems);
diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt
index a0fee2185..f9840e552 100644
--- a/test/regress/CMakeLists.txt
+++ b/test/regress/CMakeLists.txt
@@ -983,6 +983,7 @@ set(regress_0_tests
regress0/strings/issue4376.smt2
regress0/strings/issue4662-consume-nterm.smt2
regress0/strings/issue4674-recomp-nf.smt2
+ regress0/strings/issue4820.smt2
regress0/strings/itos-entail.smt2
regress0/strings/large-model.smt2
regress0/strings/leadingzero001.smt2
diff --git a/test/regress/regress0/strings/issue4820.smt2 b/test/regress/regress0/strings/issue4820.smt2
new file mode 100644
index 000000000..0bf0fac65
--- /dev/null
+++ b/test/regress/regress0/strings/issue4820.smt2
@@ -0,0 +1,15 @@
+; EXPECT: sat
+(set-logic QF_S)
+(declare-fun _substvar_270_ () String)
+(declare-fun _substvar_273_ () Bool)
+(declare-fun _substvar_275_ () Bool)
+(declare-fun _substvar_293_ () Bool)
+(declare-const v0 Bool)
+(declare-const v1 Bool)
+(declare-const v2 Bool)
+(declare-const v3 Bool)
+(declare-const Str3 String)
+(declare-const Str7 String)
+(declare-const Str15 String)
+(assert (or (xor true true (xor v0 v3 false v1 false v2 v2 v1 false v2 v1) true true (=> (or v1 (distinct Str15 _substvar_270_ Str3 (str.++ Str7 Str7)) v2) false) false true) _substvar_275_))
+(check-sat)
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback