diff --git a/src/smt/theory_str.cpp b/src/smt/theory_str.cpp index e7c99da69..4a6a6da5b 100644 --- a/src/smt/theory_str.cpp +++ b/src/smt/theory_str.cpp @@ -1,19 +1,19 @@ /*++ -Module Name: + Module Name: - theory_str.cpp + theory_str.cpp -Abstract: + Abstract: - String Theory Plugin + String Theory Plugin -Author: + Author: - Murphy Berzish and Yunhui Zheng + Murphy Berzish and Yunhui Zheng -Revision History: + Revision History: ---*/ + --*/ #include"ast_smt2_pp.h" #include"smt_context.h" #include"theory_str.h" @@ -24,13 +24,11 @@ Revision History: #include #include #include"theory_seq_empty.h" - -#include "../ast/ast.h" #include"theory_arith.h" namespace smt { - -theory_str::theory_str(ast_manager & m, theory_str_params const & params): + + theory_str::theory_str(ast_manager & m, theory_str_params const & params): theory(m.mk_family_id("seq")), m_params(params), /* Options */ @@ -64,3864 +62,3041 @@ theory_str::theory_str(ast_manager & m, theory_str_params const & params): cacheMissCount(0), m_find(*this), m_trail_stack(*this) -{ - initialize_charset(); -} + { + initialize_charset(); + } -theory_str::~theory_str() { - m_trail_stack.reset(); -} + theory_str::~theory_str() { + m_trail_stack.reset(); + } -expr * theory_str::mk_string(zstring const& str) { - if (m_params.m_StringConstantCache) { - ++totalCacheAccessCount; - expr * val; - if (stringConstantCache.find(str, val)) { - return val; + expr * theory_str::mk_string(zstring const& str) { + if (m_params.m_StringConstantCache) { + ++totalCacheAccessCount; + expr * val; + if (stringConstantCache.find(str, val)) { + return val; + } else { + val = u.str.mk_string(str); + m_trail.push_back(val); + stringConstantCache.insert(str, val); + return val; + } } else { - val = u.str.mk_string(str); - m_trail.push_back(val); - stringConstantCache.insert(str, val); - return val; + return u.str.mk_string(str); } - } else { - return u.str.mk_string(str); - } -} - -expr * theory_str::mk_string(const char * str) { - symbol sym(str); - return u.str.mk_string(sym); -} - -void theory_str::initialize_charset() { - bool defaultCharset = true; - if (defaultCharset) { - // valid C strings can't contain the null byte ('\0') - charSetSize = 255; - char_set = alloc_svect(char, charSetSize); - int idx = 0; - // small letters - for (int i = 97; i < 123; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // caps - for (int i = 65; i < 91; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // numbers - for (int i = 48; i < 58; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 1 - for (int i = 32; i < 48; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 2 - for (int i = 58; i < 65; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 3 - for (int i = 91; i < 97; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // printable marks - 4 - for (int i = 123; i < 127; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // non-printable - 1 - for (int i = 1; i < 32; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - // non-printable - 2 - for (int i = 127; i < 256; i++) { - char_set[idx] = (char) i; - charSetLookupTable[char_set[idx]] = idx; - idx++; - } - } else { - const char setset[] = { 'a', 'b', 'c' }; - int fSize = sizeof(setset) / sizeof(char); - - char_set = alloc_svect(char, fSize); - charSetSize = fSize; - for (int i = 0; i < charSetSize; i++) { - char_set[i] = setset[i]; - charSetLookupTable[setset[i]] = i; - } - } -} - -void theory_str::assert_axiom(expr * e) { - if (opt_VerifyFinalCheckProgress) { - finalCheckProgressIndicator = true; } - if (get_manager().is_true(e)) return; - TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); - context & ctx = get_context(); - if (!ctx.b_internalized(e)) { - ctx.internalize(e, false); - } - literal lit(ctx.get_literal(e)); - ctx.mark_as_relevant(lit); - ctx.mk_th_axiom(get_id(), 1, &lit); - - // crash/error avoidance: add all axioms to the trail - m_trail.push_back(e); - - //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); -} - -expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { - ast_manager & m = get_manager(); - return m.mk_or(m.mk_not(premise), conclusion); -} - -void theory_str::assert_implication(expr * premise, expr * conclusion) { - ast_manager & m = get_manager(); - TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); - expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); - assert_axiom(axiom); -} - -bool theory_str::internalize_atom(app * atom, bool gate_ctx) { - return internalize_term(atom); -} - -bool theory_str::internalize_term(app * term) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - SASSERT(term->get_family_id() == get_family_id()); - - TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); - - // emulation of user_smt_theory::internalize_term() - - unsigned num_args = term->get_num_args(); - for (unsigned i = 0; i < num_args; ++i) { - ctx.internalize(term->get_arg(i), false); - } - if (ctx.e_internalized(term)) { - enode * e = ctx.get_enode(term); - mk_var(e); - return true; - } - // m_parents.push_back(term); - enode * e = ctx.mk_enode(term, false, m.is_bool(term), true); - if (m.is_bool(term)) { - bool_var bv = ctx.mk_bool_var(term); - ctx.set_var_theory(bv, get_id()); - ctx.set_enode_flag(bv, true); - } - // make sure every argument is attached to a theory variable - for (unsigned i = 0; i < num_args; ++i) { - enode * arg = e->get_arg(i); - theory_var v_arg = mk_var(arg); - TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); + expr * theory_str::mk_string(const char * str) { + symbol sym(str); + return u.str.mk_string(sym); } - theory_var v = mk_var(e); - TRACE("str", tout << "term has theory var #" << v << std::endl;); + void theory_str::initialize_charset() { + bool defaultCharset = true; + if (defaultCharset) { + // valid C strings can't contain the null byte ('\0') + charSetSize = 255; + char_set = alloc_svect(char, charSetSize); + int idx = 0; + // small letters + for (int i = 97; i < 123; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // caps + for (int i = 65; i < 91; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // numbers + for (int i = 48; i < 58; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 1 + for (int i = 32; i < 48; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 2 + for (int i = 58; i < 65; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 3 + for (int i = 91; i < 97; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // printable marks - 4 + for (int i = 123; i < 127; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 1 + for (int i = 1; i < 32; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + // non-printable - 2 + for (int i = 127; i < 256; i++) { + char_set[idx] = (char) i; + charSetLookupTable[char_set[idx]] = idx; + idx++; + } + } else { + const char setset[] = { 'a', 'b', 'c' }; + int fSize = sizeof(setset) / sizeof(char); - if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { - TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); - m_basicstr_axiom_todo.insert(e); - } - return true; -} - -enode* theory_str::ensure_enode(expr* e) { - context& ctx = get_context(); - if (!ctx.e_internalized(e)) { - ctx.internalize(e, false); - } - enode* n = ctx.get_enode(e); - ctx.mark_as_relevant(n); - return n; -} - -void theory_str::refresh_theory_var(expr * e) { - enode * en = ensure_enode(e); - theory_var v = mk_var(en); - TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); - m_basicstr_axiom_todo.push_back(en); -} - -theory_var theory_str::mk_var(enode* n) { - TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); - ast_manager & m = get_manager(); - if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { - return null_theory_var; - } - if (is_attached_to_var(n)) { - TRACE("str", tout << "already attached to theory var" << std::endl;); - return n->get_th_var(get_id()); - } else { - theory_var v = theory::mk_var(n); - m_find.mk_var(); - TRACE("str", tout << "new theory var v#" << v << std::endl;); - get_context().attach_th_var(n, this, v); - get_context().mark_as_relevant(n); - return v; - } -} - -static void cut_vars_map_copy(std::map & dest, std::map & src) { - std::map::iterator itor = src.begin(); - for (; itor != src.end(); itor++) { - dest[itor->first] = 1; - } -} - -bool theory_str::has_self_cut(expr * n1, expr * n2) { - if (!cut_var_map.contains(n1)) { - return false; - } - if (!cut_var_map.contains(n2)) { - return false; - } - if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { - return false; + char_set = alloc_svect(char, fSize); + charSetSize = fSize; + for (int i = 0; i < charSetSize; i++) { + char_set[i] = setset[i]; + charSetLookupTable[setset[i]] = i; + } + } } - std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); - for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { - if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + void theory_str::assert_axiom(expr * e) { + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = true; + } + + if (get_manager().is_true(e)) return; + TRACE("str", tout << "asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + context & ctx = get_context(); + if (!ctx.b_internalized(e)) { + ctx.internalize(e, false); + } + literal lit(ctx.get_literal(e)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + + // crash/error avoidance: add all axioms to the trail + m_trail.push_back(e); + + //TRACE("str", tout << "done asserting " << mk_ismt2_pp(e, get_manager()) << std::endl;); + } + + expr * theory_str::rewrite_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + return m.mk_or(m.mk_not(premise), conclusion); + } + + void theory_str::assert_implication(expr * premise, expr * conclusion) { + ast_manager & m = get_manager(); + TRACE("str", tout << "asserting implication " << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); + expr_ref axiom(m.mk_or(m.mk_not(premise), conclusion), m); + assert_axiom(axiom); + } + + bool theory_str::internalize_atom(app * atom, bool gate_ctx) { + return internalize_term(atom); + } + + bool theory_str::internalize_term(app * term) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + SASSERT(term->get_family_id() == get_family_id()); + + TRACE("str", tout << "internalizing term: " << mk_ismt2_pp(term, get_manager()) << std::endl;); + + // emulation of user_smt_theory::internalize_term() + + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; ++i) { + ctx.internalize(term->get_arg(i), false); + } + if (ctx.e_internalized(term)) { + enode * e = ctx.get_enode(term); + mk_var(e); return true; } - } - return false; -} + // m_parents.push_back(term); + enode * e = ctx.mk_enode(term, false, m.is_bool(term), true); + if (m.is_bool(term)) { + bool_var bv = ctx.mk_bool_var(term); + ctx.set_var_theory(bv, get_id()); + ctx.set_enode_flag(bv, true); + } + // make sure every argument is attached to a theory variable + for (unsigned i = 0; i < num_args; ++i) { + enode * arg = e->get_arg(i); + theory_var v_arg = mk_var(arg); + TRACE("str", tout << "arg has theory var #" << v_arg << std::endl;); + } -void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { - // crash avoidance? - m_trail.push_back(baseNode); - m_trail.push_back(node); - if (!cut_var_map.contains(baseNode)) { - T_cut * varInfo = alloc(T_cut); - varInfo->level = slevel; - varInfo->vars[node] = 1; - cut_var_map.insert(baseNode, std::stack()); - cut_var_map[baseNode].push(varInfo); - TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); - } else { - if (cut_var_map[baseNode].empty()) { + theory_var v = mk_var(e); + TRACE("str", tout << "term has theory var #" << v << std::endl;); + + if (opt_EagerStringConstantLengthAssertions && u.str.is_string(term)) { + TRACE("str", tout << "eagerly asserting length of string term " << mk_pp(term, m) << std::endl;); + m_basicstr_axiom_todo.insert(e); + } + return true; + } + + enode* theory_str::ensure_enode(expr* e) { + context& ctx = get_context(); + if (!ctx.e_internalized(e)) { + ctx.internalize(e, false); + } + enode* n = ctx.get_enode(e); + ctx.mark_as_relevant(n); + return n; + } + + void theory_str::refresh_theory_var(expr * e) { + enode * en = ensure_enode(e); + theory_var v = mk_var(en); + TRACE("str", tout << "refresh " << mk_pp(e, get_manager()) << ": v#" << v << std::endl;); + m_basicstr_axiom_todo.push_back(en); + } + + theory_var theory_str::mk_var(enode* n) { + TRACE("str", tout << "mk_var for " << mk_pp(n->get_owner(), get_manager()) << std::endl;); + ast_manager & m = get_manager(); + if (!(m.get_sort(n->get_owner()) == u.str.mk_string_sort())) { + return null_theory_var; + } + if (is_attached_to_var(n)) { + TRACE("str", tout << "already attached to theory var" << std::endl;); + return n->get_th_var(get_id()); + } else { + theory_var v = theory::mk_var(n); + m_find.mk_var(); + TRACE("str", tout << "new theory var v#" << v << std::endl;); + get_context().attach_th_var(n, this, v); + get_context().mark_as_relevant(n); + return v; + } + } + + static void cut_vars_map_copy(std::map & dest, std::map & src) { + std::map::iterator itor = src.begin(); + for (; itor != src.end(); itor++) { + dest[itor->first] = 1; + } + } + + bool theory_str::has_self_cut(expr * n1, expr * n2) { + if (!cut_var_map.contains(n1)) { + return false; + } + if (!cut_var_map.contains(n2)) { + return false; + } + if (cut_var_map[n1].empty() || cut_var_map[n2].empty()) { + return false; + } + + std::map::iterator itor = cut_var_map[n1].top()->vars.begin(); + for (; itor != cut_var_map[n1].top()->vars.end(); ++itor) { + if (cut_var_map[n2].top()->vars.find(itor->first) != cut_var_map[n2].top()->vars.end()) { + return true; + } + } + return false; + } + + void theory_str::add_cut_info_one_node(expr * baseNode, int slevel, expr * node) { + // crash avoidance? + m_trail.push_back(baseNode); + m_trail.push_back(node); + if (!cut_var_map.contains(baseNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; varInfo->vars[node] = 1; + cut_var_map.insert(baseNode, std::stack()); cut_var_map[baseNode].push(varInfo); TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - if (cut_var_map[baseNode].top()->level < slevel) { + if (cut_var_map[baseNode].empty()) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); varInfo->vars[node] = 1; cut_var_map[baseNode].push(varInfo); TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); - } else if (cut_var_map[baseNode].top()->level == slevel) { - cut_var_map[baseNode].top()->vars[node] = 1; - TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + if (cut_var_map[baseNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[baseNode].top()->vars); + varInfo->vars[node] = 1; + cut_var_map[baseNode].push(varInfo); + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[baseNode].top()->level == slevel) { + cut_var_map[baseNode].top()->vars[node] = 1; + TRACE("str", tout << "add var info for baseNode=" << mk_pp(baseNode, get_manager()) << ", node=" << mk_pp(node, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("entered illegal state during add_cut_info_one_node()"); + } } } } -} -void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { - // crash avoidance? - m_trail.push_back(destNode); - m_trail.push_back(srcNode); - if (!cut_var_map.contains(srcNode)) { - get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); - } + void theory_str::add_cut_info_merge(expr * destNode, int slevel, expr * srcNode) { + // crash avoidance? + m_trail.push_back(destNode); + m_trail.push_back(srcNode); + if (!cut_var_map.contains(srcNode)) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map doesn't contain srcNode"); + } - if (cut_var_map[srcNode].empty()) { - get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); - } + if (cut_var_map[srcNode].empty()) { + get_manager().raise_exception("illegal state in add_cut_info_merge(): cut_var_map[srcNode] is empty"); + } - if (!cut_var_map.contains(destNode)) { - T_cut * varInfo = alloc(T_cut); - varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); - cut_var_map.insert(destNode, std::stack()); - cut_var_map[destNode].push(varInfo); - TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); - } else { - if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + if (!cut_var_map.contains(destNode)) { T_cut * varInfo = alloc(T_cut); varInfo->level = slevel; - cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map.insert(destNode, std::stack()); cut_var_map[destNode].push(varInfo); TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); - } else if (cut_var_map[destNode].top()->level == slevel) { - cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); - TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); } else { - get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); - } - } -} - -void theory_str::check_and_init_cut_var(expr * node) { - if (cut_var_map.contains(node)) { - return; - } else if (!u.str.is_string(node)) { - add_cut_info_one_node(node, -1, node); - } -} - -literal theory_str::mk_literal(expr* _e) { - ast_manager & m = get_manager(); - expr_ref e(_e, m); - context& ctx = get_context(); - ensure_enode(e); - return ctx.get_literal(e); -} - -app * theory_str::mk_int(int n) { - return m_autil.mk_numeral(rational(n), true); -} - -app * theory_str::mk_int(rational & q) { - return m_autil.mk_numeral(q, true); -} - -expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { - ast_manager & m = get_manager(); - - std::stringstream ss; - ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; - tmpLenTestVarCount += 1; - std::string name = ss.str(); - app * var = mk_str_var(name); - internal_lenTest_vars.insert(var); - m_trail.push_back(var); - return var; -} - -expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { - ast_manager & m = get_manager(); - std::stringstream ss; - ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; - tmpValTestVarCount += 1; - std::string name = ss.str(); - app * var = mk_str_var(name); - internal_valTest_vars.insert(var); - m_trail.push_back(var); - return var; -} - -void theory_str::track_variable_scope(expr * var) { - if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { - internal_variable_scope_levels[sLevel] = std::set(); - } - internal_variable_scope_levels[sLevel].insert(var); -} - -app * theory_str::mk_internal_xor_var() { - return mk_int_var("$$_xor"); -} - -app * theory_str::mk_int_var(std::string name) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); - - sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); - app * a = m.mk_fresh_const(name.c_str(), int_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - ctx.mark_as_relevant(a); - // I'm assuming that this combination will do the correct thing in the integer theory. - - //mk_var(ctx.get_enode(a)); - m_trail.push_back(a); - //variable_set.insert(a); - //internal_variable_set.insert(a); - //track_variable_scope(a); - - return a; -} - -app * theory_str::mk_unroll_bound_var() { - return mk_int_var("unroll"); -} - -app * theory_str::mk_unroll_test_var() { - app * v = mk_str_var("unrollTest"); // was uRt - internal_unrollTest_vars.insert(v); - track_variable_scope(v); - return v; -} - -app * theory_str::mk_str_var(std::string name) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); - - TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl - << "this->get_family_id() = " << this->get_family_id() << std::endl;); - - // I have a hunch that this may not get internalized for free... - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - // this might help?? - mk_var(ctx.get_enode(a)); - m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); - - m_trail.push_back(a); - variable_set.insert(a); - internal_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -app * theory_str::mk_regex_rep_var() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const("regex", string_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - SASSERT(ctx.e_internalized(a)); - mk_var(ctx.get_enode(a)); - m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); - TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); - - m_trail.push_back(a); - variable_set.insert(a); - //internal_variable_set.insert(a); - regex_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -void theory_str::add_nonempty_constraint(expr * s) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); - assert_axiom(ax1); - - { - // build LHS - expr_ref len_str(mk_strlen(s), m); - SASSERT(len_str); - // build RHS - expr_ref zero(m_autil.mk_numeral(rational(0), true), m); - SASSERT(zero); - // build LHS > RHS and assert - // we have to build !(LHS <= RHS) instead - expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); - SASSERT(lhs_gt_rhs); - assert_axiom(lhs_gt_rhs); - } -} - -app * theory_str::mk_nonempty_str_var() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - std::stringstream ss; - ss << tmpStringVarCount; - tmpStringVarCount++; - std::string name = "$$_str" + ss.str(); - - TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); - - sort * string_sort = u.str.mk_string_sort(); - app * a = m.mk_fresh_const(name.c_str(), string_sort); - - ctx.internalize(a, false); - SASSERT(ctx.get_enode(a) != NULL); - // this might help?? - mk_var(ctx.get_enode(a)); - - // assert a variation of the basic string axioms that ensures this string is nonempty - { - // build LHS - expr_ref len_str(mk_strlen(a), m); - SASSERT(len_str); - // build RHS - expr_ref zero(m_autil.mk_numeral(rational(0), true), m); - SASSERT(zero); - // build LHS > RHS and assert - // we have to build !(LHS <= RHS) instead - expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); - SASSERT(lhs_gt_rhs); - assert_axiom(lhs_gt_rhs); - } - - // add 'a' to variable sets, so we can keep track of it - m_trail.push_back(a); - variable_set.insert(a); - internal_variable_set.insert(a); - track_variable_scope(a); - - return a; -} - -app * theory_str::mk_unroll(expr * n, expr * bound) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * args[2] = {n, bound}; - app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); - m_trail.push_back(unrollFunc); - - expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); - items.push_back(m_autil.mk_ge(bound, mk_int(0))); - items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); - - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - return unrollFunc; -} - -app * theory_str::mk_contains(expr * haystack, expr * needle) { - app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order - m_trail.push_back(contains); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(contains, false); - set_up_axioms(contains); - return contains; -} - -app * theory_str::mk_indexof(expr * haystack, expr * needle) { - // TODO check meaning of the third argument here - app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); - m_trail.push_back(indexof); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(indexof, false); - set_up_axioms(indexof); - return indexof; -} - -app * theory_str::mk_strlen(expr * e) { - /*if (m_strutil.is_string(e)) {*/ if (false) { - zstring strval; - u.str.is_string(e, strval); - unsigned int len = strval.length(); - return m_autil.mk_numeral(rational(len), true); - } else { - if (false) { - // use cache - app * lenTerm = NULL; - if (!length_ast_map.find(e, lenTerm)) { - lenTerm = u.str.mk_length(e); - length_ast_map.insert(e, lenTerm); - m_trail.push_back(lenTerm); + if (cut_var_map[destNode].empty() || cut_var_map[destNode].top()->level < slevel) { + T_cut * varInfo = alloc(T_cut); + varInfo->level = slevel; + cut_vars_map_copy(varInfo->vars, cut_var_map[destNode].top()->vars); + cut_vars_map_copy(varInfo->vars, cut_var_map[srcNode].top()->vars); + cut_var_map[destNode].push(varInfo); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else if (cut_var_map[destNode].top()->level == slevel) { + cut_vars_map_copy(cut_var_map[destNode].top()->vars, cut_var_map[srcNode].top()->vars); + TRACE("str", tout << "merge var info for destNode=" << mk_pp(destNode, get_manager()) << ", srcNode=" << mk_pp(srcNode, get_manager()) << " [" << slevel << "]" << std::endl;); + } else { + get_manager().raise_exception("illegal state in add_cut_info_merge(): inconsistent slevels"); } - return lenTerm; - } else { - // always regen - return u.str.mk_length(e); } } -} -/* - * Returns the simplified concatenation of two expressions, - * where either both expressions are constant strings - * or one expression is the empty string. - * If this precondition does not hold, the function returns NULL. - * (note: this function was strTheory::Concat()) - */ -expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - expr * v1 = get_eqc_value(n1, n1HasEqcValue); - expr * v2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - zstring n1_str; - u.str.is_string(v1, n1_str); - zstring n2_str; - u.str.is_string(v2, n2_str); - zstring result = n1_str + n2_str; - return mk_string(result); - } else if (n1HasEqcValue && !n2HasEqcValue) { - zstring n1_str; - u.str.is_string(v1, n1_str); - if (n1_str.empty()) { - return n2; - } - } else if (!n1HasEqcValue && n2HasEqcValue) { - zstring n2_str; - u.str.is_string(v2, n2_str); - if (n2_str.empty()) { - return n1; + void theory_str::check_and_init_cut_var(expr * node) { + if (cut_var_map.contains(node)) { + return; + } else if (!u.str.is_string(node)) { + add_cut_info_one_node(node, -1, node); } } - return NULL; -} -expr * theory_str::mk_concat(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - ENSURE(n1 != NULL); - ENSURE(n2 != NULL); - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - n1 = get_eqc_value(n1, n1HasEqcValue); - n2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - return mk_concat_const_str(n1, n2); - } else if (n1HasEqcValue && !n2HasEqcValue) { - bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); - zstring n1_str; - u.str.is_string(n1, n1_str); - if (n1_str.empty()) { - return n2; - } - if (n2_isConcatFunc) { - expr * n2_arg0 = to_app(n2)->get_arg(0); - expr * n2_arg1 = to_app(n2)->get_arg(1); - if (u.str.is_string(n2_arg0)) { - n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant - n2 = n2_arg1; - } - } - } else if (!n1HasEqcValue && n2HasEqcValue) { - zstring n2_str; - u.str.is_string(n2, n2_str); - if (n2_str.empty()) { - return n1; - } - - if (u.str.is_concat(to_app(n1))) { - expr * n1_arg0 = to_app(n1)->get_arg(0); - expr * n1_arg1 = to_app(n1)->get_arg(1); - if (u.str.is_string(n1_arg1)) { - n1 = n1_arg0; - n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant - } - } - } else { - if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { - expr * n1_arg0 = to_app(n1)->get_arg(0); - expr * n1_arg1 = to_app(n1)->get_arg(1); - expr * n2_arg0 = to_app(n2)->get_arg(0); - expr * n2_arg1 = to_app(n2)->get_arg(1); - if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { - expr * tmpN1 = n1_arg0; - expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); - n1 = mk_concat(tmpN1, tmpN2); - n2 = n2_arg1; - } - } - } - - //------------------------------------------------------ - // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); - // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); - // Z3 treats (ast1) and (ast2) as two different nodes. - //------------------------------------------------------- - - expr * concatAst = NULL; - - if (!concat_astNode_map.find(n1, n2, concatAst)) { - concatAst = u.str.mk_concat(n1, n2); - m_trail.push_back(concatAst); - concat_astNode_map.insert(n1, n2, concatAst); - - expr_ref concat_length(mk_strlen(concatAst), m); - - ptr_vector childrenVector; - get_nodes_in_concat(concatAst, childrenVector); - expr_ref_vector items(m); - for (unsigned int i = 0; i < childrenVector.size(); i++) { - items.push_back(mk_strlen(childrenVector.get(i))); - } - expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); - assert_axiom(lenAssert); - } - return concatAst; -} - -bool theory_str::can_propagate() { - return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() - || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() - || !m_library_aware_axiom_todo.empty() - || !m_delayed_axiom_setup_terms.empty(); - ; -} - -void theory_str::propagate() { - context & ctx = get_context(); - while (can_propagate()) { - TRACE("str", tout << "propagating..." << std::endl;); - for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { - instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); - } - m_basicstr_axiom_todo.reset(); - TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); - - for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { - std::pair pair = m_str_eq_todo[i]; - enode * lhs = pair.first; - enode * rhs = pair.second; - handle_equality(lhs->get_owner(), rhs->get_owner()); - } - m_str_eq_todo.reset(); - - for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { - instantiate_concat_axiom(m_concat_axiom_todo[i]); - } - m_concat_axiom_todo.reset(); - - for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { - try_eval_concat(m_concat_eval_todo[i]); - } - m_concat_eval_todo.reset(); - - for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { - enode * e = m_library_aware_axiom_todo[i]; - app * a = e->get_owner(); - if (u.str.is_stoi(a)) { - instantiate_axiom_str_to_int(e); - } else if (u.str.is_itos(a)) { - instantiate_axiom_int_to_str(e); - } else if (u.str.is_at(a)) { - instantiate_axiom_CharAt(e); - } else if (u.str.is_prefix(a)) { - instantiate_axiom_prefixof(e); - } else if (u.str.is_suffix(a)) { - instantiate_axiom_suffixof(e); - } else if (u.str.is_contains(a)) { - instantiate_axiom_Contains(e); - } else if (u.str.is_index(a)) { - instantiate_axiom_Indexof(e); - /* TODO NEXT: Indexof2/Lastindexof rewrite? - } else if (is_Indexof2(e)) { - instantiate_axiom_Indexof2(e); - } else if (is_LastIndexof(e)) { - instantiate_axiom_LastIndexof(e); - */ - } else if (u.str.is_extract(a)) { - // TODO check semantics of substr vs. extract - instantiate_axiom_Substr(e); - } else if (u.str.is_replace(a)) { - instantiate_axiom_Replace(e); - } else if (u.str.is_in_re(a)) { - instantiate_axiom_RegexIn(e); - } else { - TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - } - m_library_aware_axiom_todo.reset(); - - for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { - // I think this is okay - ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); - set_up_axioms(m_delayed_axiom_setup_terms[i].get()); - } - m_delayed_axiom_setup_terms.reset(); - } -} - -/* - * Attempt to evaluate a concat over constant strings, - * and if this is possible, assert equality between the - * flattened string and the original term. - */ - -void theory_str::try_eval_concat(enode * cat) { - app * a_cat = cat->get_owner(); - SASSERT(u.str.is_concat(a_cat)); - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); - - std::stack worklist; - zstring flattenedString(""); - bool constOK = true; - - { - app * arg0 = to_app(a_cat->get_arg(0)); - app * arg1 = to_app(a_cat->get_arg(1)); - - worklist.push(arg1); - worklist.push(arg0); + literal theory_str::mk_literal(expr* _e) { + ast_manager & m = get_manager(); + expr_ref e(_e, m); + context& ctx = get_context(); + ensure_enode(e); + return ctx.get_literal(e); } - while (constOK && !worklist.empty()) { - app * evalArg = worklist.top(); worklist.pop(); - zstring nextStr; - if (u.str.is_string(evalArg, nextStr)) { - flattenedString = flattenedString + nextStr; - } else if (u.str.is_concat(evalArg)) { - app * arg0 = to_app(evalArg->get_arg(0)); - app * arg1 = to_app(evalArg->get_arg(1)); + app * theory_str::mk_int(int n) { + return m_autil.mk_numeral(rational(n), true); + } - worklist.push(arg1); - worklist.push(arg0); - } else { - TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); - constOK = false; - break; + app * theory_str::mk_int(rational & q) { + return m_autil.mk_numeral(q, true); + } + + expr * theory_str::mk_internal_lenTest_var(expr * node, int lTries) { + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << "$$_len_" << mk_ismt2_pp(node, m) << "_" << lTries << "_" << tmpLenTestVarCount; + tmpLenTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_lenTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + expr * theory_str::mk_internal_valTest_var(expr * node, int len, int vTries) { + ast_manager & m = get_manager(); + std::stringstream ss; + ss << "$$_val_" << mk_ismt2_pp(node, m) << "_" << len << "_" << vTries << "_" << tmpValTestVarCount; + tmpValTestVarCount += 1; + std::string name = ss.str(); + app * var = mk_str_var(name); + internal_valTest_vars.insert(var); + m_trail.push_back(var); + return var; + } + + void theory_str::track_variable_scope(expr * var) { + if (internal_variable_scope_levels.find(sLevel) == internal_variable_scope_levels.end()) { + internal_variable_scope_levels[sLevel] = std::set(); } - } - if (constOK) { - TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); - expr_ref constStr(mk_string(flattenedString), m); - expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); - assert_axiom(axiom); - } -} - -/* - * Instantiate an axiom of the following form: - * Length(Concat(x, y)) = Length(x) + Length(y) - */ -void theory_str::instantiate_concat_axiom(enode * cat) { - app * a_cat = cat->get_owner(); - SASSERT(u.str.is_concat(a_cat)); - - ast_manager & m = get_manager(); - - TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); - - // build LHS - expr_ref len_xy(m); - len_xy = mk_strlen(a_cat); - SASSERT(len_xy); - - // build RHS: start by extracting x and y from Concat(x, y) - unsigned nArgs = a_cat->get_num_args(); - SASSERT(nArgs == 2); - app * a_x = to_app(a_cat->get_arg(0)); - app * a_y = to_app(a_cat->get_arg(1)); - - expr_ref len_x(m); - len_x = mk_strlen(a_x); - SASSERT(len_x); - - expr_ref len_y(m); - len_y = mk_strlen(a_y); - SASSERT(len_y); - - // now build len_x + len_y - expr_ref len_x_plus_len_y(m); - len_x_plus_len_y = m_autil.mk_add(len_x, len_y); - SASSERT(len_x_plus_len_y); - - // finally assert equality between the two subexpressions - app * eq = m.mk_eq(len_xy, len_x_plus_len_y); - SASSERT(eq); - assert_axiom(eq); -} - -/* - * Add axioms that are true for any string variable: - * 1. Length(x) >= 0 - * 2. Length(x) == 0 <=> x == "" - * If the term is a string constant, we can assert something stronger: - * Length(x) == strlen(x) - */ -void theory_str::instantiate_basic_string_axioms(enode * str) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); - - // TESTING: attempt to avoid a crash here when a variable goes out of scope - if (str->get_iscope_lvl() > ctx.get_scope_level()) { - TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); - return; + internal_variable_scope_levels[sLevel].insert(var); } - // generate a stronger axiom for constant strings - app * a_str = str->get_owner(); - if (u.str.is_string(a_str)) { - expr_ref len_str(m); - len_str = mk_strlen(a_str); - SASSERT(len_str); + app * theory_str::mk_internal_xor_var() { + return mk_int_var("$$_xor"); + } - zstring strconst; - u.str.is_string(str->get_owner(), strconst); - TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); - unsigned int l = strconst.length(); - expr_ref len(m_autil.mk_numeral(rational(l), true), m); + app * theory_str::mk_int_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating integer variable " << name << " at scope level " << sLevel << std::endl;); + + sort * int_sort = m.mk_sort(m_autil.get_family_id(), INT_SORT); + app * a = m.mk_fresh_const(name.c_str(), int_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + ctx.mark_as_relevant(a); + // I'm assuming that this combination will do the correct thing in the integer theory. + + //mk_var(ctx.get_enode(a)); + m_trail.push_back(a); + //variable_set.insert(a); + //internal_variable_set.insert(a); + //track_variable_scope(a); + + return a; + } + + app * theory_str::mk_unroll_bound_var() { + return mk_int_var("unroll"); + } + + app * theory_str::mk_unroll_test_var() { + app * v = mk_str_var("unrollTest"); // was uRt + internal_unrollTest_vars.insert(v); + track_variable_scope(v); + return v; + } + + app * theory_str::mk_str_var(std::string name) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "creating string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const(name.c_str(), string_sort); + + TRACE("str", tout << "a->get_family_id() = " << a->get_family_id() << std::endl + << "this->get_family_id() = " << this->get_family_id() << std::endl;); + + // I have a hunch that this may not get internalized for free... + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + // this might help?? + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + app * theory_str::mk_regex_rep_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const("regex", string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + SASSERT(ctx.e_internalized(a)); + mk_var(ctx.get_enode(a)); + m_basicstr_axiom_todo.push_back(ctx.get_enode(a)); + TRACE("str", tout << "add " << mk_pp(a, m) << " to m_basicstr_axiom_todo" << std::endl;); + + m_trail.push_back(a); + variable_set.insert(a); + //internal_variable_set.insert(a); + regex_variable_set.insert(a); + track_variable_scope(a); + + return a; + } + + void theory_str::add_nonempty_constraint(expr * s) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref ax1(m.mk_not(ctx.mk_eq_atom(s, mk_string(""))), m); + assert_axiom(ax1); - literal lit(mk_eq(len_str, len, false)); - ctx.mark_as_relevant(lit); - ctx.mk_th_axiom(get_id(), 1, &lit); - } else { - // build axiom 1: Length(a_str) >= 0 { // build LHS - expr_ref len_str(m); - len_str = mk_strlen(a_str); + expr_ref len_str(mk_strlen(s), m); SASSERT(len_str); // build RHS - expr_ref zero(m); - zero = m_autil.mk_numeral(rational(0), true); + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); SASSERT(zero); - // build LHS >= RHS and assert - app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); - SASSERT(lhs_ge_rhs); - TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); - assert_axiom(lhs_ge_rhs); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); } + } - // build axiom 2: Length(a_str) == 0 <=> a_str == "" + app * theory_str::mk_nonempty_str_var() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + std::stringstream ss; + ss << tmpStringVarCount; + tmpStringVarCount++; + std::string name = "$$_str" + ss.str(); + + TRACE("str", tout << "creating nonempty string variable " << name << " at scope level " << sLevel << std::endl;); + + sort * string_sort = u.str.mk_string_sort(); + app * a = m.mk_fresh_const(name.c_str(), string_sort); + + ctx.internalize(a, false); + SASSERT(ctx.get_enode(a) != NULL); + // this might help?? + mk_var(ctx.get_enode(a)); + + // assert a variation of the basic string axioms that ensures this string is nonempty { - // build LHS of iff - expr_ref len_str(m); - len_str = mk_strlen(a_str); + // build LHS + expr_ref len_str(mk_strlen(a), m); SASSERT(len_str); - expr_ref zero(m); - zero = m_autil.mk_numeral(rational(0), true); + // build RHS + expr_ref zero(m_autil.mk_numeral(rational(0), true), m); SASSERT(zero); - expr_ref lhs(m); - lhs = ctx.mk_eq_atom(len_str, zero); - SASSERT(lhs); - // build RHS of iff - expr_ref empty_str(m); - empty_str = mk_string(""); - SASSERT(empty_str); - expr_ref rhs(m); - rhs = ctx.mk_eq_atom(a_str, empty_str); - SASSERT(rhs); - // build LHS <=> RHS and assert - TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); - literal l(mk_eq(lhs, rhs, true)); - ctx.mark_as_relevant(l); - ctx.mk_th_axiom(get_id(), 1, &l); + // build LHS > RHS and assert + // we have to build !(LHS <= RHS) instead + expr_ref lhs_gt_rhs(m.mk_not(m_autil.mk_le(len_str, zero)), m); + SASSERT(lhs_gt_rhs); + assert_axiom(lhs_gt_rhs); } - } -} + // add 'a' to variable sets, so we can keep track of it + m_trail.push_back(a); + variable_set.insert(a); + internal_variable_set.insert(a); + track_variable_scope(a); -/* - * Add an axiom of the form: - * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) - */ -void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * a_lhs = lhs->get_owner(); - app * a_rhs = rhs->get_owner(); - - // build premise: (lhs == rhs) - expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); - - // build conclusion: ( Length(lhs) == Length(rhs) ) - expr_ref len_lhs(mk_strlen(a_lhs), m); - SASSERT(len_lhs); - expr_ref len_rhs(mk_strlen(a_rhs), m); - SASSERT(len_rhs); - expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); - - TRACE("str", tout << "string-eq length-eq axiom: " - << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); - assert_implication(premise, conclusion); -} - -void theory_str::instantiate_axiom_CharAt(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - expr_ref ts2(mk_str_var("ts2"), m); - - expr_ref cond(m.mk_and( - m_autil.mk_ge(expr->get_arg(1), mk_int(0)), - // REWRITE for arithmetic theory: - // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) - m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) - ), m); - - expr_ref_vector and_item(m); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); - and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); - - expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); - expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); - - expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); - expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); - - SASSERT(axiom); - SASSERT(reductionVar); - - expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_prefixof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - - expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); - expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); - SASSERT(then1); - - // the top-level condition is Length(arg0) >= Length(arg1) - expr_ref topLevelCond( - m_autil.mk_ge( - m_autil.mk_add( - mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), - mk_int(0)) - , m); - SASSERT(topLevelCond); - - expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_suffixof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); - - expr_ref_vector innerItems(m); - innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); - innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); - innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); - expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); - SASSERT(then1); - - // the top-level condition is Length(arg0) >= Length(arg1) - expr_ref topLevelCond( - m_autil.mk_ge( - m_autil.mk_add( - mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), - mk_int(0)) - , m); - SASSERT(topLevelCond); - - expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Contains(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - // quick path, because this is necessary due to rewriter behaviour - // at minimum it should fix z3str/concat-006.smt2 - zstring haystackStr, needleStr; - if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { - TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); - if (haystackStr.contains(needleStr)) { - assert_axiom(ex); - } else { - assert_axiom(m.mk_not(ex)); - } - return; + return a; } - { // register Contains() - expr * str = ex->get_arg(0); - expr * substr = ex->get_arg(1); - contains_map.push_back(ex); - std::pair key = std::pair(str, substr); - contain_pair_bool_map.insert(str, substr, ex); - contain_pair_idx_map[str].insert(key); - contain_pair_idx_map[substr].insert(key); - } + app * theory_str::mk_unroll(expr * n, expr * bound) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + expr * args[2] = {n, bound}; + app * unrollFunc = get_manager().mk_app(get_id(), _OP_RE_UNROLL, 0, 0, 2, args); + m_trail.push_back(unrollFunc); - expr_ref ts0(mk_str_var("ts0"), m); - expr_ref ts1(mk_str_var("ts1"), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(bound, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + items.push_back(m_autil.mk_ge(bound, mk_int(0))); + items.push_back(m_autil.mk_ge(mk_strlen(unrollFunc), mk_int(0))); - expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); - SASSERT(breakdownAssert); - assert_axiom(breakdownAssert); -} - -void theory_str::instantiate_axiom_Indexof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref indexAst(mk_int_var("index"), m); - - expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); - SASSERT(condAst); - - // ----------------------- - // true branch - expr_ref_vector thenItems(m); - // args[0] = x1 . args[1] . x2 - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); - // indexAst = |x1| - thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); - // args[0] = x3 . x4 - // /\ |x3| = |x1| + |args[1]| - 1 - // /\ ! contains(x3, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); - SASSERT(tmpLen); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); - expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); - SASSERT(thenBranch); - - // ----------------------- - // false branch - expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); - SASSERT(elseBranch); - - expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); - SASSERT(breakdownAssert); - - expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); - SASSERT(reduceToIndex); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Indexof2(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - - // ------------------------------------------------------------------------------- - // if (arg[2] >= length(arg[0])) // ite2 - // resAst = -1 - // else - // args[0] = prefix . suffix - // /\ indexAst = indexof(suffix, arg[1]) - // /\ args[2] = len(prefix) - // /\ if (indexAst == -1) resAst = indexAst // ite3 - // else resAst = args[2] + indexAst - // ------------------------------------------------------------------------------- - - expr_ref resAst(mk_int_var("res"), m); - expr_ref indexAst(mk_int_var("index"), m); - expr_ref prefix(mk_str_var("prefix"), m); - expr_ref suffix(mk_str_var("suffix"), m); - expr_ref prefixLen(mk_strlen(prefix), m); - expr_ref zeroAst(mk_int(0), m); - expr_ref negOneAst(mk_int(-1), m); - - expr_ref ite3(m.mk_ite( - ctx.mk_eq_atom(indexAst, negOneAst), - ctx.mk_eq_atom(resAst, negOneAst), - ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) - ),m); - - expr_ref_vector ite2ElseItems(m); - ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); - ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); - ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); - ite2ElseItems.push_back(ite3); - expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); - SASSERT(ite2Else); - - expr_ref ite2(m.mk_ite( - //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), - m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), - ctx.mk_eq_atom(resAst, negOneAst), - ite2Else - ), m); - SASSERT(ite2); - - expr_ref ite1(m.mk_ite( - //m_autil.mk_lt(expr->get_arg(2), zeroAst), - m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), - ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), - ite2 - ), m); - SASSERT(ite1); - assert_axiom(ite1); - - expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); - SASSERT(reduceTerm); - assert_axiom(reduceTerm); -} - -void theory_str::instantiate_axiom_LastIndexof(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref indexAst(mk_int_var("index"), m); - expr_ref_vector items(m); - - // args[0] = x1 . args[1] . x2 - expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); - expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) - items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); - - - expr_ref condAst(arg0HasArg1, m); - //---------------------------- - // true branch - expr_ref_vector thenItems(m); - thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); - // args[0] = x1 . args[1] . x2 - // x1 doesn't contain args[1] - thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); - thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); - - bool canSkip = false; - zstring arg1Str; - if (u.str.is_string(expr->get_arg(1), arg1Str)) { - if (arg1Str.length() == 1) { - canSkip = true; - } - } - - if (!canSkip) { - // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); - } - //---------------------------- - // else branch - expr_ref_vector elseItems(m); - elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); - - items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); - - expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); - SASSERT(breakdownAssert); - - expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); - SASSERT(reduceToIndex); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Substr(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref substrBase(expr->get_arg(0), m); - expr_ref substrPos(expr->get_arg(1), m); - expr_ref substrLen(expr->get_arg(2), m); - SASSERT(substrBase); - SASSERT(substrPos); - SASSERT(substrLen); - - expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); - expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); - SASSERT(zero); - SASSERT(minusOne); - - expr_ref_vector argumentsValid_terms(m); - // pos >= 0 - argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); - // pos < strlen(base) - // --> pos + -1*strlen(base) < 0 - argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( - m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), - zero))); - // len >= 0 - argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); - - expr_ref argumentsValid(mk_and(argumentsValid_terms), m); - SASSERT(argumentsValid); - ctx.internalize(argumentsValid, false); - - // (pos+len) >= strlen(base) - // --> pos + len + -1*strlen(base) >= 0 - expr_ref lenOutOfBounds(m_autil.mk_ge( - m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), - zero), m); - SASSERT(lenOutOfBounds); - ctx.internalize(argumentsValid, false); - - // Case 1: pos < 0 or pos >= strlen(base) or len < 0 - // ==> (Substr ...) = "" - expr_ref case1_premise(m.mk_not(argumentsValid), m); - SASSERT(case1_premise); - ctx.internalize(case1_premise, false); - expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); - SASSERT(case1_conclusion); - ctx.internalize(case1_conclusion, false); - expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); - SASSERT(case1); - - // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) - // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 - expr_ref t0(mk_str_var("t0"), m); - expr_ref t1(mk_str_var("t1"), m); - expr_ref case2_conclusion(m.mk_and( - ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), - ctx.mk_eq_atom(mk_strlen(t0), substrPos), - ctx.mk_eq_atom(expr, t1)), m); - expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); - SASSERT(case2); - - // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) - // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 - expr_ref t2(mk_str_var("t2"), m); - expr_ref t3(mk_str_var("t3"), m); - expr_ref t4(mk_str_var("t4"), m); - expr_ref_vector case3_conclusion_terms(m); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); - case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); - expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); - expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); - SASSERT(case3); - - ctx.internalize(case1, false); - ctx.internalize(case2, false); - ctx.internalize(case3, false); - - expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_Replace(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * expr = e->get_owner(); - if (axiomatized_terms.contains(expr)) { - TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); - return; - } - axiomatized_terms.insert(expr); - - TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); - - expr_ref x1(mk_str_var("x1"), m); - expr_ref x2(mk_str_var("x2"), m); - expr_ref i1(mk_int_var("i1"), m); - expr_ref result(mk_str_var("result"), m); - - // condAst = Contains(args[0], args[1]) - expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); - // ----------------------- - // true branch - expr_ref_vector thenItems(m); - // args[0] = x1 . args[1] . x2 - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); - // i1 = |x1| - thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); - // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) - expr_ref x3(mk_str_var("x3"), m); - expr_ref x4(mk_str_var("x4"), m); - expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); - thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); - thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); - thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); - thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); - // ----------------------- - // false branch - expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); - - expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); - SASSERT(breakdownAssert); - - expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); - SASSERT(reduceToResult); - - expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); -} - -void theory_str::instantiate_axiom_str_to_int(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); - - // let expr = (str.to-int S) - // axiom 1: expr >= -1 - // axiom 2: expr = 0 <==> S = "0" - // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" - - expr * S = ex->get_arg(0); - { - expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); - SASSERT(axiom1); - assert_axiom(axiom1); - } - - { - expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); - expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); - expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); - SASSERT(axiom2); - assert_axiom(axiom2); - } - - { - expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); - expr_ref hd(mk_str_var("hd"), m); - expr_ref tl(mk_str_var("tl"), m); - expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); - expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); - expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); - expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); - SASSERT(premise); - SASSERT(conclusion); - assert_implication(premise, conclusion); - } -} - -void theory_str::instantiate_axiom_int_to_str(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); - - // axiom 1: N < 0 <==> (str.from-int N) = "" - expr * N = ex->get_arg(0); - { - expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); - expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); - expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); - SASSERT(axiom1); - assert_axiom(axiom1); - } -} - -expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { - app * regexIn = u.re.mk_in_re(str, regexp); - // immediately force internalization so that axiom setup does not fail - get_context().internalize(regexIn, false); - set_up_axioms(regexIn); - return regexIn; -} - -static zstring str2RegexStr(zstring str) { - zstring res(""); - int len = str.length(); - for (int i = 0; i < len; i++) { - char nc = str[i]; - // 12 special chars - if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' - || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { - res = res + zstring("\\"); - } - char tmp[2] = {(char)str[i], '\0'}; - res = res + zstring(tmp); - } - return res; -} - -zstring theory_str::get_std_regex_str(expr * regex) { - app * a_regex = to_app(regex); - if (u.re.is_to_re(a_regex)) { - expr * regAst = a_regex->get_arg(0); - zstring regAstVal; - u.str.is_string(regAst, regAstVal); - zstring regStr = str2RegexStr(regAstVal); - return regStr; - } else if (u.re.is_concat(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - zstring reg1Str = get_std_regex_str(reg1Ast); - zstring reg2Str = get_std_regex_str(reg2Ast); - return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); - } else if (u.re.is_union(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - expr * reg2Ast = a_regex->get_arg(1); - zstring reg1Str = get_std_regex_str(reg1Ast); - zstring reg2Str = get_std_regex_str(reg2Ast); - return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); - } else if (u.re.is_star(a_regex)) { - expr * reg1Ast = a_regex->get_arg(0); - zstring reg1Str = get_std_regex_str(reg1Ast); - return zstring("(") + reg1Str + zstring(")*"); - } else if (u.re.is_range(a_regex)) { - expr * range1 = a_regex->get_arg(0); - expr * range2 = a_regex->get_arg(1); - zstring range1val, range2val; - u.str.is_string(range1, range1val); - u.str.is_string(range2, range2val); - return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); - } else { - TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); - UNREACHABLE(); return zstring(""); - } -} - -void theory_str::instantiate_axiom_RegexIn(enode * e) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - app * ex = e->get_owner(); - if (axiomatized_terms.contains(ex)) { - TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); - return; - } - axiomatized_terms.insert(ex); - - TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); - - { - zstring regexStr = get_std_regex_str(ex->get_arg(1)); - std::pair key1(ex->get_arg(0), regexStr); - // skip Z3str's map check, because we already check if we set up axioms on this term - regex_in_bool_map[key1] = ex; - regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); - } - - expr_ref str(ex->get_arg(0), m); - app * regex = to_app(ex->get_arg(1)); - - if (u.re.is_to_re(regex)) { - expr_ref rxStr(regex->get_arg(0), m); - // want to assert 'expr IFF (str == rxStr)' - expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); - expr_ref finalAxiom(m.mk_iff(ex, rhs), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); - } else if (u.re.is_concat(regex)) { - expr_ref var1(mk_regex_rep_var(), m); - expr_ref var2(mk_regex_rep_var(), m); - expr_ref rhs(mk_concat(var1, var2), m); - expr_ref rx1(regex->get_arg(0), m); - expr_ref rx2(regex->get_arg(1), m); - expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); - expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); - - expr_ref_vector items(m); - items.push_back(var1InRegex1); - items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); - - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - } else if (u.re.is_union(regex)) { - expr_ref var1(mk_regex_rep_var(), m); - expr_ref var2(mk_regex_rep_var(), m); - expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); - expr_ref regex1(regex->get_arg(0), m); - expr_ref regex2(regex->get_arg(1), m); - expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); - expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); - expr_ref_vector items(m); - items.push_back(var1InRegex1); - items.push_back(var2InRegex2); - items.push_back(ctx.mk_eq_atom(ex, orVar)); - assert_axiom(mk_and(items)); - } else if (u.re.is_star(regex)) { - // slightly more complex due to the unrolling step. - expr_ref regex1(regex->get_arg(0), m); - expr_ref unrollCount(mk_unroll_bound_var(), m); - expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); - expr_ref_vector items(m); - items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); - items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); - expr_ref finalAxiom(mk_and(items), m); - SASSERT(finalAxiom); - assert_axiom(finalAxiom); - } else if (u.re.is_range(regex)) { - // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); - // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") - expr_ref lo(regex->get_arg(0), m); - expr_ref hi(regex->get_arg(1), m); - zstring str_lo, str_hi; - SASSERT(u.str.is_string(lo)); - SASSERT(u.str.is_string(hi)); - u.str.is_string(lo, str_lo); - u.str.is_string(hi, str_hi); - SASSERT(str_lo.length() == 1); - SASSERT(str_hi.length() == 1); - unsigned int c1 = str_lo[0]; - unsigned int c2 = str_hi[0]; - if (c1 > c2) { - // exchange - unsigned int tmp = c1; - c1 = c2; - c2 = tmp; - } - expr_ref_vector range_cases(m); - for (unsigned int ch = c1; ch <= c2; ++ch) { - zstring s_ch(ch); - expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); - range_cases.push_back(rhs); - } - expr_ref rhs(mk_or(range_cases), m); - expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + expr_ref finalAxiom(mk_and(items), m); SASSERT(finalAxiom); assert_axiom(finalAxiom); - } else { - TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); - NOT_IMPLEMENTED_YET(); - } -} - -void theory_str::attach_new_th_var(enode * n) { - context & ctx = get_context(); - theory_var v = mk_var(n); - ctx.attach_th_var(n, this, v); - TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); -} - -void theory_str::reset_eh() { - TRACE("str", tout << "resetting" << std::endl;); - m_trail_stack.reset(); - - m_basicstr_axiom_todo.reset(); - m_str_eq_todo.reset(); - m_concat_axiom_todo.reset(); - pop_scope_eh(get_context().get_scope_level()); -} - -/* - * Check equality among equivalence class members of LHS and RHS - * to discover an incorrect LHS == RHS. - * For example, if we have y2 == "str3" - * and the equivalence classes are - * { y2, (Concat ce m2) } - * { "str3", (Concat abc x2) } - * then y2 can't be equal to "str3". - * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") - */ -bool theory_str::new_eq_check(expr * lhs, expr * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // skip this check if we defer consistency checking, as we can do it for every EQC in final check - if (!opt_DeferEQCConsistencyCheck) { - check_concat_len_in_eqc(lhs); - check_concat_len_in_eqc(rhs); + return unrollFunc; } - // Now we iterate over all pairs of terms across both EQCs - // and check whether we can show that any pair of distinct terms - // cannot possibly be equal. - // If that's the case, we assert an axiom to that effect and stop. + app * theory_str::mk_contains(expr * haystack, expr * needle) { + app * contains = u.str.mk_contains(haystack, needle); // TODO double-check semantics/argument order + m_trail.push_back(contains); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(contains, false); + set_up_axioms(contains); + return contains; + } - expr * eqc_nn1 = lhs; - do { - expr * eqc_nn2 = rhs; - do { - TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); - // inconsistency check: value - if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { - TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); - expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); - assert_axiom(to_assert); - // this shouldn't use the integer theory at all, so we don't allow the option of quick-return - return false; - } - if (!check_length_consistency(eqc_nn1, eqc_nn2)) { - TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); - if (opt_NoQuickReturn_IntegerTheory){ - TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); - } else { - return false; - } - } - eqc_nn2 = get_eqc_next(eqc_nn2); - } while (eqc_nn2 != rhs); - eqc_nn1 = get_eqc_next(eqc_nn1); - } while (eqc_nn1 != lhs); - - if (!contains_map.empty()) { - check_contain_in_new_eq(lhs, rhs); - } - - if (!regex_in_bool_map.empty()) { - TRACE("str", tout << "checking regex consistency" << std::endl;); - check_regex_in(lhs, rhs); - } - - // okay, all checks here passed - return true; -} - -// support for user_smt_theory-style EQC handling - -app * theory_str::get_ast(theory_var i) { - return get_enode(i)->get_owner(); -} - -theory_var theory_str::get_var(expr * n) const { - if (!is_app(n)) { - return null_theory_var; - } - context & ctx = get_context(); - if (ctx.e_internalized(to_app(n))) { - enode * e = ctx.get_enode(to_app(n)); - return e->get_th_var(get_id()); - } - return null_theory_var; -} - -// simulate Z3_theory_get_eqc_next() -expr * theory_str::get_eqc_next(expr * n) { - theory_var v = get_var(n); - if (v != null_theory_var) { - theory_var r = m_find.next(v); - return get_ast(r); - } - return n; -} - -void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { - context & ctx = get_context(); - expr * eqcNode = n; - do { - app * ast = to_app(eqcNode); - if (u.str.is_concat(ast)) { - expr * simConcat = simplify_concat(ast); - if (simConcat != ast) { - if (u.str.is_concat(to_app(simConcat))) { - concats.insert(simConcat); - } else { - if (u.str.is_string(simConcat)) { - consts.insert(simConcat); - } else { - vars.insert(simConcat); - } + app * theory_str::mk_indexof(expr * haystack, expr * needle) { + // TODO check meaning of the third argument here + app * indexof = u.str.mk_index(haystack, needle, mk_int(0)); + m_trail.push_back(indexof); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(indexof, false); + set_up_axioms(indexof); + return indexof; + } + + app * theory_str::mk_strlen(expr * e) { + /*if (m_strutil.is_string(e)) {*/ if (false) { + zstring strval; + u.str.is_string(e, strval); + unsigned int len = strval.length(); + return m_autil.mk_numeral(rational(len), true); + } else { + if (false) { + // use cache + app * lenTerm = NULL; + if (!length_ast_map.find(e, lenTerm)) { + lenTerm = u.str.mk_length(e); + length_ast_map.insert(e, lenTerm); + m_trail.push_back(lenTerm); } + return lenTerm; } else { - concats.insert(simConcat); + // always regen + return u.str.mk_length(e); } - } else if (u.str.is_string(ast)) { - consts.insert(ast); - } else { - vars.insert(ast); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} - -void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { - app * a_node = to_app(node); - if (!u.str.is_concat(a_node)) { - nodeList.push_back(node); - return; - } else { - SASSERT(a_node->get_num_args() == 2); - expr * leftArg = a_node->get_arg(0); - expr * rightArg = a_node->get_arg(1); - get_nodes_in_concat(leftArg, nodeList); - get_nodes_in_concat(rightArg, nodeList); - } -} - -// previously Concat() in strTheory.cpp -// Evaluates the concatenation (n1 . n2) with respect to -// the current equivalence classes of n1 and n2. -// Returns a constant string expression representing this concatenation -// if one can be determined, or NULL if this is not possible. -expr * theory_str::eval_concat(expr * n1, expr * n2) { - bool n1HasEqcValue = false; - bool n2HasEqcValue = false; - expr * v1 = get_eqc_value(n1, n1HasEqcValue); - expr * v2 = get_eqc_value(n2, n2HasEqcValue); - if (n1HasEqcValue && n2HasEqcValue) { - zstring n1_str, n2_str; - u.str.is_string(v1, n1_str); - u.str.is_string(v2, n2_str); - zstring result = n1_str + n2_str; - return mk_string(result); - } else if (n1HasEqcValue && !n2HasEqcValue) { - zstring v1_str; - u.str.is_string(v1, v1_str); - if (v1_str.empty()) { - return n2; - } - } else if (n2HasEqcValue && !n1HasEqcValue) { - zstring v2_str; - u.str.is_string(v2, v2_str); - if (v2_str.empty()) { - return n1; - } - } - // give up - return NULL; -} - -static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { - if (x_exists) { - return x.to_string(); - } else { - return "?"; - } -} - -/* - * The inputs: - * ~ nn: non const node - * ~ eq_str: the equivalent constant string of nn - * Iterate the parent of all eqc nodes of nn, looking for: - * ~ concat node - * to see whether some concat nodes can be simplified. - */ -void theory_str::simplify_parent(expr * nn, expr * eq_str) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) - << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); - - ctx.internalize(nn, false); - - zstring eq_strValue; - u.str.is_string(eq_str, eq_strValue); - expr * n_eqNode = nn; - do { - enode * n_eq_enode = ctx.get_enode(n_eqNode); - TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl - << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); - - // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. - // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, - // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. - - enode_vector current_parents; - for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { - current_parents.insert(*parent_it); - } - - for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { - enode * e_parent = *parent_it; - SASSERT(e_parent != NULL); - - app * a_parent = e_parent->get_owner(); - TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); - - if (u.str.is_concat(a_parent)) { - expr * arg0 = a_parent->get_arg(0); - expr * arg1 = a_parent->get_arg(1); - - rational parentLen; - bool parentLen_exists = get_len_value(a_parent, parentLen); - - if (arg0 == n_eq_enode->get_owner()) { - rational arg0Len, arg1Len; - bool arg0Len_exists = get_len_value(eq_str, arg0Len); - bool arg1Len_exists = get_len_value(arg1, arg1Len); - - TRACE("str", - tout << "simplify_parent #1:" << std::endl - << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl - << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl - << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; - ); - - if (parentLen_exists && !arg1Len_exists) { - TRACE("str", tout << "make up len for arg1" << std::endl;); - expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), - ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); - rational makeUpLenArg1 = parentLen - arg0Len; - if (makeUpLenArg1.is_nonneg()) { - expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); - assert_implication(implyL11, implyR11); - } else { - expr_ref neg(m.mk_not(implyL11), m); - assert_axiom(neg); - } - } - - // (Concat n_eqNode arg1) /\ arg1 has eq const - - expr * concatResult = eval_concat(eq_str, arg1); - if (concatResult != NULL) { - bool arg1HasEqcValue = false; - expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); - expr_ref implyL(m); - if (arg1 != arg1Value) { - expr_ref eq_ast1(m); - eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(eq_ast1); - - expr_ref eq_ast2(m); - eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); - SASSERT(eq_ast2); - implyL = m.mk_and(eq_ast1, eq_ast2); - } else { - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - } - - - if (!in_same_eqc(a_parent, concatResult)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, concatResult); - SASSERT(implyR); - - assert_implication(implyL, implyR); - } - } else if (u.str.is_concat(to_app(n_eqNode))) { - expr_ref simpleConcat(m); - simpleConcat = mk_concat(eq_str, arg1); - if (!in_same_eqc(a_parent, simpleConcat)) { - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(implyL); - - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simpleConcat); - SASSERT(implyR); - assert_implication(implyL, implyR); - } - } - } // if (arg0 == n_eq_enode->get_owner()) - - if (arg1 == n_eq_enode->get_owner()) { - rational arg0Len, arg1Len; - bool arg0Len_exists = get_len_value(arg0, arg0Len); - bool arg1Len_exists = get_len_value(eq_str, arg1Len); - - TRACE("str", - tout << "simplify_parent #2:" << std::endl - << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl - << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl - << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl - << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; - ); - if (parentLen_exists && !arg0Len_exists) { - TRACE("str", tout << "make up len for arg0" << std::endl;); - expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), - ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); - rational makeUpLenArg0 = parentLen - arg1Len; - if (makeUpLenArg0.is_nonneg()) { - expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); - assert_implication(implyL11, implyR11); - } else { - expr_ref neg(m.mk_not(implyL11), m); - assert_axiom(neg); - } - } - - // (Concat arg0 n_eqNode) /\ arg0 has eq const - - expr * concatResult = eval_concat(arg0, eq_str); - if (concatResult != NULL) { - bool arg0HasEqcValue = false; - expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); - expr_ref implyL(m); - if (arg0 != arg0Value) { - expr_ref eq_ast1(m); - eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(eq_ast1); - expr_ref eq_ast2(m); - eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); - SASSERT(eq_ast2); - - implyL = m.mk_and(eq_ast1, eq_ast2); - } else { - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - } - - if (!in_same_eqc(a_parent, concatResult)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, concatResult); - SASSERT(implyR); - - assert_implication(implyL, implyR); - } - } else if (u.str.is_concat(to_app(n_eqNode))) { - expr_ref simpleConcat(m); - simpleConcat = mk_concat(arg0, eq_str); - if (!in_same_eqc(a_parent, simpleConcat)) { - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - SASSERT(implyL); - - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simpleConcat); - SASSERT(implyR); - assert_implication(implyL, implyR); - } - } - } // if (arg1 == n_eq_enode->get_owner - - - //--------------------------------------------------------- - // Case (2-1) begin: (Concat n_eqNode (Concat str var)) - if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { - app * a_arg1 = to_app(arg1); - TRACE("str", tout << "simplify_parent #3" << std::endl;); - expr * r_concat_arg0 = a_arg1->get_arg(0); - if (u.str.is_string(r_concat_arg0)) { - expr * combined_str = eval_concat(eq_str, r_concat_arg0); - SASSERT(combined_str); - expr * r_concat_arg1 = a_arg1->get_arg(1); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); - if (!in_same_eqc(a_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - // Case (2-1) end: (Concat n_eqNode (Concat str var)) - //--------------------------------------------------------- - - - //--------------------------------------------------------- - // Case (2-2) begin: (Concat (Concat var str) n_eqNode) - if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { - app * a_arg0 = to_app(arg0); - TRACE("str", tout << "simplify_parent #4" << std::endl;); - expr * l_concat_arg1 = a_arg0->get_arg(1); - if (u.str.is_string(l_concat_arg1)) { - expr * combined_str = eval_concat(l_concat_arg1, eq_str); - SASSERT(combined_str); - expr * l_concat_arg0 = a_arg0->get_arg(0); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); - if (!in_same_eqc(a_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - // Case (2-2) end: (Concat (Concat var str) n_eqNode) - //--------------------------------------------------------- - - // Have to look up one more layer: if the parent of the concat is another concat - //------------------------------------------------- - // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) - if (arg1 == n_eqNode) { - for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); - concat_parent_it != e_parent->end_parents(); concat_parent_it++) { - enode * e_concat_parent = *concat_parent_it; - app * concat_parent = e_concat_parent->get_owner(); - if (u.str.is_concat(concat_parent)) { - expr * concat_parent_arg0 = concat_parent->get_arg(0); - expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { - TRACE("str", tout << "simplify_parent #5" << std::endl;); - expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); - SASSERT(combinedStr); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(arg0, combinedStr); - if (!in_same_eqc(concat_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - } - } - // Case (3-1) end: (Concat (Concat var n_eqNode) str ) - // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) - if (arg0 == n_eqNode) { - for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); - concat_parent_it != e_parent->end_parents(); concat_parent_it++) { - enode * e_concat_parent = *concat_parent_it; - app * concat_parent = e_concat_parent->get_owner(); - if (u.str.is_concat(concat_parent)) { - expr * concat_parent_arg0 = concat_parent->get_arg(0); - expr * concat_parent_arg1 = concat_parent->get_arg(1); - if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { - TRACE("str", tout << "simplify_parent #6" << std::endl;); - expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); - SASSERT(combinedStr); - expr_ref implyL(m); - implyL = ctx.mk_eq_atom(n_eqNode, eq_str); - expr * simplifiedAst = mk_concat(combinedStr, arg1); - if (!in_same_eqc(concat_parent, simplifiedAst)) { - expr_ref implyR(m); - implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); - assert_implication(implyL, implyR); - } - } - } - } - } - // Case (3-2) end: (Concat str (Concat n_eqNode var) ) - } // if is_concat(a_parent) - } // for parent_it : n_eq_enode->begin_parents() - - - // check next EQC member - n_eqNode = get_eqc_next(n_eqNode); - } while (n_eqNode != nn); -} - -expr * theory_str::simplify_concat(expr * node) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - std::map resolvedMap; - ptr_vector argVec; - get_nodes_in_concat(node, argVec); - - for (unsigned i = 0; i < argVec.size(); ++i) { - bool vArgHasEqcValue = false; - expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); - if (vArg != argVec[i]) { - resolvedMap[argVec[i]] = vArg; - } - } - - if (resolvedMap.size() == 0) { - // no simplification possible - return node; - } else { - expr * resultAst = mk_string(""); - for (unsigned i = 0; i < argVec.size(); ++i) { - bool vArgHasEqcValue = false; - expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); - resultAst = mk_concat(resultAst, vArg); - } - TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); - - if (in_same_eqc(node, resultAst)) { - TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); - } else { - expr_ref_vector items(m); - int pos = 0; - std::map::iterator itor = resolvedMap.begin(); - for (; itor != resolvedMap.end(); ++itor) { - items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); - pos += 1; - } - expr_ref premise(mk_and(items), m); - expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); - assert_implication(premise, conclusion); - } - return resultAst; - } - -} - -// Modified signature of Z3str2's inferLenConcat(). -// Returns true iff nLen can be inferred by this method -// (i.e. the equivalent of a len_exists flag in get_len_value()). - -bool theory_str::infer_len_concat(expr * n, rational & nLen) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr * arg0 = to_app(n)->get_arg(0); - expr * arg1 = to_app(n)->get_arg(1); - - rational arg0_len, arg1_len; - bool arg0_len_exists = get_len_value(arg0, arg0_len); - bool arg1_len_exists = get_len_value(arg1, arg1_len); - rational tmp_len; - bool nLen_exists = get_len_value(n, tmp_len); - - if (arg0_len_exists && arg1_len_exists && !nLen_exists) { - expr_ref_vector l_items(m); - // if (mk_strlen(arg0) != mk_int(arg0_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); - } - - // if (mk_strlen(arg1) != mk_int(arg1_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); - } - - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - rational nnLen = arg0_len + arg1_len; - expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); - TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); - assert_implication(axl, axr); - nLen = nnLen; - return true; - } else { - return false; - } -} - -void theory_str::infer_len_concat_arg(expr * n, rational len) { - if (len.is_neg()) { - return; - } - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * arg0 = to_app(n)->get_arg(0); - expr * arg1 = to_app(n)->get_arg(1); - rational arg0_len, arg1_len; - bool arg0_len_exists = get_len_value(arg0, arg0_len); - bool arg1_len_exists = get_len_value(arg1, arg1_len); - - expr_ref_vector l_items(m); - expr_ref axr(m); - axr.reset(); - - // if (mk_length(t, n) != mk_int(ctx, len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); - } - - if (!arg0_len_exists && arg1_len_exists) { - //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); - } - rational arg0Len = len - arg1_len; - if (arg0Len.is_nonneg()) { - axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); - } else { - // could negate - } - } else if (arg0_len_exists && !arg1_len_exists) { - //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { - { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); - } - rational arg1Len = len - arg0_len; - if (arg1Len.is_nonneg()) { - axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); - } else { - // could negate - } - } else { - - } - - if (axr) { - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - assert_implication(axl, axr); - } -} - -void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { - rational nnLen; - bool nnLen_exists = get_len_value(nn1, nnLen); - if (!nnLen_exists) { - nnLen_exists = get_len_value(nn2, nnLen); - } - - // case 1: - // Known: a1_arg0 and a1_arg1 - // Unknown: nn1 - - if (u.str.is_concat(to_app(nn1))) { - rational nn1ConcatLen; - bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); - if (nnLen_exists && nn1ConcatLen_exists) { - nnLen = nn1ConcatLen; - } - } - - // case 2: - // Known: a1_arg0 and a1_arg1 - // Unknown: nn1 - - if (u.str.is_concat(to_app(nn2))) { - rational nn2ConcatLen; - bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); - if (nnLen_exists && nn2ConcatLen_exists) { - nnLen = nn2ConcatLen; - } - } - - if (nnLen_exists) { - if (u.str.is_concat(to_app(nn1))) { - infer_len_concat_arg(nn1, nnLen); - } - if (u.str.is_concat(to_app(nn2))) { - infer_len_concat_arg(nn2, nnLen); } } /* - if (isConcatFunc(t, nn2)) { - int nn2ConcatLen = inferLenConcat(t, nn2); - if (nnLen == -1 && nn2ConcatLen != -1) - nnLen = nn2ConcatLen; - } - - if (nnLen != -1) { - if (isConcatFunc(t, nn1)) { - inferLenConcatArg(t, nn1, nnLen); - } - if (isConcatFunc(t, nn2)) { - inferLenConcatArg(t, nn2, nnLen); - } - } - */ -} - -void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { - context & ctx = get_context(); - ctx.internalize(term, false); - bool_var v = ctx.get_bool_var(term); - ctx.add_theory_aware_branching_info(v, priority, phase); -} - -void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { - context & ctx = get_context(); - // pull each literal out of the arrangement disjunction - literal_vector ls; - for (unsigned i = 0; i < terms.size(); ++i) { - expr * e = terms.get(i); - literal l = ctx.get_literal(e); - ls.push_back(l); - } - ctx.mk_th_case_split(ls.size(), ls.c_ptr()); -} - -void theory_str::print_cut_var(expr * node, std::ofstream & xout) { - ast_manager & m = get_manager(); - xout << "Cut info of " << mk_pp(node, m) << std::endl; - if (cut_var_map.contains(node)) { - if (!cut_var_map[node].empty()) { - xout << "[" << cut_var_map[node].top()->level << "] "; - std::map::iterator itor = cut_var_map[node].top()->vars.begin(); - for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { - xout << mk_pp(itor->first, m) << ", "; + * Returns the simplified concatenation of two expressions, + * where either both expressions are constant strings + * or one expression is the empty string. + * If this precondition does not hold, the function returns NULL. + * (note: this function was strTheory::Concat()) + */ + expr * theory_str::mk_concat_const_str(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + zstring n2_str; + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring n1_str; + u.str.is_string(v1, n1_str); + if (n1_str.empty()) { + return n2; + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(v2, n2_str); + if (n2_str.empty()) { + return n1; } - xout << std::endl; } - } -} - -/* - * Handle two equivalent Concats. - */ -void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - app * a_nn1 = to_app(nn1); - SASSERT(a_nn1->get_num_args() == 2); - app * a_nn2 = to_app(nn2); - SASSERT(a_nn2->get_num_args() == 2); - - expr * a1_arg0 = a_nn1->get_arg(0); - expr * a1_arg1 = a_nn1->get_arg(1); - expr * a2_arg0 = a_nn2->get_arg(0); - expr * a2_arg1 = a_nn2->get_arg(1); - - rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; - - bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); - bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); - bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); - bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); - - TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl - << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); - - TRACE("str", tout - << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl - << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl - << std::endl;); - - infer_len_concat_equality(nn1, nn2); - - if (a1_arg0 == a2_arg0) { - if (!in_same_eqc(a1_arg1, a2_arg1)) { - expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); - expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); - expr_ref conclusion(m.mk_and(eq1, eq2), m); - assert_implication(premise, conclusion); - } - TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); - return; + return NULL; } - if (a1_arg1 == a2_arg1) { - if (!in_same_eqc(a1_arg0, a2_arg0)) { - expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); - expr_ref conclusion(m.mk_and(eq1, eq2), m); - assert_implication(premise, conclusion); - } - TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); - return; - } + expr * theory_str::mk_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + ENSURE(n1 != NULL); + ENSURE(n2 != NULL); + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + n1 = get_eqc_value(n1, n1HasEqcValue); + n2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + return mk_concat_const_str(n1, n2); + } else if (n1HasEqcValue && !n2HasEqcValue) { + bool n2_isConcatFunc = u.str.is_concat(to_app(n2)); + zstring n1_str; + u.str.is_string(n1, n1_str); + if (n1_str.empty()) { + return n2; + } + if (n2_isConcatFunc) { + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n2_arg0)) { + n1 = mk_concat_const_str(n1, n2_arg0); // n1 will be a constant + n2 = n2_arg1; + } + } + } else if (!n1HasEqcValue && n2HasEqcValue) { + zstring n2_str; + u.str.is_string(n2, n2_str); + if (n2_str.empty()) { + return n1; + } - // quick path - - if (in_same_eqc(a1_arg0, a2_arg0)) { - if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); - return; + if (u.str.is_concat(to_app(n1))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + if (u.str.is_string(n1_arg1)) { + n1 = n1_arg0; + n2 = mk_concat_const_str(n1_arg1, n2); // n2 will be a constant + } + } } else { - TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); - expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); - expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); - assert_implication(premise, conclusion); - return; - } - } else { - if (in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); - expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); - expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); - assert_implication(premise, conclusion); - return; - } - } - - // quick path 2-1 - if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { - if (!in_same_eqc(a1_arg0, a2_arg0)) { - TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); - expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); - expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); - - expr_ref premise(m.mk_and(ax_l1, ax_l2), m); - expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); - - assert_implication(premise, conclusion); - - if (opt_NoQuickReturn_IntegerTheory) { - TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); - } else { - return; + if (u.str.is_concat(to_app(n1)) && u.str.is_concat(to_app(n2))) { + expr * n1_arg0 = to_app(n1)->get_arg(0); + expr * n1_arg1 = to_app(n1)->get_arg(1); + expr * n2_arg0 = to_app(n2)->get_arg(0); + expr * n2_arg1 = to_app(n2)->get_arg(1); + if (u.str.is_string(n1_arg1) && u.str.is_string(n2_arg0)) { + expr * tmpN1 = n1_arg0; + expr * tmpN2 = mk_concat_const_str(n1_arg1, n2_arg0); + n1 = mk_concat(tmpN1, tmpN2); + n2 = n2_arg1; + } } } + + //------------------------------------------------------ + // * expr * ast1 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // * expr * ast2 = mk_2_arg_app(ctx, td->Concat, n1, n2); + // Z3 treats (ast1) and (ast2) as two different nodes. + //------------------------------------------------------- + + expr * concatAst = NULL; + + if (!concat_astNode_map.find(n1, n2, concatAst)) { + concatAst = u.str.mk_concat(n1, n2); + m_trail.push_back(concatAst); + concat_astNode_map.insert(n1, n2, concatAst); + + expr_ref concat_length(mk_strlen(concatAst), m); + + ptr_vector childrenVector; + get_nodes_in_concat(concatAst, childrenVector); + expr_ref_vector items(m); + for (unsigned int i = 0; i < childrenVector.size(); i++) { + items.push_back(mk_strlen(childrenVector.get(i))); + } + expr_ref lenAssert(ctx.mk_eq_atom(concat_length, m_autil.mk_add(items.size(), items.c_ptr())), m); + assert_axiom(lenAssert); + } + return concatAst; } - if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { - if (!in_same_eqc(a1_arg1, a2_arg1)) { - TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); - expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); - expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); - expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); - expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + bool theory_str::can_propagate() { + return !m_basicstr_axiom_todo.empty() || !m_str_eq_todo.empty() + || !m_concat_axiom_todo.empty() || !m_concat_eval_todo.empty() + || !m_library_aware_axiom_todo.empty() + || !m_delayed_axiom_setup_terms.empty(); + ; + } - expr_ref premise(m.mk_and(ax_l1, ax_l2), m); - expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + void theory_str::propagate() { + context & ctx = get_context(); + while (can_propagate()) { + TRACE("str", tout << "propagating..." << std::endl;); + for (unsigned i = 0; i < m_basicstr_axiom_todo.size(); ++i) { + instantiate_basic_string_axioms(m_basicstr_axiom_todo[i]); + } + m_basicstr_axiom_todo.reset(); + TRACE("str", tout << "reset m_basicstr_axiom_todo" << std::endl;); - assert_implication(premise, conclusion); - if (opt_NoQuickReturn_IntegerTheory) { - TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + for (unsigned i = 0; i < m_str_eq_todo.size(); ++i) { + std::pair pair = m_str_eq_todo[i]; + enode * lhs = pair.first; + enode * rhs = pair.second; + handle_equality(lhs->get_owner(), rhs->get_owner()); + } + m_str_eq_todo.reset(); + + for (unsigned i = 0; i < m_concat_axiom_todo.size(); ++i) { + instantiate_concat_axiom(m_concat_axiom_todo[i]); + } + m_concat_axiom_todo.reset(); + + for (unsigned i = 0; i < m_concat_eval_todo.size(); ++i) { + try_eval_concat(m_concat_eval_todo[i]); + } + m_concat_eval_todo.reset(); + + for (unsigned i = 0; i < m_library_aware_axiom_todo.size(); ++i) { + enode * e = m_library_aware_axiom_todo[i]; + app * a = e->get_owner(); + if (u.str.is_stoi(a)) { + instantiate_axiom_str_to_int(e); + } else if (u.str.is_itos(a)) { + instantiate_axiom_int_to_str(e); + } else if (u.str.is_at(a)) { + instantiate_axiom_CharAt(e); + } else if (u.str.is_prefix(a)) { + instantiate_axiom_prefixof(e); + } else if (u.str.is_suffix(a)) { + instantiate_axiom_suffixof(e); + } else if (u.str.is_contains(a)) { + instantiate_axiom_Contains(e); + } else if (u.str.is_index(a)) { + instantiate_axiom_Indexof(e); + /* TODO NEXT: Indexof2/Lastindexof rewrite? + } else if (is_Indexof2(e)) { + instantiate_axiom_Indexof2(e); + } else if (is_LastIndexof(e)) { + instantiate_axiom_LastIndexof(e); + */ + } else if (u.str.is_extract(a)) { + // TODO check semantics of substr vs. extract + instantiate_axiom_Substr(e); + } else if (u.str.is_replace(a)) { + instantiate_axiom_Replace(e); + } else if (u.str.is_in_re(a)) { + instantiate_axiom_RegexIn(e); + } else { + TRACE("str", tout << "BUG: unhandled library-aware term " << mk_pp(e->get_owner(), get_manager()) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + } + m_library_aware_axiom_todo.reset(); + + for (unsigned i = 0; i < m_delayed_axiom_setup_terms.size(); ++i) { + // I think this is okay + ctx.internalize(m_delayed_axiom_setup_terms[i].get(), false); + set_up_axioms(m_delayed_axiom_setup_terms[i].get()); + } + m_delayed_axiom_setup_terms.reset(); + } + } + + /* + * Attempt to evaluate a concat over constant strings, + * and if this is possible, assert equality between the + * flattened string and the original term. + */ + + void theory_str::try_eval_concat(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "attempting to flatten " << mk_pp(a_cat, m) << std::endl;); + + std::stack worklist; + zstring flattenedString(""); + bool constOK = true; + + { + app * arg0 = to_app(a_cat->get_arg(0)); + app * arg1 = to_app(a_cat->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); + } + + while (constOK && !worklist.empty()) { + app * evalArg = worklist.top(); worklist.pop(); + zstring nextStr; + if (u.str.is_string(evalArg, nextStr)) { + flattenedString = flattenedString + nextStr; + } else if (u.str.is_concat(evalArg)) { + app * arg0 = to_app(evalArg->get_arg(0)); + app * arg1 = to_app(evalArg->get_arg(1)); + + worklist.push(arg1); + worklist.push(arg0); } else { - return; + TRACE("str", tout << "non-constant term in concat -- giving up." << std::endl;); + constOK = false; + break; } } + if (constOK) { + TRACE("str", tout << "flattened to \"" << flattenedString.encode().c_str() << "\"" << std::endl;); + expr_ref constStr(mk_string(flattenedString), m); + expr_ref axiom(ctx.mk_eq_atom(a_cat, constStr), m); + assert_axiom(axiom); + } } - expr_ref new_nn1(simplify_concat(nn1), m); - expr_ref new_nn2(simplify_concat(nn2), m); - app * a_new_nn1 = to_app(new_nn1); - app * a_new_nn2 = to_app(new_nn2); + /* + * Instantiate an axiom of the following form: + * Length(Concat(x, y)) = Length(x) + Length(y) + */ + void theory_str::instantiate_concat_axiom(enode * cat) { + app * a_cat = cat->get_owner(); + SASSERT(u.str.is_concat(a_cat)); - TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl - << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + ast_manager & m = get_manager(); - if (new_nn1 == new_nn2) { - TRACE("str", tout << "equal concats, return" << std::endl;); - return; + TRACE("str", tout << "instantiating concat axiom for " << mk_ismt2_pp(a_cat, m) << std::endl;); + + // build LHS + expr_ref len_xy(m); + len_xy = mk_strlen(a_cat); + SASSERT(len_xy); + + // build RHS: start by extracting x and y from Concat(x, y) + unsigned nArgs = a_cat->get_num_args(); + SASSERT(nArgs == 2); + app * a_x = to_app(a_cat->get_arg(0)); + app * a_y = to_app(a_cat->get_arg(1)); + + expr_ref len_x(m); + len_x = mk_strlen(a_x); + SASSERT(len_x); + + expr_ref len_y(m); + len_y = mk_strlen(a_y); + SASSERT(len_y); + + // now build len_x + len_y + expr_ref len_x_plus_len_y(m); + len_x_plus_len_y = m_autil.mk_add(len_x, len_y); + SASSERT(len_x_plus_len_y); + + // finally assert equality between the two subexpressions + app * eq = m.mk_eq(len_xy, len_x_plus_len_y); + SASSERT(eq); + assert_axiom(eq); } - if (!can_two_nodes_eq(new_nn1, new_nn2)) { - expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); - TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); - assert_axiom(detected); - return; + /* + * Add axioms that are true for any string variable: + * 1. Length(x) >= 0 + * 2. Length(x) == 0 <=> x == "" + * If the term is a string constant, we can assert something stronger: + * Length(x) == strlen(x) + */ + void theory_str::instantiate_basic_string_axioms(enode * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "set up basic string axioms on " << mk_pp(str->get_owner(), m) << std::endl;); + + // TESTING: attempt to avoid a crash here when a variable goes out of scope + if (str->get_iscope_lvl() > ctx.get_scope_level()) { + TRACE("str", tout << "WARNING: skipping axiom setup on out-of-scope string term" << std::endl;); + return; + } + + // generate a stronger axiom for constant strings + app * a_str = str->get_owner(); + if (u.str.is_string(a_str)) { + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + + zstring strconst; + u.str.is_string(str->get_owner(), strconst); + TRACE("str", tout << "instantiating constant string axioms for \"" << strconst.encode().c_str() << "\"" << std::endl;); + unsigned int l = strconst.length(); + expr_ref len(m_autil.mk_numeral(rational(l), true), m); + + literal lit(mk_eq(len_str, len, false)); + ctx.mark_as_relevant(lit); + ctx.mk_th_axiom(get_id(), 1, &lit); + } else { + // build axiom 1: Length(a_str) >= 0 + { + // build LHS + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + // build RHS + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + // build LHS >= RHS and assert + app * lhs_ge_rhs = m_autil.mk_ge(len_str, zero); + SASSERT(lhs_ge_rhs); + TRACE("str", tout << "string axiom 1: " << mk_ismt2_pp(lhs_ge_rhs, m) << std::endl;); + assert_axiom(lhs_ge_rhs); + } + + // build axiom 2: Length(a_str) == 0 <=> a_str == "" + { + // build LHS of iff + expr_ref len_str(m); + len_str = mk_strlen(a_str); + SASSERT(len_str); + expr_ref zero(m); + zero = m_autil.mk_numeral(rational(0), true); + SASSERT(zero); + expr_ref lhs(m); + lhs = ctx.mk_eq_atom(len_str, zero); + SASSERT(lhs); + // build RHS of iff + expr_ref empty_str(m); + empty_str = mk_string(""); + SASSERT(empty_str); + expr_ref rhs(m); + rhs = ctx.mk_eq_atom(a_str, empty_str); + SASSERT(rhs); + // build LHS <=> RHS and assert + TRACE("str", tout << "string axiom 2: " << mk_ismt2_pp(lhs, m) << " <=> " << mk_ismt2_pp(rhs, m) << std::endl;); + literal l(mk_eq(lhs, rhs, true)); + ctx.mark_as_relevant(l); + ctx.mk_th_axiom(get_id(), 1, &l); + } + + } } - // check whether new_nn1 and new_nn2 are still concats + /* + * Add an axiom of the form: + * (lhs == rhs) -> ( Length(lhs) == Length(rhs) ) + */ + void theory_str::instantiate_str_eq_length_axiom(enode * lhs, enode * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - bool n1IsConcat = u.str.is_concat(a_new_nn1); - bool n2IsConcat = u.str.is_concat(a_new_nn2); - if (!n1IsConcat && n2IsConcat) { - TRACE("str", tout << "nn1_new is not a concat" << std::endl;); - if (u.str.is_string(a_new_nn1)) { - simplify_parent(new_nn2, new_nn1); - } - return; - } else if (n1IsConcat && !n2IsConcat) { - TRACE("str", tout << "nn2_new is not a concat" << std::endl;); - if (u.str.is_string(a_new_nn2)) { - simplify_parent(new_nn1, new_nn2); - } - return; - } else if (!n1IsConcat && !n2IsConcat) { - // normally this should never happen, because group_terms_by_eqc() should have pre-simplified - // as much as possible. however, we make a defensive check here just in case - TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); - return; - } + app * a_lhs = lhs->get_owner(); + app * a_rhs = rhs->get_owner(); - expr * v1_arg0 = a_new_nn1->get_arg(0); - expr * v1_arg1 = a_new_nn1->get_arg(1); - expr * v2_arg0 = a_new_nn2->get_arg(0); - expr * v2_arg1 = a_new_nn2->get_arg(1); + // build premise: (lhs == rhs) + expr_ref premise(ctx.mk_eq_atom(a_lhs, a_rhs), m); - if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { - int ii4 = 0; - expr* item[3]; - if (nn1 != new_nn1) { - item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); - } - if (nn2 != new_nn2) { - item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); - } - item[ii4++] = ctx.mk_eq_atom(nn1, nn2); - expr_ref premise(m.mk_and(ii4, item), m); - expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + // build conclusion: ( Length(lhs) == Length(rhs) ) + expr_ref len_lhs(mk_strlen(a_lhs), m); + SASSERT(len_lhs); + expr_ref len_rhs(mk_strlen(a_rhs), m); + SASSERT(len_rhs); + expr_ref conclusion(ctx.mk_eq_atom(len_lhs, len_rhs), m); + + TRACE("str", tout << "string-eq length-eq axiom: " + << mk_ismt2_pp(premise, m) << " -> " << mk_ismt2_pp(conclusion, m) << std::endl;); assert_implication(premise, conclusion); } - // start to split both concats - check_and_init_cut_var(v1_arg0); - check_and_init_cut_var(v1_arg1); - check_and_init_cut_var(v2_arg0); - check_and_init_cut_var(v2_arg1); + void theory_str::instantiate_axiom_CharAt(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - //************************************************************* - // case 1: concat(x, y) = concat(m, n) - //************************************************************* - if (is_concat_eq_type1(new_nn1, new_nn2)) { - process_concat_eq_type1(new_nn1, new_nn2); - return; + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up CharAt axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate CharAt axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + expr_ref ts2(mk_str_var("ts2"), m); + + expr_ref cond(m.mk_and( + m_autil.mk_ge(expr->get_arg(1), mk_int(0)), + // REWRITE for arithmetic theory: + // m_autil.mk_lt(expr->get_arg(1), mk_strlen(expr->get_arg(0))) + m.mk_not(m_autil.mk_ge(m_autil.mk_add(expr->get_arg(1), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), mk_int(0))) + ), m); + + expr_ref_vector and_item(m); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(ts0, mk_concat(ts1, ts2)))); + and_item.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_strlen(ts0))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_int(1))); + + expr_ref thenBranch(m.mk_and(and_item.size(), and_item.c_ptr()), m); + expr_ref elseBranch(ctx.mk_eq_atom(ts1, mk_string("")), m); + + expr_ref axiom(m.mk_ite(cond, thenBranch, elseBranch), m); + expr_ref reductionVar(ctx.mk_eq_atom(expr, ts1), m); + + SASSERT(axiom); + SASSERT(reductionVar); + + expr_ref finalAxiom(m.mk_and(axiom, reductionVar), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 2: concat(x, y) = concat(m, "str") - //************************************************************* - if (is_concat_eq_type2(new_nn1, new_nn2)) { - process_concat_eq_type2(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_prefixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up prefixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate prefixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts0), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts0, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 3: concat(x, y) = concat("str", n) - //************************************************************* - if (is_concat_eq_type3(new_nn1, new_nn2)) { - process_concat_eq_type3(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_suffixof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up suffixof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate suffixof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref_vector innerItems(m); + innerItems.push_back(ctx.mk_eq_atom(expr->get_arg(1), mk_concat(ts0, ts1))); + innerItems.push_back(ctx.mk_eq_atom(mk_strlen(ts1), mk_strlen(expr->get_arg(0)))); + innerItems.push_back(m.mk_ite(ctx.mk_eq_atom(ts1, expr->get_arg(0)), expr, m.mk_not(expr))); + expr_ref then1(m.mk_and(innerItems.size(), innerItems.c_ptr()), m); + SASSERT(then1); + + // the top-level condition is Length(arg0) >= Length(arg1) + expr_ref topLevelCond( + m_autil.mk_ge( + m_autil.mk_add( + mk_strlen(expr->get_arg(1)), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), + mk_int(0)) + , m); + SASSERT(topLevelCond); + + expr_ref finalAxiom(m.mk_ite(topLevelCond, then1, m.mk_not(expr)), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } - //************************************************************* - // case 4: concat("str1", y) = concat("str2", n) - //************************************************************* - if (is_concat_eq_type4(new_nn1, new_nn2)) { - process_concat_eq_type4(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_Contains(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up Contains axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + // quick path, because this is necessary due to rewriter behaviour + // at minimum it should fix z3str/concat-006.smt2 + zstring haystackStr, needleStr; + if (u.str.is_string(ex->get_arg(0), haystackStr) && u.str.is_string(ex->get_arg(1), needleStr)) { + TRACE("str", tout << "eval constant Contains term " << mk_pp(ex, m) << std::endl;); + if (haystackStr.contains(needleStr)) { + assert_axiom(ex); + } else { + assert_axiom(m.mk_not(ex)); + } + return; + } + + { // register Contains() + expr * str = ex->get_arg(0); + expr * substr = ex->get_arg(1); + contains_map.push_back(ex); + std::pair key = std::pair(str, substr); + contain_pair_bool_map.insert(str, substr, ex); + contain_pair_idx_map[str].insert(key); + contain_pair_idx_map[substr].insert(key); + } + + TRACE("str", tout << "instantiate Contains axiom for " << mk_pp(ex, m) << std::endl;); + + expr_ref ts0(mk_str_var("ts0"), m); + expr_ref ts1(mk_str_var("ts1"), m); + + expr_ref breakdownAssert(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(ex->get_arg(0), mk_concat(ts0, mk_concat(ex->get_arg(1), ts1)))), m); + SASSERT(breakdownAssert); + assert_axiom(breakdownAssert); } - //************************************************************* - // case 5: concat(x, "str1") = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type5(new_nn1, new_nn2)) { - process_concat_eq_type5(new_nn1, new_nn2); - return; - } - //************************************************************* - // case 6: concat("str1", y) = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type6(new_nn1, new_nn2)) { - process_concat_eq_type6(new_nn1, new_nn2); - return; + void theory_str::instantiate_axiom_Indexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Indexof axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + SASSERT(condAst); + + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // indexAst = |x1| + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + // args[0] = x3 . x4 + // /\ |x3| = |x1| + |args[1]| - 1 + // /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + SASSERT(tmpLen); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + expr_ref thenBranch(m.mk_and(thenItems.size(), thenItems.c_ptr()), m); + SASSERT(thenBranch); + + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(indexAst, mk_int(-1)), m); + SASSERT(elseBranch); + + expr_ref breakdownAssert(m.mk_ite(condAst, thenBranch, elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } -} + void theory_str::instantiate_axiom_Indexof2(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); -/* - * Returns true if attempting to process a concat equality between lhs and rhs - * will result in overlapping variables (false otherwise). - */ -bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { - ast_manager & m = get_manager(); + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); - expr_ref new_nn1(simplify_concat(lhs), m); - expr_ref new_nn2(simplify_concat(rhs), m); - app * a_new_nn1 = to_app(new_nn1); - app * a_new_nn2 = to_app(new_nn2); + TRACE("str", tout << "instantiate Indexof2 axiom for " << mk_pp(expr, m) << std::endl;); - bool n1IsConcat = u.str.is_concat(a_new_nn1); - bool n2IsConcat = u.str.is_concat(a_new_nn2); - if (!n1IsConcat && !n2IsConcat) { - // we simplified both sides to non-concat expressions... - return false; + // ------------------------------------------------------------------------------- + // if (arg[2] >= length(arg[0])) // ite2 + // resAst = -1 + // else + // args[0] = prefix . suffix + // /\ indexAst = indexof(suffix, arg[1]) + // /\ args[2] = len(prefix) + // /\ if (indexAst == -1) resAst = indexAst // ite3 + // else resAst = args[2] + indexAst + // ------------------------------------------------------------------------------- + + expr_ref resAst(mk_int_var("res"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref prefix(mk_str_var("prefix"), m); + expr_ref suffix(mk_str_var("suffix"), m); + expr_ref prefixLen(mk_strlen(prefix), m); + expr_ref zeroAst(mk_int(0), m); + expr_ref negOneAst(mk_int(-1), m); + + expr_ref ite3(m.mk_ite( + ctx.mk_eq_atom(indexAst, negOneAst), + ctx.mk_eq_atom(resAst, negOneAst), + ctx.mk_eq_atom(resAst, m_autil.mk_add(expr->get_arg(2), indexAst)) + ),m); + + expr_ref_vector ite2ElseItems(m); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(prefix, suffix))); + ite2ElseItems.push_back(ctx.mk_eq_atom(indexAst, mk_indexof(suffix, expr->get_arg(1)))); + ite2ElseItems.push_back(ctx.mk_eq_atom(expr->get_arg(2), prefixLen)); + ite2ElseItems.push_back(ite3); + expr_ref ite2Else(m.mk_and(ite2ElseItems.size(), ite2ElseItems.c_ptr()), m); + SASSERT(ite2Else); + + expr_ref ite2(m.mk_ite( + //m_autil.mk_ge(expr->get_arg(2), mk_strlen(expr->get_arg(0))), + m_autil.mk_ge(m_autil.mk_add(expr->get_arg(2), m_autil.mk_mul(mk_int(-1), mk_strlen(expr->get_arg(0)))), zeroAst), + ctx.mk_eq_atom(resAst, negOneAst), + ite2Else + ), m); + SASSERT(ite2); + + expr_ref ite1(m.mk_ite( + //m_autil.mk_lt(expr->get_arg(2), zeroAst), + m.mk_not(m_autil.mk_ge(expr->get_arg(2), zeroAst)), + ctx.mk_eq_atom(resAst, mk_indexof(expr->get_arg(0), expr->get_arg(1))), + ite2 + ), m); + SASSERT(ite1); + assert_axiom(ite1); + + expr_ref reduceTerm(ctx.mk_eq_atom(expr, resAst), m); + SASSERT(reduceTerm); + assert_axiom(reduceTerm); } - expr * v1_arg0 = a_new_nn1->get_arg(0); - expr * v1_arg1 = a_new_nn1->get_arg(1); - expr * v2_arg0 = a_new_nn2->get_arg(0); - expr * v2_arg1 = a_new_nn2->get_arg(1); + void theory_str::instantiate_axiom_LastIndexof(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); - check_and_init_cut_var(v1_arg0); - check_and_init_cut_var(v1_arg1); - check_and_init_cut_var(v2_arg0); - check_and_init_cut_var(v2_arg1); + TRACE("str", tout << "instantiate LastIndexof axiom for " << mk_pp(expr, m) << std::endl;); - //************************************************************* - // case 1: concat(x, y) = concat(m, n) - //************************************************************* - if (is_concat_eq_type1(new_nn1, new_nn2)) { - TRACE("str", tout << "Type 1 check." << std::endl;); - expr * x = to_app(new_nn1)->get_arg(0); - expr * y = to_app(new_nn1)->get_arg(1); - expr * m = to_app(new_nn2)->get_arg(0); - expr * n = to_app(new_nn2)->get_arg(1); + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref indexAst(mk_int_var("index"), m); + expr_ref_vector items(m); - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; - } else if (has_self_cut(x, n)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); - return true; - } else { - return false; + // args[0] = x1 . args[1] . x2 + expr_ref eq1(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2))), m); + expr_ref arg0HasArg1(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); // arg0HasArg1 = Contains(args[0], args[1]) + items.push_back(ctx.mk_eq_atom(arg0HasArg1, eq1)); + + + expr_ref condAst(arg0HasArg1, m); + //---------------------------- + // true branch + expr_ref_vector thenItems(m); + thenItems.push_back(m_autil.mk_ge(indexAst, mk_int(0))); + // args[0] = x1 . args[1] . x2 + // x1 doesn't contain args[1] + thenItems.push_back(m.mk_not(mk_contains(x2, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(indexAst, mk_strlen(x1))); + + bool canSkip = false; + zstring arg1Str; + if (u.str.is_string(expr->get_arg(1), arg1Str)) { + if (arg1Str.length() == 1) { + canSkip = true; + } + } + + if (!canSkip) { + // args[0] = x3 . x4 /\ |x3| = |x1| + 1 /\ ! contains(x4, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(indexAst, mk_int(1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x4, expr->get_arg(1)))); + } + //---------------------------- + // else branch + expr_ref_vector elseItems(m); + elseItems.push_back(ctx.mk_eq_atom(indexAst, mk_int(-1))); + + items.push_back(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), m.mk_and(elseItems.size(), elseItems.c_ptr()))); + + expr_ref breakdownAssert(m.mk_and(items.size(), items.c_ptr()), m); + SASSERT(breakdownAssert); + + expr_ref reduceToIndex(ctx.mk_eq_atom(expr, indexAst), m); + SASSERT(reduceToIndex); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToIndex), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Substr(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Substr axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Substr axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref substrBase(expr->get_arg(0), m); + expr_ref substrPos(expr->get_arg(1), m); + expr_ref substrLen(expr->get_arg(2), m); + SASSERT(substrBase); + SASSERT(substrPos); + SASSERT(substrLen); + + expr_ref zero(m_autil.mk_numeral(rational::zero(), true), m); + expr_ref minusOne(m_autil.mk_numeral(rational::minus_one(), true), m); + SASSERT(zero); + SASSERT(minusOne); + + expr_ref_vector argumentsValid_terms(m); + // pos >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrPos, zero)); + // pos < strlen(base) + // --> pos + -1*strlen(base) < 0 + argumentsValid_terms.push_back(m.mk_not(m_autil.mk_ge( + m_autil.mk_add(substrPos, m_autil.mk_mul(minusOne, substrLen)), + zero))); + // len >= 0 + argumentsValid_terms.push_back(m_autil.mk_ge(substrLen, zero)); + + expr_ref argumentsValid(mk_and(argumentsValid_terms), m); + SASSERT(argumentsValid); + ctx.internalize(argumentsValid, false); + + // (pos+len) >= strlen(base) + // --> pos + len + -1*strlen(base) >= 0 + expr_ref lenOutOfBounds(m_autil.mk_ge( + m_autil.mk_add(substrPos, substrLen, m_autil.mk_mul(minusOne, mk_strlen(substrBase))), + zero), m); + SASSERT(lenOutOfBounds); + ctx.internalize(argumentsValid, false); + + // Case 1: pos < 0 or pos >= strlen(base) or len < 0 + // ==> (Substr ...) = "" + expr_ref case1_premise(m.mk_not(argumentsValid), m); + SASSERT(case1_premise); + ctx.internalize(case1_premise, false); + expr_ref case1_conclusion(ctx.mk_eq_atom(expr, mk_string("")), m); + SASSERT(case1_conclusion); + ctx.internalize(case1_conclusion, false); + expr_ref case1(rewrite_implication(case1_premise, case1_conclusion), m); + SASSERT(case1); + + // Case 2: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) >= strlen(base) + // ==> base = t0.t1 AND len(t0) = pos AND (Substr ...) = t1 + expr_ref t0(mk_str_var("t0"), m); + expr_ref t1(mk_str_var("t1"), m); + expr_ref case2_conclusion(m.mk_and( + ctx.mk_eq_atom(substrBase, mk_concat(t0,t1)), + ctx.mk_eq_atom(mk_strlen(t0), substrPos), + ctx.mk_eq_atom(expr, t1)), m); + expr_ref case2(rewrite_implication(m.mk_and(argumentsValid, lenOutOfBounds), case2_conclusion), m); + SASSERT(case2); + + // Case 3: (pos >= 0 and pos < strlen(base) and len >= 0) and (pos+len) < strlen(base) + // ==> base = t2.t3.t4 AND len(t2) = pos AND len(t3) = len AND (Substr ...) = t3 + expr_ref t2(mk_str_var("t2"), m); + expr_ref t3(mk_str_var("t3"), m); + expr_ref t4(mk_str_var("t4"), m); + expr_ref_vector case3_conclusion_terms(m); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(substrBase, mk_concat(t2, mk_concat(t3, t4)))); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t2), substrPos)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(mk_strlen(t3), substrLen)); + case3_conclusion_terms.push_back(ctx.mk_eq_atom(expr, t3)); + expr_ref case3_conclusion(mk_and(case3_conclusion_terms), m); + expr_ref case3(rewrite_implication(m.mk_and(argumentsValid, m.mk_not(lenOutOfBounds)), case3_conclusion), m); + SASSERT(case3); + + ctx.internalize(case1, false); + ctx.internalize(case2, false); + ctx.internalize(case3, false); + + expr_ref finalAxiom(m.mk_and(case1, case2, case3), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_Replace(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * expr = e->get_owner(); + if (axiomatized_terms.contains(expr)) { + TRACE("str", tout << "already set up Replace axiom for " << mk_pp(expr, m) << std::endl;); + return; + } + axiomatized_terms.insert(expr); + + TRACE("str", tout << "instantiate Replace axiom for " << mk_pp(expr, m) << std::endl;); + + expr_ref x1(mk_str_var("x1"), m); + expr_ref x2(mk_str_var("x2"), m); + expr_ref i1(mk_int_var("i1"), m); + expr_ref result(mk_str_var("result"), m); + + // condAst = Contains(args[0], args[1]) + expr_ref condAst(mk_contains(expr->get_arg(0), expr->get_arg(1)), m); + // ----------------------- + // true branch + expr_ref_vector thenItems(m); + // args[0] = x1 . args[1] . x2 + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x1, mk_concat(expr->get_arg(1), x2)))); + // i1 = |x1| + thenItems.push_back(ctx.mk_eq_atom(i1, mk_strlen(x1))); + // args[0] = x3 . x4 /\ |x3| = |x1| + |args[1]| - 1 /\ ! contains(x3, args[1]) + expr_ref x3(mk_str_var("x3"), m); + expr_ref x4(mk_str_var("x4"), m); + expr_ref tmpLen(m_autil.mk_add(i1, mk_strlen(expr->get_arg(1)), mk_int(-1)), m); + thenItems.push_back(ctx.mk_eq_atom(expr->get_arg(0), mk_concat(x3, x4))); + thenItems.push_back(ctx.mk_eq_atom(mk_strlen(x3), tmpLen)); + thenItems.push_back(m.mk_not(mk_contains(x3, expr->get_arg(1)))); + thenItems.push_back(ctx.mk_eq_atom(result, mk_concat(x1, mk_concat(expr->get_arg(2), x2)))); + // ----------------------- + // false branch + expr_ref elseBranch(ctx.mk_eq_atom(result, expr->get_arg(0)), m); + + expr_ref breakdownAssert(m.mk_ite(condAst, m.mk_and(thenItems.size(), thenItems.c_ptr()), elseBranch), m); + SASSERT(breakdownAssert); + + expr_ref reduceToResult(ctx.mk_eq_atom(expr, result), m); + SASSERT(reduceToResult); + + expr_ref finalAxiom(m.mk_and(breakdownAssert, reduceToResult), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } + + void theory_str::instantiate_axiom_str_to_int(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + return; + } + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate str.to-int axiom for " << mk_pp(ex, m) << std::endl;); + + // let expr = (str.to-int S) + // axiom 1: expr >= -1 + // axiom 2: expr = 0 <==> S = "0" + // axiom 3: expr >= 1 ==> len(S) > 0 AND S[0] != "0" + + expr * S = ex->get_arg(0); + { + expr_ref axiom1(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::minus_one(), true)), m); + SASSERT(axiom1); + assert_axiom(axiom1); + } + + { + expr_ref lhs(ctx.mk_eq_atom(ex, m_autil.mk_numeral(rational::zero(), true)), m); + expr_ref rhs(ctx.mk_eq_atom(S, mk_string("0")), m); + expr_ref axiom2(ctx.mk_eq_atom(lhs, rhs), m); + SASSERT(axiom2); + assert_axiom(axiom2); + } + + { + expr_ref premise(m_autil.mk_ge(ex, m_autil.mk_numeral(rational::one(), true)), m); + expr_ref hd(mk_str_var("hd"), m); + expr_ref tl(mk_str_var("tl"), m); + expr_ref conclusion1(ctx.mk_eq_atom(S, mk_concat(hd, tl)), m); + expr_ref conclusion2(ctx.mk_eq_atom(mk_strlen(hd), m_autil.mk_numeral(rational::one(), true)), m); + expr_ref conclusion3(m.mk_not(ctx.mk_eq_atom(hd, mk_string("0"))), m); + expr_ref conclusion(m.mk_and(conclusion1, conclusion2, conclusion3), m); + SASSERT(premise); + SASSERT(conclusion); + assert_implication(premise, conclusion); } } - //************************************************************* - // case 2: concat(x, y) = concat(m, "str") - //************************************************************* - if (is_concat_eq_type2(new_nn1, new_nn2)) { + void theory_str::instantiate_axiom_int_to_str(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - expr * y = NULL; - expr * m = NULL; - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - - if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { - m = v1_arg0; - y = v2_arg1; - } else { - m = v2_arg0; - y = v1_arg1; + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + return; } + axiomatized_terms.insert(ex); - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; - } else { - return false; + TRACE("str", tout << "instantiate str.from-int axiom for " << mk_pp(ex, m) << std::endl;); + + // axiom 1: N < 0 <==> (str.from-int N) = "" + expr * N = ex->get_arg(0); + { + expr_ref axiom1_lhs(m.mk_not(m_autil.mk_ge(N, m_autil.mk_numeral(rational::zero(), true))), m); + expr_ref axiom1_rhs(ctx.mk_eq_atom(ex, mk_string("")), m); + expr_ref axiom1(ctx.mk_eq_atom(axiom1_lhs, axiom1_rhs), m); + SASSERT(axiom1); + assert_axiom(axiom1); } } - //************************************************************* - // case 3: concat(x, y) = concat("str", n) - //************************************************************* - if (is_concat_eq_type3(new_nn1, new_nn2)) { - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + expr * theory_str::mk_RegexIn(expr * str, expr * regexp) { + app * regexIn = u.re.mk_in_re(str, regexp); + // immediately force internalization so that axiom setup does not fail + get_context().internalize(regexIn, false); + set_up_axioms(regexIn); + return regexIn; + } - expr * x = NULL; - expr * n = NULL; - - if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { - n = v1_arg1; - x = v2_arg0; - } else { - n = v2_arg1; - x = v1_arg0; + static zstring str2RegexStr(zstring str) { + zstring res(""); + int len = str.length(); + for (int i = 0; i < len; i++) { + char nc = str[i]; + // 12 special chars + if (nc == '\\' || nc == '^' || nc == '$' || nc == '.' || nc == '|' || nc == '?' + || nc == '*' || nc == '+' || nc == '(' || nc == ')' || nc == '[' || nc == '{') { + res = res + zstring("\\"); + } + char tmp[2] = {(char)str[i], '\0'}; + res = res + zstring(tmp); } - if (has_self_cut(x, n)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); - return true; + return res; + } + + zstring theory_str::get_std_regex_str(expr * regex) { + app * a_regex = to_app(regex); + if (u.re.is_to_re(a_regex)) { + expr * regAst = a_regex->get_arg(0); + zstring regAstVal; + u.str.is_string(regAst, regAstVal); + zstring regStr = str2RegexStr(regAstVal); + return regStr; + } else if (u.re.is_concat(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")(") + reg2Str + zstring(")"); + } else if (u.re.is_union(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + expr * reg2Ast = a_regex->get_arg(1); + zstring reg1Str = get_std_regex_str(reg1Ast); + zstring reg2Str = get_std_regex_str(reg2Ast); + return zstring("(") + reg1Str + zstring(")|(") + reg2Str + zstring(")"); + } else if (u.re.is_star(a_regex)) { + expr * reg1Ast = a_regex->get_arg(0); + zstring reg1Str = get_std_regex_str(reg1Ast); + return zstring("(") + reg1Str + zstring(")*"); + } else if (u.re.is_range(a_regex)) { + expr * range1 = a_regex->get_arg(0); + expr * range2 = a_regex->get_arg(1); + zstring range1val, range2val; + u.str.is_string(range1, range1val); + u.str.is_string(range2, range2val); + return zstring("[") + range1val + zstring("-") + range2val + zstring("]"); } else { - return false; + TRACE("str", tout << "BUG: unrecognized regex term " << mk_pp(regex, get_manager()) << std::endl;); + UNREACHABLE(); return zstring(""); } } - //************************************************************* - // case 4: concat("str1", y) = concat("str2", n) - //************************************************************* - if (is_concat_eq_type4(new_nn1, new_nn2)) { - // This case can never result in an overlap. - return false; - } + void theory_str::instantiate_axiom_RegexIn(enode * e) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - //************************************************************* - // case 5: concat(x, "str1") = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type5(new_nn1, new_nn2)) { - // This case can never result in an overlap. - return false; - } - //************************************************************* - // case 6: concat("str1", y) = concat(m, "str2") - //************************************************************* - if (is_concat_eq_type6(new_nn1, new_nn2)) { - expr * v1_arg0 = to_app(new_nn1)->get_arg(0); - expr * v1_arg1 = to_app(new_nn1)->get_arg(1); - expr * v2_arg0 = to_app(new_nn2)->get_arg(0); - expr * v2_arg1 = to_app(new_nn2)->get_arg(1); - - expr * y = NULL; - expr * m = NULL; - - if (u.str.is_string(v1_arg0)) { - y = v1_arg1; - m = v2_arg0; - } else { - y = v2_arg1; - m = v1_arg0; + app * ex = e->get_owner(); + if (axiomatized_terms.contains(ex)) { + TRACE("str", tout << "already set up RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + return; } - if (has_self_cut(m, y)) { - TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); - return true; + axiomatized_terms.insert(ex); + + TRACE("str", tout << "instantiate RegexIn axiom for " << mk_pp(ex, m) << std::endl;); + + { + zstring regexStr = get_std_regex_str(ex->get_arg(1)); + std::pair key1(ex->get_arg(0), regexStr); + // skip Z3str's map check, because we already check if we set up axioms on this term + regex_in_bool_map[key1] = ex; + regex_in_var_reg_str_map[ex->get_arg(0)].insert(regexStr); + } + + expr_ref str(ex->get_arg(0), m); + app * regex = to_app(ex->get_arg(1)); + + if (u.re.is_to_re(regex)) { + expr_ref rxStr(regex->get_arg(0), m); + // want to assert 'expr IFF (str == rxStr)' + expr_ref rhs(ctx.mk_eq_atom(str, rxStr), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + TRACE("str", tout << "set up Str2Reg: (RegexIn " << mk_pp(str, m) << " " << mk_pp(regex, m) << ")" << std::endl;); + } else if (u.re.is_concat(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref rhs(mk_concat(var1, var2), m); + expr_ref rx1(regex->get_arg(0), m); + expr_ref rx2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, rx1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, rx2), m); + + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, rhs))); + + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_union(regex)) { + expr_ref var1(mk_regex_rep_var(), m); + expr_ref var2(mk_regex_rep_var(), m); + expr_ref orVar(m.mk_or(ctx.mk_eq_atom(str, var1), ctx.mk_eq_atom(str, var2)), m); + expr_ref regex1(regex->get_arg(0), m); + expr_ref regex2(regex->get_arg(1), m); + expr_ref var1InRegex1(mk_RegexIn(var1, regex1), m); + expr_ref var2InRegex2(mk_RegexIn(var2, regex2), m); + expr_ref_vector items(m); + items.push_back(var1InRegex1); + items.push_back(var2InRegex2); + items.push_back(ctx.mk_eq_atom(ex, orVar)); + assert_axiom(mk_and(items)); + } else if (u.re.is_star(regex)) { + // slightly more complex due to the unrolling step. + expr_ref regex1(regex->get_arg(0), m); + expr_ref unrollCount(mk_unroll_bound_var(), m); + expr_ref unrollFunc(mk_unroll(regex1, unrollCount), m); + expr_ref_vector items(m); + items.push_back(ctx.mk_eq_atom(ex, ctx.mk_eq_atom(str, unrollFunc))); + items.push_back(ctx.mk_eq_atom(ctx.mk_eq_atom(unrollCount, mk_int(0)), ctx.mk_eq_atom(unrollFunc, mk_string("")))); + expr_ref finalAxiom(mk_and(items), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); + } else if (u.re.is_range(regex)) { + // (re.range "A" "Z") unfolds to (re.union "A" "B" ... "Z"); + // we rewrite to expr IFF (str = "A" or str = "B" or ... or str = "Z") + expr_ref lo(regex->get_arg(0), m); + expr_ref hi(regex->get_arg(1), m); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(m); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(str, u.str.mk_string(s_ch)), m); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), m); + expr_ref finalAxiom(m.mk_iff(ex, rhs), m); + SASSERT(finalAxiom); + assert_axiom(finalAxiom); } else { - return false; + TRACE("str", tout << "ERROR: unknown regex expression " << mk_pp(regex, m) << "!" << std::endl;); + NOT_IMPLEMENTED_YET(); } } - TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); - return false; -} + void theory_str::attach_new_th_var(enode * n) { + context & ctx = get_context(); + theory_var v = mk_var(n); + ctx.attach_th_var(n, this, v); + TRACE("str", tout << "new theory var: " << mk_ismt2_pp(n->get_owner(), get_manager()) << " := v#" << v << std::endl;); + } -/************************************************************* - * Type 1: concat(x, y) = concat(m, n) - * x, y, m and n all variables - *************************************************************/ -bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { - expr * x = to_app(concatAst1)->get_arg(0); - expr * y = to_app(concatAst1)->get_arg(1); - expr * m = to_app(concatAst2)->get_arg(0); - expr * n = to_app(concatAst2)->get_arg(1); + void theory_str::reset_eh() { + TRACE("str", tout << "resetting" << std::endl;); + m_trail_stack.reset(); - if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { + m_basicstr_axiom_todo.reset(); + m_str_eq_todo.reset(); + m_concat_axiom_todo.reset(); + pop_scope_eh(get_context().get_scope_level()); + } + + /* + * Check equality among equivalence class members of LHS and RHS + * to discover an incorrect LHS == RHS. + * For example, if we have y2 == "str3" + * and the equivalence classes are + * { y2, (Concat ce m2) } + * { "str3", (Concat abc x2) } + * then y2 can't be equal to "str3". + * Then add an assertion: (y2 == (Concat ce m2)) AND ("str3" == (Concat abc x2)) -> (y2 != "str3") + */ + bool theory_str::new_eq_check(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // skip this check if we defer consistency checking, as we can do it for every EQC in final check + if (!opt_DeferEQCConsistencyCheck) { + check_concat_len_in_eqc(lhs); + check_concat_len_in_eqc(rhs); + } + + // Now we iterate over all pairs of terms across both EQCs + // and check whether we can show that any pair of distinct terms + // cannot possibly be equal. + // If that's the case, we assert an axiom to that effect and stop. + + expr * eqc_nn1 = lhs; + do { + expr * eqc_nn2 = rhs; + do { + TRACE("str", tout << "checking whether " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " can be equal" << std::endl;); + // inconsistency check: value + if (!can_two_nodes_eq(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " cannot be equal to " << mk_pp(eqc_nn2, m) << std::endl;); + expr_ref to_assert(m.mk_not(ctx.mk_eq_atom(eqc_nn1, eqc_nn2)), m); + assert_axiom(to_assert); + // this shouldn't use the integer theory at all, so we don't allow the option of quick-return + return false; + } + if (!check_length_consistency(eqc_nn1, eqc_nn2)) { + TRACE("str", tout << "inconsistency detected: " << mk_pp(eqc_nn1, m) << " and " << mk_pp(eqc_nn2, m) << " have inconsistent lengths" << std::endl;); + if (opt_NoQuickReturn_IntegerTheory){ + TRACE("str", tout << "continuing in new_eq_check() due to opt_NoQuickReturn_IntegerTheory" << std::endl;); + } else { + return false; + } + } + eqc_nn2 = get_eqc_next(eqc_nn2); + } while (eqc_nn2 != rhs); + eqc_nn1 = get_eqc_next(eqc_nn1); + } while (eqc_nn1 != lhs); + + if (!contains_map.empty()) { + check_contain_in_new_eq(lhs, rhs); + } + + if (!regex_in_bool_map.empty()) { + TRACE("str", tout << "checking regex consistency" << std::endl;); + check_regex_in(lhs, rhs); + } + + // okay, all checks here passed return true; - } else { + } + + // support for user_smt_theory-style EQC handling + + app * theory_str::get_ast(theory_var i) { + return get_enode(i)->get_owner(); + } + + theory_var theory_str::get_var(expr * n) const { + if (!is_app(n)) { + return null_theory_var; + } + context & ctx = get_context(); + if (ctx.e_internalized(to_app(n))) { + enode * e = ctx.get_enode(to_app(n)); + return e->get_th_var(get_id()); + } + return null_theory_var; + } + + // simulate Z3_theory_get_eqc_next() + expr * theory_str::get_eqc_next(expr * n) { + theory_var v = get_var(n); + if (v != null_theory_var) { + theory_var r = m_find.next(v); + return get_ast(r); + } + return n; + } + + void theory_str::group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts) { + context & ctx = get_context(); + expr * eqcNode = n; + do { + app * ast = to_app(eqcNode); + if (u.str.is_concat(ast)) { + expr * simConcat = simplify_concat(ast); + if (simConcat != ast) { + if (u.str.is_concat(to_app(simConcat))) { + concats.insert(simConcat); + } else { + if (u.str.is_string(simConcat)) { + consts.insert(simConcat); + } else { + vars.insert(simConcat); + } + } + } else { + concats.insert(simConcat); + } + } else if (u.str.is_string(ast)) { + consts.insert(ast); + } else { + vars.insert(ast); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } + + void theory_str::get_nodes_in_concat(expr * node, ptr_vector & nodeList) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + nodeList.push_back(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_nodes_in_concat(leftArg, nodeList); + get_nodes_in_concat(rightArg, nodeList); + } + } + + // previously Concat() in strTheory.cpp + // Evaluates the concatenation (n1 . n2) with respect to + // the current equivalence classes of n1 and n2. + // Returns a constant string expression representing this concatenation + // if one can be determined, or NULL if this is not possible. + expr * theory_str::eval_concat(expr * n1, expr * n2) { + bool n1HasEqcValue = false; + bool n2HasEqcValue = false; + expr * v1 = get_eqc_value(n1, n1HasEqcValue); + expr * v2 = get_eqc_value(n2, n2HasEqcValue); + if (n1HasEqcValue && n2HasEqcValue) { + zstring n1_str, n2_str; + u.str.is_string(v1, n1_str); + u.str.is_string(v2, n2_str); + zstring result = n1_str + n2_str; + return mk_string(result); + } else if (n1HasEqcValue && !n2HasEqcValue) { + zstring v1_str; + u.str.is_string(v1, v1_str); + if (v1_str.empty()) { + return n2; + } + } else if (n2HasEqcValue && !n1HasEqcValue) { + zstring v2_str; + u.str.is_string(v2, v2_str); + if (v2_str.empty()) { + return n1; + } + } + // give up + return NULL; + } + + static inline std::string rational_to_string_if_exists(const rational & x, bool x_exists) { + if (x_exists) { + return x.to_string(); + } else { + return "?"; + } + } + + /* + * The inputs: + * ~ nn: non const node + * ~ eq_str: the equivalent constant string of nn + * Iterate the parent of all eqc nodes of nn, looking for: + * ~ concat node + * to see whether some concat nodes can be simplified. + */ + void theory_str::simplify_parent(expr * nn, expr * eq_str) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "simplifying parents of " << mk_ismt2_pp(nn, m) + << " with respect to " << mk_ismt2_pp(eq_str, m) << std::endl;); + + ctx.internalize(nn, false); + + zstring eq_strValue; + u.str.is_string(eq_str, eq_strValue); + expr * n_eqNode = nn; + do { + enode * n_eq_enode = ctx.get_enode(n_eqNode); + TRACE("str", tout << "considering all parents of " << mk_ismt2_pp(n_eqNode, m) << std::endl + << "associated n_eq_enode has " << n_eq_enode->get_num_parents() << " parents" << std::endl;); + + // the goal of this next bit is to avoid dereferencing a bogus e_parent in the following loop. + // what I imagine is causing this bug is that, for example, we examine some parent, we add an axiom that involves it, + // and the parent_it iterator becomes invalidated, because we indirectly modified the container that we're iterating over. + + enode_vector current_parents; + for (enode_vector::const_iterator parent_it = n_eq_enode->begin_parents(); parent_it != n_eq_enode->end_parents(); parent_it++) { + current_parents.insert(*parent_it); + } + + for (enode_vector::iterator parent_it = current_parents.begin(); parent_it != current_parents.end(); ++parent_it) { + enode * e_parent = *parent_it; + SASSERT(e_parent != NULL); + + app * a_parent = e_parent->get_owner(); + TRACE("str", tout << "considering parent " << mk_ismt2_pp(a_parent, m) << std::endl;); + + if (u.str.is_concat(a_parent)) { + expr * arg0 = a_parent->get_arg(0); + expr * arg1 = a_parent->get_arg(1); + + rational parentLen; + bool parentLen_exists = get_len_value(a_parent, parentLen); + + if (arg0 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(eq_str, arg0Len); + bool arg1Len_exists = get_len_value(arg1, arg1Len); + + TRACE("str", + tout << "simplify_parent #1:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + + if (parentLen_exists && !arg1Len_exists) { + TRACE("str", tout << "make up len for arg1" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len))), m); + rational makeUpLenArg1 = parentLen - arg0Len; + if (makeUpLenArg1.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(makeUpLenArg1)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat n_eqNode arg1) /\ arg1 has eq const + + expr * concatResult = eval_concat(eq_str, arg1); + if (concatResult != NULL) { + bool arg1HasEqcValue = false; + expr * arg1Value = get_eqc_value(arg1, arg1HasEqcValue); + expr_ref implyL(m); + if (arg1 != arg1Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg1, arg1Value); + SASSERT(eq_ast2); + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(eq_str, arg1); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg0 == n_eq_enode->get_owner()) + + if (arg1 == n_eq_enode->get_owner()) { + rational arg0Len, arg1Len; + bool arg0Len_exists = get_len_value(arg0, arg0Len); + bool arg1Len_exists = get_len_value(eq_str, arg1Len); + + TRACE("str", + tout << "simplify_parent #2:" << std::endl + << "* parent = " << mk_ismt2_pp(a_parent, m) << std::endl + << "* |parent| = " << rational_to_string_if_exists(parentLen, parentLen_exists) << std::endl + << "* |arg0| = " << rational_to_string_if_exists(arg0Len, arg0Len_exists) << std::endl + << "* |arg1| = " << rational_to_string_if_exists(arg1Len, arg1Len_exists) << std::endl; + ); + if (parentLen_exists && !arg0Len_exists) { + TRACE("str", tout << "make up len for arg0" << std::endl;); + expr_ref implyL11(m.mk_and(ctx.mk_eq_atom(mk_strlen(a_parent), mk_int(parentLen)), + ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len))), m); + rational makeUpLenArg0 = parentLen - arg1Len; + if (makeUpLenArg0.is_nonneg()) { + expr_ref implyR11(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(makeUpLenArg0)), m); + assert_implication(implyL11, implyR11); + } else { + expr_ref neg(m.mk_not(implyL11), m); + assert_axiom(neg); + } + } + + // (Concat arg0 n_eqNode) /\ arg0 has eq const + + expr * concatResult = eval_concat(arg0, eq_str); + if (concatResult != NULL) { + bool arg0HasEqcValue = false; + expr * arg0Value = get_eqc_value(arg0, arg0HasEqcValue); + expr_ref implyL(m); + if (arg0 != arg0Value) { + expr_ref eq_ast1(m); + eq_ast1 = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(eq_ast1); + expr_ref eq_ast2(m); + eq_ast2 = ctx.mk_eq_atom(arg0, arg0Value); + SASSERT(eq_ast2); + + implyL = m.mk_and(eq_ast1, eq_ast2); + } else { + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + } + + if (!in_same_eqc(a_parent, concatResult)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, concatResult); + SASSERT(implyR); + + assert_implication(implyL, implyR); + } + } else if (u.str.is_concat(to_app(n_eqNode))) { + expr_ref simpleConcat(m); + simpleConcat = mk_concat(arg0, eq_str); + if (!in_same_eqc(a_parent, simpleConcat)) { + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + SASSERT(implyL); + + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simpleConcat); + SASSERT(implyR); + assert_implication(implyL, implyR); + } + } + } // if (arg1 == n_eq_enode->get_owner + + + //--------------------------------------------------------- + // Case (2-1) begin: (Concat n_eqNode (Concat str var)) + if (arg0 == n_eqNode && u.str.is_concat(to_app(arg1))) { + app * a_arg1 = to_app(arg1); + TRACE("str", tout << "simplify_parent #3" << std::endl;); + expr * r_concat_arg0 = a_arg1->get_arg(0); + if (u.str.is_string(r_concat_arg0)) { + expr * combined_str = eval_concat(eq_str, r_concat_arg0); + SASSERT(combined_str); + expr * r_concat_arg1 = a_arg1->get_arg(1); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combined_str, r_concat_arg1); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-1) end: (Concat n_eqNode (Concat str var)) + //--------------------------------------------------------- + + + //--------------------------------------------------------- + // Case (2-2) begin: (Concat (Concat var str) n_eqNode) + if (u.str.is_concat(to_app(arg0)) && arg1 == n_eqNode) { + app * a_arg0 = to_app(arg0); + TRACE("str", tout << "simplify_parent #4" << std::endl;); + expr * l_concat_arg1 = a_arg0->get_arg(1); + if (u.str.is_string(l_concat_arg1)) { + expr * combined_str = eval_concat(l_concat_arg1, eq_str); + SASSERT(combined_str); + expr * l_concat_arg0 = a_arg0->get_arg(0); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(l_concat_arg0, combined_str); + if (!in_same_eqc(a_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(a_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + // Case (2-2) end: (Concat (Concat var str) n_eqNode) + //--------------------------------------------------------- + + // Have to look up one more layer: if the parent of the concat is another concat + //------------------------------------------------- + // Case (3-1) begin: (Concat (Concat var n_eqNode) str ) + if (arg1 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg0 == a_parent && u.str.is_string(concat_parent_arg1)) { + TRACE("str", tout << "simplify_parent #5" << std::endl;); + expr * combinedStr = eval_concat(eq_str, concat_parent_arg1); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(arg0, combinedStr); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-1) end: (Concat (Concat var n_eqNode) str ) + // Case (3-2) begin: (Concat str (Concat n_eqNode var) ) + if (arg0 == n_eqNode) { + for (enode_vector::iterator concat_parent_it = e_parent->begin_parents(); + concat_parent_it != e_parent->end_parents(); concat_parent_it++) { + enode * e_concat_parent = *concat_parent_it; + app * concat_parent = e_concat_parent->get_owner(); + if (u.str.is_concat(concat_parent)) { + expr * concat_parent_arg0 = concat_parent->get_arg(0); + expr * concat_parent_arg1 = concat_parent->get_arg(1); + if (concat_parent_arg1 == a_parent && u.str.is_string(concat_parent_arg0)) { + TRACE("str", tout << "simplify_parent #6" << std::endl;); + expr * combinedStr = eval_concat(concat_parent_arg0, eq_str); + SASSERT(combinedStr); + expr_ref implyL(m); + implyL = ctx.mk_eq_atom(n_eqNode, eq_str); + expr * simplifiedAst = mk_concat(combinedStr, arg1); + if (!in_same_eqc(concat_parent, simplifiedAst)) { + expr_ref implyR(m); + implyR = ctx.mk_eq_atom(concat_parent, simplifiedAst); + assert_implication(implyL, implyR); + } + } + } + } + } + // Case (3-2) end: (Concat str (Concat n_eqNode var) ) + } // if is_concat(a_parent) + } // for parent_it : n_eq_enode->begin_parents() + + + // check next EQC member + n_eqNode = get_eqc_next(n_eqNode); + } while (n_eqNode != nn); + } + + expr * theory_str::simplify_concat(expr * node) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + std::map resolvedMap; + ptr_vector argVec; + get_nodes_in_concat(node, argVec); + + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + if (vArg != argVec[i]) { + resolvedMap[argVec[i]] = vArg; + } + } + + if (resolvedMap.size() == 0) { + // no simplification possible + return node; + } else { + expr * resultAst = mk_string(""); + for (unsigned i = 0; i < argVec.size(); ++i) { + bool vArgHasEqcValue = false; + expr * vArg = get_eqc_value(argVec[i], vArgHasEqcValue); + resultAst = mk_concat(resultAst, vArg); + } + TRACE("str", tout << mk_ismt2_pp(node, m) << " is simplified to " << mk_ismt2_pp(resultAst, m) << std::endl;); + + if (in_same_eqc(node, resultAst)) { + TRACE("str", tout << "SKIP: both concats are already in the same equivalence class" << std::endl;); + } else { + expr_ref_vector items(m); + int pos = 0; + std::map::iterator itor = resolvedMap.begin(); + for (; itor != resolvedMap.end(); ++itor) { + items.push_back(ctx.mk_eq_atom(itor->first, itor->second)); + pos += 1; + } + expr_ref premise(mk_and(items), m); + expr_ref conclusion(ctx.mk_eq_atom(node, resultAst), m); + assert_implication(premise, conclusion); + } + return resultAst; + } + + } + + // Modified signature of Z3str2's inferLenConcat(). + // Returns true iff nLen can be inferred by this method + // (i.e. the equivalent of a len_exists flag in get_len_value()). + + bool theory_str::infer_len_concat(expr * n, rational & nLen) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + rational tmp_len; + bool nLen_exists = get_len_value(n, tmp_len); + + if (arg0_len_exists && arg1_len_exists && !nLen_exists) { + expr_ref_vector l_items(m); + // if (mk_strlen(arg0) != mk_int(arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + + // if (mk_strlen(arg1) != mk_int(arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + rational nnLen = arg0_len + arg1_len; + expr_ref axr(ctx.mk_eq_atom(mk_strlen(n), mk_int(nnLen)), m); + TRACE("str", tout << "inferred (Length " << mk_pp(n, m) << ") = " << nnLen << std::endl;); + assert_implication(axl, axr); + nLen = nnLen; + return true; + } else { + return false; + } + } + + void theory_str::infer_len_concat_arg(expr * n, rational len) { + if (len.is_neg()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * arg0 = to_app(n)->get_arg(0); + expr * arg1 = to_app(n)->get_arg(1); + rational arg0_len, arg1_len; + bool arg0_len_exists = get_len_value(arg0, arg0_len); + bool arg1_len_exists = get_len_value(arg1, arg1_len); + + expr_ref_vector l_items(m); + expr_ref axr(m); + axr.reset(); + + // if (mk_length(t, n) != mk_int(ctx, len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(len))); + } + + if (!arg0_len_exists && arg1_len_exists) { + //if (mk_length(t, arg1) != mk_int(ctx, arg1_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1_len))); + } + rational arg0Len = len - arg1_len; + if (arg0Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0Len)); + } else { + // could negate + } + } else if (arg0_len_exists && !arg1_len_exists) { + //if (mk_length(t, arg0) != mk_int(ctx, arg0_len)) { + { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(arg0), mk_int(arg0_len))); + } + rational arg1Len = len - arg0_len; + if (arg1Len.is_nonneg()) { + axr = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // could negate + } + } else { + + } + + if (axr) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + assert_implication(axl, axr); + } + } + + void theory_str::infer_len_concat_equality(expr * nn1, expr * nn2) { + rational nnLen; + bool nnLen_exists = get_len_value(nn1, nnLen); + if (!nnLen_exists) { + nnLen_exists = get_len_value(nn2, nnLen); + } + + // case 1: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn1))) { + rational nn1ConcatLen; + bool nn1ConcatLen_exists = infer_len_concat(nn1, nn1ConcatLen); + if (nnLen_exists && nn1ConcatLen_exists) { + nnLen = nn1ConcatLen; + } + } + + // case 2: + // Known: a1_arg0 and a1_arg1 + // Unknown: nn1 + + if (u.str.is_concat(to_app(nn2))) { + rational nn2ConcatLen; + bool nn2ConcatLen_exists = infer_len_concat(nn2, nn2ConcatLen); + if (nnLen_exists && nn2ConcatLen_exists) { + nnLen = nn2ConcatLen; + } + } + + if (nnLen_exists) { + if (u.str.is_concat(to_app(nn1))) { + infer_len_concat_arg(nn1, nnLen); + } + if (u.str.is_concat(to_app(nn2))) { + infer_len_concat_arg(nn2, nnLen); + } + } + + /* + if (isConcatFunc(t, nn2)) { + int nn2ConcatLen = inferLenConcat(t, nn2); + if (nnLen == -1 && nn2ConcatLen != -1) + nnLen = nn2ConcatLen; + } + + if (nnLen != -1) { + if (isConcatFunc(t, nn1)) { + inferLenConcatArg(t, nn1, nnLen); + } + if (isConcatFunc(t, nn2)) { + inferLenConcatArg(t, nn2, nnLen); + } + } + */ + } + + void theory_str::add_theory_aware_branching_info(expr * term, double priority, lbool phase) { + context & ctx = get_context(); + ctx.internalize(term, false); + bool_var v = ctx.get_bool_var(term); + ctx.add_theory_aware_branching_info(v, priority, phase); + } + + void theory_str::generate_mutual_exclusion(expr_ref_vector & terms) { + context & ctx = get_context(); + // pull each literal out of the arrangement disjunction + literal_vector ls; + for (unsigned i = 0; i < terms.size(); ++i) { + expr * e = terms.get(i); + literal l = ctx.get_literal(e); + ls.push_back(l); + } + ctx.mk_th_case_split(ls.size(), ls.c_ptr()); + } + + void theory_str::print_cut_var(expr * node, std::ofstream & xout) { + ast_manager & m = get_manager(); + xout << "Cut info of " << mk_pp(node, m) << std::endl; + if (cut_var_map.contains(node)) { + if (!cut_var_map[node].empty()) { + xout << "[" << cut_var_map[node].top()->level << "] "; + std::map::iterator itor = cut_var_map[node].top()->vars.begin(); + for (; itor != cut_var_map[node].top()->vars.end(); ++itor) { + xout << mk_pp(itor->first, m) << ", "; + } + xout << std::endl; + } + } + } + + /* + * Handle two equivalent Concats. + */ + void theory_str::simplify_concat_equality(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + app * a_nn1 = to_app(nn1); + SASSERT(a_nn1->get_num_args() == 2); + app * a_nn2 = to_app(nn2); + SASSERT(a_nn2->get_num_args() == 2); + + expr * a1_arg0 = a_nn1->get_arg(0); + expr * a1_arg1 = a_nn1->get_arg(1); + expr * a2_arg0 = a_nn2->get_arg(0); + expr * a2_arg1 = a_nn2->get_arg(1); + + rational a1_arg0_len, a1_arg1_len, a2_arg0_len, a2_arg1_len; + + bool a1_arg0_len_exists = get_len_value(a1_arg0, a1_arg0_len); + bool a1_arg1_len_exists = get_len_value(a1_arg1, a1_arg1_len); + bool a2_arg0_len_exists = get_len_value(a2_arg0, a2_arg0_len); + bool a2_arg1_len_exists = get_len_value(a2_arg1, a2_arg1_len); + + TRACE("str", tout << "nn1 = " << mk_ismt2_pp(nn1, m) << std::endl + << "nn2 = " << mk_ismt2_pp(nn2, m) << std::endl;); + + TRACE("str", tout + << "len(" << mk_pp(a1_arg0, m) << ") = " << (a1_arg0_len_exists ? a1_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a1_arg1, m) << ") = " << (a1_arg1_len_exists ? a1_arg1_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg0, m) << ") = " << (a2_arg0_len_exists ? a2_arg0_len.to_string() : "?") << std::endl + << "len(" << mk_pp(a2_arg1, m) << ") = " << (a2_arg1_len_exists ? a2_arg1_len.to_string() : "?") << std::endl + << std::endl;); + + infer_len_concat_equality(nn1, nn2); + + if (a1_arg0 == a2_arg0) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg0 == a2_arg0" << std::endl;); + return; + } + + if (a1_arg1 == a2_arg1) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + expr_ref premise(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref eq1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref eq2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref conclusion(m.mk_and(eq1, eq2), m); + assert_implication(premise, conclusion); + } + TRACE("str", tout << "SKIP: a1_arg1 == a2_arg1" << std::endl;); + return; + } + + // quick path + + if (in_same_eqc(a1_arg0, a2_arg0)) { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "SKIP: a1_arg0 =~ a2_arg0 and a1_arg1 =~ a2_arg1" << std::endl;); + return; + } else { + TRACE("str", tout << "quick path 1-1: a1_arg0 =~ a2_arg0" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg0, a2_arg0)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg1, a2_arg1), ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1))), m); + assert_implication(premise, conclusion); + return; + } + } else { + if (in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 1-2: a1_arg1 =~ a2_arg1" << std::endl;); + expr_ref premise(m.mk_and(ctx.mk_eq_atom(nn1, nn2), ctx.mk_eq_atom(a1_arg1, a2_arg1)), m); + expr_ref conclusion(m.mk_and(ctx.mk_eq_atom(a1_arg0, a2_arg0), ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0))), m); + assert_implication(premise, conclusion); + return; + } + } + + // quick path 2-1 + if (a1_arg0_len_exists && a2_arg0_len_exists && a1_arg0_len == a2_arg0_len) { + if (!in_same_eqc(a1_arg0, a2_arg0)) { + TRACE("str", tout << "quick path 2-1: len(nn1.arg0) == len(nn2.arg0)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg0), mk_strlen(a2_arg0)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + if (a1_arg1_len_exists && a2_arg1_len_exists && a1_arg1_len == a2_arg1_len) { + if (!in_same_eqc(a1_arg1, a2_arg1)) { + TRACE("str", tout << "quick path 2-2: len(nn1.arg1) == len(nn2.arg1)" << std::endl;); + expr_ref ax_l1(ctx.mk_eq_atom(nn1, nn2), m); + expr_ref ax_l2(ctx.mk_eq_atom(mk_strlen(a1_arg1), mk_strlen(a2_arg1)), m); + expr_ref ax_r1(ctx.mk_eq_atom(a1_arg0, a2_arg0), m); + expr_ref ax_r2(ctx.mk_eq_atom(a1_arg1, a2_arg1), m); + + expr_ref premise(m.mk_and(ax_l1, ax_l2), m); + expr_ref conclusion(m.mk_and(ax_r1, ax_r2), m); + + assert_implication(premise, conclusion); + if (opt_NoQuickReturn_IntegerTheory) { + TRACE("str", tout << "bypassing quick return from the end of this case" << std::endl;); + } else { + return; + } + } + } + + expr_ref new_nn1(simplify_concat(nn1), m); + expr_ref new_nn2(simplify_concat(nn2), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + TRACE("str", tout << "new_nn1 = " << mk_ismt2_pp(new_nn1, m) << std::endl + << "new_nn2 = " << mk_ismt2_pp(new_nn2, m) << std::endl;); + + if (new_nn1 == new_nn2) { + TRACE("str", tout << "equal concats, return" << std::endl;); + return; + } + + if (!can_two_nodes_eq(new_nn1, new_nn2)) { + expr_ref detected(m.mk_not(ctx.mk_eq_atom(new_nn1, new_nn2)), m); + TRACE("str", tout << "inconsistency detected: " << mk_ismt2_pp(detected, m) << std::endl;); + assert_axiom(detected); + return; + } + + // check whether new_nn1 and new_nn2 are still concats + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && n2IsConcat) { + TRACE("str", tout << "nn1_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn1)) { + simplify_parent(new_nn2, new_nn1); + } + return; + } else if (n1IsConcat && !n2IsConcat) { + TRACE("str", tout << "nn2_new is not a concat" << std::endl;); + if (u.str.is_string(a_new_nn2)) { + simplify_parent(new_nn1, new_nn2); + } + return; + } else if (!n1IsConcat && !n2IsConcat) { + // normally this should never happen, because group_terms_by_eqc() should have pre-simplified + // as much as possible. however, we make a defensive check here just in case + TRACE("str", tout << "WARNING: nn1_new and nn2_new both simplify to non-concat terms" << std::endl;); + return; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + if (!in_same_eqc(new_nn1, new_nn2) && (nn1 != new_nn1 || nn2 != new_nn2)) { + int ii4 = 0; + expr* item[3]; + if (nn1 != new_nn1) { + item[ii4++] = ctx.mk_eq_atom(nn1, new_nn1); + } + if (nn2 != new_nn2) { + item[ii4++] = ctx.mk_eq_atom(nn2, new_nn2); + } + item[ii4++] = ctx.mk_eq_atom(nn1, nn2); + expr_ref premise(m.mk_and(ii4, item), m); + expr_ref conclusion(ctx.mk_eq_atom(new_nn1, new_nn2), m); + assert_implication(premise, conclusion); + } + + // start to split both concats + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + process_concat_eq_type1(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + process_concat_eq_type2(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + process_concat_eq_type3(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + process_concat_eq_type4(new_nn1, new_nn2); + return; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + process_concat_eq_type5(new_nn1, new_nn2); + return; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + process_concat_eq_type6(new_nn1, new_nn2); + return; + } + + } + + /* + * Returns true if attempting to process a concat equality between lhs and rhs + * will result in overlapping variables (false otherwise). + */ + bool theory_str::will_result_in_overlap(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + + expr_ref new_nn1(simplify_concat(lhs), m); + expr_ref new_nn2(simplify_concat(rhs), m); + app * a_new_nn1 = to_app(new_nn1); + app * a_new_nn2 = to_app(new_nn2); + + bool n1IsConcat = u.str.is_concat(a_new_nn1); + bool n2IsConcat = u.str.is_concat(a_new_nn2); + if (!n1IsConcat && !n2IsConcat) { + // we simplified both sides to non-concat expressions... + return false; + } + + expr * v1_arg0 = a_new_nn1->get_arg(0); + expr * v1_arg1 = a_new_nn1->get_arg(1); + expr * v2_arg0 = a_new_nn2->get_arg(0); + expr * v2_arg1 = a_new_nn2->get_arg(1); + + TRACE("str", tout << "checking whether " << mk_pp(new_nn1, m) << " and " << mk_pp(new_nn1, m) << " might overlap." << std::endl;); + + check_and_init_cut_var(v1_arg0); + check_and_init_cut_var(v1_arg1); + check_and_init_cut_var(v2_arg0); + check_and_init_cut_var(v2_arg1); + + //************************************************************* + // case 1: concat(x, y) = concat(m, n) + //************************************************************* + if (is_concat_eq_type1(new_nn1, new_nn2)) { + TRACE("str", tout << "Type 1 check." << std::endl;); + expr * x = to_app(new_nn1)->get_arg(0); + expr * y = to_app(new_nn1)->get_arg(1); + expr * m = to_app(new_nn2)->get_arg(0); + expr * n = to_app(new_nn2)->get_arg(1); + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 2: concat(x, y) = concat(m, "str") + //************************************************************* + if (is_concat_eq_type2(new_nn1, new_nn2)) { + + expr * y = NULL; + expr * m = NULL; + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + y = v1_arg1; + } + + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 3: concat(x, y) = concat("str", n) + //************************************************************* + if (is_concat_eq_type3(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * x = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + n = v1_arg1; + x = v2_arg0; + } else { + n = v2_arg1; + x = v1_arg0; + } + if (has_self_cut(x, n)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(x, tout); print_cut_var(n, tout);); + return true; + } else { + return false; + } + } + + //************************************************************* + // case 4: concat("str1", y) = concat("str2", n) + //************************************************************* + if (is_concat_eq_type4(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + + //************************************************************* + // case 5: concat(x, "str1") = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type5(new_nn1, new_nn2)) { + // This case can never result in an overlap. + return false; + } + //************************************************************* + // case 6: concat("str1", y) = concat(m, "str2") + //************************************************************* + if (is_concat_eq_type6(new_nn1, new_nn2)) { + expr * v1_arg0 = to_app(new_nn1)->get_arg(0); + expr * v1_arg1 = to_app(new_nn1)->get_arg(1); + expr * v2_arg0 = to_app(new_nn2)->get_arg(0); + expr * v2_arg1 = to_app(new_nn2)->get_arg(1); + + expr * y = NULL; + expr * m = NULL; + + if (u.str.is_string(v1_arg0)) { + y = v1_arg1; + m = v2_arg0; + } else { + y = v2_arg1; + m = v1_arg0; + } + if (has_self_cut(m, y)) { + TRACE("str", tout << "Possible overlap found" << std::endl; print_cut_var(m, tout); print_cut_var(y, tout);); + return true; + } else { + return false; + } + } + + TRACE("str", tout << "warning: unrecognized concat case" << std::endl;); return false; } -} -void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); + /************************************************************* + * Type 1: concat(x, y) = concat(m, n) + * x, y, m and n all variables + *************************************************************/ + bool theory_str::is_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - expr * x = to_app(concatAst1)->get_arg(0); - expr * y = to_app(concatAst1)->get_arg(1); - expr * m = to_app(concatAst2)->get_arg(0); - expr * n = to_app(concatAst2)->get_arg(1); - - rational x_len, y_len, m_len, n_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - bool m_len_exists = get_len_value(m, m_len); - bool n_len_exists = get_len_value(n, n_len); - - int splitType = -1; - if (x_len_exists && m_len_exists) { - TRACE("str", tout << "length values found: x/m" << std::endl;); - if (x_len < m_len) { - splitType = 0; - } else if (x_len == m_len) { - splitType = 1; + if (!u.str.is_string(x) && !u.str.is_string(y) && !u.str.is_string(m) && !u.str.is_string(n)) { + return true; } else { - splitType = 2; + return false; } } - if (splitType == -1 && y_len_exists && n_len_exists) { - TRACE("str", tout << "length values found: y/n" << std::endl;); - if (y_len > n_len) { - splitType = 0; - } else if (y_len == n_len) { - splitType = 1; - } else { - splitType = 2; + void theory_str::process_concat_eq_type1(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 1" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; } - } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + expr * x = to_app(concatAst1)->get_arg(0); + expr * y = to_app(concatAst1)->get_arg(1); + expr * m = to_app(concatAst2)->get_arg(0); + expr * n = to_app(concatAst2)->get_arg(1); - TRACE("str", tout - << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl - << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl - << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl - << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl - << "split type " << splitType << std::endl; - ); + rational x_len, y_len, m_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool n_len_exists = get_len_value(n, n_len); - expr * t1 = NULL; - expr * t2 = NULL; - expr * xorFlag = NULL; + int splitType = -1; + if (x_len_exists && m_len_exists) { + TRACE("str", tout << "length values found: x/m" << std::endl;); + if (x_len < m_len) { + splitType = 0; + } else if (x_len == m_len) { + splitType = 1; + } else { + splitType = 2; + } + } - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); + if (splitType == -1 && y_len_exists && n_len_exists) { + TRACE("str", tout << "length values found: y/n" << std::endl;); + if (y_len > n_len) { + splitType = 0; + } else if (y_len == n_len) { + splitType = 1; + } else { + splitType = 2; + } + } - // check the entries in this map to make sure they're still in scope - // before we use them. + TRACE("str", tout + << "len(x) = " << (x_len_exists ? x_len.to_string() : "?") << std::endl + << "len(y) = " << (y_len_exists ? y_len.to_string() : "?") << std::endl + << "len(m) = " << (m_len_exists ? m_len.to_string() : "?") << std::endl + << "len(n) = " << (n_len_exists ? n_len.to_string() : "?") << std::endl + << "split type " << splitType << std::endl; + ); - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + expr * t1 = NULL; + expr * t2 = NULL; + expr * xorFlag = NULL; - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { entry1InScope = false; } else { - entry1InScope = true; + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[2]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } } - } - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { entry2InScope = false; } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - if (!entry1InScope && !entry2InScope) { - t1 = mk_nonempty_str_var(); - t2 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - check_and_init_cut_var(t1); - check_and_init_cut_var(t2); - varForBreakConcat[key1][0] = t1; - varForBreakConcat[key1][1] = t2; - varForBreakConcat[key1][2] = xorFlag; - } else { - // match found - if (entry1InScope) { - t1 = varForBreakConcat[key1][0]; - t2 = varForBreakConcat[key1][1]; - xorFlag = varForBreakConcat[key1][2]; - } else { - t1 = varForBreakConcat[key2][0]; - t2 = varForBreakConcat[key2][1]; - xorFlag = varForBreakConcat[key2][2]; - } - refresh_theory_var(t1); - add_nonempty_constraint(t1); - refresh_theory_var(t2); - add_nonempty_constraint(t2); - } - - // For split types 0 through 2, we can get away with providing - // fewer split options since more length information is available. - if (splitType == 0) { - //-------------------------------------- - // Type 0: M cuts Y. - // len(x) < len(m) || len(y) > len(n) - //-------------------------------------- - expr_ref_vector ax_l_items(mgr); - expr_ref_vector ax_r_items(mgr); - - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref x_t1(mk_concat(x, t1), mgr); - expr_ref t1_n(mk_concat(t1, n), mgr); - - ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); - ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = m_len - x_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); - } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational y_sub_n = y_len - n_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); - } - - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); - - if (!has_self_cut(m, y)) { - // Cut Info - add_cut_info_merge(t1, sLevel, m); - add_cut_info_merge(t1, sLevel, y); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[2]) == internal_variable_set.end() */) { + entry2InScope = false; } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } + entry2InScope = true; } } - } else if (splitType == 1) { - // Type 1: - // len(x) = len(m) || len(y) = len(n) - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); - assert_implication(ax_l, ax_r); - } else if (splitType == 2) { - // Type 2: X cuts N. - // len(x) > len(m) || len(y) < len(n) - expr_ref m_t2(mk_concat(m, t2), mgr); - expr_ref t2_y(mk_concat(t2, y), mgr); - expr_ref_vector ax_l_items(mgr); - ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - expr_ref_vector ax_r_items(mgr); - ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); - ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); - - if (m_len_exists && x_len_exists) { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational x_sub_m = x_len - m_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + if (!entry1InScope && !entry2InScope) { + t1 = mk_nonempty_str_var(); + t2 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + check_and_init_cut_var(t1); + check_and_init_cut_var(t2); + varForBreakConcat[key1][0] = t1; + varForBreakConcat[key1][1] = t2; + varForBreakConcat[key1][2] = xorFlag; } else { - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - rational n_sub_y = n_len - y_len; - ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + // match found + if (entry1InScope) { + t1 = varForBreakConcat[key1][0]; + t2 = varForBreakConcat[key1][1]; + xorFlag = varForBreakConcat[key1][2]; + } else { + t1 = varForBreakConcat[key2][0]; + t2 = varForBreakConcat[key2][1]; + xorFlag = varForBreakConcat[key2][2]; + } + refresh_theory_var(t1); + add_nonempty_constraint(t1); + refresh_theory_var(t2); + add_nonempty_constraint(t2); } - expr_ref ax_l(mk_and(ax_l_items), mgr); - expr_ref ax_r(mk_and(ax_r_items), mgr); + // For split types 0 through 2, we can get away with providing + // fewer split options since more length information is available. + if (splitType == 0) { + //-------------------------------------- + // Type 0: M cuts Y. + // len(x) < len(m) || len(y) > len(n) + //-------------------------------------- + expr_ref_vector ax_l_items(mgr); + expr_ref_vector ax_r_items(mgr); - if (!has_self_cut(x, n)) { - // Cut Info - add_cut_info_merge(t2, sLevel, x); - add_cut_info_merge(t2, sLevel, n); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } - } - } - } else if (splitType == -1) { - // Here we don't really have a choice. We have no length information at all... - - // This vector will eventually contain one term for each possible arrangement we explore. - expr_ref_vector arrangement_disjunction(mgr); - - // break option 1: m cuts y - // len(x) < len(m) || len(y) > len(n) - if (!avoidLoopCut || !has_self_cut(m, y)) { - expr_ref_vector and_item(mgr); - // break down option 1-1 expr_ref x_t1(mk_concat(x, t1), mgr); expr_ref t1_n(mk_concat(t1, n), mgr); - and_item.push_back(ctx.mk_eq_atom(m, x_t1)); - and_item.push_back(ctx.mk_eq_atom(y, t1_n)); + ax_r_items.push_back(ctx.mk_eq_atom(m, x_t1)); + ax_r_items.push_back(ctx.mk_eq_atom(y, t1_n)); - expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); - // These were crashing the solver because the integer theory - // expects a constant on the right-hand side. - // The things we want to assert here are len(m) > len(x) and len(y) > len(n). - // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), - // and then, *because we aren't allowed to use subtraction*, - // as not(A + -1*B <= 0) - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), - mk_int(0))) ); - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), - mk_int(0))) ); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - - add_cut_info_merge(t1, ctx.get_scope_level(), m); - add_cut_info_merge(t1, ctx.get_scope_level(), y); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = m_len - x_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(m_sub_x))); } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - - // break option 2: - // x = m . t2 - // n = t2 . y - if (!avoidLoopCut || !has_self_cut(x, n)) { - expr_ref_vector and_item(mgr); - // break down option 1-2 - expr_ref m_t2(mk_concat(m, t2), mgr); - expr_ref t2_y(mk_concat(t2, y), mgr); - - and_item.push_back(ctx.mk_eq_atom(x, m_t2)); - and_item.push_back(ctx.mk_eq_atom(n, t2_y)); - - - expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); - // want len(x) > len(m) and len(n) > len(y) - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), - mk_int(0))) ); - and_item.push_back( - mgr.mk_not(m_autil.mk_le( - m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), - mk_int(0))) ); - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.1, l_true); - - add_cut_info_merge(t2, ctx.get_scope_level(), x); - add_cut_info_merge(t2, ctx.get_scope_level(), n); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - - // option 3: - // x = m, y = n - if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { - expr_ref_vector and_item(mgr); - - and_item.push_back(ctx.mk_eq_atom(x, m)); - and_item.push_back(ctx.mk_eq_atom(y, n)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); - - expr_ref option3(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option3); - // prioritize this case, it is easier - add_theory_aware_branching_info(option3, 0.5, l_true); - } - - if (!arrangement_disjunction.empty()) { - expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref conclusion(mk_or(arrangement_disjunction), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(premise, conclusion); - } - // assert mutual exclusion between each branch of the arrangement - generate_mutual_exclusion(arrangement_disjunction); - } else { - TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); - } - } // (splitType == -1) -} - -/************************************************************* - * Type 2: concat(x, y) = concat(m, "str") - *************************************************************/ -bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) - && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { - return true; - } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) - && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; - expr * m = NULL; - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { - m = v1_arg0; - strAst = v1_arg1; - x = v2_arg0; - y = v2_arg1; - } else { - m = v2_arg0; - strAst = v2_arg1; - x = v1_arg0; - y = v1_arg1; - } - - zstring strValue; - u.str.is_string(strAst, strValue); - - rational x_len, y_len, m_len, str_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - bool m_len_exists = get_len_value(m, m_len); - bool str_len_exists = true; - str_len = rational(strValue.length()); - - // setup - - expr * xorFlag = NULL; - expr * temp1 = NULL; - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - // prevent checking scope for the XOR term, as it's always in the same scope as the split var - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ - ) { - entry1InScope = false; - } else { - entry1InScope = true; - } - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ - ) { - entry2InScope = false; - } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - - if (!entry1InScope && !entry2InScope) { - temp1 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = temp1; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; - } else if (entry2InScope) { - temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; - } - refresh_theory_var(temp1); - add_nonempty_constraint(temp1); - } - - int splitType = -1; - if (x_len_exists && m_len_exists) { - if (x_len < m_len) - splitType = 0; - else if (x_len == m_len) - splitType = 1; - else - splitType = 2; - } - if (splitType == -1 && y_len_exists && str_len_exists) { - if (y_len > str_len) - splitType = 0; - else if (y_len == str_len) - splitType = 1; - else - splitType = 2; - } - - TRACE("str", tout << "Split type " << splitType << std::endl;); - - // Provide fewer split options when length information is available. - - if (splitType == 0) { - // M cuts Y - // | x | y | - // | m | str | - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); - if (can_two_nodes_eq(y, temp1_strAst)) { - expr_ref_vector l_items(mgr); - l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - - expr_ref_vector r_items(mgr); - expr_ref x_temp1(mk_concat(x, temp1), mgr); - r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); - r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - - if (x_len_exists && m_len_exists) { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - rational m_sub_x = (m_len - x_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); - } else { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); - rational y_sub_str = (y_len - str_len); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational y_sub_n = y_len - n_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t1), mk_int(y_sub_n))); } - expr_ref ax_l(mk_and(l_items), mgr); - expr_ref ax_r(mk_and(r_items), mgr); + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); - if (!avoidLoopCut || !(has_self_cut(m, y))) { - // break down option 2-1 - add_cut_info_merge(temp1, sLevel, y); - add_cut_info_merge(temp1, sLevel, m); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - loopDetected = true; - - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - assert_implication(ax_l, tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); - } - } - } - } - } else if (splitType == 1) { - // | x | y | - // | m | str | - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or( - ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), - ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); - assert_implication(ax_l, ax_r); - } else if (splitType == 2) { - // m cut y, - // | x | y | - // | m | str | - rational lenDelta; - expr_ref_vector l_items(mgr); - l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - if (x_len_exists && m_len_exists) { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); - lenDelta = x_len - m_len; - } else { - l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - lenDelta = str_len - y_len; - } - TRACE("str", - tout - << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl - << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl - << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl - << "xLen = " << x_len.to_string() << std::endl - << "yLen = " << y_len.to_string() << std::endl - << "mLen = " << m_len.to_string() << std::endl - << "strLen = " << str_len.to_string() << std::endl - << "lenDelta = " << lenDelta.to_string() << std::endl - << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" - ; - ); - - zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); - zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); - - expr_ref prefixStr(mk_string(part1Str), mgr); - expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(mk_string(part2Str), mgr); - - if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, x_concat)); - r_items.push_back(ctx.mk_eq_atom(y, cropStr)); - expr_ref ax_l(mk_and(l_items), mgr); - expr_ref ax_r(mk_and(r_items), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } else { - // negate! It's impossible to split str with these lengths - TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); - expr_ref ax_l(mk_and(l_items), mgr); - assert_axiom(mgr.mk_not(ax_l)); - } - } else { - // Split type -1: no idea about the length... - expr_ref_vector arrangement_disjunction(mgr); - - expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); - - // m cuts y - if (can_two_nodes_eq(y, temp1_strAst)) { - if (!avoidLoopCut || !has_self_cut(m, y)) { - // break down option 2-1 - expr_ref_vector and_item(mgr); - - expr_ref x_temp1(mk_concat(x, temp1), mgr); - and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); - and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - add_cut_info_merge(temp1, ctx.get_scope_level(), y); - add_cut_info_merge(temp1, ctx.get_scope_level(), m); - } else { - loopDetected = true; - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); - - if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - } - } - } - } - - for (unsigned int i = 0; i <= strValue.length(); ++i) { - zstring part1Str = strValue.extract(0, i); - zstring part2Str = strValue.extract(i, strValue.length() - i); - expr_ref prefixStr(mk_string(part1Str), mgr); - expr_ref x_concat(mk_concat(m, prefixStr), mgr); - expr_ref cropStr(mk_string(part2Str), mgr); - if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { - // break down option 2-2 - expr_ref_vector and_item(mgr); - and_item.push_back(ctx.mk_eq_atom(x, x_concat)); - and_item.push_back(ctx.mk_eq_atom(y, cropStr)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - double priority; - // prioritize the option where y is equal to the original string - if (i == 0) { - priority = 0.5; - } else { - priority = 0.1; - } - add_theory_aware_branching_info(option2, priority, l_true); - } - } - - if (!arrangement_disjunction.empty()) { - expr_ref implyR(mk_or(arrangement_disjunction), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - generate_mutual_exclusion(arrangement_disjunction); - } else { - TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); - } - } // (splitType == -1) -} - -/************************************************************* - * Type 3: concat(x, y) = concat("str", n) - *************************************************************/ -bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { - return true; - } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) - && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - bool overlapAssumptionUsed = false; - - TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * x = NULL; - expr * y = NULL; - expr * strAst = NULL; - expr * n = NULL; - - if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { - strAst = v1_arg0; - n = v1_arg1; - x = v2_arg0; - y = v2_arg1; - } else { - strAst = v2_arg0; - n = v2_arg1; - x = v1_arg0; - y = v1_arg1; - } - - zstring strValue; - u.str.is_string(strAst, strValue); - - rational x_len, y_len, str_len, n_len; - bool x_len_exists = get_len_value(x, x_len); - bool y_len_exists = get_len_value(y, y_len); - str_len = rational((unsigned)(strValue.length())); - bool n_len_exists = get_len_value(n, n_len); - - expr_ref xorFlag(mgr); - expr_ref temp1(mgr); - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { - entry1InScope = false; - } else { - entry1InScope = true; - } - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { - entry2InScope = false; - } else { - entry2InScope = true; - } - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - - if (!entry1InScope && !entry2InScope) { - temp1 = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - - varForBreakConcat[key1][0] = temp1; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - temp1 = varForBreakConcat[key1][0]; - xorFlag = varForBreakConcat[key1][1]; - } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { - temp1 = varForBreakConcat[key2][0]; - xorFlag = varForBreakConcat[key2][1]; - } - refresh_theory_var(temp1); - add_nonempty_constraint(temp1); - } - - - - int splitType = -1; - if (x_len_exists) { - if (x_len < str_len) - splitType = 0; - else if (x_len == str_len) - splitType = 1; - else - splitType = 2; - } - if (splitType == -1 && y_len_exists && n_len_exists) { - if (y_len > n_len) - splitType = 0; - else if (y_len == n_len) - splitType = 1; - else - splitType = 2; - } - - TRACE("str", tout << "Split type " << splitType << std::endl;); - - // Provide fewer split options when length information is available. - if (splitType == 0) { - // | x | y | - // | str | n | - expr_ref_vector litems(mgr); - litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - rational prefixLen; - if (!x_len_exists) { - prefixLen = str_len - (y_len - n_len); - litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - } else { - prefixLen = x_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - } - zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); - rational str_sub_prefix = str_len - prefixLen; - zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); - expr_ref prefixAst(mk_string(prefixStr), mgr); - expr_ref suffixAst(mk_string(suffixStr), mgr); - expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); - - expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); - if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); - r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, mk_and(r_items)); - } - } else { - // negate! It's impossible to split str with these lengths - TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); - assert_axiom(mgr.mk_not(ax_l)); - } - } - else if (splitType == 1) { - expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_l2(mgr.mk_or( - ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), - ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); - expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); - expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ax_l, ax_r); - } - } - else if (splitType == 2) { - // | x | y | - // | str | n | - expr_ref_vector litems(mgr); - litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - rational tmpLen; - if (!x_len_exists) { - tmpLen = n_len - y_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); - litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); - } else { - tmpLen = x_len - str_len; - litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); - } - expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); - - expr_ref str_temp1(mk_concat(strAst, temp1), mgr); - expr_ref temp1_y(mk_concat(temp1, y), mgr); - - if (can_two_nodes_eq(x, str_temp1)) { - if (!avoidLoopCut || !(has_self_cut(x, n))) { - expr_ref_vector r_items(mgr); - r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); - r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); - r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); - expr_ref ax_r(mk_and(r_items), mgr); - - //Cut Info - add_cut_info_merge(temp1, sLevel, x); - add_cut_info_merge(temp1, sLevel, n); + if (!has_self_cut(m, y)) { + // Cut Info + add_cut_info_merge(t1, sLevel, m); + add_cut_info_merge(t1, sLevel, y); if (m_params.m_StrongArrangements) { expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); @@ -3937,82 +3112,117 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); - TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); } } } - } - // else { - // // negate! It's impossible to split str with these lengths - // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); - // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); - // } - } - else { - // Split type -1. We know nothing about the length... + } else if (splitType == 1) { + // Type 1: + // len(x) = len(m) || len(y) = len(n) + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x,m), ctx.mk_eq_atom(y,n)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // Type 2: X cuts N. + // len(x) > len(m) || len(y) < len(n) + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); - expr_ref_vector arrangement_disjunction(mgr); + expr_ref_vector ax_l_items(mgr); + ax_l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - int pos = 1; - for (unsigned int i = 0; i <= strValue.length(); i++) { - zstring part1Str = strValue.extract(0, i); - zstring part2Str = strValue.extract(i, strValue.length() - i); - expr_ref cropStr(mk_string(part1Str), mgr); - expr_ref suffixStr(mk_string(part2Str), mgr); - expr_ref y_concat(mk_concat(suffixStr, n), mgr); + expr_ref_vector ax_r_items(mgr); + ax_r_items.push_back(ctx.mk_eq_atom(x, m_t2)); + ax_r_items.push_back(ctx.mk_eq_atom(t2_y, n)); - if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + if (m_len_exists && x_len_exists) { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational x_sub_m = x_len - m_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(x_sub_m))); + } else { + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + ax_l_items.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + rational n_sub_y = n_len - y_len; + ax_r_items.push_back(ctx.mk_eq_atom(mk_strlen(t2), mk_int(n_sub_y))); + } + + expr_ref ax_l(mk_and(ax_l_items), mgr); + expr_ref ax_r(mk_and(ax_r_items), mgr); + + if (!has_self_cut(x, n)) { + // Cut Info + add_cut_info_merge(t2, sLevel, x); + add_cut_info_merge(t2, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } else if (splitType == -1) { + // Here we don't really have a choice. We have no length information at all... + + // This vector will eventually contain one term for each possible arrangement we explore. + expr_ref_vector arrangement_disjunction(mgr); + + // break option 1: m cuts y + // len(x) < len(m) || len(y) > len(n) + if (!avoidLoopCut || !has_self_cut(m, y)) { expr_ref_vector and_item(mgr); - // break down option 3-1 - expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + // break down option 1-1 + expr_ref x_t1(mk_concat(x, t1), mgr); + expr_ref t1_n(mk_concat(t1, n), mgr); - and_item.push_back(x_eq_str); ++pos; - and_item.push_back(ctx.mk_eq_atom(y, y_concat)); - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + and_item.push_back(ctx.mk_eq_atom(m, x_t1)); + and_item.push_back(ctx.mk_eq_atom(y, t1_n)); - // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); - // adding length constraint for _ = constStr seems slowing things down. + expr_ref x_plus_t1(m_autil.mk_add(mk_strlen(x), mk_strlen(t1)), mgr); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), x_plus_t1)); + // These were crashing the solver because the integer theory + // expects a constant on the right-hand side. + // The things we want to assert here are len(m) > len(x) and len(y) > len(n). + // We rewrite A > B as A-B > 0 and then as not(A-B <= 0), + // and then, *because we aren't allowed to use subtraction*, + // as not(A + -1*B <= 0) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(m), m_autil.mk_mul(mk_int(-1), mk_strlen(x))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(y),m_autil.mk_mul(mk_int(-1), mk_strlen(n))), + mk_int(0))) ); expr_ref option1(mk_and(and_item), mgr); arrangement_disjunction.push_back(option1); - double priority; - if (i == strValue.length()) { - priority = 0.5; - } else { - priority = 0.1; - } - add_theory_aware_branching_info(option1, priority, l_true); - } - } + add_theory_aware_branching_info(option1, 0.1, l_true); - expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); - - - //-------------------------------------------------------- - // x cut n - //-------------------------------------------------------- - if (can_two_nodes_eq(x, strAst_temp1)) { - if (!avoidLoopCut || !(has_self_cut(x, n))) { - // break down option 3-2 - expr_ref_vector and_item(mgr); - - expr_ref temp1_y(mk_concat(temp1, y), mgr); - and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; - and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), - m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - add_theory_aware_branching_info(option2, 0.1, l_true); - - add_cut_info_merge(temp1, sLevel, x); - add_cut_info_merge(temp1, sLevel, n); + add_cut_info_merge(t1, ctx.get_scope_level(), m); + add_cut_info_merge(t1, ctx.get_scope_level(), y); } else { loopDetected = true; if (m_params.m_FiniteOverlapModels) { @@ -4020,6259 +3230,3742 @@ void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { arrangement_disjunction.push_back(tester); add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + + // break option 2: + // x = m . t2 + // n = t2 . y + if (!avoidLoopCut || !has_self_cut(x, n)) { + expr_ref_vector and_item(mgr); + // break down option 1-2 + expr_ref m_t2(mk_concat(m, t2), mgr); + expr_ref t2_y(mk_concat(t2, y), mgr); + + and_item.push_back(ctx.mk_eq_atom(x, m_t2)); + and_item.push_back(ctx.mk_eq_atom(n, t2_y)); + + + expr_ref m_plus_t2(m_autil.mk_add(mk_strlen(m), mk_strlen(t2)), mgr); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), m_plus_t2)); + // want len(x) > len(m) and len(n) > len(y) + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(x), m_autil.mk_mul(mk_int(-1), mk_strlen(m))), + mk_int(0))) ); + and_item.push_back( + mgr.mk_not(m_autil.mk_le( + m_autil.mk_add(mk_strlen(n), m_autil.mk_mul(mk_int(-1), mk_strlen(y))), + mk_int(0))) ); + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(t2, ctx.get_scope_level(), x); + add_cut_info_merge(t2, ctx.get_scope_level(), n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); if (!overlapAssumptionUsed) { - overlapAssumptionUsed = true; - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); } } } - } + // option 3: + // x = m, y = n + if (can_two_nodes_eq(x, m) && can_two_nodes_eq(y, n)) { + expr_ref_vector and_item(mgr); - if (!arrangement_disjunction.empty()) { - expr_ref implyR(mk_or(arrangement_disjunction), mgr); + and_item.push_back(ctx.mk_eq_atom(x, m)); + and_item.push_back(ctx.mk_eq_atom(y, n)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m))); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))); - if (m_params.m_StrongArrangements) { - expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); - expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); - assert_axiom(ax_strong); + expr_ref option3(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option3); + // prioritize this case, it is easier + add_theory_aware_branching_info(option3, 0.5, l_true); + } + + if (!arrangement_disjunction.empty()) { + expr_ref premise(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref conclusion(mk_or(arrangement_disjunction), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(premise, conclusion), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(premise, conclusion); + } + // assert mutual exclusion between each branch of the arrangement + generate_mutual_exclusion(arrangement_disjunction); } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + TRACE("str", tout << "STOP: no split option found for two EQ concats." << std::endl;); } - generate_mutual_exclusion(arrangement_disjunction); + } // (splitType == -1) + } + + /************************************************************* + * Type 2: concat(x, y) = concat(m, "str") + *************************************************************/ + bool theory_str::is_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if ((!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; } else { - TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); + return false; } } -} + void theory_str::process_concat_eq_type2(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); -/************************************************************* - * Type 4: concat("str1", y) = concat("str2", n) - *************************************************************/ -bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + bool overlapAssumptionUsed = false; - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { - return true; - } else { - return false; - } -} + TRACE("str", tout << "process_concat_eq TYPE 2" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); -void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * str1Ast = v1_arg0; - expr * y = v1_arg1; - expr * str2Ast = v2_arg0; - expr * n = v2_arg1; - - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); - - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); - - int commonLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { - TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) - << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); - expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); - assert_axiom(toNegate); - return; - } else { - if (str1Len > str2Len) { - zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); - expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); - if (!in_same_eqc(tmpAst, n)) { - // break down option 4-1 - expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else if (str1Len == str2Len) { - if (!in_same_eqc(n, y)) { - //break down option 4-2 - expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else { - zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); - expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); - if (!in_same_eqc(y, tmpAst)) { - //break down option 4-3 - expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; } - } -} - -/************************************************************* - * case 5: concat(x, "str1") = concat(m, "str2") - *************************************************************/ -bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) - && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { - return true; - } else { - return false; - } -} - -void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); - - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } - - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); - - expr * x = v1_arg0; - expr * str1Ast = v1_arg1; - expr * m = v2_arg0; - expr * str2Ast = v2_arg1; - - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); - - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); - - int cLen = (str1Len > str2Len) ? str2Len : str1Len; - if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { - TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) - << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); - expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); - assert_axiom(toNegate); - return; - } else { - if (str1Len > str2Len) { - zstring deltaStr = str1Value.extract(0, str1Len - str2Len); - expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); - if (!in_same_eqc(m, x_deltaStr)) { - expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else if (str1Len == str2Len) { - // test - if (!in_same_eqc(x, m)) { - expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } - } else { - zstring deltaStr = str2Value.extract(0, str2Len - str1Len); - expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); - if (!in_same_eqc(x, m_deltaStr)) { - expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; } - } -} -/************************************************************* - * case 6: concat("str1", y) = concat(m, "str2") - *************************************************************/ -bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * m = NULL; - if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) - && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { - return true; - } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) - && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { - return true; - } else { - return false; - } -} + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); -void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl - << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl - << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; - ); + if (u.str.is_string(v1_arg1) && !u.str.is_string(v2_arg1)) { + m = v1_arg0; + strAst = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + m = v2_arg0; + strAst = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } - if (!u.str.is_concat(to_app(concatAst1))) { - TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); - return; - } - if (!u.str.is_concat(to_app(concatAst2))) { - TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); - return; - } + zstring strValue; + u.str.is_string(strAst, strValue); - expr * v1_arg0 = to_app(concatAst1)->get_arg(0); - expr * v1_arg1 = to_app(concatAst1)->get_arg(1); - expr * v2_arg0 = to_app(concatAst2)->get_arg(0); - expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + rational x_len, y_len, m_len, str_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + bool m_len_exists = get_len_value(m, m_len); + bool str_len_exists = true; + str_len = rational(strValue.length()); + // setup - expr * str1Ast = NULL; - expr * y = NULL; - expr * m = NULL; - expr * str2Ast = NULL; + expr * xorFlag = NULL; + expr * temp1 = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); - if (u.str.is_string(v1_arg0)) { - str1Ast = v1_arg0; - y = v1_arg1; - m = v2_arg0; - str2Ast = v2_arg1; - } else { - str1Ast = v2_arg0; - y = v2_arg1; - m = v1_arg0; - str2Ast = v1_arg1; - } + // check the entries in this map to make sure they're still in scope + // before we use them. - zstring str1Value, str2Value; - u.str.is_string(str1Ast, str1Value); - u.str.is_string(str2Ast, str2Value); + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - unsigned int str1Len = str1Value.length(); - unsigned int str2Len = str2Value.length(); + // prevent checking scope for the XOR term, as it's always in the same scope as the split var - //---------------------------------------- - //(a) |---str1---|----y----| - // |--m--|-----str2-----| - // - //(b) |---str1---|----y----| - // |-----m----|--str2---| - // - //(c) |---str1---|----y----| - // |------m------|-str2-| - //---------------------------------------- - - std::list overlapLen; - overlapLen.push_back(0); - - for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { - if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) - overlapLen.push_back(i); - } - - //---------------------------------------------------------------- - expr * commonVar = NULL; - expr * xorFlag = NULL; - std::pair key1(concatAst1, concatAst2); - std::pair key2(concatAst2, concatAst1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - entry1InScope = false; - } else { - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { entry1InScope = false; } else { - entry1InScope = true; + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end()*/ + ) { + entry1InScope = false; + } else { + entry1InScope = true; + } } - } - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - entry2InScope = false; - } else { - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() - /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { entry2InScope = false; } else { - entry2InScope = true; + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /*|| internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end()*/ + ) { + entry2InScope = false; + } else { + entry2InScope = true; + } } - } - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - if (!entry1InScope && !entry2InScope) { - commonVar = mk_nonempty_str_var(); - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = commonVar; - varForBreakConcat[key1][1] = xorFlag; - } else { - if (entry1InScope) { - commonVar = (entry1->second)[0]; - xorFlag = (entry1->second)[1]; + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; } else { - commonVar = (entry2->second)[0]; - xorFlag = (entry2->second)[1]; - } - refresh_theory_var(commonVar); - add_nonempty_constraint(commonVar); - } - - bool overlapAssumptionUsed = false; - - expr_ref_vector arrangement_disjunction(mgr); - int pos = 1; - - if (!avoidLoopCut || !has_self_cut(m, y)) { - expr_ref_vector and_item(mgr); - - expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); - and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); - pos += 1; - - expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); - and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); - pos += 1; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), - m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); - pos += 1; - - // addItems[0] = mk_length(t, commonVar); - // addItems[1] = mk_length(t, str2Ast); - // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); - - expr_ref option1(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option1); - add_theory_aware_branching_info(option1, 0.1, l_true); - } else { - loopDetected = true; - - if (m_params.m_FiniteOverlapModels) { - expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); - arrangement_disjunction.push_back(tester); - add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); - } else { - TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); - TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); - - // only add the overlap assumption one time - if (!overlapAssumptionUsed) { - arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); - overlapAssumptionUsed = true; + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (entry2InScope) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); } - } - for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { - unsigned int overLen = *itor; - zstring prefix = str1Value.extract(0, str1Len - overLen); - zstring suffix = str2Value.extract(overLen, str2Len - overLen); - - expr_ref_vector and_item(mgr); - - expr_ref prefixAst(mk_string(prefix), mgr); - expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); - and_item.push_back(x_eq_prefix); - pos += 1; - - and_item.push_back( - ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); - pos += 1; - - // adding length constraint for _ = constStr seems slowing things down. - - expr_ref suffixAst(mk_string(suffix), mgr); - expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); - and_item.push_back(y_eq_suffix); - pos += 1; - - and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); - pos += 1; - - expr_ref option2(mk_and(and_item), mgr); - arrangement_disjunction.push_back(option2); - double priority; - // prefer the option "str1" = x - if (prefix == str1Value) { - priority = 0.5; - } else { - priority = 0.1; + int splitType = -1; + if (x_len_exists && m_len_exists) { + if (x_len < m_len) + splitType = 0; + else if (x_len == m_len) + splitType = 1; + else + splitType = 2; } - add_theory_aware_branching_info(option2, priority, l_true); - } - - // case 6: concat("str1", y) = concat(m, "str2") - - expr_ref implyR(mk_or(arrangement_disjunction), mgr); - - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); - assert_axiom(ax_strong); - } else { - assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); - } - generate_mutual_exclusion(arrangement_disjunction); -} - -void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { - ast_manager & m = get_manager(); - - if (!u.re.is_unroll(to_app(unrollFunc))) { - return; - } - if (!u.str.is_string(constStr)) { - return; - } - - expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); - zstring strValue; - u.str.is_string(constStr, strValue); - - TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl - << "constStr: " << mk_pp(constStr, m) << std::endl;); - - if (strValue == "") { - return; - } - - if (u.re.is_to_re(to_app(funcInUnroll))) { - unroll_str2reg_constStr(unrollFunc, constStr); - return; - } -} - -void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); - - std::pair key = std::make_pair(concat, unroll); - expr_ref toAssert(mgr); - - if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { - expr_ref arg1(to_app(concat)->get_arg(0), mgr); - expr_ref arg2(to_app(concat)->get_arg(1), mgr); - expr_ref r1(to_app(unroll)->get_arg(0), mgr); - expr_ref t1(to_app(unroll)->get_arg(1), mgr); - - expr_ref v1(mk_regex_rep_var(), mgr); - expr_ref v2(mk_regex_rep_var(), mgr); - expr_ref v3(mk_regex_rep_var(), mgr); - expr_ref v4(mk_regex_rep_var(), mgr); - expr_ref v5(mk_regex_rep_var(), mgr); - - expr_ref t2(mk_unroll_bound_var(), mgr); - expr_ref t3(mk_unroll_bound_var(), mgr); - expr_ref emptyStr(mk_string(""), mgr); - - expr_ref unroll1(mk_unroll(r1, t2), mgr); - expr_ref unroll2(mk_unroll(r1, t3), mgr); - - expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); - expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); - - expr_ref_vector op1Items(mgr); - expr_ref_vector op2Items(mgr); - - op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); - op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); - op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); - op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); - expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); - - expr_ref v1v2(mk_concat(v1, v2), mgr); - op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); - expr_ref v3v4(mk_concat(v3, v4), mgr); - op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); - - op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); - op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); - expr_ref v2v3(mk_concat(v2, v3), mgr); - op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); - reduce_virtual_regex_in(v5, r1, op2Items); - op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); - op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); - expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); - - toAssert = mgr.mk_and(opAnd1, opAnd2); - m_trail.push_back(toAssert); - concat_eq_unroll_ast_map[key] = toAssert; - } else { - toAssert = concat_eq_unroll_ast_map[key]; - } - - assert_axiom(toAssert); -} - -void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); - expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); - expr * oriCnt = to_app(unrollFunc)->get_arg(1); - - zstring strValue; - u.str.is_string(eqConstStr, strValue); - zstring regStrValue; - u.str.is_string(strInStr2RegFunc, regStrValue); - unsigned int strLen = strValue.length(); - unsigned int regStrLen = regStrValue.length(); - SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere - unsigned int cnt = strLen / regStrLen; - - expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); - expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); - expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); - expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); - SASSERT(implyL); - SASSERT(axiomRHS); - assert_implication(implyL, axiomRHS); -} - -/* - * Look through the equivalence class of n to find a string constant. - * Return that constant if it is found, and set hasEqcValue to true. - * Otherwise, return n, and set hasEqcValue to false. - */ - -expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { - return z3str2_get_eqc_value(n, hasEqcValue); -} - - -// Simulate the behaviour of get_eqc_value() from Z3str2. -// We only check m_find for a string constant. - -expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { - expr * curr = n; - do { - if (u.str.is_string(curr)) { - hasEqcValue = true; - return curr; + if (splitType == -1 && y_len_exists && str_len_exists) { + if (y_len > str_len) + splitType = 0; + else if (y_len == str_len) + splitType = 1; + else + splitType = 2; } - curr = get_eqc_next(curr); - } while (curr != n); - hasEqcValue = false; - return n; -} -// from Z3: theory_seq.cpp + TRACE("str", tout << "Split type " << splitType << std::endl;); -static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { - theory* th = ctx.get_theory(afid); - if (th && ctx.e_internalized(e)) { - return dynamic_cast(th); - } - else { - return 0; - } -} + // Provide fewer split options when length information is available. -bool theory_str::get_value(expr* e, rational& val) const { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } + if (splitType == 0) { + // M cuts Y + // | x | y | + // | m | str | + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + if (can_two_nodes_eq(y, temp1_strAst)) { + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); - if (!tha) { - return false; - } - TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); - expr_ref _val(m); - enode * en_e = ctx.get_enode(e); - enode * it = en_e; - do { - if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { - // found an arithmetic term - TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" - << std::endl;); - return true; - } else { - TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); - } - it = it->get_next(); - } while (it != en_e); - TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); - return false; -} + expr_ref_vector r_items(mgr); + expr_ref x_temp1(mk_concat(x, temp1), mgr); + r_items.push_back(ctx.mk_eq_atom(m, x_temp1)); + r_items.push_back(ctx.mk_eq_atom(y, temp1_strAst)); -bool theory_str::lower_bound(expr* _e, rational& lo) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _lo(m); - if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; - return m_autil.is_numeral(_lo, lo) && lo.is_int(); -} - -bool theory_str::upper_bound(expr* _e, rational& hi) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); - expr_ref _hi(m); - if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; - return m_autil.is_numeral(_hi, hi) && hi.is_int(); -} - -bool theory_str::get_len_value(expr* e, rational& val) { - if (opt_DisableIntegerTheoryIntegration) { - TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); - return false; - } - - context& ctx = get_context(); - ast_manager & m = get_manager(); - - theory* th = ctx.get_theory(m_autil.get_family_id()); - if (!th) { - TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); - return false; - } - theory_mi_arith* tha = dynamic_cast(th); - if (!tha) { - TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); - return false; - } - - TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); - - rational val1; - expr_ref len(m), len_val(m); - expr* e1, *e2; - ptr_vector todo; - todo.push_back(e); - val.reset(); - while (!todo.empty()) { - expr* c = todo.back(); - todo.pop_back(); - if (u.str.is_concat(to_app(c))) { - e1 = to_app(c)->get_arg(0); - e2 = to_app(c)->get_arg(1); - todo.push_back(e1); - todo.push_back(e2); - } - else if (u.str.is_string(to_app(c))) { - zstring tmp; - u.str.is_string(to_app(c), tmp); - unsigned int sl = tmp.length(); - val += rational(sl); - } - else { - len = mk_strlen(c); - - // debugging - TRACE("str", { - tout << mk_pp(len, m) << ":" << std::endl - << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl - << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl - ; - if (ctx.e_internalized(len)) { - enode * e_len = ctx.get_enode(len); - tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; - - // eqc debugging - { - tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; - enode * nNode = ctx.get_enode(len); - enode * eqcNode = nNode; - do { - app * ast = eqcNode->get_owner(); - tout << mk_pp(ast, get_manager()) << std::endl; - eqcNode = eqcNode->get_next(); - } while (eqcNode != nNode); - } - } - }); - - if (ctx.e_internalized(len) && get_value(len, val1)) { - val += val1; - TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); - } - else { - TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); - return false; - } - } - } - - TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); - return val.is_int(); -} - -/* - * Decide whether n1 and n2 are already in the same equivalence class. - * This only checks whether the core considers them to be equal; - * they may not actually be equal. - */ -bool theory_str::in_same_eqc(expr * n1, expr * n2) { - if (n1 == n2) return true; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // similar to get_eqc_value(), make absolutely sure - // that we've set this up properly for the context - - if (!ctx.e_internalized(n1)) { - TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); - ctx.internalize(n1, false); - } - if (!ctx.e_internalized(n2)) { - TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); - ctx.internalize(n2, false); - } - - expr * curr = get_eqc_next(n1); - while (curr != n1) { - if (curr == n2) - return true; - curr = get_eqc_next(curr); - } - return false; -} - -expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { - context & ctx = get_context(); - expr * constStrNode = NULL; - - expr * ex = n; - do { - if (u.str.is_string(to_app(ex))) { - constStrNode = ex; - } - eqcSet.push_back(ex); - - ex = get_eqc_next(ex); - } while (ex != n); - return constStrNode; -} - -/* - * Collect constant strings (from left to right) in an AST node. - */ -void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { - ast_manager & m = get_manager(); - if (u.str.is_string(node)) { - astList.push_back(node); - //} else if (getNodeType(t, node) == my_Z3_Func) { - } else if (is_app(node)) { - app * func_app = to_app(node); - unsigned int argCount = func_app->get_num_args(); - for (unsigned int i = 0; i < argCount; i++) { - expr * argAst = func_app->get_arg(i); - get_const_str_asts_in_node(argAst, astList); - } - } -} - -void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); - - expr_ref_vector litems(m); - - if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { - std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); - for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { - expr * strAst = itor1->first; - expr * substrAst = itor1->second; - - expr * boolVar; - if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); - } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); - - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. - - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); - - if (varNode != strAst && varNode != substrAst) { - TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - - // varEqcNode is str - if (strAst == varNode) { - expr_ref implyR(m); - litems.reset(); - - if (strAst != constNode) { - litems.push_back(ctx.mk_eq_atom(strAst, constNode)); - } - zstring strConst; - u.str.is_string(constNode, strConst); - bool subStrHasEqcValue = false; - expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); - if (substrValue != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + rational m_sub_x = (m_len - x_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(m_sub_x))); + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(strAst), mk_int(str_len))); + rational y_sub_str = (y_len - str_len); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(y_sub_str))); } - if (subStrHasEqcValue) { - // subStr has an eqc constant value - zstring subStrConst; - u.str.is_string(substrValue, subStrConst); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); - TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + if (!avoidLoopCut || !(has_self_cut(m, y))) { + // break down option 2-1 + add_cut_info_merge(temp1, sLevel, y); + add_cut_info_merge(temp1, sLevel, m); - if (strConst.contains(subStrConst)) { - //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); } else { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); + assert_implication(ax_l, ax_r); } } else { - // ------------------------------------------------------------------------------------------------ - // subStr doesn't have an eqc contant value - // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) - // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false - // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet - // ------------------------------------------------------------------------------------------------ - // collect eqc concat - std::set eqcConcats; - get_concats_in_eqc(substrAst, eqcConcats); - for (std::set::iterator concatItor = eqcConcats.begin(); - concatItor != eqcConcats.end(); concatItor++) { - expr_ref_vector constList(m); - bool counterEgFound = false; - // get constant strings in concat - expr * aConcat = *concatItor; - get_const_str_asts_in_node(aConcat, constList); - for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { - zstring pieceStr; - u.str.is_string(*cstItor, pieceStr); - if (!strConst.contains(pieceStr)) { - counterEgFound = true; - if (aConcat != substrAst) { - litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); - } - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); - break; + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIP" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + } else if (splitType == 1) { + // | x | y | + // | m | str | + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(m)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(strAst))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, m), ctx.mk_eq_atom(y, strAst)), mgr); + assert_implication(ax_l, ax_r); + } else if (splitType == 2) { + // m cut y, + // | x | y | + // | m | str | + rational lenDelta; + expr_ref_vector l_items(mgr); + l_items.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + if (x_len_exists && m_len_exists) { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + l_items.push_back(ctx.mk_eq_atom(mk_strlen(m), mk_int(m_len))); + lenDelta = x_len - m_len; + } else { + l_items.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + lenDelta = str_len - y_len; + } + TRACE("str", + tout + << "xLen? " << (x_len_exists ? "yes" : "no") << std::endl + << "mLen? " << (m_len_exists ? "yes" : "no") << std::endl + << "yLen? " << (y_len_exists ? "yes" : "no") << std::endl + << "xLen = " << x_len.to_string() << std::endl + << "yLen = " << y_len.to_string() << std::endl + << "mLen = " << m_len.to_string() << std::endl + << "strLen = " << str_len.to_string() << std::endl + << "lenDelta = " << lenDelta.to_string() << std::endl + << "strValue = \"" << strValue << "\" (len=" << strValue.length() << ")" << "\n" + ; + ); + + zstring part1Str = strValue.extract(0, lenDelta.get_unsigned()); + zstring part2Str = strValue.extract(lenDelta.get_unsigned(), strValue.length() - lenDelta.get_unsigned()); + + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, x_concat)); + r_items.push_back(ctx.mk_eq_atom(y, cropStr)); + expr_ref ax_l(mk_and(l_items), mgr); + expr_ref ax_r(mk_and(r_items), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + expr_ref ax_l(mk_and(l_items), mgr); + assert_axiom(mgr.mk_not(ax_l)); + } + } else { + // Split type -1: no idea about the length... + expr_ref_vector arrangement_disjunction(mgr); + + expr_ref temp1_strAst(mk_concat(temp1, strAst), mgr); + + // m cuts y + if (can_two_nodes_eq(y, temp1_strAst)) { + if (!avoidLoopCut || !has_self_cut(m, y)) { + // break down option 2-1 + expr_ref_vector and_item(mgr); + + expr_ref x_temp1(mk_concat(x, temp1), mgr); + and_item.push_back(ctx.mk_eq_atom(m, x_temp1)); + and_item.push_back(ctx.mk_eq_atom(y, temp1_strAst)); + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(x), mk_strlen(temp1)))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + add_cut_info_merge(temp1, ctx.get_scope_level(), y); + add_cut_info_merge(temp1, ctx.get_scope_level(), m); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(m, tout); print_cut_var(y, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + for (unsigned int i = 0; i <= strValue.length(); ++i) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref prefixStr(mk_string(part1Str), mgr); + expr_ref x_concat(mk_concat(m, prefixStr), mgr); + expr_ref cropStr(mk_string(part2Str), mgr); + if (can_two_nodes_eq(x, x_concat) && can_two_nodes_eq(y, cropStr)) { + // break down option 2-2 + expr_ref_vector and_item(mgr); + and_item.push_back(ctx.mk_eq_atom(x, x_concat)); + and_item.push_back(ctx.mk_eq_atom(y, cropStr)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(part2Str.length()))); + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prioritize the option where y is equal to the original string + if (i == 0) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + } + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref implyLHS(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(implyLHS, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: Should not split two EQ concats." << std::endl;); + } + } // (splitType == -1) + } + + /************************************************************* + * Type 3: concat(x, y) = concat("str", n) + *************************************************************/ + bool theory_str::is_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && (!u.str.is_string(v2_arg1))) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && (!u.str.is_string(v1_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type3(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + bool overlapAssumptionUsed = false; + + TRACE("str", tout << "process_concat_eq TYPE 3" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = NULL; + expr * y = NULL; + expr * strAst = NULL; + expr * n = NULL; + + if (u.str.is_string(v1_arg0) && !u.str.is_string(v2_arg0)) { + strAst = v1_arg0; + n = v1_arg1; + x = v2_arg0; + y = v2_arg1; + } else { + strAst = v2_arg0; + n = v2_arg1; + x = v1_arg0; + y = v1_arg1; + } + + zstring strValue; + u.str.is_string(strAst, strValue); + + rational x_len, y_len, str_len, n_len; + bool x_len_exists = get_len_value(x, x_len); + bool y_len_exists = get_len_value(y, y_len); + str_len = rational((unsigned)(strValue.length())); + bool n_len_exists = get_len_value(n, n_len); + + expr_ref xorFlag(mgr); + expr_ref temp1(mgr); + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + + if (!entry1InScope && !entry2InScope) { + temp1 = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + + varForBreakConcat[key1][0] = temp1; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + temp1 = varForBreakConcat[key1][0]; + xorFlag = varForBreakConcat[key1][1]; + } else if (varForBreakConcat.find(key2) != varForBreakConcat.end()) { + temp1 = varForBreakConcat[key2][0]; + xorFlag = varForBreakConcat[key2][1]; + } + refresh_theory_var(temp1); + add_nonempty_constraint(temp1); + } + + + + int splitType = -1; + if (x_len_exists) { + if (x_len < str_len) + splitType = 0; + else if (x_len == str_len) + splitType = 1; + else + splitType = 2; + } + if (splitType == -1 && y_len_exists && n_len_exists) { + if (y_len > n_len) + splitType = 0; + else if (y_len == n_len) + splitType = 1; + else + splitType = 2; + } + + TRACE("str", tout << "Split type " << splitType << std::endl;); + + // Provide fewer split options when length information is available. + if (splitType == 0) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational prefixLen; + if (!x_len_exists) { + prefixLen = str_len - (y_len - n_len); + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + prefixLen = x_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + zstring prefixStr = strValue.extract(0, prefixLen.get_unsigned()); + rational str_sub_prefix = str_len - prefixLen; + zstring suffixStr = strValue.extract(prefixLen.get_unsigned(), str_sub_prefix.get_unsigned()); + expr_ref prefixAst(mk_string(prefixStr), mgr); + expr_ref suffixAst(mk_string(suffixStr), mgr); + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref suf_n_concat(mk_concat(suffixAst, n), mgr); + if (can_two_nodes_eq(x, prefixAst) && can_two_nodes_eq(y, suf_n_concat)) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, prefixAst)); + r_items.push_back(ctx.mk_eq_atom(y, suf_n_concat)); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, mk_and(r_items)), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, mk_and(r_items)); + } + } else { + // negate! It's impossible to split str with these lengths + TRACE("str", tout << "CONFLICT: Impossible to split str with these lengths." << std::endl;); + assert_axiom(mgr.mk_not(ax_l)); + } + } + else if (splitType == 1) { + expr_ref ax_l1(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_l2(mgr.mk_or( + ctx.mk_eq_atom(mk_strlen(x), mk_strlen(strAst)), + ctx.mk_eq_atom(mk_strlen(y), mk_strlen(n))), mgr); + expr_ref ax_l(mgr.mk_and(ax_l1, ax_l2), mgr); + expr_ref ax_r(mgr.mk_and(ctx.mk_eq_atom(x, strAst), ctx.mk_eq_atom(y, n)), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } + else if (splitType == 2) { + // | x | y | + // | str | n | + expr_ref_vector litems(mgr); + litems.push_back(ctx.mk_eq_atom(concatAst1, concatAst2)); + rational tmpLen; + if (!x_len_exists) { + tmpLen = n_len - y_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_int(y_len))); + litems.push_back(ctx.mk_eq_atom(mk_strlen(n), mk_int(n_len))); + } else { + tmpLen = x_len - str_len; + litems.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_int(x_len))); + } + expr_ref ax_l(mgr.mk_and(litems.size(), litems.c_ptr()), mgr); + + expr_ref str_temp1(mk_concat(strAst, temp1), mgr); + expr_ref temp1_y(mk_concat(temp1, y), mgr); + + if (can_two_nodes_eq(x, str_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + expr_ref_vector r_items(mgr); + r_items.push_back(ctx.mk_eq_atom(x, str_temp1)); + r_items.push_back(ctx.mk_eq_atom(n, temp1_y)); + r_items.push_back(ctx.mk_eq_atom(mk_strlen(temp1), mk_int(tmpLen))); + expr_ref ax_r(mk_and(r_items), mgr); + + //Cut Info + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(ax_l, ax_r), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ax_l, ax_r); + } + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + assert_implication(ax_l, tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED" << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + assert_implication(ax_l, m_theoryStrOverlapAssumption_term); + } + } + } + } + // else { + // // negate! It's impossible to split str with these lengths + // __debugPrint(logFile, "[Conflict] Negate! It's impossible to split str with these lengths @ %d.\n", __LINE__); + // addAxiom(t, Z3_mk_not(ctx, ax_l), __LINE__); + // } + } + else { + // Split type -1. We know nothing about the length... + + expr_ref_vector arrangement_disjunction(mgr); + + int pos = 1; + for (unsigned int i = 0; i <= strValue.length(); i++) { + zstring part1Str = strValue.extract(0, i); + zstring part2Str = strValue.extract(i, strValue.length() - i); + expr_ref cropStr(mk_string(part1Str), mgr); + expr_ref suffixStr(mk_string(part2Str), mgr); + expr_ref y_concat(mk_concat(suffixStr, n), mgr); + + if (can_two_nodes_eq(x, cropStr) && can_two_nodes_eq(y, y_concat)) { + expr_ref_vector and_item(mgr); + // break down option 3-1 + expr_ref x_eq_str(ctx.mk_eq_atom(x, cropStr), mgr); + + and_item.push_back(x_eq_str); ++pos; + and_item.push_back(ctx.mk_eq_atom(y, y_concat)); + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), mk_strlen(cropStr))); ++pos; + + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), mk_length(t, y_concat))); + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + double priority; + if (i == strValue.length()) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option1, priority, l_true); + } + } + + expr_ref strAst_temp1(mk_concat(strAst, temp1), mgr); + + + //-------------------------------------------------------- + // x cut n + //-------------------------------------------------------- + if (can_two_nodes_eq(x, strAst_temp1)) { + if (!avoidLoopCut || !(has_self_cut(x, n))) { + // break down option 3-2 + expr_ref_vector and_item(mgr); + + expr_ref temp1_y(mk_concat(temp1, y), mgr); + and_item.push_back(ctx.mk_eq_atom(x, strAst_temp1)); ++pos; + and_item.push_back(ctx.mk_eq_atom(n, temp1_y)); ++pos; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(x), + m_autil.mk_add(mk_strlen(strAst), mk_strlen(temp1)) ) ); ++pos; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + add_theory_aware_branching_info(option2, 0.1, l_true); + + add_cut_info_merge(temp1, sLevel, x); + add_cut_info_merge(temp1, sLevel, n); + } else { + loopDetected = true; + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", {print_cut_var(x, tout); print_cut_var(n, tout);}); + + if (!overlapAssumptionUsed) { + overlapAssumptionUsed = true; + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + } + } + } + } + + + if (!arrangement_disjunction.empty()) { + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_lhs(ctx.mk_eq_atom(concatAst1, concatAst2), mgr); + expr_ref ax_strong(ctx.mk_eq_atom(ax_lhs, implyR), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } else { + TRACE("str", tout << "STOP: should not split two eq. concats" << std::endl;); + } + } + + } + + /************************************************************* + * Type 4: concat("str1", y) = concat("str2", n) + *************************************************************/ + bool theory_str::is_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1))) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type4(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 4" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * str1Ast = v1_arg0; + expr * y = v1_arg1; + expr * str2Ast = v2_arg0; + expr * n = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int commonLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(0, commonLen) != str2Value.extract(0, commonLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common prefix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(str2Len, str1Len - str2Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), y), mgr); + if (!in_same_eqc(tmpAst, n)) { + // break down option 4-1 + expr_ref implyR(ctx.mk_eq_atom(n, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + if (!in_same_eqc(n, y)) { + //break down option 4-2 + expr_ref implyR(ctx.mk_eq_atom(n, y), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(str1Len, str2Len - str1Len); + expr_ref tmpAst(mk_concat(mk_string(deltaStr), n), mgr); + if (!in_same_eqc(y, tmpAst)) { + //break down option 4-3 + expr_ref implyR(ctx.mk_eq_atom(y, tmpAst), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 5: concat(x, "str1") = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if ((!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type5(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 5" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + expr * x = v1_arg0; + expr * str1Ast = v1_arg1; + expr * m = v2_arg0; + expr * str2Ast = v2_arg1; + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + int cLen = (str1Len > str2Len) ? str2Len : str1Len; + if (str1Value.extract(str1Len - cLen, cLen) != str2Value.extract(str2Len - cLen, cLen)) { + TRACE("str", tout << "Conflict: " << mk_ismt2_pp(concatAst1, mgr) + << " has no common suffix with " << mk_ismt2_pp(concatAst2, mgr) << std::endl;); + expr_ref toNegate(mgr.mk_not(ctx.mk_eq_atom(concatAst1, concatAst2)), mgr); + assert_axiom(toNegate); + return; + } else { + if (str1Len > str2Len) { + zstring deltaStr = str1Value.extract(0, str1Len - str2Len); + expr_ref x_deltaStr(mk_concat(x, mk_string(deltaStr)), mgr); + if (!in_same_eqc(m, x_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(m, x_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else if (str1Len == str2Len) { + // test + if (!in_same_eqc(x, m)) { + expr_ref implyR(ctx.mk_eq_atom(x, m), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } else { + zstring deltaStr = str2Value.extract(0, str2Len - str1Len); + expr_ref m_deltaStr(mk_concat(m, mk_string(deltaStr)), mgr); + if (!in_same_eqc(x, m_deltaStr)) { + expr_ref implyR(ctx.mk_eq_atom(x, m_deltaStr), mgr); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + } + } + } + } + + /************************************************************* + * case 6: concat("str1", y) = concat(m, "str2") + *************************************************************/ + bool theory_str::is_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + if (u.str.is_string(v1_arg0) && (!u.str.is_string(v1_arg1)) + && (!u.str.is_string(v2_arg0)) && u.str.is_string(v2_arg1)) { + return true; + } else if (u.str.is_string(v2_arg0) && (!u.str.is_string(v2_arg1)) + && (!u.str.is_string(v1_arg0)) && u.str.is_string(v1_arg1)) { + return true; + } else { + return false; + } + } + + void theory_str::process_concat_eq_type6(expr * concatAst1, expr * concatAst2) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + TRACE("str", tout << "process_concat_eq TYPE 6" << std::endl + << "concatAst1 = " << mk_ismt2_pp(concatAst1, mgr) << std::endl + << "concatAst2 = " << mk_ismt2_pp(concatAst2, mgr) << std::endl; + ); + + if (!u.str.is_concat(to_app(concatAst1))) { + TRACE("str", tout << "concatAst1 is not a concat function" << std::endl;); + return; + } + if (!u.str.is_concat(to_app(concatAst2))) { + TRACE("str", tout << "concatAst2 is not a concat function" << std::endl;); + return; + } + + expr * v1_arg0 = to_app(concatAst1)->get_arg(0); + expr * v1_arg1 = to_app(concatAst1)->get_arg(1); + expr * v2_arg0 = to_app(concatAst2)->get_arg(0); + expr * v2_arg1 = to_app(concatAst2)->get_arg(1); + + + expr * str1Ast = NULL; + expr * y = NULL; + expr * m = NULL; + expr * str2Ast = NULL; + + if (u.str.is_string(v1_arg0)) { + str1Ast = v1_arg0; + y = v1_arg1; + m = v2_arg0; + str2Ast = v2_arg1; + } else { + str1Ast = v2_arg0; + y = v2_arg1; + m = v1_arg0; + str2Ast = v1_arg1; + } + + zstring str1Value, str2Value; + u.str.is_string(str1Ast, str1Value); + u.str.is_string(str2Ast, str2Value); + + unsigned int str1Len = str1Value.length(); + unsigned int str2Len = str2Value.length(); + + //---------------------------------------- + //(a) |---str1---|----y----| + // |--m--|-----str2-----| + // + //(b) |---str1---|----y----| + // |-----m----|--str2---| + // + //(c) |---str1---|----y----| + // |------m------|-str2-| + //---------------------------------------- + + std::list overlapLen; + overlapLen.push_back(0); + + for (unsigned int i = 1; i <= str1Len && i <= str2Len; i++) { + if (str1Value.extract(str1Len - i, i) == str2Value.extract(0, i)) + overlapLen.push_back(i); + } + + //---------------------------------------------------------------- + expr * commonVar = NULL; + expr * xorFlag = NULL; + std::pair key1(concatAst1, concatAst2); + std::pair key2(concatAst2, concatAst1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + entry1InScope = false; + } else { + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry1->second)[1]) == internal_variable_set.end() */) { + entry1InScope = false; + } else { + entry1InScope = true; + } + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + entry2InScope = false; + } else { + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end() + /* || internal_variable_set.find((entry2->second)[1]) == internal_variable_set.end() */) { + entry2InScope = false; + } else { + entry2InScope = true; + } + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + commonVar = mk_nonempty_str_var(); + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = commonVar; + varForBreakConcat[key1][1] = xorFlag; + } else { + if (entry1InScope) { + commonVar = (entry1->second)[0]; + xorFlag = (entry1->second)[1]; + } else { + commonVar = (entry2->second)[0]; + xorFlag = (entry2->second)[1]; + } + refresh_theory_var(commonVar); + add_nonempty_constraint(commonVar); + } + + bool overlapAssumptionUsed = false; + + expr_ref_vector arrangement_disjunction(mgr); + int pos = 1; + + if (!avoidLoopCut || !has_self_cut(m, y)) { + expr_ref_vector and_item(mgr); + + expr_ref str1_commonVar(mk_concat(str1Ast, commonVar), mgr); + and_item.push_back(ctx.mk_eq_atom(m, str1_commonVar)); + pos += 1; + + expr_ref commonVar_str2(mk_concat(commonVar, str2Ast), mgr); + and_item.push_back(ctx.mk_eq_atom(y, commonVar_str2)); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(m), + m_autil.mk_add(mk_strlen(str1Ast), mk_strlen(commonVar)) )); + pos += 1; + + // addItems[0] = mk_length(t, commonVar); + // addItems[1] = mk_length(t, str2Ast); + // and_item[pos++] = Z3_mk_eq(ctx, or_item[option], Z3_mk_eq(ctx, mk_length(t, y), Z3_mk_add(ctx, 2, addItems))); + + expr_ref option1(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option1); + add_theory_aware_branching_info(option1, 0.1, l_true); + } else { + loopDetected = true; + + if (m_params.m_FiniteOverlapModels) { + expr_ref tester = set_up_finite_model_test(concatAst1, concatAst2); + arrangement_disjunction.push_back(tester); + add_theory_aware_branching_info(tester, m_params.m_OverlapTheoryAwarePriority, l_true); + } else { + TRACE("str", tout << "AVOID LOOP: SKIPPED." << std::endl;); + TRACE("str", print_cut_var(m, tout); print_cut_var(y, tout);); + + // only add the overlap assumption one time + if (!overlapAssumptionUsed) { + arrangement_disjunction.push_back(m_theoryStrOverlapAssumption_term); + overlapAssumptionUsed = true; + } + } + } + + for (std::list::iterator itor = overlapLen.begin(); itor != overlapLen.end(); itor++) { + unsigned int overLen = *itor; + zstring prefix = str1Value.extract(0, str1Len - overLen); + zstring suffix = str2Value.extract(overLen, str2Len - overLen); + + expr_ref_vector and_item(mgr); + + expr_ref prefixAst(mk_string(prefix), mgr); + expr_ref x_eq_prefix(ctx.mk_eq_atom(m, prefixAst), mgr); + and_item.push_back(x_eq_prefix); + pos += 1; + + and_item.push_back( + ctx.mk_eq_atom(mk_strlen(m), mk_strlen(prefixAst))); + pos += 1; + + // adding length constraint for _ = constStr seems slowing things down. + + expr_ref suffixAst(mk_string(suffix), mgr); + expr_ref y_eq_suffix(ctx.mk_eq_atom(y, suffixAst), mgr); + and_item.push_back(y_eq_suffix); + pos += 1; + + and_item.push_back(ctx.mk_eq_atom(mk_strlen(y), mk_strlen(suffixAst))); + pos += 1; + + expr_ref option2(mk_and(and_item), mgr); + arrangement_disjunction.push_back(option2); + double priority; + // prefer the option "str1" = x + if (prefix == str1Value) { + priority = 0.5; + } else { + priority = 0.1; + } + add_theory_aware_branching_info(option2, priority, l_true); + } + + // case 6: concat("str1", y) = concat(m, "str2") + + expr_ref implyR(mk_or(arrangement_disjunction), mgr); + + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom( ctx.mk_eq_atom(concatAst1, concatAst2), implyR ), mgr); + assert_axiom(ax_strong); + } else { + assert_implication(ctx.mk_eq_atom(concatAst1, concatAst2), implyR); + } + generate_mutual_exclusion(arrangement_disjunction); + } + + void theory_str::process_unroll_eq_const_str(expr * unrollFunc, expr * constStr) { + ast_manager & m = get_manager(); + + if (!u.re.is_unroll(to_app(unrollFunc))) { + return; + } + if (!u.str.is_string(constStr)) { + return; + } + + expr * funcInUnroll = to_app(unrollFunc)->get_arg(0); + zstring strValue; + u.str.is_string(constStr, strValue); + + TRACE("str", tout << "unrollFunc: " << mk_pp(unrollFunc, m) << std::endl + << "constStr: " << mk_pp(constStr, m) << std::endl;); + + if (strValue == "") { + return; + } + + if (u.re.is_to_re(to_app(funcInUnroll))) { + unroll_str2reg_constStr(unrollFunc, constStr); + return; + } + } + + void theory_str::process_concat_eq_unroll(expr * concat, expr * unroll) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "concat = " << mk_pp(concat, mgr) << ", unroll = " << mk_pp(unroll, mgr) << std::endl;); + + std::pair key = std::make_pair(concat, unroll); + expr_ref toAssert(mgr); + + if (concat_eq_unroll_ast_map.find(key) == concat_eq_unroll_ast_map.end()) { + expr_ref arg1(to_app(concat)->get_arg(0), mgr); + expr_ref arg2(to_app(concat)->get_arg(1), mgr); + expr_ref r1(to_app(unroll)->get_arg(0), mgr); + expr_ref t1(to_app(unroll)->get_arg(1), mgr); + + expr_ref v1(mk_regex_rep_var(), mgr); + expr_ref v2(mk_regex_rep_var(), mgr); + expr_ref v3(mk_regex_rep_var(), mgr); + expr_ref v4(mk_regex_rep_var(), mgr); + expr_ref v5(mk_regex_rep_var(), mgr); + + expr_ref t2(mk_unroll_bound_var(), mgr); + expr_ref t3(mk_unroll_bound_var(), mgr); + expr_ref emptyStr(mk_string(""), mgr); + + expr_ref unroll1(mk_unroll(r1, t2), mgr); + expr_ref unroll2(mk_unroll(r1, t3), mgr); + + expr_ref op0(ctx.mk_eq_atom(t1, mk_int(0)), mgr); + expr_ref op1(m_autil.mk_ge(t1, mk_int(1)), mgr); + + expr_ref_vector op1Items(mgr); + expr_ref_vector op2Items(mgr); + + op1Items.push_back(ctx.mk_eq_atom(arg1, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(arg2, emptyStr)); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(0))); + op1Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(0))); + expr_ref opAnd1(ctx.mk_eq_atom(op0, mk_and(op1Items)), mgr); + + expr_ref v1v2(mk_concat(v1, v2), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg1, v1v2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), m_autil.mk_add(mk_strlen(v1), mk_strlen(v2)))); + expr_ref v3v4(mk_concat(v3, v4), mgr); + op2Items.push_back(ctx.mk_eq_atom(arg2, v3v4)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), m_autil.mk_add(mk_strlen(v3), mk_strlen(v4)))); + + op2Items.push_back(ctx.mk_eq_atom(v1, unroll1)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v1), mk_strlen(unroll1))); + op2Items.push_back(ctx.mk_eq_atom(v4, unroll2)); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v4), mk_strlen(unroll2))); + expr_ref v2v3(mk_concat(v2, v3), mgr); + op2Items.push_back(ctx.mk_eq_atom(v5, v2v3)); + reduce_virtual_regex_in(v5, r1, op2Items); + op2Items.push_back(ctx.mk_eq_atom(mk_strlen(v5), m_autil.mk_add(mk_strlen(v2), mk_strlen(v3)))); + op2Items.push_back(ctx.mk_eq_atom(m_autil.mk_add(t2, t3), m_autil.mk_add(t1, mk_int(-1)))); + expr_ref opAnd2(ctx.mk_eq_atom(op1, mk_and(op2Items)), mgr); + + toAssert = mgr.mk_and(opAnd1, opAnd2); + m_trail.push_back(toAssert); + concat_eq_unroll_ast_map[key] = toAssert; + } else { + toAssert = concat_eq_unroll_ast_map[key]; + } + + assert_axiom(toAssert); + } + + void theory_str::unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * str2RegFunc = to_app(unrollFunc)->get_arg(0); + expr * strInStr2RegFunc = to_app(str2RegFunc)->get_arg(0); + expr * oriCnt = to_app(unrollFunc)->get_arg(1); + + zstring strValue; + u.str.is_string(eqConstStr, strValue); + zstring regStrValue; + u.str.is_string(strInStr2RegFunc, regStrValue); + unsigned int strLen = strValue.length(); + unsigned int regStrLen = regStrValue.length(); + SASSERT(regStrLen != 0); // this should never occur -- the case for empty string is handled elsewhere + unsigned int cnt = strLen / regStrLen; + + expr_ref implyL(ctx.mk_eq_atom(unrollFunc, eqConstStr), m); + expr_ref implyR1(ctx.mk_eq_atom(oriCnt, mk_int(cnt)), m); + expr_ref implyR2(ctx.mk_eq_atom(mk_strlen(unrollFunc), mk_int(strLen)), m); + expr_ref axiomRHS(m.mk_and(implyR1, implyR2), m); + SASSERT(implyL); + SASSERT(axiomRHS); + assert_implication(implyL, axiomRHS); + } + + /* + * Look through the equivalence class of n to find a string constant. + * Return that constant if it is found, and set hasEqcValue to true. + * Otherwise, return n, and set hasEqcValue to false. + */ + + expr * theory_str::get_eqc_value(expr * n, bool & hasEqcValue) { + return z3str2_get_eqc_value(n, hasEqcValue); + } + + + // Simulate the behaviour of get_eqc_value() from Z3str2. + // We only check m_find for a string constant. + + expr * theory_str::z3str2_get_eqc_value(expr * n , bool & hasEqcValue) { + expr * curr = n; + do { + if (u.str.is_string(curr)) { + hasEqcValue = true; + return curr; + } + curr = get_eqc_next(curr); + } while (curr != n); + hasEqcValue = false; + return n; + } + + // from Z3: theory_seq.cpp + + static theory_mi_arith* get_th_arith(context& ctx, theory_id afid, expr* e) { + theory* th = ctx.get_theory(afid); + if (th && ctx.e_internalized(e)) { + return dynamic_cast(th); + } + else { + return 0; + } + } + + bool theory_str::get_value(expr* e, rational& val) const { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), e); + if (!tha) { + return false; + } + TRACE("str", tout << "checking eqc of " << mk_pp(e, m) << " for arithmetic value" << std::endl;); + expr_ref _val(m); + enode * en_e = ctx.get_enode(e); + enode * it = en_e; + do { + if (m_autil.is_numeral(it->get_owner(), val) && val.is_int()) { + // found an arithmetic term + TRACE("str", tout << mk_pp(it->get_owner(), m) << " is an integer ( ~= " << val << " )" + << std::endl;); + return true; + } else { + TRACE("str", tout << mk_pp(it->get_owner(), m) << " not a numeral" << std::endl;); + } + it = it->get_next(); + } while (it != en_e); + TRACE("str", tout << "no arithmetic values found in eqc" << std::endl;); + return false; + } + + bool theory_str::lower_bound(expr* _e, rational& lo) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _lo(m); + if (!tha || !tha->get_lower(ctx.get_enode(_e), _lo)) return false; + return m_autil.is_numeral(_lo, lo) && lo.is_int(); + } + + bool theory_str::upper_bound(expr* _e, rational& hi) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + theory_mi_arith* tha = get_th_arith(ctx, m_autil.get_family_id(), _e); + expr_ref _hi(m); + if (!tha || !tha->get_upper(ctx.get_enode(_e), _hi)) return false; + return m_autil.is_numeral(_hi, hi) && hi.is_int(); + } + + bool theory_str::get_len_value(expr* e, rational& val) { + if (opt_DisableIntegerTheoryIntegration) { + TRACE("str", tout << "WARNING: integer theory integration disabled" << std::endl;); + return false; + } + + context& ctx = get_context(); + ast_manager & m = get_manager(); + + theory* th = ctx.get_theory(m_autil.get_family_id()); + if (!th) { + TRACE("str", tout << "oops, can't get m_autil's theory" << std::endl;); + return false; + } + theory_mi_arith* tha = dynamic_cast(th); + if (!tha) { + TRACE("str", tout << "oops, can't cast to theory_mi_arith" << std::endl;); + return false; + } + + TRACE("str", tout << "checking len value of " << mk_ismt2_pp(e, m) << std::endl;); + + rational val1; + expr_ref len(m), len_val(m); + expr* e1, *e2; + ptr_vector todo; + todo.push_back(e); + val.reset(); + while (!todo.empty()) { + expr* c = todo.back(); + todo.pop_back(); + if (u.str.is_concat(to_app(c))) { + e1 = to_app(c)->get_arg(0); + e2 = to_app(c)->get_arg(1); + todo.push_back(e1); + todo.push_back(e2); + } + else if (u.str.is_string(to_app(c))) { + zstring tmp; + u.str.is_string(to_app(c), tmp); + unsigned int sl = tmp.length(); + val += rational(sl); + } + else { + len = mk_strlen(c); + + // debugging + TRACE("str", { + tout << mk_pp(len, m) << ":" << std::endl + << (ctx.is_relevant(len.get()) ? "relevant" : "not relevant") << std::endl + << (ctx.e_internalized(len) ? "internalized" : "not internalized") << std::endl + ; + if (ctx.e_internalized(len)) { + enode * e_len = ctx.get_enode(len); + tout << "has " << e_len->get_num_th_vars() << " theory vars" << std::endl; + + // eqc debugging + { + tout << "dump equivalence class of " << mk_pp(len, get_manager()) << std::endl; + enode * nNode = ctx.get_enode(len); + enode * eqcNode = nNode; + do { + app * ast = eqcNode->get_owner(); + tout << mk_pp(ast, get_manager()) << std::endl; + eqcNode = eqcNode->get_next(); + } while (eqcNode != nNode); } } - if (counterEgFound) { - TRACE("str", tout << "Inconsistency found!" << std::endl;); - break; - } - } + }); + + if (ctx.e_internalized(len) && get_value(len, val1)) { + val += val1; + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has length " << val1 << std::endl;); } - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); + else { + TRACE("str", tout << "integer theory: subexpression " << mk_ismt2_pp(len, m) << " has no length assignment; bailing out" << std::endl;); + return false; } } - // varEqcNode is subStr - else if (substrAst == varNode) { - expr_ref implyR(m); - litems.reset(); + } - if (substrAst != constNode) { - litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); - } - bool strHasEqcValue = false; - expr * strValue = get_eqc_value(strAst, strHasEqcValue); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); - } + TRACE("str", tout << "length of " << mk_ismt2_pp(e, m) << " is " << val << std::endl;); + return val.is_int(); + } - if (strHasEqcValue) { - zstring strConst, subStrConst; - u.str.is_string(strValue, strConst); - u.str.is_string(constNode, subStrConst); - if (strConst.contains(subStrConst)) { - //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); - implyR = boolVar; - } else { - // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); - implyR = m.mk_not(boolVar); - } - } + /* + * Decide whether n1 and n2 are already in the same equivalence class. + * This only checks whether the core considers them to be equal; + * they may not actually be equal. + */ + bool theory_str::in_same_eqc(expr * n1, expr * n2) { + if (n1 == n2) return true; + context & ctx = get_context(); + ast_manager & m = get_manager(); - // add assertion - if (implyR) { - expr_ref implyLHS(mk_and(litems), m); - assert_implication(implyLHS, implyR); - } + // similar to get_eqc_value(), make absolutely sure + // that we've set this up properly for the context + + if (!ctx.e_internalized(n1)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n1, m) << " was not internalized" << std::endl;); + ctx.internalize(n1, false); + } + if (!ctx.e_internalized(n2)) { + TRACE("str", tout << "WARNING: expression " << mk_ismt2_pp(n2, m) << " was not internalized" << std::endl;); + ctx.internalize(n2, false); + } + + expr * curr = get_eqc_next(n1); + while (curr != n1) { + if (curr == n2) + return true; + curr = get_eqc_next(curr); + } + return false; + } + + expr * theory_str::collect_eq_nodes(expr * n, expr_ref_vector & eqcSet) { + context & ctx = get_context(); + expr * constStrNode = NULL; + + expr * ex = n; + do { + if (u.str.is_string(to_app(ex))) { + constStrNode = ex; } - } // for (itor1 : contains_map) - } // if varNode in contain_pair_idx_map -} + eqcSet.push_back(ex); -void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector litems(m); + ex = get_eqc_next(ex); + } while (ex != n); + return constStrNode; + } - if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { - std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); - for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { - expr * strAst = itor1->first; - expr * substrAst = itor1->second; - - expr * boolVar; - if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { - TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + /* + * Collect constant strings (from left to right) in an AST node. + */ + void theory_str::get_const_str_asts_in_node(expr * node, expr_ref_vector & astList) { + ast_manager & m = get_manager(); + if (u.str.is_string(node)) { + astList.push_back(node); + //} else if (getNodeType(t, node) == my_Z3_Func) { + } else if (is_app(node)) { + app * func_app = to_app(node); + unsigned int argCount = func_app->get_num_args(); + for (unsigned int i = 0; i < argCount; i++) { + expr * argAst = func_app->get_arg(i); + get_const_str_asts_in_node(argAst, astList); } - // boolVar is actually a Contains term - app * containsApp = to_app(boolVar); + } + } - // we only want to inspect the Contains terms where either of strAst or substrAst - // are equal to varNode. + void theory_str::check_contain_by_eqc_val(expr * varNode, expr * constNode) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + TRACE("str", tout << "varNode = " << mk_pp(varNode, m) << ", constNode = " << mk_pp(constNode, m) << std::endl;); - if (varNode != strAst && varNode != substrAst) { - TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); - continue; - } - TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + expr_ref_vector litems(m); - if (substrAst == varNode) { - bool strAstHasVal = false; - expr * strValue = get_eqc_value(strAst, strAstHasVal); - if (strAstHasVal) { - TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); - if (strValue != strAst) { - litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); + continue; + } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); + + // varEqcNode is str + if (strAst == varNode) { + expr_ref implyR(m); + litems.reset(); + + if (strAst != constNode) { + litems.push_back(ctx.mk_eq_atom(strAst, constNode)); } zstring strConst; - u.str.is_string(strValue, strConst); - // iterate eqc (also eqc-to-be) of substr - for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { - bool counterEgFound = false; - if (u.str.is_concat(to_app(*itAst))) { + u.str.is_string(constNode, strConst); + bool subStrHasEqcValue = false; + expr * substrValue = get_eqc_value(substrAst, subStrHasEqcValue); + if (substrValue != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, substrValue)); + } + + if (subStrHasEqcValue) { + // subStr has an eqc constant value + zstring subStrConst; + u.str.is_string(substrValue, subStrConst); + + TRACE("t_str_detail", tout << "strConst = " << strConst << ", subStrConst = " << subStrConst << "\n";); + + if (strConst.contains(subStrConst)) { + //implyR = ctx.mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } else { + // ------------------------------------------------------------------------------------------------ + // subStr doesn't have an eqc contant value + // however, subStr equals to some concat(arg_1, arg_2, ..., arg_n) + // if arg_j is a constant and is not a part of the strConst, it's sure that the contains is false + // ** This check is needed here because the "strConst" and "strAst" may not be in a same eqc yet + // ------------------------------------------------------------------------------------------------ + // collect eqc concat + std::set eqcConcats; + get_concats_in_eqc(substrAst, eqcConcats); + for (std::set::iterator concatItor = eqcConcats.begin(); + concatItor != eqcConcats.end(); concatItor++) { expr_ref_vector constList(m); + bool counterEgFound = false; // get constant strings in concat - app * aConcat = to_app(*itAst); + expr * aConcat = *concatItor; get_const_str_asts_in_node(aConcat, constList); for (expr_ref_vector::iterator cstItor = constList.begin(); - cstItor != constList.end(); cstItor++) { + cstItor != constList.end(); cstItor++) { zstring pieceStr; u.str.is_string(*cstItor, pieceStr); if (!strConst.contains(pieceStr)) { - TRACE("str", tout << "Inconsistency found!" << std::endl;); counterEgFound = true; if (aConcat != substrAst) { litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); } - expr_ref implyLHS(mk_and(litems), m); - expr_ref implyR(m.mk_not(boolVar), m); - assert_implication(implyLHS, implyR); + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); break; } } - } - if (counterEgFound) { - break; + if (counterEgFound) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + break; + } } } + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } } - } - } - } // varNode in contain_pair_idx_map -} + // varEqcNode is subStr + else if (substrAst == varNode) { + expr_ref implyR(m); + litems.reset(); -bool theory_str::in_contain_idx_map(expr * n) { - return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); -} + if (substrAst != constNode) { + litems.push_back(ctx.mk_eq_atom(substrAst, constNode)); + } + bool strHasEqcValue = false; + expr * strValue = get_eqc_value(strAst, strHasEqcValue); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } -void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); + if (strHasEqcValue) { + zstring strConst, subStrConst; + u.str.is_string(strValue, strConst); + u.str.is_string(constNode, subStrConst); + if (strConst.contains(subStrConst)) { + //implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_true(ctx)); + implyR = boolVar; + } else { + // implyR = Z3_mk_eq(ctx, boolVar, Z3_mk_false(ctx)); + implyR = m.mk_not(boolVar); + } + } - if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { - std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); - std::set >::iterator keysItor2; - - for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { - // keysItor1 is on set {<.., n1>, ..., , ...} - std::pair key1 = *keysItor1; - if (key1.first == n1 && key1.second == n2) { - expr_ref implyL(m); - expr_ref implyR(contain_pair_bool_map[key1], m); - if (n1 != n2) { - implyL = ctx.mk_eq_atom(n1, n2); - assert_implication(implyL, implyR); - } else { - assert_axiom(implyR); + // add assertion + if (implyR) { + expr_ref implyLHS(mk_and(litems), m); + assert_implication(implyLHS, implyR); + } } - } + } // for (itor1 : contains_map) + } // if varNode in contain_pair_idx_map + } - for (keysItor2 = contain_pair_idx_map[n2].begin(); - keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { - // keysItor2 is on set {<.., n2>, ..., , ...} - std::pair key2 = *keysItor2; - // skip if the pair is eq - if (key1 == key2) { + void theory_str::check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector litems(m); + + if (contain_pair_idx_map.find(varNode) != contain_pair_idx_map.end()) { + std::set >::iterator itor1 = contain_pair_idx_map[varNode].begin(); + for (; itor1 != contain_pair_idx_map[varNode].end(); ++itor1) { + expr * strAst = itor1->first; + expr * substrAst = itor1->second; + + expr * boolVar; + if (!contain_pair_bool_map.find(strAst, substrAst, boolVar)) { + TRACE("str", tout << "warning: no entry for boolVar in contain_pair_bool_map" << std::endl;); + } + // boolVar is actually a Contains term + app * containsApp = to_app(boolVar); + + // we only want to inspect the Contains terms where either of strAst or substrAst + // are equal to varNode. + + TRACE("t_str_detail", tout << "considering Contains with strAst = " << mk_pp(strAst, m) << ", substrAst = " << mk_pp(substrAst, m) << "..." << std::endl;); + + if (varNode != strAst && varNode != substrAst) { + TRACE("str", tout << "varNode not equal to strAst or substrAst, skip" << std::endl;); continue; } + TRACE("str", tout << "varNode matched one of strAst or substrAst. Continuing" << std::endl;); - // *************************** - // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n - // *************************** - if (key1.first == n1 && key2.first == n2) { - expr * subAst1 = key1.second; - expr * subAst2 = key2.second; - bool subAst1HasValue = false; - bool subAst2HasValue = false; - expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); - expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); - - TRACE("str", - tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; - tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; - if (subAst1 != subValue1) { - tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + if (substrAst == varNode) { + bool strAstHasVal = false; + expr * strValue = get_eqc_value(strAst, strAstHasVal); + if (strAstHasVal) { + TRACE("str", tout << mk_pp(strAst, m) << " has constant eqc value " << mk_pp(strValue, m) << std::endl;); + if (strValue != strAst) { + litems.push_back(ctx.mk_eq_atom(strAst, strValue)); + } + zstring strConst; + u.str.is_string(strValue, strConst); + // iterate eqc (also eqc-to-be) of substr + for (expr_ref_vector::iterator itAst = willEqClass.begin(); itAst != willEqClass.end(); itAst++) { + bool counterEgFound = false; + if (u.str.is_concat(to_app(*itAst))) { + expr_ref_vector constList(m); + // get constant strings in concat + app * aConcat = to_app(*itAst); + get_const_str_asts_in_node(aConcat, constList); + for (expr_ref_vector::iterator cstItor = constList.begin(); + cstItor != constList.end(); cstItor++) { + zstring pieceStr; + u.str.is_string(*cstItor, pieceStr); + if (!strConst.contains(pieceStr)) { + TRACE("str", tout << "Inconsistency found!" << std::endl;); + counterEgFound = true; + if (aConcat != substrAst) { + litems.push_back(ctx.mk_eq_atom(substrAst, aConcat)); + } + expr_ref implyLHS(mk_and(litems), m); + expr_ref implyR(m.mk_not(boolVar), m); + assert_implication(implyLHS, implyR); + break; + } + } } - if (subAst2 != subValue2) { - tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; - } - ); - - if (subAst1HasValue && subAst2HasValue) { - expr_ref_vector litems1(m); - if (n1 != n2) { - litems1.push_back(ctx.mk_eq_atom(n1, n2)); - } - if (subValue1 != subAst1) { - litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); - } - if (subValue2 != subAst2) { - litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); - } - - zstring subConst1, subConst2; - u.str.is_string(subValue1, subConst1); - u.str.is_string(subValue2, subConst2); - expr_ref implyR(m); - if (subConst1 == subConst2) { - // key1.first = key2.first /\ key1.second = key2.second - // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) - implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst1.contains(subConst2)) { - // key1.first = key2.first /\ Contains(key1.second, key2.second) - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (subConst2.contains(subConst1)) { - // key1.first = key2.first /\ Contains(key2.second, key1.second) - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); - } - - if (implyR) { - if (litems1.empty()) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems1), implyR); + if (counterEgFound) { + break; } } + } + } + } + } // varNode in contain_pair_idx_map + } + + bool theory_str::in_contain_idx_map(expr * n) { + return contain_pair_idx_map.find(n) != contain_pair_idx_map.end(); + } + + void theory_str::check_contain_by_eq_nodes(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (in_contain_idx_map(n1) && in_contain_idx_map(n2)) { + std::set >::iterator keysItor1 = contain_pair_idx_map[n1].begin(); + std::set >::iterator keysItor2; + + for (; keysItor1 != contain_pair_idx_map[n1].end(); keysItor1++) { + // keysItor1 is on set {<.., n1>, ..., , ...} + std::pair key1 = *keysItor1; + if (key1.first == n1 && key1.second == n2) { + expr_ref implyL(m); + expr_ref implyR(contain_pair_bool_map[key1], m); + if (n1 != n2) { + implyL = ctx.mk_eq_atom(n1, n2); + assert_implication(implyL, implyR); } else { - expr_ref_vector subAst1Eqc(m); - expr_ref_vector subAst2Eqc(m); - collect_eq_nodes(subAst1, subAst1Eqc); - collect_eq_nodes(subAst2, subAst2Eqc); + assert_axiom(implyR); + } + } - if (subAst1Eqc.contains(subAst2)) { - // ----------------------------------------------------------- - // * key1.first = key2.first /\ key1.second = key2.second - // --> containPairBoolMap[key1] = containPairBoolMap[key2] - // ----------------------------------------------------------- - expr_ref_vector litems2(m); + for (keysItor2 = contain_pair_idx_map[n2].begin(); + keysItor2 != contain_pair_idx_map[n2].end(); keysItor2++) { + // keysItor2 is on set {<.., n2>, ..., , ...} + std::pair key2 = *keysItor2; + // skip if the pair is eq + if (key1 == key2) { + continue; + } + + // *************************** + // Case 1: Contains(m, ...) /\ Contains(n, ) /\ m = n + // *************************** + if (key1.first == n1 && key2.first == n2) { + expr * subAst1 = key1.second; + expr * subAst2 = key2.second; + bool subAst1HasValue = false; + bool subAst2HasValue = false; + expr * subValue1 = get_eqc_value(subAst1, subAst1HasValue); + expr * subValue2 = get_eqc_value(subAst2, subAst2HasValue); + + TRACE("str", + tout << "(Contains " << mk_pp(n1, m) << " " << mk_pp(subAst1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(n2, m) << " " << mk_pp(subAst2, m) << ")" << std::endl; + if (subAst1 != subValue1) { + tout << mk_pp(subAst1, m) << " = " << mk_pp(subValue1, m) << std::endl; + } + if (subAst2 != subValue2) { + tout << mk_pp(subAst2, m) << " = " << mk_pp(subValue2, m) << std::endl; + } + ); + + if (subAst1HasValue && subAst2HasValue) { + expr_ref_vector litems1(m); if (n1 != n2) { - litems2.push_back(ctx.mk_eq_atom(n1, n2)); + litems1.push_back(ctx.mk_eq_atom(n1, n2)); } - if (subAst1 != subAst2) { - litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + if (subValue1 != subAst1) { + litems1.push_back(ctx.mk_eq_atom(subAst1, subValue1)); } - expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - if (litems2.empty()) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems2), implyR); + if (subValue2 != subAst2) { + litems1.push_back(ctx.mk_eq_atom(subAst2, subValue2)); + } + + zstring subConst1, subConst2; + u.str.is_string(subValue1, subConst1); + u.str.is_string(subValue2, subConst2); + expr_ref implyR(m); + if (subConst1 == subConst2) { + // key1.first = key2.first /\ key1.second = key2.second + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst1.contains(subConst2)) { + // key1.first = key2.first /\ Contains(key1.second, key2.second) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (subConst2.contains(subConst1)) { + // key1.first = key2.first /\ Contains(key2.second, key1.second) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } + + if (implyR) { + if (litems1.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } } } else { - // ----------------------------------------------------------- - // * key1.first = key2.first - // check eqc(key1.second) and eqc(key2.second) - // ----------------------------------------------------------- - expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); - for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { - expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); - for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - // ------------ - { - expr_ref_vector litems3(m); - if (n1 != n2) { - litems3.push_back(ctx.mk_eq_atom(n1, n2)); + expr_ref_vector subAst1Eqc(m); + expr_ref_vector subAst2Eqc(m); + collect_eq_nodes(subAst1, subAst1Eqc); + collect_eq_nodes(subAst2, subAst2Eqc); + + if (subAst1Eqc.contains(subAst2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (subAst1 != subAst2) { + litems2.push_back(ctx.mk_eq_atom(subAst1, subAst2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } + } else { + // ----------------------------------------------------------- + // * key1.first = key2.first + // check eqc(key1.second) and eqc(key2.second) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorSub1 = subAst1Eqc.begin(); + for (; eqItorSub1 != subAst1Eqc.end(); eqItorSub1++) { + expr_ref_vector::iterator eqItorSub2 = subAst2Eqc.begin(); + for (; eqItorSub2 != subAst2Eqc.end(); eqItorSub2++) { + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems3), implR); + } } - expr * eqSubVar1 = *eqItorSub1; - if (eqSubVar1 != subAst1) { - litems3.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); - } - expr * eqSubVar2 = *eqItorSub2; - if (eqSubVar2 != subAst2) { - litems3.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); - } - std::pair tryKey1 = std::make_pair(eqSubVar1, eqSubVar2); - if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("str", tout << "(Contains " << mk_pp(eqSubVar1, m) << " " << mk_pp(eqSubVar2, m) << ")" << std::endl;); - litems3.push_back(contain_pair_bool_map[tryKey1]); - expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - assert_implication(mk_and(litems3), implR); - } - } - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - // ------------ - { - expr_ref_vector litems4(m); - if (n1 != n2) { - litems4.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqSubVar1 = *eqItorSub1; - if (eqSubVar1 != subAst1) { - litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); - } - expr * eqSubVar2 = *eqItorSub2; - if (eqSubVar2 != subAst2) { - litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); - } - std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); - if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); - litems4.push_back(contain_pair_bool_map[tryKey2]); - expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); - assert_implication(mk_and(litems4), implR); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqSubVar1 = *eqItorSub1; + if (eqSubVar1 != subAst1) { + litems4.push_back(ctx.mk_eq_atom(subAst1, eqSubVar1)); + } + expr * eqSubVar2 = *eqItorSub2; + if (eqSubVar2 != subAst2) { + litems4.push_back(ctx.mk_eq_atom(subAst2, eqSubVar2)); + } + std::pair tryKey2 = std::make_pair(eqSubVar2, eqSubVar1); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqSubVar2, m) << " " << mk_pp(eqSubVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems4), implR); + } } } } } } } - } - // *************************** - // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n - // *************************** - else if (key1.second == n1 && key2.second == n2) { - expr * str1 = key1.first; - expr * str2 = key2.first; - bool str1HasValue = false; - bool str2HasValue = false; - expr * strVal1 = get_eqc_value(str1, str1HasValue); - expr * strVal2 = get_eqc_value(str2, str2HasValue); + // *************************** + // Case 2: Contains(..., m) /\ Contains(... , n) /\ m = n + // *************************** + else if (key1.second == n1 && key2.second == n2) { + expr * str1 = key1.first; + expr * str2 = key2.first; + bool str1HasValue = false; + bool str2HasValue = false; + expr * strVal1 = get_eqc_value(str1, str1HasValue); + expr * strVal2 = get_eqc_value(str2, str2HasValue); - TRACE("str", - tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; - tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; - if (str1 != strVal1) { - tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; - } - if (str2 != strVal2) { - tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; - } - ); + TRACE("str", + tout << "(Contains " << mk_pp(str1, m) << " " << mk_pp(n1, m) << ")" << std::endl; + tout << "(Contains " << mk_pp(str2, m) << " " << mk_pp(n2, m) << ")" << std::endl; + if (str1 != strVal1) { + tout << mk_pp(str1, m) << " = " << mk_pp(strVal1, m) << std::endl; + } + if (str2 != strVal2) { + tout << mk_pp(str2, m) << " = " << mk_pp(strVal2, m) << std::endl; + } + ); - if (str1HasValue && str2HasValue) { - expr_ref_vector litems1(m); - if (n1 != n2) { - litems1.push_back(ctx.mk_eq_atom(n1, n2)); - } - if (strVal1 != str1) { - litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); - } - if (strVal2 != str2) { - litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); - } - - zstring const1, const2; - u.str.is_string(strVal1, const1); - u.str.is_string(strVal2, const2); - expr_ref implyR(m); - - if (const1 == const2) { - // key1.second = key2.second /\ key1.first = key2.first - // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) - implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } else if (const1.contains(const2)) { - // key1.second = key2.second /\ Contains(key1.first, key2.first) - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); - } else if (const2.contains(const1)) { - // key1.first = key2.first /\ Contains(key2.first, key1.first) - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); - } - - if (implyR) { - if (litems1.size() == 0) { - assert_axiom(implyR); - } else { - assert_implication(mk_and(litems1), implyR); - } - } - } - - else { - expr_ref_vector str1Eqc(m); - expr_ref_vector str2Eqc(m); - collect_eq_nodes(str1, str1Eqc); - collect_eq_nodes(str2, str2Eqc); - - if (str1Eqc.contains(str2)) { - // ----------------------------------------------------------- - // * key1.first = key2.first /\ key1.second = key2.second - // --> containPairBoolMap[key1] = containPairBoolMap[key2] - // ----------------------------------------------------------- - expr_ref_vector litems2(m); + if (str1HasValue && str2HasValue) { + expr_ref_vector litems1(m); if (n1 != n2) { - litems2.push_back(ctx.mk_eq_atom(n1, n2)); + litems1.push_back(ctx.mk_eq_atom(n1, n2)); } - if (str1 != str2) { - litems2.push_back(ctx.mk_eq_atom(str1, str2)); + if (strVal1 != str1) { + litems1.push_back(ctx.mk_eq_atom(str1, strVal1)); } - expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - if (litems2.empty()) { - assert_axiom(implyR); + if (strVal2 != str2) { + litems1.push_back(ctx.mk_eq_atom(str2, strVal2)); + } + + zstring const1, const2; + u.str.is_string(strVal1, const1); + u.str.is_string(strVal2, const2); + expr_ref implyR(m); + + if (const1 == const2) { + // key1.second = key2.second /\ key1.first = key2.first + // ==> (containPairBoolMap[key1] = containPairBoolMap[key2]) + implyR = ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } else if (const1.contains(const2)) { + // key1.second = key2.second /\ Contains(key1.first, key2.first) + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + implyR = rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]); + } else if (const2.contains(const1)) { + // key1.first = key2.first /\ Contains(key2.first, key1.first) + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + implyR = rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]); + } + + if (implyR) { + if (litems1.size() == 0) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems1), implyR); + } + } + } + + else { + expr_ref_vector str1Eqc(m); + expr_ref_vector str2Eqc(m); + collect_eq_nodes(str1, str1Eqc); + collect_eq_nodes(str2, str2Eqc); + + if (str1Eqc.contains(str2)) { + // ----------------------------------------------------------- + // * key1.first = key2.first /\ key1.second = key2.second + // --> containPairBoolMap[key1] = containPairBoolMap[key2] + // ----------------------------------------------------------- + expr_ref_vector litems2(m); + if (n1 != n2) { + litems2.push_back(ctx.mk_eq_atom(n1, n2)); + } + if (str1 != str2) { + litems2.push_back(ctx.mk_eq_atom(str1, str2)); + } + expr_ref implyR(ctx.mk_eq_atom(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + if (litems2.empty()) { + assert_axiom(implyR); + } else { + assert_implication(mk_and(litems2), implyR); + } } else { - assert_implication(mk_and(litems2), implyR); - } - } else { - // ----------------------------------------------------------- - // * key1.second = key2.second - // check eqc(key1.first) and eqc(key2.first) - // ----------------------------------------------------------- - expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); - for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { - expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); - for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { - { - expr_ref_vector litems3(m); - if (n1 != n2) { - litems3.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqStrVar1 = *eqItorStr1; - if (eqStrVar1 != str1) { - litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); - } - expr * eqStrVar2 = *eqItorStr2; - if (eqStrVar2 != str2) { - litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); - } - std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); - if (contain_pair_bool_map.contains(tryKey1)) { - TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); - litems3.push_back(contain_pair_bool_map[tryKey1]); + // ----------------------------------------------------------- + // * key1.second = key2.second + // check eqc(key1.first) and eqc(key2.first) + // ----------------------------------------------------------- + expr_ref_vector::iterator eqItorStr1 = str1Eqc.begin(); + for (; eqItorStr1 != str1Eqc.end(); eqItorStr1++) { + expr_ref_vector::iterator eqItorStr2 = str2Eqc.begin(); + for (; eqItorStr2 != str2Eqc.end(); eqItorStr2++) { + { + expr_ref_vector litems3(m); + if (n1 != n2) { + litems3.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems3.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr * eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems3.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey1 = std::make_pair(eqStrVar1, eqStrVar2); + if (contain_pair_bool_map.contains(tryKey1)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar1, m) << " " << mk_pp(eqStrVar2, m) << ")" << std::endl;); + litems3.push_back(contain_pair_bool_map[tryKey1]); - // ------------ - // key1.second = key2.second /\ containPairBoolMap[] - // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) - // ------------ - expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); - assert_implication(mk_and(litems3), implR); + // ------------ + // key1.second = key2.second /\ containPairBoolMap[] + // ==> (containPairBoolMap[key2] --> containPairBoolMap[key1]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key2], contain_pair_bool_map[key1]), m); + assert_implication(mk_and(litems3), implR); + } } - } - { - expr_ref_vector litems4(m); - if (n1 != n2) { - litems4.push_back(ctx.mk_eq_atom(n1, n2)); - } - expr * eqStrVar1 = *eqItorStr1; - if (eqStrVar1 != str1) { - litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); - } - expr *eqStrVar2 = *eqItorStr2; - if (eqStrVar2 != str2) { - litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); - } - std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); + { + expr_ref_vector litems4(m); + if (n1 != n2) { + litems4.push_back(ctx.mk_eq_atom(n1, n2)); + } + expr * eqStrVar1 = *eqItorStr1; + if (eqStrVar1 != str1) { + litems4.push_back(ctx.mk_eq_atom(str1, eqStrVar1)); + } + expr *eqStrVar2 = *eqItorStr2; + if (eqStrVar2 != str2) { + litems4.push_back(ctx.mk_eq_atom(str2, eqStrVar2)); + } + std::pair tryKey2 = std::make_pair(eqStrVar2, eqStrVar1); - if (contain_pair_bool_map.contains(tryKey2)) { - TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); - litems4.push_back(contain_pair_bool_map[tryKey2]); - // ------------ - // key1.first = key2.first /\ containPairBoolMap[] - // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) - // ------------ - expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); - assert_implication(mk_and(litems4), implR); + if (contain_pair_bool_map.contains(tryKey2)) { + TRACE("str", tout << "(Contains " << mk_pp(eqStrVar2, m) << " " << mk_pp(eqStrVar1, m) << ")" << std::endl;); + litems4.push_back(contain_pair_bool_map[tryKey2]); + // ------------ + // key1.first = key2.first /\ containPairBoolMap[] + // ==> (containPairBoolMap[key1] --> containPairBoolMap[key2]) + // ------------ + expr_ref implR(rewrite_implication(contain_pair_bool_map[key1], contain_pair_bool_map[key2]), m); + assert_implication(mk_and(litems4), implR); + } } } } } } - } - } - } - - if (n1 == n2) { - break; - } - } - } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) -} - -void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { - if (contains_map.empty()) { - return; - } - - context & ctx = get_context(); - ast_manager & m = get_manager(); - TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); - - expr_ref_vector willEqClass(m); - expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); - expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); - expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; - - TRACE("str", tout << "eqc of n1 is {"; - for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { - expr * el = *it; - tout << " " << mk_pp(el, m); - } - tout << std::endl; - if (constStrAst == NULL) { - tout << "constStrAst = NULL" << std::endl; - } else { - tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; - } - ); - - // step 1: we may have constant values for Contains checks now - if (constStrAst != NULL) { - expr_ref_vector::iterator itAst = willEqClass.begin(); - for (; itAst != willEqClass.end(); itAst++) { - if (*itAst == constStrAst) { - continue; - } - check_contain_by_eqc_val(*itAst, constStrAst); - } - } else { - // no concrete value to be put in eqc, solely based on context - // Check here is used to detected the facts as follows: - // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M - // * new fact: M = concat(..., "jio", ...) - // Note that in this branch, either M or concat(..., "jio", ...) has a constant value - // So, only need to check - // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and - // * If strAst registered has an eqc constant in the context - // ------------------------------------------------------------- - expr_ref_vector::iterator itAst = willEqClass.begin(); - for (; itAst != willEqClass.end(); ++itAst) { - check_contain_by_substr(*itAst, willEqClass); - } - } - - // ------------------------------------------ - // step 2: check for b1 = contains(x, m), b2 = contains(y, n) - // (1) x = y /\ m = n ==> b1 = b2 - // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) - // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) - // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) - // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) - // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) - // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) - // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) - // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) - // ------------------------------------------ - - expr_ref_vector::iterator varItor1 = willEqClass.begin(); - for (; varItor1 != willEqClass.end(); ++varItor1) { - expr * varAst1 = *varItor1; - expr_ref_vector::iterator varItor2 = varItor1; - for (; varItor2 != willEqClass.end(); ++varItor2) { - expr * varAst2 = *varItor2; - check_contain_by_eq_nodes(varAst1, varAst2); - } - } -} - -expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { - if (variable_set.find(node) != variable_set.end()) { - return get_alias_index_ast(varAliasMap, node); - } else if (u.str.is_concat(to_app(node))) { - return get_alias_index_ast(concatAliasMap, node); - } - return node; -} - -void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap, - std::map, std::set > > & groundedMap) { - if (u.re.is_unroll(to_app(node))) { - return; - } - // ************************************************** - // first deAlias the node if it is a var or concat - // ************************************************** - node = dealias_node(node, varAliasMap, concatAliasMap); - - if (groundedMap.find(node) != groundedMap.end()) { - return; - } - - // haven't computed grounded concats for "node" (de-aliased) - // --------------------------------------------------------- - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // const strings: node is de-aliased - if (u.str.is_string(node)) { - std::vector concatNodes; - concatNodes.push_back(node); - groundedMap[node][concatNodes].clear(); // no condition - } - // Concat functions - else if (u.str.is_concat(to_app(node))) { - // if "node" equals to a constant string, thenjust push the constant into the concat vector - // Again "node" has been de-aliased at the very beginning - if (concatConstMap.find(node) != concatConstMap.end()) { - std::vector concatNodes; - concatNodes.push_back(concatConstMap[node]); - groundedMap[node][concatNodes].clear(); - groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); - } - // node doesn't have eq constant value. Process its children. - else { - // merge arg0 and arg1 - expr * arg0 = to_app(node)->get_arg(0); - expr * arg1 = to_app(node)->get_arg(1); - expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); - expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); - get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - - std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); - std::map, std::set >::iterator arg1_grdItor; - for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { - arg1_grdItor = groundedMap[arg1DeAlias].begin(); - for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { - std::vector ndVec; - ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); - int arg0VecSize = arg0_grdItor->first.size(); - int arg1VecSize = arg1_grdItor->first.size(); - if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { - ndVec.pop_back(); - ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); - for (int i = 1; i < arg1VecSize; i++) { - ndVec.push_back(arg1_grdItor->first[i]); - } - } else { - ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); - } - // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough - if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { - groundedMap[node][ndVec]; - if (arg0 != arg0DeAlias) { - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); - } - groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); - - if (arg1 != arg1DeAlias) { - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); - } - groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); } } + + if (n1 == n2) { + break; + } + } + } // (in_contain_idx_map(n1) && in_contain_idx_map(n2)) + } + + void theory_str::check_contain_in_new_eq(expr * n1, expr * n2) { + if (contains_map.empty()) { + return; + } + + context & ctx = get_context(); + ast_manager & m = get_manager(); + TRACE("str", tout << "consistency check for contains wrt. " << mk_pp(n1, m) << " and " << mk_pp(n2, m) << std::endl;); + + expr_ref_vector willEqClass(m); + expr * constStrAst_1 = collect_eq_nodes(n1, willEqClass); + expr * constStrAst_2 = collect_eq_nodes(n2, willEqClass); + expr * constStrAst = (constStrAst_1 != NULL) ? constStrAst_1 : constStrAst_2; + + TRACE("str", tout << "eqc of n1 is {"; + for (expr_ref_vector::iterator it = willEqClass.begin(); it != willEqClass.end(); ++it) { + expr * el = *it; + tout << " " << mk_pp(el, m); + } + tout << std::endl; + if (constStrAst == NULL) { + tout << "constStrAst = NULL" << std::endl; + } else { + tout << "constStrAst = " << mk_pp(constStrAst, m) << std::endl; + } + ); + + // step 1: we may have constant values for Contains checks now + if (constStrAst != NULL) { + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); itAst++) { + if (*itAst == constStrAst) { + continue; + } + check_contain_by_eqc_val(*itAst, constStrAst); + } + } else { + // no concrete value to be put in eqc, solely based on context + // Check here is used to detected the facts as follows: + // * known: contains(Z, Y) /\ Z = "abcdefg" /\ Y = M + // * new fact: M = concat(..., "jio", ...) + // Note that in this branch, either M or concat(..., "jio", ...) has a constant value + // So, only need to check + // * "EQC(M) U EQC(concat(..., "jio", ...))" as substr and + // * If strAst registered has an eqc constant in the context + // ------------------------------------------------------------- + expr_ref_vector::iterator itAst = willEqClass.begin(); + for (; itAst != willEqClass.end(); ++itAst) { + check_contain_by_substr(*itAst, willEqClass); + } + } + + // ------------------------------------------ + // step 2: check for b1 = contains(x, m), b2 = contains(y, n) + // (1) x = y /\ m = n ==> b1 = b2 + // (2) x = y /\ Contains(const(m), const(n)) ==> (b1 -> b2) + // (3) x = y /\ Contains(const(n), const(m)) ==> (b2 -> b1) + // (4) x = y /\ containPairBoolMap[] ==> (b1 -> b2) + // (5) x = y /\ containPairBoolMap[] ==> (b2 -> b1) + // (6) Contains(const(x), const(y)) /\ m = n ==> (b2 -> b1) + // (7) Contains(const(y), const(x)) /\ m = n ==> (b1 -> b2) + // (8) containPairBoolMap[] /\ m = n ==> (b2 -> b1) + // (9) containPairBoolMap[] /\ m = n ==> (b1 -> b2) + // ------------------------------------------ + + expr_ref_vector::iterator varItor1 = willEqClass.begin(); + for (; varItor1 != willEqClass.end(); ++varItor1) { + expr * varAst1 = *varItor1; + expr_ref_vector::iterator varItor2 = varItor1; + for (; varItor2 != willEqClass.end(); ++varItor2) { + expr * varAst2 = *varItor2; + check_contain_by_eq_nodes(varAst1, varAst2); } } } - // string variables - else if (variable_set.find(node) != variable_set.end()) { - // deAliasedVar = Constant - if (varConstMap.find(node) != varConstMap.end()) { - std::vector concatNodes; - concatNodes.push_back(varConstMap[node]); - groundedMap[node][concatNodes].clear(); - groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); - } - // deAliasedVar = someConcat - else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { - expr * eqConcat = varEqConcatMap[node].begin()->first; - expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); - get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); - for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { - std::vector ndVec; - ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); - // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough - if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { - // condition: node = deAliasedEqConcat - groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); - // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" - groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); - } - } + expr * theory_str::dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap) { + if (variable_set.find(node) != variable_set.end()) { + return get_alias_index_ast(varAliasMap, node); + } else if (u.str.is_concat(to_app(node))) { + return get_alias_index_ast(concatAliasMap, node); } - // node (has been de-aliased) != constant && node (has been de-aliased) != any concat - // just push in the deAliasedVar - else { + return node; + } + + void theory_str::get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, + std::map, std::set > > & groundedMap) { + if (u.re.is_unroll(to_app(node))) { + return; + } + // ************************************************** + // first deAlias the node if it is a var or concat + // ************************************************** + node = dealias_node(node, varAliasMap, concatAliasMap); + + if (groundedMap.find(node) != groundedMap.end()) { + return; + } + + // haven't computed grounded concats for "node" (de-aliased) + // --------------------------------------------------------- + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // const strings: node is de-aliased + if (u.str.is_string(node)) { std::vector concatNodes; concatNodes.push_back(node); - groundedMap[node][concatNodes]; + groundedMap[node][concatNodes].clear(); // no condition + } + // Concat functions + else if (u.str.is_concat(to_app(node))) { + // if "node" equals to a constant string, thenjust push the constant into the concat vector + // Again "node" has been de-aliased at the very beginning + if (concatConstMap.find(node) != concatConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(concatConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, concatConstMap[node])); + } + // node doesn't have eq constant value. Process its children. + else { + // merge arg0 and arg1 + expr * arg0 = to_app(node)->get_arg(0); + expr * arg1 = to_app(node)->get_arg(1); + expr * arg0DeAlias = dealias_node(arg0, varAliasMap, concatAliasMap); + expr * arg1DeAlias = dealias_node(arg1, varAliasMap, concatAliasMap); + get_grounded_concats(arg0DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(arg1DeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator arg0_grdItor = groundedMap[arg0DeAlias].begin(); + std::map, std::set >::iterator arg1_grdItor; + for (; arg0_grdItor != groundedMap[arg0DeAlias].end(); arg0_grdItor++) { + arg1_grdItor = groundedMap[arg1DeAlias].begin(); + for (; arg1_grdItor != groundedMap[arg1DeAlias].end(); arg1_grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), arg0_grdItor->first.begin(), arg0_grdItor->first.end()); + int arg0VecSize = arg0_grdItor->first.size(); + int arg1VecSize = arg1_grdItor->first.size(); + if (arg0VecSize > 0 && arg1VecSize > 0 && u.str.is_string(arg0_grdItor->first[arg0VecSize - 1]) && u.str.is_string(arg1_grdItor->first[0])) { + ndVec.pop_back(); + ndVec.push_back(mk_concat(arg0_grdItor->first[arg0VecSize - 1], arg1_grdItor->first[0])); + for (int i = 1; i < arg1VecSize; i++) { + ndVec.push_back(arg1_grdItor->first[i]); + } + } else { + ndVec.insert(ndVec.end(), arg1_grdItor->first.begin(), arg1_grdItor->first.end()); + } + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + groundedMap[node][ndVec]; + if (arg0 != arg0DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg0, arg0DeAlias)); + } + groundedMap[node][ndVec].insert(arg0_grdItor->second.begin(), arg0_grdItor->second.end()); + + if (arg1 != arg1DeAlias) { + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(arg1, arg1DeAlias)); + } + groundedMap[node][ndVec].insert(arg1_grdItor->second.begin(), arg1_grdItor->second.end()); + } + } + } + } + } + // string variables + else if (variable_set.find(node) != variable_set.end()) { + // deAliasedVar = Constant + if (varConstMap.find(node) != varConstMap.end()) { + std::vector concatNodes; + concatNodes.push_back(varConstMap[node]); + groundedMap[node][concatNodes].clear(); + groundedMap[node][concatNodes].insert(ctx.mk_eq_atom(node, varConstMap[node])); + } + // deAliasedVar = someConcat + else if (varEqConcatMap.find(node) != varEqConcatMap.end()) { + expr * eqConcat = varEqConcatMap[node].begin()->first; + expr * deAliasedEqConcat = dealias_node(eqConcat, varAliasMap, concatAliasMap); + get_grounded_concats(deAliasedEqConcat, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + std::map, std::set >::iterator grdItor = groundedMap[deAliasedEqConcat].begin(); + for (; grdItor != groundedMap[deAliasedEqConcat].end(); grdItor++) { + std::vector ndVec; + ndVec.insert(ndVec.end(), grdItor->first.begin(), grdItor->first.end()); + // only insert if we don't know "node = concat(ndVec)" since one set of condition leads to this is enough + if (groundedMap[node].find(ndVec) == groundedMap[node].end()) { + // condition: node = deAliasedEqConcat + groundedMap[node][ndVec].insert(ctx.mk_eq_atom(node, deAliasedEqConcat)); + // appending conditions for "deAliasedEqConcat = CONCAT(ndVec)" + groundedMap[node][ndVec].insert(grdItor->second.begin(), grdItor->second.end()); + } + } + } + // node (has been de-aliased) != constant && node (has been de-aliased) != any concat + // just push in the deAliasedVar + else { + std::vector concatNodes; + concatNodes.push_back(node); + groundedMap[node][concatNodes]; + } } } -} -void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { - ast_manager & m = get_manager(); - TRACE("str", tout << mk_pp(node, m) << std::endl;); - if (groundedMap.find(node) != groundedMap.end()) { - std::map, std::set >::iterator itor = groundedMap[node].begin(); - for (; itor != groundedMap[node].end(); ++itor) { - TRACE("str", - tout << "\t[grounded] "; - std::vector::const_iterator vIt = itor->first.begin(); - for (; vIt != itor->first.end(); ++vIt) { - tout << mk_pp(*vIt, m) << ", "; - } - tout << std::endl; - tout << "\t[condition] "; - std::set::iterator sIt = itor->second.begin(); - for (; sIt != itor->second.end(); sIt++) { - tout << mk_pp(*sIt, m) << ", "; - } - tout << std::endl; - ); + void theory_str::print_grounded_concat(expr * node, std::map, std::set > > & groundedMap) { + ast_manager & m = get_manager(); + TRACE("str", tout << mk_pp(node, m) << std::endl;); + if (groundedMap.find(node) != groundedMap.end()) { + std::map, std::set >::iterator itor = groundedMap[node].begin(); + for (; itor != groundedMap[node].end(); ++itor) { + TRACE("str", + tout << "\t[grounded] "; + std::vector::const_iterator vIt = itor->first.begin(); + for (; vIt != itor->first.end(); ++vIt) { + tout << mk_pp(*vIt, m) << ", "; + } + tout << std::endl; + tout << "\t[condition] "; + std::set::iterator sIt = itor->second.begin(); + for (; sIt != itor->second.end(); sIt++) { + tout << mk_pp(*sIt, m) << ", "; + } + tout << std::endl; + ); + } + } else { + TRACE("str", tout << "not found" << std::endl;); } - } else { - TRACE("str", tout << "not found" << std::endl;); - } -} - -bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { - int strCnt = strVec.size(); - int subStrCnt = subStrVec.size(); - - if (strCnt == 0 || subStrCnt == 0) { - return false; } - // The assumption is that all consecutive constant strings are merged into one node - if (strCnt < subStrCnt) { - return false; - } + bool theory_str::is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec) { + int strCnt = strVec.size(); + int subStrCnt = subStrVec.size(); - if (subStrCnt == 1) { - zstring subStrVal; - if (u.str.is_string(subStrVec[0], subStrVal)) { - for (int i = 0; i < strCnt; i++) { - zstring strVal; - if (u.str.is_string(strVec[i], strVal)) { - if (strVal.contains(subStrVal)) { + if (strCnt == 0 || subStrCnt == 0) { + return false; + } + + // The assumption is that all consecutive constant strings are merged into one node + if (strCnt < subStrCnt) { + return false; + } + + if (subStrCnt == 1) { + zstring subStrVal; + if (u.str.is_string(subStrVec[0], subStrVal)) { + for (int i = 0; i < strCnt; i++) { + zstring strVal; + if (u.str.is_string(strVec[i], strVal)) { + if (strVal.contains(subStrVal)) { + return true; + } + } + } + } else { + for (int i = 0; i < strCnt; i++) { + if (strVec[i] == subStrVec[0]) { return true; } } } + return false; } else { - for (int i = 0; i < strCnt; i++) { - if (strVec[i] == subStrVec[0]) { - return true; - } - } - } - return false; - } else { - for (int i = 0; i <= (strCnt - subStrCnt); i++) { - // The first node in subStrVect should be - // * constant: a suffix of a note in strVec[i] - // * variable: - bool firstNodesOK = true; - zstring subStrHeadVal; - if (u.str.is_string(subStrVec[0], subStrHeadVal)) { - zstring strHeadVal; - if (u.str.is_string(strVec[i], strHeadVal)) { - if (strHeadVal.length() >= subStrHeadVal.length()) { - zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); - if (suffix != subStrHeadVal) { + for (int i = 0; i <= (strCnt - subStrCnt); i++) { + // The first node in subStrVect should be + // * constant: a suffix of a note in strVec[i] + // * variable: + bool firstNodesOK = true; + zstring subStrHeadVal; + if (u.str.is_string(subStrVec[0], subStrHeadVal)) { + zstring strHeadVal; + if (u.str.is_string(strVec[i], strHeadVal)) { + if (strHeadVal.length() >= subStrHeadVal.length()) { + zstring suffix = strHeadVal.extract(strHeadVal.length() - subStrHeadVal.length(), subStrHeadVal.length()); + if (suffix != subStrHeadVal) { + firstNodesOK = false; + } + } else { firstNodesOK = false; } } else { - firstNodesOK = false; - } - } else { - if (subStrVec[0] != strVec[i]) { - firstNodesOK = false; + if (subStrVec[0] != strVec[i]) { + firstNodesOK = false; + } } } - } - if (!firstNodesOK) { - continue; - } - - // middle nodes - bool midNodesOK = true; - for (int j = 1; j < subStrCnt - 1; j++) { - if (subStrVec[j] != strVec[i + j]) { - midNodesOK = false; - break; + if (!firstNodesOK) { + continue; } - } - if (!midNodesOK) { - continue; - } - // tail nodes - int tailIdx = i + subStrCnt - 1; - zstring subStrTailVal; - if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { - zstring strTailVal; - if (u.str.is_string(strVec[tailIdx], strTailVal)) { - if (strTailVal.length() >= subStrTailVal.length()) { - zstring prefix = strTailVal.extract(0, subStrTailVal.length()); - if (prefix == subStrTailVal) { - return true; + // middle nodes + bool midNodesOK = true; + for (int j = 1; j < subStrCnt - 1; j++) { + if (subStrVec[j] != strVec[i + j]) { + midNodesOK = false; + break; + } + } + if (!midNodesOK) { + continue; + } + + // tail nodes + int tailIdx = i + subStrCnt - 1; + zstring subStrTailVal; + if (u.str.is_string(subStrVec[subStrCnt - 1], subStrTailVal)) { + zstring strTailVal; + if (u.str.is_string(strVec[tailIdx], strTailVal)) { + if (strTailVal.length() >= subStrTailVal.length()) { + zstring prefix = strTailVal.extract(0, subStrTailVal.length()); + if (prefix == subStrTailVal) { + return true; + } else { + continue; + } } else { continue; } + } + } else { + if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { + return true; } else { continue; } } - } else { - if (subStrVec[subStrCnt - 1] == strVec[tailIdx]) { - return true; - } else { - continue; - } } - } - return false; - } -} - -void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, - std::map, std::set > > & groundedMap) { - - context & ctx = get_context(); - ast_manager & m = get_manager(); - std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); - std::map, std::set >::iterator itorSubStr; - for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { - itorSubStr = groundedMap[subStrDeAlias].begin(); - for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { - bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); - if (contain) { - expr_ref_vector litems(m); - if (str != strDeAlias) { - litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); - } - if (subStr != subStrDeAlias) { - litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); - } - - //litems.insert(itorStr->second.begin(), itorStr->second.end()); - //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); - for (std::set::const_iterator i1 = itorStr->second.begin(); - i1 != itorStr->second.end(); ++i1) { - litems.push_back(*i1); - } - for (std::set::const_iterator i1 = itorSubStr->second.begin(); - i1 != itorSubStr->second.end(); ++i1) { - litems.push_back(*i1); - } - - expr_ref implyR(boolVar, m); - - if (litems.empty()) { - assert_axiom(implyR); - } else { - expr_ref implyL(mk_and(litems), m); - assert_implication(implyL, implyR); - } - - } - } - } -} - -void theory_str::compute_contains(std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap) { - std::map, std::set > > groundedMap; - theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); - for (; containItor != contain_pair_bool_map.end(); containItor++) { - expr* containBoolVar = containItor->get_value(); - expr* str = containItor->get_key1(); - expr* subStr = containItor->get_key2(); - - expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); - expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); - - get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); - - // debugging - print_grounded_concat(strDeAlias, groundedMap); - print_grounded_concat(subStrDeAlias, groundedMap); - - check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); - } -} - -bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { - unsigned int strLen = str.length(); - if (u.str.is_concat(to_app(concat))) { - ptr_vector args; - get_nodes_in_concat(concat, args); - expr * ml_node = args[0]; - expr * mr_node = args[args.size() - 1]; - - zstring ml_str; - if (u.str.is_string(ml_node, ml_str)) { - unsigned int ml_len = ml_str.length(); - if (ml_len > strLen) { - return false; - } - unsigned int cLen = ml_len; - if (ml_str != str.extract(0, cLen)) { - return false; - } - } - - zstring mr_str; - if (u.str.is_string(mr_node, mr_str)) { - unsigned int mr_len = mr_str.length(); - if (mr_len > strLen) { - return false; - } - unsigned int cLen = mr_len; - if (mr_str != str.extract(strLen - cLen, cLen)) { - return false; - } - } - - unsigned int sumLen = 0; - for (unsigned int i = 0 ; i < args.size() ; i++) { - expr * oneArg = args[i]; - zstring arg_str; - if (u.str.is_string(oneArg, arg_str)) { - if (!str.contains(arg_str)) { - return false; - } - sumLen += arg_str.length(); - } - } - - if (sumLen > strLen) { - return false; - } - } - return true; -} - -bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { - if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { - { - // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). - expr * concat1_mostL = getMostLeftNodeInConcat(concat1); - expr * concat2_mostL = getMostLeftNodeInConcat(concat2); - // if both X and M are constant strings, check whether they have the same prefix - zstring concat1_mostL_str, concat2_mostL_str; - if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { - unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); - if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { - return false; - } - } - } - - { - // Similarly, if both Y and N are constant strings, check whether they have the same suffix - expr * concat1_mostR = getMostRightNodeInConcat(concat1); - expr * concat2_mostR = getMostRightNodeInConcat(concat2); - zstring concat1_mostR_str, concat2_mostR_str; - if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { - unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); - if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != - concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { - return false; - } - } - } - } - return true; -} - -/* - * Check whether n1 and n2 could be equal. - * Returns true if n1 could equal n2 (maybe), - * and false if n1 is definitely not equal to n2 (no). - */ -bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { - app * n1_curr = to_app(n1); - app * n2_curr = to_app(n2); - - // case 0: n1_curr is const string, n2_curr is const string - if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { - if (n1_curr != n2_curr) { - return false; - } - } - // case 1: n1_curr is concat, n2_curr is const string - else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { - zstring n2_curr_str; - u.str.is_string(n2_curr, n2_curr_str); - if (!can_concat_eq_str(n1_curr, n2_curr_str)) { return false; } } - // case 2: n2_curr is concat, n1_curr is const string - else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { - zstring n1_curr_str; - u.str.is_string(n1_curr, n1_curr_str); - if (!can_concat_eq_str(n2_curr, n1_curr_str)) { - return false; + + void theory_str::check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + std::map, std::set >::iterator itorStr = groundedMap[strDeAlias].begin(); + std::map, std::set >::iterator itorSubStr; + for (; itorStr != groundedMap[strDeAlias].end(); itorStr++) { + itorSubStr = groundedMap[subStrDeAlias].begin(); + for (; itorSubStr != groundedMap[subStrDeAlias].end(); itorSubStr++) { + bool contain = is_partial_in_grounded_concat(itorStr->first, itorSubStr->first); + if (contain) { + expr_ref_vector litems(m); + if (str != strDeAlias) { + litems.push_back(ctx.mk_eq_atom(str, strDeAlias)); + } + if (subStr != subStrDeAlias) { + litems.push_back(ctx.mk_eq_atom(subStr, subStrDeAlias)); + } + + //litems.insert(itorStr->second.begin(), itorStr->second.end()); + //litems.insert(itorSubStr->second.begin(), itorSubStr->second.end()); + for (std::set::const_iterator i1 = itorStr->second.begin(); + i1 != itorStr->second.end(); ++i1) { + litems.push_back(*i1); + } + for (std::set::const_iterator i1 = itorSubStr->second.begin(); + i1 != itorSubStr->second.end(); ++i1) { + litems.push_back(*i1); + } + + expr_ref implyR(boolVar, m); + + if (litems.empty()) { + assert_axiom(implyR); + } else { + expr_ref implyL(mk_and(litems), m); + assert_implication(implyL, implyR); + } + + } + } } } - // case 3: both are concats - else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { - if (!can_concat_eq_concat(n1_curr, n2_curr)) { - return false; - } + + void theory_str::compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap) { + std::map, std::set > > groundedMap; + theory_str_contain_pair_bool_map_t::iterator containItor = contain_pair_bool_map.begin(); + for (; containItor != contain_pair_bool_map.end(); containItor++) { + expr* containBoolVar = containItor->get_value(); + expr* str = containItor->get_key1(); + expr* subStr = containItor->get_key2(); + + expr* strDeAlias = dealias_node(str, varAliasMap, concatAliasMap); + expr* subStrDeAlias = dealias_node(subStr, varAliasMap, concatAliasMap); + + get_grounded_concats(strDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + get_grounded_concats(subStrDeAlias, varAliasMap, concatAliasMap, varConstMap, concatConstMap, varEqConcatMap, groundedMap); + + // debugging + print_grounded_concat(strDeAlias, groundedMap); + print_grounded_concat(subStrDeAlias, groundedMap); + + check_subsequence(str, strDeAlias, subStr, subStrDeAlias, containBoolVar, groundedMap); + } } - return true; -} + bool theory_str::can_concat_eq_str(expr * concat, zstring& str) { + unsigned int strLen = str.length(); + if (u.str.is_concat(to_app(concat))) { + ptr_vector args; + get_nodes_in_concat(concat, args); + expr * ml_node = args[0]; + expr * mr_node = args[args.size() - 1]; -// was checkLength2ConstStr() in Z3str2 -// returns true if everything is OK, or false if inconsistency detected -// - note that these are different from the semantics in Z3str2 -bool theory_str::check_length_const_string(expr * n1, expr * constStr) { - ast_manager & mgr = get_manager(); - context & ctx = get_context(); - - zstring tmp; - u.str.is_string(constStr, tmp); - rational strLen(tmp.length()); - - if (u.str.is_concat(to_app(n1))) { - ptr_vector args; - expr_ref_vector items(mgr); - - get_nodes_in_concat(n1, args); - - rational sumLen(0); - for (unsigned int i = 0; i < args.size(); ++i) { - rational argLen; - bool argLen_exists = get_len_value(args[i], argLen); - if (argLen_exists) { - if (!u.str.is_string(args[i])) { - items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + zstring ml_str; + if (u.str.is_string(ml_node, ml_str)) { + unsigned int ml_len = ml_str.length(); + if (ml_len > strLen) { + return false; } - TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); - sumLen += argLen; - if (sumLen > strLen) { - items.push_back(ctx.mk_eq_atom(n1, constStr)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); - assert_axiom(toAssert); + unsigned int cLen = ml_len; + if (ml_str != str.extract(0, cLen)) { return false; } } + + zstring mr_str; + if (u.str.is_string(mr_node, mr_str)) { + unsigned int mr_len = mr_str.length(); + if (mr_len > strLen) { + return false; + } + unsigned int cLen = mr_len; + if (mr_str != str.extract(strLen - cLen, cLen)) { + return false; + } + } + + unsigned int sumLen = 0; + for (unsigned int i = 0 ; i < args.size() ; i++) { + expr * oneArg = args[i]; + zstring arg_str; + if (u.str.is_string(oneArg, arg_str)) { + if (!str.contains(arg_str)) { + return false; + } + sumLen += arg_str.length(); + } + } + + if (sumLen > strLen) { + return false; + } } - } else { // !is_concat(n1) - rational oLen; - bool oLen_exists = get_len_value(n1, oLen); - if (oLen_exists && oLen != strLen) { - TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + return true; + } + + bool theory_str::can_concat_eq_concat(expr * concat1, expr * concat2) { + if (u.str.is_concat(to_app(concat1)) && u.str.is_concat(to_app(concat2))) { + { + // Suppose concat1 = (Concat X Y) and concat2 = (Concat M N). + expr * concat1_mostL = getMostLeftNodeInConcat(concat1); + expr * concat2_mostL = getMostLeftNodeInConcat(concat2); + // if both X and M are constant strings, check whether they have the same prefix + zstring concat1_mostL_str, concat2_mostL_str; + if (u.str.is_string(concat1_mostL, concat1_mostL_str) && u.str.is_string(concat2_mostL, concat2_mostL_str)) { + unsigned int cLen = std::min(concat1_mostL_str.length(), concat2_mostL_str.length()); + if (concat1_mostL_str.extract(0, cLen) != concat2_mostL_str.extract(0, cLen)) { + return false; + } + } + } + + { + // Similarly, if both Y and N are constant strings, check whether they have the same suffix + expr * concat1_mostR = getMostRightNodeInConcat(concat1); + expr * concat2_mostR = getMostRightNodeInConcat(concat2); + zstring concat1_mostR_str, concat2_mostR_str; + if (u.str.is_string(concat1_mostR, concat1_mostR_str) && u.str.is_string(concat2_mostR, concat2_mostR_str)) { + unsigned int cLen = std::min(concat1_mostR_str.length(), concat2_mostR_str.length()); + if (concat1_mostR_str.extract(concat1_mostR_str.length() - cLen, cLen) != + concat2_mostR_str.extract(concat2_mostR_str.length() - cLen, cLen)) { + return false; + } + } + } + } + return true; + } + + /* + * Check whether n1 and n2 could be equal. + * Returns true if n1 could equal n2 (maybe), + * and false if n1 is definitely not equal to n2 (no). + */ + bool theory_str::can_two_nodes_eq(expr * n1, expr * n2) { + app * n1_curr = to_app(n1); + app * n2_curr = to_app(n2); + + // case 0: n1_curr is const string, n2_curr is const string + if (u.str.is_string(n1_curr) && u.str.is_string(n2_curr)) { + if (n1_curr != n2_curr) { + return false; + } + } + // case 1: n1_curr is concat, n2_curr is const string + else if (u.str.is_concat(n1_curr) && u.str.is_string(n2_curr)) { + zstring n2_curr_str; + u.str.is_string(n2_curr, n2_curr_str); + if (!can_concat_eq_str(n1_curr, n2_curr_str)) { + return false; + } + } + // case 2: n2_curr is concat, n1_curr is const string + else if (u.str.is_concat(n2_curr) && u.str.is_string(n1_curr)) { + zstring n1_curr_str; + u.str.is_string(n1_curr, n1_curr_str); + if (!can_concat_eq_str(n2_curr, n1_curr_str)) { + return false; + } + } + // case 3: both are concats + else if (u.str.is_concat(n1_curr) && u.str.is_concat(n2_curr)) { + if (!can_concat_eq_concat(n1_curr, n2_curr)) { + return false; + } + } + + return true; + } + + // was checkLength2ConstStr() in Z3str2 + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_const_string(expr * n1, expr * constStr) { + ast_manager & mgr = get_manager(); + context & ctx = get_context(); + + zstring tmp; + u.str.is_string(constStr, tmp); + rational strLen(tmp.length()); + + if (u.str.is_concat(to_app(n1))) { + ptr_vector args; + expr_ref_vector items(mgr); + + get_nodes_in_concat(n1, args); + + rational sumLen(0); + for (unsigned int i = 0; i < args.size(); ++i) { + rational argLen; + bool argLen_exists = get_len_value(args[i], argLen); + if (argLen_exists) { + if (!u.str.is_string(args[i])) { + items.push_back(ctx.mk_eq_atom(mk_strlen(args[i]), mk_int(argLen))); + } + TRACE("str", tout << "concat arg: " << mk_pp(args[i], mgr) << " has len = " << argLen.to_string() << std::endl;); + sumLen += argLen; + if (sumLen > strLen) { + items.push_back(ctx.mk_eq_atom(n1, constStr)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + TRACE("str", tout << "inconsistent length: concat (len = " << sumLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + assert_axiom(toAssert); + return false; + } + } + } + } else { // !is_concat(n1) + rational oLen; + bool oLen_exists = get_len_value(n1, oLen); + if (oLen_exists && oLen != strLen) { + TRACE("str", tout << "inconsistent length: var (len = " << oLen << ") <==> string constant (len = " << strLen << ")" << std::endl;); + expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); + expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); + assert_implication(l, r); + return false; + } + } + rational unused; + if (get_len_value(n1, unused) == false) { expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); assert_implication(l, r); - return false; } - } - rational unused; - if (get_len_value(n1, unused) == false) { - expr_ref l(ctx.mk_eq_atom(n1, constStr), mgr); - expr_ref r(ctx.mk_eq_atom(mk_strlen(n1), mk_strlen(constStr)), mgr); - assert_implication(l, r); - } - return true; -} - -bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - ptr_vector concat1Args; - ptr_vector concat2Args; - get_nodes_in_concat(n1, concat1Args); - get_nodes_in_concat(n2, concat2Args); - - bool concat1LenFixed = true; - bool concat2LenFixed = true; - - expr_ref_vector items(mgr); - - rational sum1(0), sum2(0); - - for (unsigned int i = 0; i < concat1Args.size(); ++i) { - expr * oneArg = concat1Args[i]; - rational argLen; - bool argLen_exists = get_len_value(oneArg, argLen); - if (argLen_exists) { - sum1 += argLen; - if (!u.str.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - } - } else { - concat1LenFixed = false; - } - } - - for (unsigned int i = 0; i < concat2Args.size(); ++i) { - expr * oneArg = concat2Args[i]; - rational argLen; - bool argLen_exists = get_len_value(oneArg, argLen); - if (argLen_exists) { - sum2 += argLen; - if (!u.str.is_string(oneArg)) { - items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); - } - } else { - concat2LenFixed = false; - } - } - - items.push_back(ctx.mk_eq_atom(n1, n2)); - - bool conflict = false; - - if (concat1LenFixed && concat2LenFixed) { - if (sum1 != sum2) { - conflict = true; - } - } else if (!concat1LenFixed && concat2LenFixed) { - if (sum1 > sum2) { - conflict = true; - } - } else if (concat1LenFixed && !concat2LenFixed) { - if (sum1 < sum2) { - conflict = true; - } - } - - if (conflict) { - TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } - return true; -} - -bool theory_str::check_length_concat_var(expr * concat, expr * var) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - rational varLen; - bool varLen_exists = get_len_value(var, varLen); - if (!varLen_exists) { return true; - } else { - rational sumLen(0); - ptr_vector args; + } + + bool theory_str::check_length_concat_concat(expr * n1, expr * n2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + ptr_vector concat1Args; + ptr_vector concat2Args; + get_nodes_in_concat(n1, concat1Args); + get_nodes_in_concat(n2, concat2Args); + + bool concat1LenFixed = true; + bool concat2LenFixed = true; + expr_ref_vector items(mgr); - get_nodes_in_concat(concat, args); - for (unsigned int i = 0; i < args.size(); ++i) { - expr * oneArg = args[i]; + + rational sum1(0), sum2(0); + + for (unsigned int i = 0; i < concat1Args.size(); ++i) { + expr * oneArg = concat1Args[i]; rational argLen; bool argLen_exists = get_len_value(oneArg, argLen); if (argLen_exists) { - if (!u.str.is_string(oneArg) && !argLen.is_zero()) { + sum1 += argLen; + if (!u.str.is_string(oneArg)) { items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); } - sumLen += argLen; - if (sumLen > varLen) { - TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); - items.push_back(ctx.mk_eq_atom(concat, var)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } + } else { + concat1LenFixed = false; } } + + for (unsigned int i = 0; i < concat2Args.size(); ++i) { + expr * oneArg = concat2Args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + sum2 += argLen; + if (!u.str.is_string(oneArg)) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + } else { + concat2LenFixed = false; + } + } + + items.push_back(ctx.mk_eq_atom(n1, n2)); + + bool conflict = false; + + if (concat1LenFixed && concat2LenFixed) { + if (sum1 != sum2) { + conflict = true; + } + } else if (!concat1LenFixed && concat2LenFixed) { + if (sum1 > sum2) { + conflict = true; + } + } else if (concat1LenFixed && !concat2LenFixed) { + if (sum1 < sum2) { + conflict = true; + } + } + + if (conflict) { + TRACE("str", tout << "inconsistent length detected in concat <==> concat" << std::endl;); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } return true; } -} -bool theory_str::check_length_var_var(expr * var1, expr * var2) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); + bool theory_str::check_length_concat_var(expr * concat, expr * var) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); - rational var1Len, var2Len; - bool var1Len_exists = get_len_value(var1, var1Len); - bool var2Len_exists = get_len_value(var2, var2Len); - - if (var1Len_exists && var2Len_exists && var1Len != var2Len) { - TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); - expr_ref_vector items(mgr); - items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); - items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); - items.push_back(ctx.mk_eq_atom(var1, var2)); - expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); - assert_axiom(toAssert); - return false; - } - return true; -} - -// returns true if everything is OK, or false if inconsistency detected -// - note that these are different from the semantics in Z3str2 -bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { - // n1 and n2 are not const string: either variable or concat - bool n1Concat = u.str.is_concat(to_app(n1)); - bool n2Concat = u.str.is_concat(to_app(n2)); - if (n1Concat && n2Concat) { - return check_length_concat_concat(n1, n2); - } - // n1 is concat, n2 is variable - else if (n1Concat && (!n2Concat)) { - return check_length_concat_var(n1, n2); - } - // n1 is variable, n2 is concat - else if ((!n1Concat) && n2Concat) { - return check_length_concat_var(n2, n1); - } - // n1 and n2 are both variables - else { - return check_length_var_var(n1, n2); - } - return true; -} - -// returns false if an inconsistency is detected, or true if no inconsistencies were found -// - note that these are different from the semantics of checkLengConsistency() in Z3str2 -bool theory_str::check_length_consistency(expr * n1, expr * n2) { - if (u.str.is_string(n1) && u.str.is_string(n2)) { - // consistency has already been checked in can_two_nodes_eq(). - return true; - } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { - return check_length_const_string(n2, n1); - } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { - return check_length_const_string(n1, n2); - } else { - // n1 and n2 are vars or concats - return check_length_eq_var_concat(n1, n2); - } - return true; -} - -// Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. -// (This is used for deferred consistency checking) -bool theory_str::check_concat_len_in_eqc(expr * concat) { - context & ctx = get_context(); - - bool no_assertions = true; - - expr * eqc_n = concat; - do { - if (u.str.is_concat(to_app(eqc_n))) { - rational unused; - bool status = infer_len_concat(eqc_n, unused); - if (status) { - no_assertions = false; - } - } - eqc_n = get_eqc_next(eqc_n); - } while (eqc_n != concat); - - return no_assertions; -} - -// Convert a regular expression to an e-NFA using Thompson's construction -void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { - start = next_id(); - end = next_id(); - if (u.re.is_to_re(e)) { - app * a = to_app(e); - expr * arg_str = a->get_arg(0); - zstring str; - if (u.str.is_string(arg_str, str)) { - TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); - /* - * For an n-character string, we make (n-1) intermediate states, - * labelled i_(0) through i_(n-2). - * Then we construct the following transitions: - * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final - */ - unsigned last = start; - for (int i = 0; i <= ((int)str.length()) - 2; ++i) { - unsigned i_state = next_id(); - make_transition(last, str[i], i_state); - TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); - last = i_state; - } - make_transition(last, str[(str.length() - 1)], end); - TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + rational varLen; + bool varLen_exists = get_len_value(var, varLen); + if (!varLen_exists) { + return true; } else { - TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); + rational sumLen(0); + ptr_vector args; + expr_ref_vector items(mgr); + get_nodes_in_concat(concat, args); + for (unsigned int i = 0; i < args.size(); ++i) { + expr * oneArg = args[i]; + rational argLen; + bool argLen_exists = get_len_value(oneArg, argLen); + if (argLen_exists) { + if (!u.str.is_string(oneArg) && !argLen.is_zero()) { + items.push_back(ctx.mk_eq_atom(mk_strlen(oneArg), mk_int(argLen))); + } + sumLen += argLen; + if (sumLen > varLen) { + TRACE("str", tout << "inconsistent length detected in concat <==> var" << std::endl;); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_int(varLen))); + items.push_back(ctx.mk_eq_atom(concat, var)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + } + } + return true; + } + } + + bool theory_str::check_length_var_var(expr * var1, expr * var2) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + rational var1Len, var2Len; + bool var1Len_exists = get_len_value(var1, var1Len); + bool var2Len_exists = get_len_value(var2, var2Len); + + if (var1Len_exists && var2Len_exists && var1Len != var2Len) { + TRACE("str", tout << "inconsistent length detected in var <==> var" << std::endl;); + expr_ref_vector items(mgr); + items.push_back(ctx.mk_eq_atom(mk_strlen(var1), mk_int(var1Len))); + items.push_back(ctx.mk_eq_atom(mk_strlen(var2), mk_int(var2Len))); + items.push_back(ctx.mk_eq_atom(var1, var2)); + expr_ref toAssert(mgr.mk_not(mk_and(items)), mgr); + assert_axiom(toAssert); + return false; + } + return true; + } + + // returns true if everything is OK, or false if inconsistency detected + // - note that these are different from the semantics in Z3str2 + bool theory_str::check_length_eq_var_concat(expr * n1, expr * n2) { + // n1 and n2 are not const string: either variable or concat + bool n1Concat = u.str.is_concat(to_app(n1)); + bool n2Concat = u.str.is_concat(to_app(n2)); + if (n1Concat && n2Concat) { + return check_length_concat_concat(n1, n2); + } + // n1 is concat, n2 is variable + else if (n1Concat && (!n2Concat)) { + return check_length_concat_var(n1, n2); + } + // n1 is variable, n2 is concat + else if ((!n1Concat) && n2Concat) { + return check_length_concat_var(n2, n1); + } + // n1 and n2 are both variables + else { + return check_length_var_var(n1, n2); + } + return true; + } + + // returns false if an inconsistency is detected, or true if no inconsistencies were found + // - note that these are different from the semantics of checkLengConsistency() in Z3str2 + bool theory_str::check_length_consistency(expr * n1, expr * n2) { + if (u.str.is_string(n1) && u.str.is_string(n2)) { + // consistency has already been checked in can_two_nodes_eq(). + return true; + } else if (u.str.is_string(n1) && (!u.str.is_string(n2))) { + return check_length_const_string(n2, n1); + } else if (u.str.is_string(n2) && (!u.str.is_string(n1))) { + return check_length_const_string(n1, n2); + } else { + // n1 and n2 are vars or concats + return check_length_eq_var_concat(n1, n2); + } + return true; + } + + // Modified signature: returns true if nothing was learned, or false if at least one axiom was asserted. + // (This is used for deferred consistency checking) + bool theory_str::check_concat_len_in_eqc(expr * concat) { + context & ctx = get_context(); + + bool no_assertions = true; + + expr * eqc_n = concat; + do { + if (u.str.is_concat(to_app(eqc_n))) { + rational unused; + bool status = infer_len_concat(eqc_n, unused); + if (status) { + no_assertions = false; + } + } + eqc_n = get_eqc_next(eqc_n); + } while (eqc_n != concat); + + return no_assertions; + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void nfa::convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u) { + start = next_id(); + end = next_id(); + if (u.re.is_to_re(e)) { + app * a = to_app(e); + expr * arg_str = a->get_arg(0); + zstring str; + if (u.str.is_string(arg_str, str)) { + TRACE("str", tout << "build NFA for '" << str << "'" << "\n";); + /* + * For an n-character string, we make (n-1) intermediate states, + * labelled i_(0) through i_(n-2). + * Then we construct the following transitions: + * start --str[0]--> i_(0) --str[1]--> i_(1) --...--> i_(n-2) --str[n-1]--> final + */ + unsigned last = start; + for (int i = 0; i <= ((int)str.length()) - 2; ++i) { + unsigned i_state = next_id(); + make_transition(last, str[i], i_state); + TRACE("str", tout << "string transition " << last << "--" << str[i] << "--> " << i_state << "\n";); + last = i_state; + } + make_transition(last, str[(str.length() - 1)], end); + TRACE("str", tout << "string transition " << last << "--" << str[(str.length() - 1)] << "--> " << end << "\n";); + } else { + TRACE("str", tout << "invalid string constant in Str2Reg" << std::endl;); + m_valid = false; + return; + } + } else if (u.re.is_concat(e)){ + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(end1, start2); + make_epsilon_move(end2, end); + TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_union(e)) { + app * a = to_app(e); + expr * re1 = a->get_arg(0); + expr * re2 = a->get_arg(1); + unsigned start1, end1; + convert_re(re1, start1, end1, u); + unsigned start2, end2; + convert_re(re2, start2, end2, u); + + // start --e--> start1 ; start --e--> start2 + // end1 --e--> end ; end2 --e--> end + make_epsilon_move(start, start1); + make_epsilon_move(start, start2); + make_epsilon_move(end1, end); + make_epsilon_move(end2, end); + TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_star(e)) { + app * a = to_app(e); + expr * subex = a->get_arg(0); + unsigned start_subex, end_subex; + convert_re(subex, start_subex, end_subex, u); + // start --e--> start_subex, start --e--> end + // end_subex --e--> start_subex, end_subex --e--> end + make_epsilon_move(start, start_subex); + make_epsilon_move(start, end); + make_epsilon_move(end_subex, start_subex); + make_epsilon_move(end_subex, end); + TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); + } else if (u.re.is_range(e)) { + // range('a', 'z') + // start --'a'--> end + // start --'b'--> end + // ... + // start --'z'--> end + app * a = to_app(e); + expr * c1 = a->get_arg(0); + expr * c2 = a->get_arg(1); + zstring s_c1, s_c2; + u.str.is_string(c1, s_c1); + u.str.is_string(c2, s_c2); + + unsigned int id1 = s_c1[0]; + unsigned int id2 = s_c2[0]; + if (id1 > id2) { + unsigned int tmp = id1; + id1 = id2; + id2 = tmp; + } + + for (unsigned int i = id1; i <= id2; ++i) { + char ch = (char)i; + make_transition(start, ch, end); + } + + TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); + } else { + TRACE("str", tout << "invalid regular expression" << std::endl;); m_valid = false; return; } - } else if (u.re.is_concat(e)){ - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, u); - unsigned start2, end2; - convert_re(re2, start2, end2, u); - // start --e--> start1 --...--> end1 --e--> start2 --...--> end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(end1, start2); - make_epsilon_move(end2, end); - TRACE("str", tout << "concat NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_union(e)) { - app * a = to_app(e); - expr * re1 = a->get_arg(0); - expr * re2 = a->get_arg(1); - unsigned start1, end1; - convert_re(re1, start1, end1, u); - unsigned start2, end2; - convert_re(re2, start2, end2, u); - - // start --e--> start1 ; start --e--> start2 - // end1 --e--> end ; end2 --e--> end - make_epsilon_move(start, start1); - make_epsilon_move(start, start2); - make_epsilon_move(end1, end); - make_epsilon_move(end2, end); - TRACE("str", tout << "union NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_star(e)) { - app * a = to_app(e); - expr * subex = a->get_arg(0); - unsigned start_subex, end_subex; - convert_re(subex, start_subex, end_subex, u); - // start --e--> start_subex, start --e--> end - // end_subex --e--> start_subex, end_subex --e--> end - make_epsilon_move(start, start_subex); - make_epsilon_move(start, end); - make_epsilon_move(end_subex, start_subex); - make_epsilon_move(end_subex, end); - TRACE("str", tout << "star NFA: start = " << start << ", end = " << end << std::endl;); - } else if (u.re.is_range(e)) { - // range('a', 'z') - // start --'a'--> end - // start --'b'--> end - // ... - // start --'z'--> end - app * a = to_app(e); - expr * c1 = a->get_arg(0); - expr * c2 = a->get_arg(1); - zstring s_c1, s_c2; - u.str.is_string(c1, s_c1); - u.str.is_string(c2, s_c2); - - unsigned int id1 = s_c1[0]; - unsigned int id2 = s_c2[0]; - if (id1 > id2) { - unsigned int tmp = id1; - id1 = id2; - id2 = tmp; - } - - for (unsigned int i = id1; i <= id2; ++i) { - char ch = (char)i; - make_transition(start, ch, end); - } - - TRACE("str", tout << "range NFA: start = " << start << ", end = " << end << std::endl;); - } else { - TRACE("str", tout << "invalid regular expression" << std::endl;); - m_valid = false; - return; } -} -void nfa::epsilon_closure(unsigned start, std::set & closure) { - std::deque worklist; - closure.insert(start); - worklist.push_back(start); + void nfa::epsilon_closure(unsigned start, std::set & closure) { + std::deque worklist; + closure.insert(start); + worklist.push_back(start); - while(!worklist.empty()) { - unsigned state = worklist.front(); - worklist.pop_front(); - if (epsilon_map.find(state) != epsilon_map.end()) { - for (std::set::iterator it = epsilon_map[state].begin(); - it != epsilon_map[state].end(); ++it) { - unsigned new_state = *it; - if (closure.find(new_state) == closure.end()) { - closure.insert(new_state); - worklist.push_back(new_state); + while(!worklist.empty()) { + unsigned state = worklist.front(); + worklist.pop_front(); + if (epsilon_map.find(state) != epsilon_map.end()) { + for (std::set::iterator it = epsilon_map[state].begin(); + it != epsilon_map[state].end(); ++it) { + unsigned new_state = *it; + if (closure.find(new_state) == closure.end()) { + closure.insert(new_state); + worklist.push_back(new_state); + } + } + } + } + } + + bool nfa::matches(zstring input) { + /* + * Keep a set of all states the NFA can currently be in. + * Initially this is the e-closure of m_start_state + * For each character A in the input string, + * the set of next states contains + * all states in transition_map[S][A] for each S in current_states, + * and all states in epsilon_map[S] for each S in current_states. + * After consuming the entire input string, + * the match is successful iff current_states contains m_end_state. + */ + std::set current_states; + epsilon_closure(m_start_state, current_states); + for (unsigned i = 0; i < input.length(); ++i) { + char A = (char)input[i]; + std::set next_states; + for (std::set::iterator it = current_states.begin(); + it != current_states.end(); ++it) { + unsigned S = *it; + // check transition_map + if (transition_map[S].find(A) != transition_map[S].end()) { + next_states.insert(transition_map[S][A]); + } + } + + // take e-closure over next_states to compute the actual next_states + std::set epsilon_next_states; + for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { + unsigned S = *it; + std::set closure; + epsilon_closure(S, closure); + epsilon_next_states.insert(closure.begin(), closure.end()); + } + current_states = epsilon_next_states; + } + if (current_states.find(m_end_state) != current_states.end()) { + return true; + } else { + return false; + } + } + + void theory_str::check_regex_in(expr * nn1, expr * nn2) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector eqNodeSet(m); + + expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); + expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); + expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; + + if (constStr == NULL) { + return; + } else { + expr_ref_vector::iterator itor = eqNodeSet.begin(); + for (; itor != eqNodeSet.end(); itor++) { + if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { + std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); + for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { + zstring regStr = *strItor; + zstring constStrValue; + u.str.is_string(constStr, constStrValue); + std::pair key1 = std::make_pair(*itor, regStr); + if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { + expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term + app * a_regexIn = to_app(boolVar); + expr * regexTerm = a_regexIn->get_arg(1); + + // TODO figure out regex NFA stuff + if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { + TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); + regex_nfa_cache[regexTerm] = nfa(u, regexTerm); + } else { + TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); + } + + nfa regexNFA = regex_nfa_cache[regexTerm]; + ENSURE(regexNFA.is_valid()); + bool matchRes = regexNFA.matches(constStrValue); + + TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); + + expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); + if (matchRes) { + assert_implication(implyL, boolVar); + } else { + assert_implication(implyL, m.mk_not(boolVar)); + } + } + } } } } } -} -bool nfa::matches(zstring input) { /* - * Keep a set of all states the NFA can currently be in. - * Initially this is the e-closure of m_start_state - * For each character A in the input string, - * the set of next states contains - * all states in transition_map[S][A] for each S in current_states, - * and all states in epsilon_map[S] for each S in current_states. - * After consuming the entire input string, - * the match is successful iff current_states contains m_end_state. + * strArgmt::solve_concat_eq_str() + * Solve concatenations of the form: + * const == Concat(const, X) + * const == Concat(X, const) */ - std::set current_states; - epsilon_closure(m_start_state, current_states); - for (unsigned i = 0; i < input.length(); ++i) { - char A = (char)input[i]; - std::set next_states; - for (std::set::iterator it = current_states.begin(); - it != current_states.end(); ++it) { - unsigned S = *it; - // check transition_map - if (transition_map[S].find(A) != transition_map[S].end()) { - next_states.insert(transition_map[S][A]); - } - } - - // take e-closure over next_states to compute the actual next_states - std::set epsilon_next_states; - for (std::set::iterator it = next_states.begin(); it != next_states.end(); ++it) { - unsigned S = *it; - std::set closure; - epsilon_closure(S, closure); - epsilon_next_states.insert(closure.begin(), closure.end()); - } - current_states = epsilon_next_states; - } - if (current_states.find(m_end_state) != current_states.end()) { - return true; - } else { - return false; - } -} - -void theory_str::check_regex_in(expr * nn1, expr * nn2) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector eqNodeSet(m); - - expr * constStr_1 = collect_eq_nodes(nn1, eqNodeSet); - expr * constStr_2 = collect_eq_nodes(nn2, eqNodeSet); - expr * constStr = (constStr_1 != NULL) ? constStr_1 : constStr_2; - - if (constStr == NULL) { - return; - } else { - expr_ref_vector::iterator itor = eqNodeSet.begin(); - for (; itor != eqNodeSet.end(); itor++) { - if (regex_in_var_reg_str_map.find(*itor) != regex_in_var_reg_str_map.end()) { - std::set::iterator strItor = regex_in_var_reg_str_map[*itor].begin(); - for (; strItor != regex_in_var_reg_str_map[*itor].end(); strItor++) { - zstring regStr = *strItor; - zstring constStrValue; - u.str.is_string(constStr, constStrValue); - std::pair key1 = std::make_pair(*itor, regStr); - if (regex_in_bool_map.find(key1) != regex_in_bool_map.end()) { - expr * boolVar = regex_in_bool_map[key1]; // actually the RegexIn term - app * a_regexIn = to_app(boolVar); - expr * regexTerm = a_regexIn->get_arg(1); - - // TODO figure out regex NFA stuff - if (regex_nfa_cache.find(regexTerm) == regex_nfa_cache.end()) { - TRACE("str", tout << "regex_nfa_cache: cache miss" << std::endl;); - regex_nfa_cache[regexTerm] = nfa(u, regexTerm); - } else { - TRACE("str", tout << "regex_nfa_cache: cache hit" << std::endl;); - } - - nfa regexNFA = regex_nfa_cache[regexTerm]; - ENSURE(regexNFA.is_valid()); - bool matchRes = regexNFA.matches(constStrValue); - - TRACE("str", tout << mk_pp(*itor, m) << " in " << regStr << " : " << (matchRes ? "yes" : "no") << std::endl;); - - expr_ref implyL(ctx.mk_eq_atom(*itor, constStr), m); - if (matchRes) { - assert_implication(implyL, boolVar); - } else { - assert_implication(implyL, m.mk_not(boolVar)); - } - } - } - } - } - } -} - -/* - * strArgmt::solve_concat_eq_str() - * Solve concatenations of the form: - * const == Concat(const, X) - * const == Concat(X, const) - */ -void theory_str::solve_concat_eq_str(expr * concat, expr * str) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); - - zstring const_str; - if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { - app * a_concat = to_app(concat); - SASSERT(a_concat->get_num_args() == 2); - expr * a1 = a_concat->get_arg(0); - expr * a2 = a_concat->get_arg(1); - - if (const_str.empty()) { - TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); - // assert the following axiom: - // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) - - - expr_ref premise(ctx.mk_eq_atom(concat, str), m); - expr_ref c1(ctx.mk_eq_atom(a1, str), m); - expr_ref c2(ctx.mk_eq_atom(a2, str), m); - expr_ref conclusion(m.mk_and(c1, c2), m); - assert_implication(premise, conclusion); - - return; - } - bool arg1_has_eqc_value = false; - bool arg2_has_eqc_value = false; - expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); - expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); - expr_ref newConcat(m); - if (arg1 != a1 || arg2 != a2) { - TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); - int iPos = 0; - expr_ref_vector item1(m); - if (a1 != arg1) { - item1.push_back(ctx.mk_eq_atom(a1, arg1)); - iPos += 1; - } - if (a2 != arg2) { - item1.push_back(ctx.mk_eq_atom(a2, arg2)); - iPos += 1; - } - expr_ref implyL1(mk_and(item1), m); - newConcat = mk_concat(arg1, arg2); - if (newConcat != str) { - expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); - assert_implication(implyL1, implyR1); - } - } else { - newConcat = concat; - } - if (newConcat == str) { - return; - } - if (!u.str.is_concat(to_app(newConcat))) { - return; - } - if (arg1_has_eqc_value && arg2_has_eqc_value) { - // Case 1: Concat(const, const) == const - TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); - zstring arg1_str, arg2_str; - u.str.is_string(arg1, arg1_str); - u.str.is_string(arg2, arg2_str); - - zstring result_str = arg1_str + arg2_str; - if (result_str != const_str) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg1_str << "\" + \"" << arg2_str << - "\" != \"" << const_str << "\"" << "\n";); - expr_ref equality(ctx.mk_eq_atom(concat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } - } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { - // Case 2: Concat(var, const) == const - TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); - zstring arg2_str; - u.str.is_string(arg2, arg2_str); - unsigned int resultStrLen = const_str.length(); - unsigned int arg2StrLen = arg2_str.length(); - if (resultStrLen < arg2StrLen) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg2_str << - "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - int varStrLen = resultStrLen - arg2StrLen; - zstring firstPart = const_str.extract(0, varStrLen); - zstring secondPart = const_str.extract(varStrLen, arg2StrLen); - if (arg2_str != secondPart) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: " - << "suffix of concatenation result expected \"" << secondPart << "\", " - << "actually \"" << arg2_str << "\"" - << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - expr_ref tmpStrConst(mk_string(firstPart), m); - expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); - expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); - assert_implication(premise, conclusion); - return; - } - } - } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { - // Case 3: Concat(const, var) == const - TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); - zstring arg1_str; - u.str.is_string(arg1, arg1_str); - unsigned int resultStrLen = const_str.length(); - unsigned int arg1StrLen = arg1_str.length(); - if (resultStrLen < arg1StrLen) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: \"" - << arg1_str << - "\" is longer than \"" << const_str << "\"," - << " so cannot be concatenated with anything to form it" << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - int varStrLen = resultStrLen - arg1StrLen; - zstring firstPart = const_str.extract(0, arg1StrLen); - zstring secondPart = const_str.extract(arg1StrLen, varStrLen); - if (arg1_str != firstPart) { - // Inconsistency - TRACE("str", tout << "inconsistency detected: " - << "prefix of concatenation result expected \"" << secondPart << "\", " - << "actually \"" << arg1_str << "\"" - << "\n";); - expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); - expr_ref diseq(m.mk_not(equality), m); - assert_axiom(diseq); - return; - } else { - expr_ref tmpStrConst(mk_string(secondPart), m); - expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); - expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); - assert_implication(premise, conclusion); - return; - } - } - } else { - // Case 4: Concat(var, var) == const - TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); - if (eval_concat(arg1, arg2) == NULL) { - rational arg1Len, arg2Len; - bool arg1Len_exists = get_len_value(arg1, arg1Len); - bool arg2Len_exists = get_len_value(arg2, arg2Len); - rational concatStrLen((unsigned)const_str.length()); - if (arg1Len_exists || arg2Len_exists) { - expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); - expr_ref ax_l2(m); - zstring prefixStr, suffixStr; - if (arg1Len_exists) { - if (arg1Len.is_neg()) { - TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); - assert_axiom(toAssert); - return; - } else if (arg1Len > concatStrLen) { - TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); - assert_implication(ax_l1, ax_r1); - return; - } - - prefixStr = const_str.extract(0, arg1Len.get_unsigned()); - rational concat_minus_arg1 = concatStrLen - arg1Len; - suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); - ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); - } else { - // arg2's length is available - if (arg2Len.is_neg()) { - TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); - assert_axiom(toAssert); - return; - } else if (arg2Len > concatStrLen) { - TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); - expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); - assert_implication(ax_l1, ax_r1); - return; - } - - rational concat_minus_arg2 = concatStrLen - arg2Len; - prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); - suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); - ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); - } - // consistency check - if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { - expr_ref ax_r(m.mk_not(ax_l2), m); - assert_implication(ax_l1, ax_r); - return; - } - if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { - expr_ref ax_r(m.mk_not(ax_l2), m); - assert_implication(ax_l1, ax_r); - return; - } - expr_ref_vector r_items(m); - r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); - r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); - if (!arg1Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); - } - if (!arg2Len_exists) { - r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); - } - expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); - expr_ref rhs(mk_and(r_items), m); - assert_implication(lhs, rhs); - } else { /* ! (arg1Len != 1 || arg2Len != 1) */ - expr_ref xorFlag(m); - std::pair key1(arg1, arg2); - std::pair key2(arg2, arg1); - - // check the entries in this map to make sure they're still in scope - // before we use them. - - std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); - std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); - - bool entry1InScope; - if (entry1 == varForBreakConcat.end()) { - TRACE("str", tout << "key1 no entry" << std::endl;); - entry1InScope = false; - } else { - // OVERRIDE. - entry1InScope = true; - TRACE("str", tout << "key1 entry" << std::endl;); - /* - if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { - TRACE("str", tout << "key1 entry not in scope" << std::endl;); - entry1InScope = false; - } else { - TRACE("str", tout << "key1 entry in scope" << std::endl;); - entry1InScope = true; - } - */ - } - - bool entry2InScope; - if (entry2 == varForBreakConcat.end()) { - TRACE("str", tout << "key2 no entry" << std::endl;); - entry2InScope = false; - } else { - // OVERRIDE. - entry2InScope = true; - TRACE("str", tout << "key2 entry" << std::endl;); - /* - if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { - TRACE("str", tout << "key2 entry not in scope" << std::endl;); - entry2InScope = false; - } else { - TRACE("str", tout << "key2 entry in scope" << std::endl;); - entry2InScope = true; - } - */ - } - - TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl - << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); - - if (!entry1InScope && !entry2InScope) { - xorFlag = mk_internal_xor_var(); - varForBreakConcat[key1][0] = xorFlag; - } else if (entry1InScope) { - xorFlag = varForBreakConcat[key1][0]; - } else { // entry2InScope - xorFlag = varForBreakConcat[key2][0]; - } - - int concatStrLen = const_str.length(); - int and_count = 1; - - expr_ref_vector arrangement_disjunction(m); - - for (int i = 0; i < concatStrLen + 1; ++i) { - expr_ref_vector and_items(m); - zstring prefixStr = const_str.extract(0, i); - zstring suffixStr = const_str.extract(i, concatStrLen - i); - // skip invalid options - if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { - continue; - } - if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { - continue; - } - - expr_ref prefixAst(mk_string(prefixStr), m); - expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); - and_items.push_back(arg1_eq); - and_count += 1; - - expr_ref suffixAst(mk_string(suffixStr), m); - expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); - and_items.push_back(arg2_eq); - and_count += 1; - - arrangement_disjunction.push_back(mk_and(and_items)); - } - - expr_ref implyL(ctx.mk_eq_atom(concat, str), m); - expr_ref implyR1(m); - if (arrangement_disjunction.empty()) { - // negate - expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); - expr_ref negate_ast(m.mk_not(concat_eq_str), m); - assert_axiom(negate_ast); - } else { - implyR1 = mk_or(arrangement_disjunction); - if (m_params.m_StrongArrangements) { - expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); - assert_axiom(ax_strong); - } else { - assert_implication(implyL, implyR1); - } - generate_mutual_exclusion(arrangement_disjunction); - } - } /* (arg1Len != 1 || arg2Len != 1) */ - } /* if (Concat(arg1, arg2) == NULL) */ - } - } -} - -expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE("str", tout << "activating finite model testing for overlapping concats " - << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); - std::map concatMap; - std::map unrollMap; - std::map varMap; - classify_ast_by_type(lhs, varMap, concatMap, unrollMap); - classify_ast_by_type(rhs, varMap, concatMap, unrollMap); - TRACE("str", tout << "found vars:"; - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - tout << " " << mk_pp(it->first, m); - } - tout << std::endl; - ); - - expr_ref testvar(mk_str_var("finiteModelTest"), m); - m_trail.push_back(testvar); - ptr_vector varlist; - - for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { - expr * v = it->first; - varlist.push_back(v); - } - - // make things easy for the core wrt. testvar - expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); - expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); - expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); - assert_axiom(testvaraxiom); - - finite_model_test_varlists.insert(testvar, varlist); - m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); - return t_yes; -} - -void theory_str::finite_model_test(expr * testvar, expr * str) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - zstring s; - if (!u.str.is_string(str, s)) return; - if (s == "yes") { - TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); - ptr_vector & vars = finite_model_test_varlists[testvar]; - for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { - expr * v = *it; - bool v_has_eqc = false; - get_eqc_value(v, v_has_eqc); - if (v_has_eqc) { - TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); - continue; - } - // check for any sort of existing length tester we might interfere with - if (m_params.m_UseBinarySearch) { - if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { - TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); - continue; - } else { - // start binary search as normal - expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); - expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); - assert_implication(implLhs, implRhs); - } - } else { - bool map_effectively_empty = false; - if (!fvar_len_count_map.contains(v)) { - map_effectively_empty = true; - } - - if (!map_effectively_empty) { - map_effectively_empty = true; - ptr_vector indicator_set = fvar_lenTester_map[v]; - for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { - expr * indicator = *it; - if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - map_effectively_empty = false; - break; - } - } - } - - if (map_effectively_empty) { - TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); - rational v_len; - rational v_lower_bound; - rational v_upper_bound; - expr_ref vLengthExpr(mk_strlen(v), m); - if (get_len_value(v, v_len)) { - TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); - v_lower_bound = v_len; - v_upper_bound = v_len; - } else { - bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); - bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); - TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") - << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); - - // make sure the bounds are non-negative - if (lower_bound_exists && v_lower_bound.is_neg()) { - v_lower_bound = rational::zero(); - } - if (upper_bound_exists && v_upper_bound.is_neg()) { - v_upper_bound = rational::zero(); - } - - if (lower_bound_exists && upper_bound_exists) { - // easiest case. we will search within these bounds - } else if (upper_bound_exists && !lower_bound_exists) { - // search between 0 and the upper bound - v_lower_bound == rational::zero(); - } else if (lower_bound_exists && !upper_bound_exists) { - // check some finite portion of the search space - v_upper_bound = v_lower_bound + rational(10); - } else { - // no bounds information - v_lower_bound = rational::zero(); - v_upper_bound = v_lower_bound + rational(10); - } - } - // now create a fake length tester over this finite disjunction of lengths - - fvar_len_count_map[v] = 1; - unsigned int testNum = fvar_len_count_map[v]; - - expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); - SASSERT(indicator); - m_trail.push_back(indicator); - - fvar_lenTester_map[v].shrink(0); - fvar_lenTester_map[v].push_back(indicator); - lenTester_fvar_map[indicator] = v; - - expr_ref_vector orList(m); - expr_ref_vector andList(m); - - for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { - zstring lStr = zstring(l.to_string().c_str()); - expr_ref str_indicator(mk_string(lStr), m); - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); - orList.push_back(or_expr); - expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); - andList.push_back(and_expr); - } - andList.push_back(mk_or(orList)); - expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); - expr_ref implRhs(mk_and(andList), m); - assert_implication(implLhs, implRhs); - } else { - TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); - continue; - } - } - } // foreach (v in vars) - } // (s == "yes") -} - -void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { - ast_manager & m = get_manager(); - if (lenTester_fvar_map.contains(lenTester)) { - expr * fVar = lenTester_fvar_map[lenTester]; - expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); - TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } -} - -void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { - ast_manager & m = get_manager(); - - expr * fVar = valueTester_fvar_map[valTester]; - if (m_params.m_UseBinarySearch) { - if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { - TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); - bool hasEqcValue; - expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); - if (!hasEqcValue) { - TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); - } else { - // safety check - zstring effectiveLenIndiStr; - u.str.is_string(len_indicator_value, effectiveLenIndiStr); - if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { - TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); - UNREACHABLE(); - } - expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (valueAssert != NULL) { - assert_axiom(valueAssert); - } - } - } else { - int lenTesterCount = fvar_lenTester_map[fVar].size(); - - expr * effectiveLenInd = NULL; - zstring effectiveLenIndiStr = ""; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; - bool indicatorHasEqcValue = false; - expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - if (indicatorHasEqcValue) { - zstring len_pIndiStr; - u.str.is_string(len_indicator_value, len_pIndiStr); - if (len_pIndiStr != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = len_pIndiStr; - break; - } - } - } - expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); - TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); - if (valueAssert != NULL) { - assert_axiom(valueAssert); - } - } -} - -bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { - ast_manager & m = get_manager(); - zstring nn2_str; - if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { - TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) - << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); - more_len_tests(nn1, nn2_str); - return true; - } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { - if (nn2_str == "more") { - TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) - << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); - more_value_tests(nn1, nn2_str); - } - return true; - } else if (internal_unrollTest_vars.contains(nn1)) { - return true; - } else { - return false; - } -} - -void theory_str::handle_equality(expr * lhs, expr * rhs) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - // both terms must be of sort String - sort * lhs_sort = m.get_sort(lhs); - sort * rhs_sort = m.get_sort(rhs); - sort * str_sort = u.str.mk_string_sort(); - - if (lhs_sort != str_sort || rhs_sort != str_sort) { - TRACE("str", tout << "skip equality: not String sort" << std::endl;); - return; - } - - /* // temporarily disabled, we are borrowing these testers for something else - if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { - if (finite_model_test_varlists.contains(lhs)) { - finite_model_test(lhs, rhs); return; - } else if (finite_model_test_varlists.contains(rhs)) { - finite_model_test(rhs, lhs); return; - } - } - */ - - if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { - return; - } - - if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { - bool nn1HasEqcValue = false; - bool nn2HasEqcValue = false; - expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); - expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); - if (nn1HasEqcValue && !nn2HasEqcValue) { - simplify_parent(rhs, nn1_value); - } - if (!nn1HasEqcValue && nn2HasEqcValue) { - simplify_parent(lhs, nn2_value); - } - - expr * nn1_arg0 = to_app(lhs)->get_arg(0); - expr * nn1_arg1 = to_app(lhs)->get_arg(1); - expr * nn2_arg0 = to_app(rhs)->get_arg(0); - expr * nn2_arg1 = to_app(rhs)->get_arg(1); - if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { - TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); - return; - } - - if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { - TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); - return; - } - } - - if (opt_DeferEQCConsistencyCheck) { - TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); - } else { - // newEqCheck() -- check consistency wrt. existing equivalence classes - if (!new_eq_check(lhs, rhs)) { - return; - } - } - - // BEGIN new_eq_handler() in strTheory - - { - rational nn1Len, nn2Len; - bool nn1Len_exists = get_len_value(lhs, nn1Len); - bool nn2Len_exists = get_len_value(rhs, nn2Len); - expr * emptyStr = mk_string(""); - - if (nn1Len_exists && nn1Len.is_zero()) { - if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - - if (nn2Len_exists && nn2Len.is_zero()) { - if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { - expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); - expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); - expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); - assert_axiom(toAssert); - } - } - } - - instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); - - // group terms by equivalence class (groupNodeInEqc()) - - std::set eqc_concat_lhs; - std::set eqc_var_lhs; - std::set eqc_const_lhs; - group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); - - std::set eqc_concat_rhs; - std::set eqc_var_rhs; - std::set eqc_const_rhs; - group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); - - TRACE("str", - tout << "lhs eqc:" << std::endl; - tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - - tout << "rhs eqc:" << std::endl; - tout << "Concats:" << std::endl; - for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Variables:" << std::endl; - for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - tout << "Constants:" << std::endl; - for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { - expr * ex = *it; - tout << mk_ismt2_pp(ex, get_manager()) << std::endl; - } - ); - - // step 1: Concat == Concat - int hasCommon = 0; - if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { - std::set::iterator itor1 = eqc_concat_lhs.begin(); - std::set::iterator itor2 = eqc_concat_rhs.begin(); - for (; itor1 != eqc_concat_lhs.end(); itor1++) { - if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { - hasCommon = 1; - break; - } - } - for (; itor2 != eqc_concat_rhs.end(); itor2++) { - if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { - hasCommon = 1; - break; - } - } - if (hasCommon == 0) { - if (opt_ConcatOverlapAvoid) { - bool found = false; - // check each pair and take the first ones that won't immediately overlap - for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { - expr * concat_lhs = *itor1; - for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { - expr * concat_rhs = *itor2; - if (will_result_in_overlap(concat_lhs, concat_rhs)) { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); - } else { - TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " - << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); - simplify_concat_equality(concat_lhs, concat_rhs); - found = true; - break; - } - } - } - if (!found) { - TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } else { - // default behaviour - simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); - } - } - } - - // step 2: Concat == Constant - - if (eqc_const_lhs.size() != 0) { - expr * conStr = *(eqc_const_lhs.begin()); - std::set::iterator itor2 = eqc_concat_rhs.begin(); - for (; itor2 != eqc_concat_rhs.end(); itor2++) { - solve_concat_eq_str(*itor2, conStr); - } - } else if (eqc_const_rhs.size() != 0) { - expr* conStr = *(eqc_const_rhs.begin()); - std::set::iterator itor1 = eqc_concat_lhs.begin(); - for (; itor1 != eqc_concat_lhs.end(); itor1++) { - solve_concat_eq_str(*itor1, conStr); - } - } - - // simplify parents wrt. the equivalence class of both sides - bool nn1HasEqcValue = false; - bool nn2HasEqcValue = false; - // we want the Z3str2 eqc check here... - expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); - expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); - if (nn1HasEqcValue && !nn2HasEqcValue) { - simplify_parent(rhs, nn1_value); - } - - if (!nn1HasEqcValue && nn2HasEqcValue) { - simplify_parent(lhs, nn2_value); - } - - expr * nn1EqConst = NULL; - std::set nn1EqUnrollFuncs; - get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); - expr * nn2EqConst = NULL; - std::set nn2EqUnrollFuncs; - get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); - - if (nn2EqConst != NULL) { - for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { - process_unroll_eq_const_str(*itor1, nn2EqConst); - } - } - - if (nn1EqConst != NULL) { - for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { - process_unroll_eq_const_str(*itor2, nn1EqConst); - } - } - -} - -void theory_str::set_up_axioms(expr * ex) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - sort * ex_sort = m.get_sort(ex); - sort * str_sort = u.str.mk_string_sort(); - sort * bool_sort = m.mk_bool_sort(); - - family_id m_arith_fid = m.mk_family_id("arith"); - sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); - - if (ex_sort == str_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort String" << std::endl;); - // set up basic string axioms - enode * n = ctx.get_enode(ex); - SASSERT(n); - m_basicstr_axiom_todo.push_back(n); - TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); - - - if (is_app(ex)) { - app * ap = to_app(ex); - if (u.str.is_concat(ap)) { - // if ex is a concat, set up concat axioms later - m_concat_axiom_todo.push_back(n); - // we also want to check whether we can eval this concat, - // in case the rewriter did not totally finish with this term - m_concat_eval_todo.push_back(n); - } else if (u.str.is_length(ap)) { - // if the argument is a variable, - // keep track of this for later, we'll need it during model gen - expr * var = ap->get_arg(0); - app * aVar = to_app(var); - if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { - input_var_in_len.insert(var); - } - } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { - m_library_aware_axiom_todo.push_back(n); - } else if (u.str.is_itos(ap)) { - TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); - string_int_conversion_terms.push_back(ap); - m_library_aware_axiom_todo.push_back(n); - } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { - // if ex is a variable, add it to our list of variables - TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); - variable_set.insert(ex); - ctx.mark_as_relevant(ex); - // this might help?? - theory_var v = mk_var(n); - TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); - } - } - } else if (ex_sort == bool_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort Bool" << std::endl;); - // set up axioms for boolean terms - - ensure_enode(ex); - if (ctx.e_internalized(ex)) { - enode * n = ctx.get_enode(ex); - SASSERT(n); - - if (is_app(ex)) { - app * ap = to_app(ex); - if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { - m_library_aware_axiom_todo.push_back(n); - } - } - } else { - TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); - ENSURE(!search_started); // infinite loop prevention - m_delayed_axiom_setup_terms.push_back(ex); - return; - } - } else if (ex_sort == int_sort) { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of sort Int" << std::endl;); - // set up axioms for integer terms - enode * n = ensure_enode(ex); - SASSERT(n); - - if (is_app(ex)) { - app * ap = to_app(ex); - // TODO indexof2/lastindexof - if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { - m_library_aware_axiom_todo.push_back(n); - } else if (u.str.is_stoi(ap)) { - TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); - string_int_conversion_terms.push_back(ap); - m_library_aware_axiom_todo.push_back(n); - } - } - } else { - TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << - ": expr is of wrong sort, ignoring" << std::endl;); - } - - // if expr is an application, recursively inspect all arguments - if (is_app(ex)) { - app * term = (app*)ex; - unsigned num_args = term->get_num_args(); - for (unsigned i = 0; i < num_args; i++) { - set_up_axioms(term->get_arg(i)); - } - } -} - -void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { - TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); - symbol strOverlap("!!TheoryStrOverlapAssumption!!"); - seq_util m_sequtil(get_manager()); - sort * s = get_manager().mk_bool_sort(); - m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); - assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); -} - -lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { - bool assumptionFound = false; - - app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); - get_context().internalize(target_term, false); - for (unsigned i = 0; i < unsat_core.size(); ++i) { - app * core_term = to_app(unsat_core.get(i)); - // not sure if this is the correct way to compare terms in this context - enode * e1; - enode * e2; - e1 = get_context().get_enode(target_term); - e2 = get_context().get_enode(core_term); - if (e1 == e2) { - TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); - assumptionFound = true; - return l_undef; - } - } - - return l_false; -} - -void theory_str::init_search_eh() { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", - tout << "dumping all asserted formulas:" << std::endl; - unsigned nFormulas = ctx.get_num_asserted_formulas(); - for (unsigned i = 0; i < nFormulas; ++i) { - expr * ex = ctx.get_asserted_formula(i); - tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; - } - ); - /* - * Recursive descent through all asserted formulas to set up axioms. - * Note that this is just the input structure and not necessarily things - * that we know to be true or false. We're just doing this to see - * which terms are explicitly mentioned. - */ - unsigned nFormulas = ctx.get_num_asserted_formulas(); - for (unsigned i = 0; i < nFormulas; ++i) { - expr * ex = ctx.get_asserted_formula(i); - set_up_axioms(ex); - } - - /* - * Similar recursive descent, except over all initially assigned terms. - * This is done to find equalities between terms, etc. that we otherwise - * might not get a chance to see. - */ - - /* - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - if (m.is_eq(ex)) { - TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << - ": expr is equality" << std::endl;); - app * eq = (app*)ex; - SASSERT(eq->get_num_args() == 2); - expr * lhs = eq->get_arg(0); - expr * rhs = eq->get_arg(1); - - enode * e_lhs = ctx.get_enode(lhs); - enode * e_rhs = ctx.get_enode(rhs); - std::pair eq_pair(e_lhs, e_rhs); - m_str_eq_todo.push_back(eq_pair); - } else { - TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) - << ": expr ignored" << std::endl;); - } - } - */ - - // this might be cheating but we need to make sure that certain maps are populated - // before the first call to new_eq_eh() - propagate(); - - TRACE("str", tout << "search started" << std::endl;); - search_started = true; -} - -void theory_str::new_eq_eh(theory_var x, theory_var y) { - //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); - TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << - mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); - - /* - if (m_find.find(x) == m_find.find(y)) { - return; - } - */ - handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); - - // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality - m_find.merge(x, y); -} - -void theory_str::new_diseq_eh(theory_var x, theory_var y) { - //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); - TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << - mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); -} - -void theory_str::relevant_eh(app * n) { - TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); -} - -void theory_str::assign_eh(bool_var v, bool is_true) { - context & ctx = get_context(); - TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); -} - -void theory_str::push_scope_eh() { - theory::push_scope_eh(); - m_trail_stack.push_scope(); - - sLevel += 1; - TRACE("str", tout << "push to " << sLevel << std::endl;); - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); -} - -void theory_str::recursive_check_variable_scope(expr * ex) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - if (is_app(ex)) { - app * a = to_app(ex); - if (a->get_num_args() == 0) { - // we only care about string variables - sort * s = m.get_sort(ex); - sort * string_sort = u.str.mk_string_sort(); - if (s != string_sort) { - return; - } - // base case: string constant / var - if (u.str.is_string(a)) { - return; - } else { - // assume var - if (variable_set.find(ex) == variable_set.end() - && internal_variable_set.find(ex) == internal_variable_set.end()) { - TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); - } - } - } else { - for (unsigned i = 0; i < a->get_num_args(); ++i) { - recursive_check_variable_scope(a->get_arg(i)); - } - } - } -} - -void theory_str::check_variable_scope() { - if (!opt_CheckVariableScope) { - return; - } - - if (!is_trace_enabled("t_str_detail")) { - return; - } - - TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - recursive_check_variable_scope(ex); - } -} - -void theory_str::pop_scope_eh(unsigned num_scopes) { - sLevel -= num_scopes; - TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); - context & ctx = get_context(); - ast_manager & m = get_manager(); - - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); - - // list of expr* to remove from cut_var_map - ptr_vector cutvarmap_removes; - - obj_map >::iterator varItor = cut_var_map.begin(); - while (varItor != cut_var_map.end()) { - expr * e = varItor->m_key; - std::stack & val = cut_var_map[varItor->m_key]; - while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { - TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); - T_cut * aCut = val.top(); - val.pop(); - // dealloc(aCut); - } - if (val.size() == 0) { - cutvarmap_removes.insert(varItor->m_key); - } - varItor++; - } - - if (!cutvarmap_removes.empty()) { - ptr_vector::iterator it = cutvarmap_removes.begin(); - for (; it != cutvarmap_removes.end(); ++it) { - expr * ex = *it; - cut_var_map.remove(ex); - } - } - - ptr_vector new_m_basicstr; - for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { - enode * e = *it; - app * a = e->get_owner(); - TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) - << ", enode scope level is " << e->get_iscope_lvl() - << std::endl;); - if (e->get_iscope_lvl() <= (unsigned)sLevel) { - new_m_basicstr.push_back(e); - } - } - m_basicstr_axiom_todo.reset(); - m_basicstr_axiom_todo = new_m_basicstr; - - m_trail_stack.pop_scope(num_scopes); - theory::pop_scope_eh(num_scopes); - - //check_variable_scope(); -} - -void theory_str::dump_assignments() { - TRACE_CODE( + void theory_str::solve_concat_eq_str(expr * concat, expr * str) { ast_manager & m = get_manager(); context & ctx = get_context(); - tout << "dumping all assignments:" << std::endl; - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { - expr * ex = *i; - tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; - } - ); -} -void theory_str::classify_ast_by_type(expr * node, std::map & varMap, - std::map & concatMap, std::map & unrollMap) { + TRACE("str", tout << mk_ismt2_pp(concat, m) << " == " << mk_ismt2_pp(str, m) << std::endl;); - // check whether the node is a string variable; - // testing set membership here bypasses several expensive checks. - // note that internal variables don't count if they're only length tester / value tester vars. - if (variable_set.find(node) != variable_set.end() - && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() - && internal_valTest_vars.find(node) == internal_valTest_vars.end() - && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { - if (varMap[node] != 1) { - TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); - } - varMap[node] = 1; - } - // check whether the node is a function that we want to inspect - else if (is_app(node)) { - app * aNode = to_app(node); - if (u.str.is_length(aNode)) { - // Length - return; - } else if (u.str.is_concat(aNode)) { - expr * arg0 = aNode->get_arg(0); - expr * arg1 = aNode->get_arg(1); - bool arg0HasEq = false; - bool arg1HasEq = false; - expr * arg0Val = get_eqc_value(arg0, arg0HasEq); - expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + zstring const_str; + if (u.str.is_concat(to_app(concat)) && u.str.is_string(to_app(str), const_str)) { + app * a_concat = to_app(concat); + SASSERT(a_concat->get_num_args() == 2); + expr * a1 = a_concat->get_arg(0); + expr * a2 = a_concat->get_arg(1); - int canskip = 0; - zstring tmp; - u.str.is_string(arg0Val, tmp); - if (arg0HasEq && tmp.empty()) { - canskip = 1; - } - u.str.is_string(arg1Val, tmp); - if (canskip == 0 && arg1HasEq && tmp.empty()) { - canskip = 1; - } - if (canskip == 0 && concatMap.find(node) == concatMap.end()) { - concatMap[node] = 1; - } - } else if (u.re.is_unroll(aNode)) { - // Unroll - if (unrollMap.find(node) == unrollMap.end()) { - unrollMap[node] = 1; - } - } - // recursively visit all arguments - for (unsigned i = 0; i < aNode->get_num_args(); ++i) { - expr * arg = aNode->get_arg(i); - classify_ast_by_type(arg, varMap, concatMap, unrollMap); - } - } -} + if (const_str.empty()) { + TRACE("str", tout << "quick path: concat == \"\"" << std::endl;); + // assert the following axiom: + // ( (Concat a1 a2) == "" ) -> ( (a1 == "") AND (a2 == "") ) -// NOTE: this function used to take an argument `Z3_ast node`; -// it was not used and so was removed from the signature -void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, - std::map & concatMap, std::map & unrollMap) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); + expr_ref premise(ctx.mk_eq_atom(concat, str), m); + expr_ref c1(ctx.mk_eq_atom(a1, str), m); + expr_ref c2(ctx.mk_eq_atom(a2, str), m); + expr_ref conclusion(m.mk_and(c1, c2), m); + assert_implication(premise, conclusion); - for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { - expr * argAst = *it; - // the original code jumped through some hoops to check whether the AST node - // is a function, then checked whether that function is "interesting". - // however, the only thing that's considered "interesting" is an equality predicate. - // so we bypass a huge amount of work by doing the following... - - if (m.is_eq(argAst)) { - TRACE("str", tout - << "eq ast " << mk_pp(argAst, m) << " is between args of sort " - << m.get_sort(to_app(argAst)->get_arg(0))->get_name() - << std::endl;); - classify_ast_by_type(argAst, varMap, concatMap, unrollMap); - } - } -} - -inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { - if (aliasIndexMap.find(node) != aliasIndexMap.end()) - return aliasIndexMap[node]; - else - return node; -} - -inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { - app * aNode = to_app(node); - if (!u.str.is_concat(aNode)) { - return node; - } else { - expr * concatArgL = aNode->get_arg(0); - return getMostLeftNodeInConcat(concatArgL); - } -} - -inline expr * theory_str::getMostRightNodeInConcat(expr * node) { - app * aNode = to_app(node); - if (!u.str.is_concat(aNode)) { - return node; - } else { - expr * concatArgR = aNode->get_arg(1); - return getMostRightNodeInConcat(concatArgR); - } -} - -void theory_str::trace_ctx_dep(std::ofstream & tout, - std::map & aliasIndexMap, - std::map & var_eq_constStr_map, - std::map > & var_eq_concat_map, - std::map > & var_eq_unroll_map, - std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map, - std::map > & unrollGroupMap) { -#ifdef _TRACE - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - { - tout << "(0) alias: variables" << std::endl; - std::map > aliasSumMap; - std::map::iterator itor0 = aliasIndexMap.begin(); - for (; itor0 != aliasIndexMap.end(); itor0++) { - aliasSumMap[itor0->second][itor0->first] = 1; - } - std::map >::iterator keyItor = aliasSumMap.begin(); - for (; keyItor != aliasSumMap.end(); keyItor++) { - tout << " * "; - tout << mk_pp(keyItor->first, mgr); - tout << " : "; - std::map::iterator innerItor = keyItor->second.begin(); - for (; innerItor != keyItor->second.end(); innerItor++) { - tout << mk_pp(innerItor->first, mgr); - tout << ", "; + return; } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(1) var = constStr:" << std::endl; - std::map::iterator itor1 = var_eq_constStr_map.begin(); - for (; itor1 != var_eq_constStr_map.end(); itor1++) { - tout << " * "; - tout << mk_pp(itor1->first, mgr); - tout << " = "; - tout << mk_pp(itor1->second, mgr); - if (!in_same_eqc(itor1->first, itor1->second)) { - tout << " (not true in ctx)"; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(2) var = concat:" << std::endl; - std::map >::iterator itor2 = var_eq_concat_map.begin(); - for (; itor2 != var_eq_concat_map.end(); itor2++) { - tout << " * "; - tout << mk_pp(itor2->first, mgr); - tout << " = { "; - std::map::iterator i_itor = itor2->second.begin(); - for (; i_itor != itor2->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr); - tout << ", "; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(3) var = unrollFunc:" << std::endl; - std::map >::iterator itor2 = var_eq_unroll_map.begin(); - for (; itor2 != var_eq_unroll_map.end(); itor2++) { - tout << " * " << mk_pp(itor2->first, mgr) << " = { "; - std::map::iterator i_itor = itor2->second.begin(); - for (; i_itor != itor2->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr) << ", "; - } - tout << " }" << std::endl; - } - tout << std::endl; - } - - { - tout << "(4) concat = constStr:" << std::endl; - std::map::iterator itor3 = concat_eq_constStr_map.begin(); - for (; itor3 != concat_eq_constStr_map.end(); itor3++) { - tout << " * "; - tout << mk_pp(itor3->first, mgr); - tout << " = "; - tout << mk_pp(itor3->second, mgr); - tout << std::endl; - - } - tout << std::endl; - } - - { - tout << "(5) eq concats:" << std::endl; - std::map >::iterator itor4 = concat_eq_concat_map.begin(); - for (; itor4 != concat_eq_concat_map.end(); itor4++) { - if (itor4->second.size() > 1) { - std::map::iterator i_itor = itor4->second.begin(); - tout << " * "; - for (; i_itor != itor4->second.end(); i_itor++) { - tout << mk_pp(i_itor->first, mgr); - tout << " , "; + bool arg1_has_eqc_value = false; + bool arg2_has_eqc_value = false; + expr * arg1 = get_eqc_value(a1, arg1_has_eqc_value); + expr * arg2 = get_eqc_value(a2, arg2_has_eqc_value); + expr_ref newConcat(m); + if (arg1 != a1 || arg2 != a2) { + TRACE("str", tout << "resolved concat argument(s) to eqc string constants" << std::endl;); + int iPos = 0; + expr_ref_vector item1(m); + if (a1 != arg1) { + item1.push_back(ctx.mk_eq_atom(a1, arg1)); + iPos += 1; } - tout << std::endl; - } - } - tout << std::endl; - } - - { - tout << "(6) eq unrolls:" << std::endl; - std::map >::iterator itor5 = unrollGroupMap.begin(); - for (; itor5 != unrollGroupMap.end(); itor5++) { - tout << " * "; - std::set::iterator i_itor = itor5->second.begin(); - for (; i_itor != itor5->second.end(); i_itor++) { - tout << mk_pp(*i_itor, mgr) << ", "; - } - tout << std::endl; - } - tout << std::endl; - } - - { - tout << "(7) unroll = concats:" << std::endl; - std::map >::iterator itor5 = unrollGroupMap.begin(); - for (; itor5 != unrollGroupMap.end(); itor5++) { - tout << " * "; - expr * unroll = itor5->first; - tout << mk_pp(unroll, mgr) << std::endl; - enode * e_curr = ctx.get_enode(unroll); - enode * e_curr_end = e_curr; - do { - app * curr = e_curr->get_owner(); - if (u.str.is_concat(curr)) { - tout << " >>> " << mk_pp(curr, mgr) << std::endl; + if (a2 != arg2) { + item1.push_back(ctx.mk_eq_atom(a2, arg2)); + iPos += 1; } - e_curr = e_curr->get_next(); - } while (e_curr != e_curr_end); - tout << std::endl; - } - tout << std::endl; - } -#else - return; -#endif // _TRACE -} - - -/* - * Dependence analysis from current context assignment - * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. - * But it's possible that it's bounded by unrolls - * For the case of - * (1) var1 = unroll(r1, t1) - * var1 is in the freeVarMap - * > should unroll r1 for var1 - * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) - * var2, var3 are all in freeVar - * > should split the unroll function so that var2 and var3 are bounded by new unrolls - */ -int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { - std::map concatMap; - std::map unrollMap; - std::map aliasIndexMap; - std::map var_eq_constStr_map; - std::map concat_eq_constStr_map; - std::map > var_eq_unroll_map; - std::map > concat_eq_concat_map; - std::map > depMap; - - context & ctx = get_context(); - ast_manager & m = get_manager(); - - // note that the old API concatenated these assignments into - // a massive conjunction; we may have the opportunity to avoid that here - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - - // Step 1: get variables / concat AST appearing in the context - // the thing we iterate over should just be variable_set - internal_variable_set - // so we avoid computing the set difference (but this might be slower) - for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { - expr* var = *it; - if (internal_variable_set.find(var) == internal_variable_set.end()) { - TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); - strVarMap[*it] = 1; - } - } - classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); - - std::map aliasUnrollSet; - std::map::iterator unrollItor = unrollMap.begin(); - for (; unrollItor != unrollMap.end(); ++unrollItor) { - if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { - continue; - } - expr * aRoot = NULL; - enode * e_currEqc = ctx.get_enode(unrollItor->first); - enode * e_curr = e_currEqc; - do { - app * curr = e_currEqc->get_owner(); - if (u.re.is_unroll(curr)) { - if (aRoot == NULL) { - aRoot = curr; - } - aliasUnrollSet[curr] = aRoot; - } - e_currEqc = e_currEqc->get_next(); - } while (e_currEqc != e_curr); - } - - for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { - expr * unrFunc = unrollItor->first; - expr * urKey = aliasUnrollSet[unrFunc]; - unrollGroupMap[urKey].insert(unrFunc); - } - - // Step 2: collect alias relation - // e.g. suppose we have the equivalence class {x, y, z}; - // then we set aliasIndexMap[y] = x - // and aliasIndexMap[z] = x - - std::map::iterator varItor = strVarMap.begin(); - for (; varItor != strVarMap.end(); ++varItor) { - if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { - continue; - } - expr * aRoot = NULL; - expr * curr = varItor->first; - do { - if (variable_set.find(curr) != variable_set.end()) { - if (aRoot == NULL) { - aRoot = curr; - } else { - aliasIndexMap[curr] = aRoot; - } - } - curr = get_eqc_next(curr); - } while (curr != varItor->first); - } - - // Step 3: Collect interested cases - - varItor = strVarMap.begin(); - for (; varItor != strVarMap.end(); ++varItor) { - expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); - // Case 1: variable = string constant - // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" - - if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { - bool nodeHasEqcValue = false; - expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); - if (nodeHasEqcValue) { - var_eq_constStr_map[deAliasNode] = nodeValue; - } - } - - // Case 2: var_eq_concat - // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 - // var_eq_unroll - // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 - - if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { - expr * curr = get_eqc_next(deAliasNode); - while (curr != deAliasNode) { - app * aCurr = to_app(curr); - // collect concat - if (u.str.is_concat(aCurr)) { - expr * arg0 = aCurr->get_arg(0); - expr * arg1 = aCurr->get_arg(1); - bool arg0HasEqcValue = false; - bool arg1HasEqcValue = false; - expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); - expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); - - bool is_arg0_emptyStr = false; - if (arg0HasEqcValue) { - zstring strval; - u.str.is_string(arg0_value, strval); - if (strval.empty()) { - is_arg0_emptyStr = true; - } - } - - bool is_arg1_emptyStr = false; - if (arg1HasEqcValue) { - zstring strval; - u.str.is_string(arg1_value, strval); - if (strval.empty()) { - is_arg1_emptyStr = true; - } - } - - if (!is_arg0_emptyStr && !is_arg1_emptyStr) { - var_eq_concat_map[deAliasNode][curr] = 1; - } - } else if (u.re.is_unroll(to_app(curr))) { - var_eq_unroll_map[deAliasNode][curr] = 1; - } - - curr = get_eqc_next(curr); - } - } - - } // for(varItor in strVarMap) - - // -------------------------------------------------- - // * collect aliasing relation among eq concats - // e.g EQC={concat1, concat2, concat3} - // concats_eq_Index_map[concat2] = concat1 - // concats_eq_Index_map[concat3] = concat1 - // -------------------------------------------------- - - std::map concats_eq_index_map; - std::map::iterator concatItor = concatMap.begin(); - for(; concatItor != concatMap.end(); ++concatItor) { - if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { - continue; - } - expr * aRoot = NULL; - expr * curr = concatItor->first; - do { - if (u.str.is_concat(to_app(curr))) { - if (aRoot == NULL) { - aRoot = curr; - } else { - concats_eq_index_map[curr] = aRoot; - } - } - curr = get_eqc_next(curr); - } while (curr != concatItor->first); - } - - concatItor = concatMap.begin(); - for(; concatItor != concatMap.end(); ++concatItor) { - expr * deAliasConcat = NULL; - if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { - deAliasConcat = concats_eq_index_map[concatItor->first]; - } else { - deAliasConcat = concatItor->first; - } - - // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" - if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { - bool nodeHasEqcValue = false; - expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); - if (nodeHasEqcValue) { - concat_eq_constStr_map[deAliasConcat] = nodeValue; - } - } - - // (4) concat_eq_concat, e.g. - // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) - if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { - expr * curr = deAliasConcat; - do { - if (u.str.is_concat(to_app(curr))) { - // curr cannot be reduced - if (concatMap.find(curr) != concatMap.end()) { - concat_eq_concat_map[deAliasConcat][curr] = 1; - } - } - curr = get_eqc_next(curr); - } while (curr != deAliasConcat); - } - } - - // print some debugging info - TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map, - var_eq_concat_map, var_eq_unroll_map, - concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap);); - - if (!contain_pair_bool_map.empty()) { - compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); - } - - // step 4: dependence analysis - - // (1) var = string constant - for (std::map::iterator itor = var_eq_constStr_map.begin(); - itor != var_eq_constStr_map.end(); ++itor) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - expr * strAst = itor->second; - depMap[var][strAst] = 1; - } - - // (2) var = concat - for (std::map >::iterator itor = var_eq_concat_map.begin(); - itor != var_eq_concat_map.end(); ++itor) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { - expr * concat = itor1->first; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { - expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); - if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { - depMap[var][varInConcat] = 2; - } - } - } - } - - for (std::map >::iterator itor = var_eq_unroll_map.begin(); - itor != var_eq_unroll_map.end(); itor++) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - expr * unrollFunc = itor1->first; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { - expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); - - TRACE("str", tout << "var in unroll = " << - mk_ismt2_pp(itor2->first, m) << std::endl - << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); - - // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), - // while $$_regVar_0 = "abcd" - // have to exclude such cases - bool varHasValue = false; - get_eqc_value(varInFunc, varHasValue); - if (varHasValue) - continue; - - if (depMap[var].find(varInFunc) == depMap[var].end()) { - depMap[var][varInFunc] = 6; - } - } - } - } - - // (3) concat = string constant - for (std::map::iterator itor = concat_eq_constStr_map.begin(); - itor != concat_eq_constStr_map.end(); itor++) { - expr * concatAst = itor->first; - expr * constStr = itor->second; - std::map inVarMap; - std::map inConcatMap; - std::map inUnrollMap; - classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); - for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { - expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); - if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) - depMap[varInConcat][constStr] = 3; - } - } - - // (4) equivalent concats - // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) - // ==> v2, v5 are constrained by "str" - // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) - // ==> v2, v4 are constrained by "str" - //-------------------------------------------------------------- - - std::map mostLeftNodes; - std::map mostRightNodes; - - std::map mLIdxMap; - std::map > mLMap; - std::map mRIdxMap; - std::map > mRMap; - std::set nSet; - - for (std::map >::iterator itor = concat_eq_concat_map.begin(); - itor != concat_eq_concat_map.end(); itor++) { - mostLeftNodes.clear(); - mostRightNodes.clear(); - - expr * mLConst = NULL; - expr * mRConst = NULL; - - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - expr * concatNode = itor1->first; - expr * mLNode = getMostLeftNodeInConcat(concatNode); - zstring strval; - if (u.str.is_string(to_app(mLNode), strval)) { - if (mLConst == NULL && strval.empty()) { - mLConst = mLNode; - } - } else { - mostLeftNodes[mLNode] = concatNode; - } - - expr * mRNode = getMostRightNodeInConcat(concatNode); - if (u.str.is_string(to_app(mRNode), strval)) { - if (mRConst == NULL && strval.empty()) { - mRConst = mRNode; - } - } else { - mostRightNodes[mRNode] = concatNode; - } - } - - if (mLConst != NULL) { - // ------------------------------------------------------------------------------------- - // The left most variable in a concat is constrained by a constant string in eqc concat - // ------------------------------------------------------------------------------------- - // e.g. Concat(x, ...) = Concat("abc", ...) - // ------------------------------------------------------------------------------------- - for (std::map::iterator itor1 = mostLeftNodes.begin(); - itor1 != mostLeftNodes.end(); itor1++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); - if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { - depMap[deVar][mLConst] = 4; - } - } - } - - { - // ------------------------------------------------------------------------------------- - // The left most variables in eqc concats are constrained by each other - // ------------------------------------------------------------------------------------- - // e.g. concat(x, ...) = concat(u, ...) = ... - // x and u are constrained by each other - // ------------------------------------------------------------------------------------- - nSet.clear(); - std::map::iterator itl = mostLeftNodes.begin(); - for (; itl != mostLeftNodes.end(); itl++) { - bool lfHasEqcValue = false; - get_eqc_value(itl->first, lfHasEqcValue); - if (lfHasEqcValue) - continue; - expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); - nSet.insert(deVar); - } - - if (nSet.size() > 1) { - int lId = -1; - for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { - lId = mLIdxMap[*itor2]; - break; - } - } - if (lId == -1) - lId = mLMap.size(); - for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - bool itorHasEqcValue = false; - get_eqc_value(*itor2, itorHasEqcValue); - if (itorHasEqcValue) - continue; - mLIdxMap[*itor2] = lId; - mLMap[lId].insert(*itor2); - } - } - } - - if (mRConst != NULL) { - for (std::map::iterator itor1 = mostRightNodes.begin(); - itor1 != mostRightNodes.end(); itor1++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); - if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { - depMap[deVar][mRConst] = 5; - } - } - } - - { - nSet.clear(); - std::map::iterator itr = mostRightNodes.begin(); - for (; itr != mostRightNodes.end(); itr++) { - expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); - nSet.insert(deVar); - } - if (nSet.size() > 1) { - int rId = -1; - std::set::iterator itor2 = nSet.begin(); - for (; itor2 != nSet.end(); itor2++) { - if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { - rId = mRIdxMap[*itor2]; - break; - } - } - if (rId == -1) - rId = mRMap.size(); - for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { - bool rHasEqcValue = false; - get_eqc_value(*itor2, rHasEqcValue); - if (rHasEqcValue) - continue; - mRIdxMap[*itor2] = rId; - mRMap[rId].insert(*itor2); - } - } - } - } - - // print the dependence map - TRACE("str", - tout << "Dependence Map" << std::endl; - for(std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { - tout << mk_pp(itor->first, m); - rational nnLen; - bool nnLen_exists = get_len_value(itor->first, nnLen); - tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; - } - tout << std::endl; - } - ); - - // step, errr, 5: compute free variables based on the dependence map - - // the case dependence map is empty, every var in VarMap is free - //--------------------------------------------------------------- - // remove L/R most var in eq concat since they are constrained with each other - std::map > lrConstrainedMap; - for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { - for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { - std::set::iterator it2 = it1; - it2++; - for (; it2 != itor->second.end(); it2++) { - expr * n1 = *it1; - expr * n2 = *it2; - lrConstrainedMap[n1][n2] = 1; - lrConstrainedMap[n2][n1] = 1; - } - } - } - for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { - for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { - std::set::iterator it2 = it1; - it2++; - for (; it2 != itor->second.end(); it2++) { - expr * n1 = *it1; - expr * n2 = *it2; - lrConstrainedMap[n1][n2] = 1; - lrConstrainedMap[n2][n1] = 1; - } - } - } - - if (depMap.size() == 0) { - std::map::iterator itor = strVarMap.begin(); - for (; itor != strVarMap.end(); itor++) { - expr * var = get_alias_index_ast(aliasIndexMap, itor->first); - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } else { - // if the keys in aliasIndexMap are not contained in keys in depMap, they are free - // e.g., x= y /\ x = z /\ t = "abc" - // aliasIndexMap[y]= x, aliasIndexMap[z] = x - // depMap t ~ "abc"(1) - // x should be free - std::map::iterator itor2 = strVarMap.begin(); - for (; itor2 != strVarMap.end(); itor2++) { - if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { - expr * var = aliasIndexMap[itor2->first]; - if (depMap.find(var) == depMap.end()) { - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { - // if a variable is not in aliasIndexMap and not in depMap, it's free - if (depMap.find(itor2->first) == depMap.end()) { - expr * var = itor2->first; - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - } - } - } - - std::map >::iterator itor = depMap.begin(); - for (; itor != depMap.end(); itor++) { - for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { - if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var - expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); - // if a var is dep on itself and all dependence are type 2, it's a free variable - // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free - { - if (depMap.find(var) == depMap.end()) { - if (freeVarMap.find(var) == freeVarMap.end()) { - if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { - freeVarMap[var] = 1; - } else { - int lrConstainted = 0; - std::map::iterator lrit = freeVarMap.begin(); - for (; lrit != freeVarMap.end(); lrit++) { - if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { - lrConstainted = 1; - break; - } - } - if (lrConstainted == 0) { - freeVarMap[var] = 1; - } - } - - } else { - freeVarMap[var] = freeVarMap[var] + 1; - } - } - } - } - } - } - } - - return 0; -} - -// Check agreement between integer and string theories for the term a = (str.to-int S). -// Returns true if axioms were added, and false otherwise. -bool theory_str::finalcheck_str2int(app * a) { - bool axiomAdd = false; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * S = a->get_arg(0); - - // check integer theory - rational Ival; - bool Ival_exists = get_value(a, Ival); - if (Ival_exists) { - TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); - // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" - if (!Ival.is_minus_one()) { - zstring Ival_str(Ival.to_string().c_str()); - expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); - expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); - expr_ref axiom(rewrite_implication(premise, conclusion), m); - if (!string_int_axioms.contains(axiom)) { - string_int_axioms.insert(axiom); - assert_axiom(axiom); - m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); - axiomAdd = true; - } - } - } else { - TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - - return axiomAdd; -} - -bool theory_str::finalcheck_int2str(app * a) { - bool axiomAdd = false; - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr * N = a->get_arg(0); - - // check string theory - bool Sval_expr_exists; - expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); - if (Sval_expr_exists) { - zstring Sval; - u.str.is_string(Sval_expr, Sval); - TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); - // empty string --> integer value < 0 - if (Sval.empty()) { - // ignore this. we should already assert the axiom for what happens when the string is "" - } else { - // nonempty string --> convert to correct integer value, or disallow it - rational convertedRepresentation(0); - rational ten(10); - bool conversionOK = true; - for (unsigned i = 0; i < Sval.length(); ++i) { - char digit = (int)Sval[i]; - if (isdigit((int)digit)) { - std::string sDigit(1, digit); - int val = atoi(sDigit.c_str()); - convertedRepresentation = (ten * convertedRepresentation) + rational(val); - } else { - // not a digit, invalid - TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); - conversionOK = false; - break; - } - } - if (conversionOK) { - expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); - expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); - expr_ref axiom(rewrite_implication(premise, conclusion), m); - if (!string_int_axioms.contains(axiom)) { - string_int_axioms.insert(axiom); - assert_axiom(axiom); - m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); - axiomAdd = true; + expr_ref implyL1(mk_and(item1), m); + newConcat = mk_concat(arg1, arg2); + if (newConcat != str) { + expr_ref implyR1(ctx.mk_eq_atom(concat, newConcat), m); + assert_implication(implyL1, implyR1); } } else { - expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); - // always assert this axiom because this is a conflict clause - assert_axiom(axiom); - axiomAdd = true; + newConcat = concat; } - } - } else { - TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); - NOT_IMPLEMENTED_YET(); - } - return axiomAdd; -} - -void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { - if (variable_set.find(node) != variable_set.end()) { - if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { - varSet.insert(node); - } - } - else if (is_app(node)) { - app * aNode = to_app(node); - if (u.str.is_length(aNode)) { - // Length - return; - } - if (u.str.is_concat(aNode)) { - expr * arg0 = aNode->get_arg(0); - expr * arg1 = aNode->get_arg(1); - if (concatSet.find(node) == concatSet.end()) { - concatSet.insert(node); + if (newConcat == str) { + return; } - } - // recursively visit all arguments - for (unsigned i = 0; i < aNode->get_num_args(); ++i) { - expr * arg = aNode->get_arg(i); - collect_var_concat(arg, varSet, concatSet); - } - } -} - -bool theory_str::propagate_length_within_eqc(expr * var) { - bool res = false; - ast_manager & m = get_manager(); - context & ctx = get_context(); - - TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); - - enode * n_eq_enode = ctx.get_enode(var); - rational varLen; - if (! get_len_value(var, varLen)) { - bool hasLen = false; - expr * nodeWithLen= var; - do { - if (get_len_value(nodeWithLen, varLen)) { - hasLen = true; - break; + if (!u.str.is_concat(to_app(newConcat))) { + return; } - nodeWithLen = get_eqc_next(nodeWithLen); - } while (nodeWithLen != var); + if (arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 1: Concat(const, const) == const + TRACE("str", tout << "Case 1: Concat(const, const) == const" << std::endl;); + zstring arg1_str, arg2_str; + u.str.is_string(arg1, arg1_str); + u.str.is_string(arg2, arg2_str); - if (hasLen) { - // var = nodeWithLen --> |var| = |nodeWithLen| - expr_ref_vector l_items(m); - expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); - l_items.push_back(varEqNode); - - expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); - expr_ref varLenExpr (mk_int(varLen), m); - expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); - l_items.push_back(lenEqNum); - - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - expr_ref varLen(mk_strlen(var), m); - expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); - assert_implication(axl, axr); - TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); - res = true; - } - } - return res; -} - -bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - bool axiomAdded = false; - // collect all concats in context - for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { - if (! ctx.is_relevant(*it)) { - continue; - } - if (m.is_eq(*it)) { - collect_var_concat(*it, varSet, concatSet); - } - } - // iterate each concat - // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved - for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { - expr * concat = *it; - rational lenValue; - expr_ref concatlenExpr (mk_strlen(concat), m) ; - bool allLeafResolved = true; - if (! get_value(concatlenExpr, lenValue)) { - // the length fo concat is unresolved yet - if (get_len_value(concat, lenValue)) { - // but all leaf nodes have length information - TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); - std::set leafNodes; - get_unique_non_concat_nodes(concat, leafNodes); - expr_ref_vector l_items(m); - for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { - rational leafLenValue; - if (get_len_value(*leafIt, leafLenValue)) { - expr_ref leafItLenExpr (mk_strlen(*leafIt), m); - expr_ref leafLenValueExpr (mk_int(leafLenValue), m); - expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); - l_items.push_back(lcExpr); + zstring result_str = arg1_str + arg2_str; + if (result_str != const_str) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << "\" + \"" << arg2_str << + "\" != \"" << const_str << "\"" << "\n";); + expr_ref equality(ctx.mk_eq_atom(concat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } + } else if (!arg1_has_eqc_value && arg2_has_eqc_value) { + // Case 2: Concat(var, const) == const + TRACE("str", tout << "Case 2: Concat(var, const) == const" << std::endl;); + zstring arg2_str; + u.str.is_string(arg2, arg2_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg2StrLen = arg2_str.length(); + if (resultStrLen < arg2StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg2_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg2StrLen; + zstring firstPart = const_str.extract(0, varStrLen); + zstring secondPart = const_str.extract(varStrLen, arg2StrLen); + if (arg2_str != secondPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "suffix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg2_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; } else { - allLeafResolved = false; - break; + expr_ref tmpStrConst(mk_string(firstPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg1, tmpStrConst), m); + assert_implication(premise, conclusion); + return; } } - if (allLeafResolved) { - expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); - expr_ref lenValueExpr (mk_int(lenValue), m); - expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); - assert_implication(axl, axr); - TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); - axiomAdded = true; + } else if (arg1_has_eqc_value && !arg2_has_eqc_value) { + // Case 3: Concat(const, var) == const + TRACE("str", tout << "Case 3: Concat(const, var) == const" << std::endl;); + zstring arg1_str; + u.str.is_string(arg1, arg1_str); + unsigned int resultStrLen = const_str.length(); + unsigned int arg1StrLen = arg1_str.length(); + if (resultStrLen < arg1StrLen) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: \"" + << arg1_str << + "\" is longer than \"" << const_str << "\"," + << " so cannot be concatenated with anything to form it" << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + int varStrLen = resultStrLen - arg1StrLen; + zstring firstPart = const_str.extract(0, arg1StrLen); + zstring secondPart = const_str.extract(arg1StrLen, varStrLen); + if (arg1_str != firstPart) { + // Inconsistency + TRACE("str", tout << "inconsistency detected: " + << "prefix of concatenation result expected \"" << secondPart << "\", " + << "actually \"" << arg1_str << "\"" + << "\n";); + expr_ref equality(ctx.mk_eq_atom(newConcat, str), m); + expr_ref diseq(m.mk_not(equality), m); + assert_axiom(diseq); + return; + } else { + expr_ref tmpStrConst(mk_string(secondPart), m); + expr_ref premise(ctx.mk_eq_atom(newConcat, str), m); + expr_ref conclusion(ctx.mk_eq_atom(arg2, tmpStrConst), m); + assert_implication(premise, conclusion); + return; + } } - } - } - } - // if no concat length is propagated, check the length of variables. - if (! axiomAdded) { - for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { - expr * var = *it; - rational lenValue; - expr_ref varlen (mk_strlen(var), m) ; - bool allLeafResolved = true; - if (! get_value(varlen, lenValue)) { - if (propagate_length_within_eqc(var)) { - axiomAdded = true; - } - } - } - - } - return axiomAdded; -} - -void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { - app * a_node = to_app(node); - if (!u.str.is_concat(a_node)) { - argSet.insert(node); - return; - } else { - SASSERT(a_node->get_num_args() == 2); - expr * leftArg = a_node->get_arg(0); - expr * rightArg = a_node->get_arg(1); - get_unique_non_concat_nodes(leftArg, argSet); - get_unique_non_concat_nodes(rightArg, argSet); - } -} - -final_check_status theory_str::final_check_eh() { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - expr_ref_vector assignments(m); - ctx.get_assignments(assignments); - - if (opt_VerifyFinalCheckProgress) { - finalCheckProgressIndicator = false; - } - - TRACE("str", tout << "final check" << std::endl;); - TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); - check_variable_scope(); - - if (opt_DeferEQCConsistencyCheck) { - TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); - std::set eqc_roots; - for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { - enode * e = *it; - enode * root = e->get_root(); - eqc_roots.insert(root); - } - - bool found_inconsistency = false; - - for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { - enode * e = *it; - app * a = e->get_owner(); - if (!(m.get_sort(a) == u.str.mk_string_sort())) { - TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); } else { - TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); - // first call check_concat_len_in_eqc() on each member of the eqc - enode * e_it = e; - enode * e_root = e_it; - do { - bool status = check_concat_len_in_eqc(e_it->get_owner()); - if (!status) { - TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); - found_inconsistency = true; - } - e_it = e_it->get_next(); - } while (e_it != e_root); + // Case 4: Concat(var, var) == const + TRACE("str", tout << "Case 4: Concat(var, var) == const" << std::endl;); + if (eval_concat(arg1, arg2) == NULL) { + rational arg1Len, arg2Len; + bool arg1Len_exists = get_len_value(arg1, arg1Len); + bool arg2Len_exists = get_len_value(arg2, arg2Len); + rational concatStrLen((unsigned)const_str.length()); + if (arg1Len_exists || arg2Len_exists) { + expr_ref ax_l1(ctx.mk_eq_atom(concat, str), m); + expr_ref ax_l2(m); + zstring prefixStr, suffixStr; + if (arg1Len_exists) { + if (arg1Len.is_neg()) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg1), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg1Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg1Len = " << arg1Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg1), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } - // now grab any two distinct elements from the EQC and call new_eq_check() on them - enode * e1 = e; - enode * e2 = e1->get_next(); - if (e1 != e2) { - TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); - bool result = new_eq_check(e1->get_owner(), e2->get_owner()); - if (!result) { - TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); - found_inconsistency = true; - } - } + prefixStr = const_str.extract(0, arg1Len.get_unsigned()); + rational concat_minus_arg1 = concatStrLen - arg1Len; + suffixStr = const_str.extract(arg1Len.get_unsigned(), concat_minus_arg1.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg1), mk_int(arg1Len)); + } else { + // arg2's length is available + if (arg2Len.is_neg()) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref toAssert(m_autil.mk_ge(mk_strlen(arg2), mk_int(0)), m); + assert_axiom(toAssert); + return; + } else if (arg2Len > concatStrLen) { + TRACE("str", tout << "length conflict: arg2Len = " << arg2Len << ", concatStrLen = " << concatStrLen << std::endl;); + expr_ref ax_r1(m_autil.mk_le(mk_strlen(arg2), mk_int(concatStrLen)), m); + assert_implication(ax_l1, ax_r1); + return; + } + + rational concat_minus_arg2 = concatStrLen - arg2Len; + prefixStr = const_str.extract(0, concat_minus_arg2.get_unsigned()); + suffixStr = const_str.extract(concat_minus_arg2.get_unsigned(), arg2Len.get_unsigned()); + ax_l2 = ctx.mk_eq_atom(mk_strlen(arg2), mk_int(arg2Len)); + } + // consistency check + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + expr_ref ax_r(m.mk_not(ax_l2), m); + assert_implication(ax_l1, ax_r); + return; + } + expr_ref_vector r_items(m); + r_items.push_back(ctx.mk_eq_atom(arg1, mk_string(prefixStr))); + r_items.push_back(ctx.mk_eq_atom(arg2, mk_string(suffixStr))); + if (!arg1Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg1), mk_int(prefixStr.length()))); + } + if (!arg2Len_exists) { + r_items.push_back(ctx.mk_eq_atom(mk_strlen(arg2), mk_int(suffixStr.length()))); + } + expr_ref lhs(m.mk_and(ax_l1, ax_l2), m); + expr_ref rhs(mk_and(r_items), m); + assert_implication(lhs, rhs); + } else { /* ! (arg1Len != 1 || arg2Len != 1) */ + expr_ref xorFlag(m); + std::pair key1(arg1, arg2); + std::pair key2(arg2, arg1); + + // check the entries in this map to make sure they're still in scope + // before we use them. + + std::map, std::map >::iterator entry1 = varForBreakConcat.find(key1); + std::map, std::map >::iterator entry2 = varForBreakConcat.find(key2); + + bool entry1InScope; + if (entry1 == varForBreakConcat.end()) { + TRACE("str", tout << "key1 no entry" << std::endl;); + entry1InScope = false; + } else { + // OVERRIDE. + entry1InScope = true; + TRACE("str", tout << "key1 entry" << std::endl;); + /* + if (internal_variable_set.find((entry1->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key1 entry not in scope" << std::endl;); + entry1InScope = false; + } else { + TRACE("str", tout << "key1 entry in scope" << std::endl;); + entry1InScope = true; + } + */ + } + + bool entry2InScope; + if (entry2 == varForBreakConcat.end()) { + TRACE("str", tout << "key2 no entry" << std::endl;); + entry2InScope = false; + } else { + // OVERRIDE. + entry2InScope = true; + TRACE("str", tout << "key2 entry" << std::endl;); + /* + if (internal_variable_set.find((entry2->second)[0]) == internal_variable_set.end()) { + TRACE("str", tout << "key2 entry not in scope" << std::endl;); + entry2InScope = false; + } else { + TRACE("str", tout << "key2 entry in scope" << std::endl;); + entry2InScope = true; + } + */ + } + + TRACE("str", tout << "entry 1 " << (entry1InScope ? "in scope" : "not in scope") << std::endl + << "entry 2 " << (entry2InScope ? "in scope" : "not in scope") << std::endl;); + + if (!entry1InScope && !entry2InScope) { + xorFlag = mk_internal_xor_var(); + varForBreakConcat[key1][0] = xorFlag; + } else if (entry1InScope) { + xorFlag = varForBreakConcat[key1][0]; + } else { // entry2InScope + xorFlag = varForBreakConcat[key2][0]; + } + + int concatStrLen = const_str.length(); + int and_count = 1; + + expr_ref_vector arrangement_disjunction(m); + + for (int i = 0; i < concatStrLen + 1; ++i) { + expr_ref_vector and_items(m); + zstring prefixStr = const_str.extract(0, i); + zstring suffixStr = const_str.extract(i, concatStrLen - i); + // skip invalid options + if (u.str.is_concat(to_app(arg1)) && !can_concat_eq_str(arg1, prefixStr)) { + continue; + } + if (u.str.is_concat(to_app(arg2)) && !can_concat_eq_str(arg2, suffixStr)) { + continue; + } + + expr_ref prefixAst(mk_string(prefixStr), m); + expr_ref arg1_eq (ctx.mk_eq_atom(arg1, prefixAst), m); + and_items.push_back(arg1_eq); + and_count += 1; + + expr_ref suffixAst(mk_string(suffixStr), m); + expr_ref arg2_eq (ctx.mk_eq_atom(arg2, suffixAst), m); + and_items.push_back(arg2_eq); + and_count += 1; + + arrangement_disjunction.push_back(mk_and(and_items)); + } + + expr_ref implyL(ctx.mk_eq_atom(concat, str), m); + expr_ref implyR1(m); + if (arrangement_disjunction.empty()) { + // negate + expr_ref concat_eq_str(ctx.mk_eq_atom(concat, str), m); + expr_ref negate_ast(m.mk_not(concat_eq_str), m); + assert_axiom(negate_ast); + } else { + implyR1 = mk_or(arrangement_disjunction); + if (m_params.m_StrongArrangements) { + expr_ref ax_strong(ctx.mk_eq_atom(implyL, implyR1), m); + assert_axiom(ax_strong); + } else { + assert_implication(implyL, implyR1); + } + generate_mutual_exclusion(arrangement_disjunction); + } + } /* (arg1Len != 1 || arg2Len != 1) */ + } /* if (Concat(arg1, arg2) == NULL) */ } } - - if (found_inconsistency) { - TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); - return FC_CONTINUE; - } else { - TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); - } } - // run dependence analysis to find free string variables - std::map varAppearInAssign; - std::map freeVar_map; - std::map > unrollGroup_map; - std::map > var_eq_concat_map; - int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); - if (conflictInDep == -1) { - // return Z3_TRUE; - return FC_DONE; - } - - // enhancement: improved backpropagation of string constants into var=concat terms - bool backpropagation_occurred = false; - for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); - veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { - expr * var = veqc_map_it->first; - for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); - concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { - app * concat = to_app(concat_map_it->first); - expr * concat_lhs = concat->get_arg(0); - expr * concat_rhs = concat->get_arg(1); - // If the concat LHS and RHS both have a string constant in their EQC, - // but the var does not, then we assert an axiom of the form - // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" - bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; - expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); - expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); - expr * var_str = get_eqc_value(var, var_haseqc); - if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { - TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl - << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); - zstring lhsString, rhsString; - u.str.is_string(concat_lhs_str, lhsString); - u.str.is_string(concat_rhs_str, rhsString); - zstring concatString = lhsString + rhsString; - expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); - expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); - expr_ref lhs(m.mk_and(lhs1, lhs2), m); - expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); - assert_implication(lhs, rhs); - backpropagation_occurred = true; - } - } - } - - if (backpropagation_occurred) { - TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); - return FC_CONTINUE; - } - - // enhancement: improved backpropagation of length information - { - std::set varSet; - std::set concatSet; - std::map exprLenMap; - - bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); - if (length_propagation_occurred) { - TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); - return FC_CONTINUE; - } - } - - bool needToAssignFreeVars = false; - std::set free_variables; - std::set unused_internal_variables; - { // Z3str2 free variables check - std::map::iterator itor = varAppearInAssign.begin(); - for (; itor != varAppearInAssign.end(); ++itor) { - /* - std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); - if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") - continue; - */ - if (internal_variable_set.find(itor->first) != internal_variable_set.end() - || regex_variable_set.find(itor->first) != regex_variable_set.end()) { - // this can be ignored, I think - TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); - continue; - } - bool hasEqcValue = false; - expr * eqcString = get_eqc_value(itor->first, hasEqcValue); - if (!hasEqcValue) { - TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); - needToAssignFreeVars = true; - free_variables.insert(itor->first); - // break; - } else { - // debug - TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); - } - } - } - - if (!needToAssignFreeVars) { - - // check string-int terms - bool addedStrIntAxioms = false; - for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { - app * ex = to_app(string_int_conversion_terms[i].get()); - if (u.str.is_stoi(ex)) { - bool axiomAdd = finalcheck_str2int(ex); - if (axiomAdd) { - addedStrIntAxioms = true; - } - } else if (u.str.is_itos(ex)) { - bool axiomAdd = finalcheck_int2str(ex); - if (axiomAdd) { - addedStrIntAxioms = true; - } - } else { - UNREACHABLE(); - } - } - if (addedStrIntAxioms) { - TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); - return FC_CONTINUE; - } - - if (unused_internal_variables.empty()) { - TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); - return FC_DONE; - } else { - TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); - for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { - expr * var = *it; - expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); - assert_axiom(assignment); - } - return FC_CONTINUE; - } - } - - CTRACE("str", needToAssignFreeVars, - tout << "Need to assign values to the following free variables:" << std::endl; - for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { - tout << mk_ismt2_pp(*itx, m) << std::endl; - } - tout << "freeVar_map has the following entries:" << std::endl; - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * var = fvIt->first; - tout << mk_ismt2_pp(var, m) << std::endl; - } - ); - - // ----------------------------------------------------------- - // variables in freeVar are those not bounded by Concats - // classify variables in freeVarMap: - // (1) freeVar = unroll(r1, t1) - // (2) vars are not bounded by either concat or unroll - // ----------------------------------------------------------- - std::map > fv_unrolls_map; - std::set tmpSet; - expr * constValue = NULL; - for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { - expr * var = fvIt2->first; - tmpSet.clear(); - get_eqc_allUnroll(var, constValue, tmpSet); - if (tmpSet.size() > 0) { - fv_unrolls_map[var] = tmpSet; - } - } - // erase var bounded by an unroll function from freeVar_map - for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); - fvIt3 != fv_unrolls_map.end(); fvIt3++) { - expr * var = fvIt3->first; - TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); - freeVar_map.erase(var); - } - - // collect the case: - // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) - // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} - - std::map > concatEqUnrollsMap; - for (std::map >::iterator urItor = unrollGroup_map.begin(); - urItor != unrollGroup_map.end(); urItor++) { - expr * unroll = urItor->first; - expr * curr = unroll; - do { - if (u.str.is_concat(to_app(curr))) { - concatEqUnrollsMap[curr].insert(unroll); - concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); - } - enode * e_curr = ctx.get_enode(curr); - curr = e_curr->get_next()->get_owner(); - // curr = get_eqc_next(curr); - } while (curr != unroll); - } - - std::map > concatFreeArgsEqUnrollsMap; - std::set fvUnrollSet; - for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); - concatItor != concatEqUnrollsMap.end(); concatItor++) { - expr * concat = concatItor->first; - expr * concatArg1 = to_app(concat)->get_arg(0); - expr * concatArg2 = to_app(concat)->get_arg(1); - bool arg1Bounded = false; - bool arg2Bounded = false; - // arg1 - if (variable_set.find(concatArg1) != variable_set.end()) { - if (freeVar_map.find(concatArg1) == freeVar_map.end()) { - arg1Bounded = true; - } else { - fvUnrollSet.insert(concatArg1); - } - } else if (u.str.is_concat(to_app(concatArg1))) { - if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { - arg1Bounded = true; - } - } - // arg2 - if (variable_set.find(concatArg2) != variable_set.end()) { - if (freeVar_map.find(concatArg2) == freeVar_map.end()) { - arg2Bounded = true; - } else { - fvUnrollSet.insert(concatArg2); - } - } else if (u.str.is_concat(to_app(concatArg2))) { - if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { - arg2Bounded = true; - } - } - if (!arg1Bounded && !arg2Bounded) { - concatFreeArgsEqUnrollsMap[concat].insert( - concatEqUnrollsMap[concat].begin(), - concatEqUnrollsMap[concat].end()); - } - } - for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { - TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); - freeVar_map.erase(*vItor); - } - - // Assign free variables - std::set fSimpUnroll; - - constValue = NULL; - - { - TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; - for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { - expr * freeVar = freeVarItor1->first; - rational lenValue; - bool lenValue_exists = get_len_value(freeVar, lenValue); - tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " - << (lenValue_exists ? lenValue.to_string() : "?") - << "]" << std::endl; - } - ); - } - - for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); - fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { - expr * concat = fvIt2->first; - for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { - expr * unroll = *urItor; - process_concat_eq_unroll(concat, unroll); - } - } - - // -------- - // experimental free variable assignment - begin - // * special handling for variables that are not used in concat - // -------- - bool testAssign = true; - if (!testAssign) { - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * freeVar = fvIt->first; - /* - std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); - if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { - continue; - } - */ - expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - } else { - process_free_var(freeVar_map); - } - // experimental free variable assignment - end - - // now deal with removed free variables that are bounded by an unroll - TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); - for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); - fvIt1 != fv_unrolls_map.end(); fvIt1++) { - expr * var = fvIt1->first; - fSimpUnroll.clear(); - get_eqc_simpleUnroll(var, constValue, fSimpUnroll); - if (fSimpUnroll.size() == 0) { - gen_assign_unroll_reg(fv_unrolls_map[var]); - } else { - expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - } - - if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { - TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); - m.raise_exception("no progress in theory_str final check"); - } - - return FC_CONTINUE; // since by this point we've added axioms -} - -inline zstring int_to_string(int i) { - std::stringstream ss; - ss << i; - std::string str = ss.str(); - return zstring(str.c_str()); -} - -inline std::string longlong_to_string(long long i) { - std::stringstream ss; - ss << i; - return ss.str(); -} - -void theory_str::print_value_tester_list(svector > & testerList) { - ast_manager & m = get_manager(); - TRACE("str", - int ss = testerList.size(); - tout << "valueTesterList = {"; - for (int i = 0; i < ss; ++i) { - if (i % 4 == 0) { - tout << std::endl; - } - tout << "(" << testerList[i].first << ", "; - tout << mk_ismt2_pp(testerList[i].second, m); - tout << "), "; - } - tout << std::endl << "}" << std::endl; - ); -} - -zstring theory_str::gen_val_string(int len, int_vector & encoding) { - SASSERT(charSetSize > 0); - SASSERT(char_set != NULL); - - std::string re(len, char_set[0]); - for (int i = 0; i < (int) encoding.size() - 1; i++) { - int idx = encoding[i]; - re[len - 1 - i] = char_set[idx]; - } - return zstring(re.c_str()); -} - -/* - * The return value indicates whether we covered the search space. - * - If the next encoding is valid, return false - * - Otherwise, return true - */ -bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { - SASSERT(charSetSize > 0); - - TRACE("str", tout << "base vector: [ "; - for (unsigned i = 0; i < base.size(); ++i) { - tout << base[i] << " "; - } - tout << "]" << std::endl; - ); - - int s = 0; - int carry = 0; - next.reset(); - - for (int i = 0; i < (int) base.size(); i++) { - if (i == 0) { - s = base[i] + 1; - carry = s / charSetSize; - s = s % charSetSize; - next.push_back(s); - } else { - s = base[i] + carry; - carry = s / charSetSize; - s = s % charSetSize; - next.push_back(s); - } - } - if (next[next.size() - 1] > 0) { - next.reset(); - return true; - } else { - return false; - } -} - -expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - zstring lenStr, int tries) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - int distance = 32; - - // ---------------------------------------------------------------------------------------- - // generate value options encoding - // encoding is a vector of size (len + 1) - // e.g, len = 2, - // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" - // the last item in the encoding indicates whether the whole space is covered - // for example, if the charSet = {a, b}. All valid encodings are - // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} - // if add 1 to the last one, we get - // {0, 0, 1} - // the last item "1" shows this is not a valid encoding, and we have covered all space - // ---------------------------------------------------------------------------------------- - int len = atoi(lenStr.encode().c_str()); - bool coverAll = false; - svector options; - int_vector base; - - TRACE("str", tout - << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl - << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl - << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl - << "lenstr = " << lenStr << "\n" - << "tries = " << tries << "\n"; - if (m_params.m_AggressiveValueTesting) { - tout << "note: aggressive value testing is enabled" << std::endl; - } - ); - - if (tries == 0) { - base = int_vector(len + 1, 0); - coverAll = false; - } else { - expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; - TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); - coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); - } - - long long l = (tries) * distance; - long long h = l; - for (int i = 0; i < distance; i++) { - if (coverAll) - break; - options.push_back(base); - h++; - coverAll = get_next_val_encode(options[options.size() - 1], base); - } - val_range_map[val_indicator] = options[options.size() - 1]; - - TRACE("str", - tout << "value tester encoding " << "{" << std::endl; - int_vector vec = val_range_map[val_indicator]; - - for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { - tout << *it << std::endl; - } - tout << "}" << std::endl; - ); - - // ---------------------------------------------------------------------------------------- - - ptr_vector orList; - ptr_vector andList; - - for (long long i = l; i < h; i++) { - orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); - if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - zstring aStr = gen_val_string(len, options[i - l]); - expr * strAst; - if (m_params.m_UseFastValueTesterCache) { - if (!valueTesterCache.find(aStr, strAst)) { - strAst = mk_string(aStr); - valueTesterCache.insert(aStr, strAst); - m_trail.push_back(strAst); - } - } else { - strAst = mk_string(aStr); - } - andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); - } - if (!coverAll) { - orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); - if (m_params.m_AggressiveValueTesting) { - literal l = mk_eq(val_indicator, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - } - - expr ** or_items = alloc_svect(expr*, orList.size()); - expr ** and_items = alloc_svect(expr*, andList.size() + 1); - - for (int i = 0; i < (int) orList.size(); i++) { - or_items[i] = orList[i]; - } - if (orList.size() > 1) - and_items[0] = m.mk_or(orList.size(), or_items); - else - and_items[0] = or_items[0]; - - for (int i = 0; i < (int) andList.size(); i++) { - and_items[i + 1] = andList[i]; - } - expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); - - // --------------------------------------- - // If the new value tester is $$_val_x_16_i - // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") - // --------------------------------------- - andList.reset(); - andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); - for (int i = 0; i < tries; i++) { - expr * vTester = fvar_valueTester_map[freeVar][len][i].second; - if (vTester != val_indicator) - andList.push_back(m.mk_eq(vTester, mk_string("more"))); - } - expr * assertL = NULL; - if (andList.size() == 1) { - assertL = andList[0]; - } else { - expr ** and_items = alloc_svect(expr*, andList.size()); - for (int i = 0; i < (int) andList.size(); i++) { - and_items[i] = andList[i]; - } - assertL = m.mk_and(andList.size(), and_items); - } - - // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) - valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); - return valTestAssert; -} - -expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, - zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { - ast_manager & m = get_manager(); - - int len = atoi(len_valueStr.encode().c_str()); - - // check whether any value tester is actually in scope - TRACE("str", tout << "checking scope of previous value testers" << std::endl;); - bool map_effectively_empty = true; - if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { - // there's *something* in the map, but check its scope - svector > entries = fvar_valueTester_map[freeVar][len]; - for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { - std::pair entry = *it; - expr * aTester = entry.second; - if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); - } else { - TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); - map_effectively_empty = false; - break; - } - } - } - - if (map_effectively_empty) { - TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); - int tries = 0; - expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); - valueTester_fvar_map[val_indicator] = freeVar; - fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); - } else { - TRACE("str", tout << "checking previous value testers" << std::endl;); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - - // go through all previous value testers - // If some doesn't have an eqc value, add its assertion again. - int testerTotal = fvar_valueTester_map[freeVar][len].size(); - int i = 0; - for (; i < testerTotal; i++) { - expr * aTester = fvar_valueTester_map[freeVar][len][i].second; - - // it's probably worth checking scope here, actually - if (internal_variable_set.find(aTester) == internal_variable_set.end()) { - TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); - continue; - } - - if (aTester == valTesterInCbEq) { - break; - } - - bool anEqcHasValue = false; - // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); - expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); - if (!anEqcHasValue) { - TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) - << " doesn't have an equivalence class value." << std::endl;); - refresh_theory_var(aTester); - - expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); - - TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl - << mk_ismt2_pp(makeupAssert, m) << std::endl;); - assert_axiom(makeupAssert); - } else { - TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) - << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); - } - } - - if (valTesterValueStr == "more") { - expr * valTester = NULL; - if (i + 1 < testerTotal) { - valTester = fvar_valueTester_map[freeVar][len][i + 1].second; - refresh_theory_var(valTester); - } else { - valTester = mk_internal_valTest_var(freeVar, len, i + 1); - valueTester_fvar_map[valTester] = freeVar; - fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); - print_value_tester_list(fvar_valueTester_map[freeVar][len]); - } - expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); - return nextAssert; - } - - return NULL; - } -} - -void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); - - app * regexFuncDecl = to_app(regex); - if (u.re.is_to_re(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in Str2Reg(s1) - // ==> - // var = s1 /\ length(var) = length(s1) - // --------------------------------------------------------- - expr * strInside = to_app(regex)->get_arg(0); - items.push_back(ctx.mk_eq_atom(var, strInside)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); - return; - } - // RegexUnion - else if (u.re.is_union(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in RegexUnion(r1, r2) - // ==> - // (var = newVar1 \/ var = newVar2) - // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) - // /\ (newVar1 \in r1) /\ (newVar2 \in r2) - // --------------------------------------------------------- - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); - items.push_back(mgr.mk_or( - mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), - ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); - items.push_back(mgr.mk_or( - mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), - ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); - - expr * regArg1 = to_app(regex)->get_arg(0); - reduce_virtual_regex_in(newVar1, regArg1, items); - - expr * regArg2 = to_app(regex)->get_arg(1); - reduce_virtual_regex_in(newVar2, regArg2, items); - - return; - } - // RegexConcat - else if (u.re.is_concat(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in RegexConcat(r1, r2) - // ==> - // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) - // /\ (newVar1 \in r1) /\ (newVar2 \in r2) - // --------------------------------------------------------- - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); - items.push_back(ctx.mk_eq_atom(var, concatAst)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), - m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); - - expr * regArg1 = to_app(regex)->get_arg(0); - reduce_virtual_regex_in(newVar1, regArg1, items); - expr * regArg2 = to_app(regex)->get_arg(1); - reduce_virtual_regex_in(newVar2, regArg2, items); - return; - } - // Unroll - else if (u.re.is_star(regexFuncDecl)) { - // --------------------------------------------------------- - // var \in Star(r1) - // ==> - // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| - // --------------------------------------------------------- - expr * regArg = to_app(regex)->get_arg(0); - expr_ref unrollCnt(mk_unroll_bound_var(), mgr); - expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); - items.push_back(ctx.mk_eq_atom(var, unrollFunc)); - items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); - return; - } - // re.range - else if (u.re.is_range(regexFuncDecl)) { - // var in range("a", "z") - // ==> - // (var = "a" or var = "b" or ... or var = "z") - expr_ref lo(regexFuncDecl->get_arg(0), mgr); - expr_ref hi(regexFuncDecl->get_arg(1), mgr); - zstring str_lo, str_hi; - SASSERT(u.str.is_string(lo)); - SASSERT(u.str.is_string(hi)); - u.str.is_string(lo, str_lo); - u.str.is_string(hi, str_hi); - SASSERT(str_lo.length() == 1); - SASSERT(str_hi.length() == 1); - unsigned int c1 = str_lo[0]; - unsigned int c2 = str_hi[0]; - if (c1 > c2) { - // exchange - unsigned int tmp = c1; - c1 = c2; - c2 = tmp; - } - expr_ref_vector range_cases(mgr); - for (unsigned int ch = c1; ch <= c2; ++ch) { - zstring s_ch(ch); - expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); - range_cases.push_back(rhs); - } - expr_ref rhs(mk_or(range_cases), mgr); - SASSERT(rhs); - assert_axiom(rhs); - return; - } else { - get_manager().raise_exception("unrecognized regex operator"); - UNREACHABLE(); - } -} - -void theory_str::gen_assign_unroll_reg(std::set & unrolls) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - expr_ref_vector items(mgr); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * unrFunc = *itor; - TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); - - expr * regexInUnr = to_app(unrFunc)->get_arg(0); - expr * cntInUnr = to_app(unrFunc)->get_arg(1); - items.reset(); - - rational low, high; - bool low_exists = lower_bound(cntInUnr, low); - bool high_exists = upper_bound(cntInUnr, high); - - TRACE("str", - tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; - rational unrLenValue; - bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); - tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; - rational cntInUnrValue; - bool cntHasValue = get_value(cntInUnr, cntInUnrValue); - tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") - << " low = " - << (low_exists ? low.to_string() : "?") - << " high = " - << (high_exists ? high.to_string() : "?") - << std::endl; - ); - - expr_ref toAssert(mgr); - if (low.is_neg()) { - toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); - } else { - if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { - - expr_ref newVar1(mk_regex_rep_var(), mgr); - expr_ref newVar2(mk_regex_rep_var(), mgr); - expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); - expr_ref newCnt(mk_unroll_bound_var(), mgr); - expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); - - // unroll(r1, t1) = newVar1 . newVar2 - items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); - items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); - // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) - items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); - items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); - // newVar1 \in r1 - reduce_virtual_regex_in(newVar1, regexInUnr, items); - items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); - items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); - items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); - toAssert = ctx.mk_eq_atom( - m_autil.mk_ge(cntInUnr, mk_int(1)), - mk_and(items)); - - // option 0 - expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); - expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); - expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); - expr_ref and1(mgr.mk_and(ast1, ast2), mgr); - - // put together - toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); - - unroll_var_map[unrFunc] = toAssert; - } else { - toAssert = unroll_var_map[unrFunc]; - } - } - m_trail.push_back(toAssert); - assert_axiom(toAssert); - } -} - -static int computeGCD(int x, int y) { - if (x == 0) { - return y; - } - while (y != 0) { - if (x > y) { - x = x - y; - } else { - y = y - x; - } - } - return x; -} - -static int computeLCM(int a, int b) { - int temp = computeGCD(a, b); - return temp ? (a / temp * b) : 0; -} - -static zstring get_unrolled_string(zstring core, int count) { - zstring res(""); - for (int i = 0; i < count; i++) { - res = res + core; - } - return res; -} - -expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - int lcm = 1; - int coreValueCount = 0; - expr * oneUnroll = NULL; - zstring oneCoreStr(""); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * str2RegFunc = to_app(*itor)->get_arg(0); - expr * coreVal = to_app(str2RegFunc)->get_arg(0); - zstring coreStr; - u.str.is_string(coreVal, coreStr); - if (oneUnroll == NULL) { - oneUnroll = *itor; - oneCoreStr = coreStr; - } - coreValueCount++; - int core1Len = coreStr.length(); - lcm = computeLCM(lcm, core1Len); - } - // - bool canHaveNonEmptyAssign = true; - expr_ref_vector litems(mgr); - zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr * str2RegFunc = to_app(*itor)->get_arg(0); - expr * coreVal = to_app(str2RegFunc)->get_arg(0); - zstring coreStr; - u.str.is_string(coreVal, coreStr); - unsigned int core1Len = coreStr.length(); - zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); - if (uStr != lcmStr) { - canHaveNonEmptyAssign = false; - } - litems.push_back(ctx.mk_eq_atom(n, *itor)); - } - - if (canHaveNonEmptyAssign) { - return gen_unroll_conditional_options(n, unrolls, lcmStr); - } else { - expr_ref implyL(mk_and(litems), mgr); - expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); - // want to return (implyL -> implyR) - expr * final_axiom = rewrite_implication(implyL, implyR); - return final_axiom; - } -} - -expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - int dist = opt_LCMUnrollStep; - expr_ref_vector litems(mgr); - expr_ref moreAst(mk_string("more"), mgr); - for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { - expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); - TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); - litems.push_back(item); - } - - // handle out-of-scope entries in unroll_tries_map - - ptr_vector outOfScopeTesters; - - for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); - it != unroll_tries_map[var][unrolls].end(); ++it) { - expr * tester = *it; - bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); - TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) - << (inScope ? " in scope" : " out of scope") - << std::endl;); - if (!inScope) { - outOfScopeTesters.push_back(tester); - } - } - - for (ptr_vector::iterator it = outOfScopeTesters.begin(); - it != outOfScopeTesters.end(); ++it) { - unroll_tries_map[var][unrolls].erase(*it); - } - - - if (unroll_tries_map[var][unrolls].size() == 0) { - unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); - } - - int tries = unroll_tries_map[var][unrolls].size(); - for (int i = 0; i < tries; i++) { - expr * tester = unroll_tries_map[var][unrolls][i]; - // TESTING - refresh_theory_var(tester); - bool testerHasValue = false; - expr * testerVal = get_eqc_value(tester, testerHasValue); - if (!testerHasValue) { - // generate make-up assertion - int l = i * dist; - int h = (i + 1) * dist; - expr_ref lImp(mk_and(litems), mgr); - expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); - - SASSERT(lImp); - TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); - SASSERT(rImp); - TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); - - expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); - SASSERT(toAssert); - TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); - m_trail.push_back(toAssert); - return toAssert; - - // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. - // the return is in the same place - - // insert [tester = "more"] to litems so that the implyL for next tester is correct - litems.push_back(ctx.mk_eq_atom(tester, moreAst)); - } else { - zstring testerStr; - u.str.is_string(testerVal, testerStr); - TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); - if (testerStr == "more") { - litems.push_back(ctx.mk_eq_atom(tester, moreAst)); - } - } - } - expr * tester = mk_unroll_test_var(); - unroll_tries_map[var][unrolls].push_back(tester); - int l = tries * dist; - int h = (tries + 1) * dist; - expr_ref lImp(mk_and(litems), mgr); - expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); - SASSERT(lImp); - SASSERT(rImp); - expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); - SASSERT(toAssert); - TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); - m_trail.push_back(toAssert); - return toAssert; -} - -expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { - context & ctx = get_context(); - ast_manager & mgr = get_manager(); - - TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr - << ", l = " << l << ", h = " << h << "\n";); - - if (m_params.m_AggressiveUnrollTesting) { - TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); - } - - expr_ref_vector orItems(mgr); - expr_ref_vector andItems(mgr); - - for (int i = l; i < h; i++) { - zstring iStr = int_to_string(i); - expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); - TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); - if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, mk_string(iStr), false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - orItems.push_back(testerEqAst); - zstring unrollStrInstance = get_unrolled_string(lcmStr, i); - - expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); - TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); - andItems.push_back(x1); - - expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); - TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); - andItems.push_back(x2); - } - expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); - TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); - if (m_params.m_AggressiveUnrollTesting) { - literal l = mk_eq(testerVar, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - - orItems.push_back(testerEqMore); - int nextLowerLenBound = h * lcmStr.length(); - expr_ref more2(ctx.mk_eq_atom(testerEqMore, - //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) - m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) - ), mgr); - TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); - andItems.push_back(more2); - - expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); - TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); - andItems.push_back(mk_or(orItems)); - - expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); - TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); - - // doing the following avoids a segmentation fault - m_trail.push_back(finalAND); - return finalAND; -} - -expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - expr_ref freeVarLen(mk_strlen(freeVar), m); - SASSERT(freeVarLen); - - expr_ref_vector orList(m); - expr_ref_vector andList(m); - - int distance = 3; - int l = (tries - 1) * distance; - int h = tries * distance; - - TRACE("str", - tout << "building andList and orList" << std::endl; - if (m_params.m_AggressiveLengthTesting) { - tout << "note: aggressive length testing is active" << std::endl; - } - ); - - // experimental theory-aware case split support - literal_vector case_split_literals; - - for (int i = l; i < h; ++i) { - expr_ref str_indicator(m); - if (m_params.m_UseFastLengthTesterCache) { - rational ri(i); - expr * lookup_val; - if(lengthTesterCache.find(ri, lookup_val)) { - str_indicator = expr_ref(lookup_val, m); - } else { - // no match; create and insert - zstring i_str = int_to_string(i); - expr_ref new_val(mk_string(i_str), m); - lengthTesterCache.insert(ri, new_val); - m_trail.push_back(new_val); - str_indicator = expr_ref(new_val, m); - } - } else { - zstring i_str = int_to_string(i); - str_indicator = expr_ref(mk_string(i_str), m); - } - expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); - orList.push_back(or_expr); - - double priority; - // give high priority to small lengths if this is available - if (i <= 5) { - priority = 0.3; - } else { - // prioritize over "more" - priority = 0.2; - } - add_theory_aware_branching_info(or_expr, priority, l_true); - - if (m_params.m_AggressiveLengthTesting) { - literal l = mk_eq(indicator, str_indicator, false); - ctx.mark_as_relevant(l); - ctx.force_phase(l); - } - - case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); - - expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); - andList.push_back(and_expr); - } - - expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); - orList.push_back(more_option); - // decrease priority of this option - add_theory_aware_branching_info(more_option, -0.1, l_true); - if (m_params.m_AggressiveLengthTesting) { - literal l = mk_eq(indicator, mk_string("more"), false); - ctx.mark_as_relevant(l); - ctx.force_phase(~l); - } - - andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); - - /* - { // more experimental theory case split support - expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); - ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); - case_split_literals.push_back(ctx.get_literal(tmp)); - ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - } - */ - - expr_ref_vector or_items(m); - expr_ref_vector and_items(m); - - for (unsigned i = 0; i < orList.size(); ++i) { - or_items.push_back(orList.get(i)); - } - - and_items.push_back(mk_or(or_items)); - for(unsigned i = 0; i < andList.size(); ++i) { - and_items.push_back(andList.get(i)); - } - - TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); - - expr_ref lenTestAssert = mk_and(and_items); - SASSERT(lenTestAssert); - TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); - - int testerCount = tries - 1; - if (testerCount > 0) { - expr_ref_vector and_items_LHS(m); - expr_ref moreAst(mk_string("more"), m); - for (int i = 0; i < testerCount; ++i) { - expr * indicator = fvar_lenTester_map[freeVar][i]; - if (internal_variable_set.find(indicator) == internal_variable_set.end()) { - TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); - continue; - } else { - TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); - and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); - } - } - expr_ref assertL(mk_and(and_items_LHS), m); - SASSERT(assertL); - expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); - SASSERT(finalAxiom != NULL); - TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); - return finalAxiom; - } else { - TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); - m_trail.push_back(lenTestAssert.get()); - return lenTestAssert.get(); - } -} - -// Return an expression of the form -// (tester = "less" | tester = "N" | tester = "more") & -// (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) -expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - rational N = bounds.midPoint; - rational N_minus_one = N - rational::one(); - rational N_plus_one = N + rational::one(); - expr_ref lenFreeVar(mk_strlen(freeVar), m); - - TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) - << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); - - expr_ref_vector combinedCaseSplit(m); - expr_ref_vector testerCases(m); - - expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); - testerCases.push_back(caseLess); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); - - expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); - testerCases.push_back(caseMore); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); - - expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); - testerCases.push_back(caseEq); - combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); - - combinedCaseSplit.push_back(mk_or(testerCases)); - - // force internalization on all terms in testerCases so we can extract literals - for (unsigned i = 0; i < testerCases.size(); ++i) { - expr * testerCase = testerCases.get(i); - if (!ctx.b_internalized(testerCase)) { - ctx.internalize(testerCase, false); - } - literal l = ctx.get_literal(testerCase); - case_split.push_back(l); - } - - expr_ref final_term(mk_and(combinedCaseSplit), m); - SASSERT(final_term); - TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); - return final_term; -} - -expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { - ast_manager & m = get_manager(); - context & ctx = get_context(); - - if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { - TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; - for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); - it != binary_search_len_tester_stack[freeVar].end(); ++it) { - expr * tester = *it; - tout << mk_pp(tester, m) << ": "; - if (binary_search_len_tester_info.contains(tester)) { - binary_search_info & bounds = binary_search_len_tester_info[tester]; - tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; - } else { - tout << "[WARNING: no bounds info available]"; - } - bool hasEqcValue; - expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); - if (hasEqcValue) { - tout << " = " << mk_pp(testerEqcValue, m); - } else { - tout << " [no eqc value]"; - } - tout << std::endl; - } - ); - expr * lastTester = binary_search_len_tester_stack[freeVar].back(); - bool lastTesterHasEqcValue; - expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); - zstring lastTesterConstant; - if (!lastTesterHasEqcValue) { - TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); - // check previousLenTester - if (previousLenTester == lastTester) { - lastTesterConstant = previousLenTesterValue; - TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); - } else { - TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); - UNREACHABLE(); return NULL; - } - } else { - u.str.is_string(lastTesterValue, lastTesterConstant); - } - TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); - if (lastTesterConstant == "more" || lastTesterConstant == "less") { - // use the previous bounds info to generate a new midpoint - binary_search_info lastBounds; - if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { - // unexpected - TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - UNREACHABLE(); - } - TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); - binary_search_info newBounds; - expr * newTester; - if (lastTesterConstant == "more") { - // special case: if the midpoint, upper bound, and window size are all equal, - // we double the window size and adjust the bounds - if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { - TRACE("str", tout << "search hit window size; expanding" << std::endl;); - newBounds.lowerBound = lastBounds.windowSize + rational::one(); - newBounds.windowSize = lastBounds.windowSize * rational(2); - newBounds.upperBound = newBounds.windowSize; - newBounds.calculate_midpoint(); - } else if (false) { - // handle the case where the midpoint can't be increased further - // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") - } else { - // general case - newBounds.lowerBound = lastBounds.midPoint + rational::one(); - newBounds.windowSize = lastBounds.windowSize; - newBounds.upperBound = lastBounds.upperBound; - newBounds.calculate_midpoint(); - } - if (!binary_search_next_var_high.find(lastTester, newTester)) { - newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); - binary_search_next_var_high.insert(lastTester, newTester); - } - refresh_theory_var(newTester); - } else if (lastTesterConstant == "less") { - if (false) { - // handle the case where the midpoint can't be decreased further - // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" - } else { - // general case - newBounds.upperBound = lastBounds.midPoint - rational::one(); - newBounds.windowSize = lastBounds.windowSize; - newBounds.lowerBound = lastBounds.lowerBound; - newBounds.calculate_midpoint(); - } - if (!binary_search_next_var_low.find(lastTester, newTester)) { - newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); - binary_search_next_var_low.insert(lastTester, newTester); - } - refresh_theory_var(newTester); - } - TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); - binary_search_len_tester_stack[freeVar].push_back(newTester); - m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); - binary_search_len_tester_info.insert(newTester, newBounds); - m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); - - literal_vector case_split_literals; - expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); - m_trail.push_back(next_case_split); - // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - return next_case_split; - } else { // lastTesterConstant is a concrete value - TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); - // defensive check that this length did not converge on a negative value. - binary_search_info lastBounds; - if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { - // unexpected - TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); - UNREACHABLE(); - } - if (lastBounds.midPoint.is_neg()) { - TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); - expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); - return axiom; - } - // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); - return valueAssert; - } - } else { - // no length testers yet - TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); - binary_search_len_tester_stack.insert(freeVar, ptr_vector()); - - expr * firstTester; - rational lowerBound(0); - rational upperBound(m_params.m_BinarySearchInitialUpperBound); - rational windowSize(upperBound); - rational midPoint(floor(upperBound / rational(2))); - if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { - firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); - binary_search_starting_len_tester.insert(freeVar, firstTester); - } - refresh_theory_var(firstTester); - - binary_search_len_tester_stack[freeVar].push_back(firstTester); - m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); - binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); - binary_search_len_tester_info.insert(firstTester, new_info); - m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); - - literal_vector case_split_literals; - expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); - m_trail.push_back(initial_case_split); - // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); - return initial_case_split; - } -} - -// ----------------------------------------------------------------------------------------------------- -// True branch will be taken in final_check: -// - When we discover a variable is "free" for the first time -// lenTesterInCbEq = NULL -// lenTesterValue = "" -// False branch will be taken when invoked by new_eq_eh(). -// - After we set up length tester for a "free" var in final_check, -// when the tester is assigned to some value (e.g. "more" or "4"), -// lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue -// The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc -// ----------------------------------------------------------------------------------------------------- -expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { - - ast_manager & m = get_manager(); - - TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); - - if (m_params.m_UseBinarySearch) { - TRACE("str", tout << "using binary search heuristic" << std::endl;); - return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); - } else { - bool map_effectively_empty = false; - if (!fvar_len_count_map.contains(freeVar)) { - TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); - map_effectively_empty = true; + expr_ref theory_str::set_up_finite_model_test(expr * lhs, expr * rhs) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE("str", tout << "activating finite model testing for overlapping concats " + << mk_pp(lhs, m) << " and " << mk_pp(rhs, m) << std::endl;); + std::map concatMap; + std::map unrollMap; + std::map varMap; + classify_ast_by_type(lhs, varMap, concatMap, unrollMap); + classify_ast_by_type(rhs, varMap, concatMap, unrollMap); + TRACE("str", tout << "found vars:"; + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + tout << " " << mk_pp(it->first, m); + } + tout << std::endl; + ); + + expr_ref testvar(mk_str_var("finiteModelTest"), m); + m_trail.push_back(testvar); + ptr_vector varlist; + + for (std::map::iterator it = varMap.begin(); it != varMap.end(); ++it) { + expr * v = it->first; + varlist.push_back(v); } - if (!map_effectively_empty) { - // check whether any entries correspond to variables that went out of scope; - // if every entry is out of scope then the map counts as being empty + // make things easy for the core wrt. testvar + expr_ref t1(ctx.mk_eq_atom(testvar, mk_string("")), m); + expr_ref t_yes(ctx.mk_eq_atom(testvar, mk_string("yes")), m); + expr_ref testvaraxiom(m.mk_or(t1, t_yes), m); + assert_axiom(testvaraxiom); - // assume empty and find a counterexample - map_effectively_empty = true; - ptr_vector indicator_set = fvar_lenTester_map[freeVar]; - for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { - expr * indicator = *it; - if (internal_variable_set.find(indicator) != internal_variable_set.end()) { - TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) - << " in fvar_lenTester_map[freeVar]" << std::endl;); - map_effectively_empty = false; - break; - } - } - CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); - } + finite_model_test_varlists.insert(testvar, varlist); + m_trail_stack.push(insert_obj_map >(finite_model_test_varlists, testvar) ); + return t_yes; + } - if (map_effectively_empty) { - // no length assertions for this free variable have ever been added. - TRACE("str", tout << "no length assertions yet" << std::endl;); + void theory_str::finite_model_test(expr * testvar, expr * str) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - fvar_len_count_map.insert(freeVar, 1); - unsigned int testNum = fvar_len_count_map[freeVar]; - - expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); - SASSERT(indicator); - - // since the map is "effectively empty", we can remove those variables that have left scope... - fvar_lenTester_map[freeVar].shrink(0); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map.insert(indicator, freeVar); - - expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); - SASSERT(lenTestAssert != NULL); - return lenTestAssert; - } else { - TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); - - expr * effectiveLenInd = NULL; - zstring effectiveLenIndiStr(""); - int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); - - TRACE("str", - tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; - for (int i = 0; i < lenTesterCount; ++i) { - expr * len_indicator = fvar_lenTester_map[freeVar][i]; - tout << mk_pp(len_indicator, m) << ": "; - bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); - tout << (effectiveInScope ? "in scope" : "NOT in scope"); - tout << std::endl; - } - ); - - int i = 0; - for (; i < lenTesterCount; ++i) { - expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; - // check whether this is in scope as well - if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { - TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + zstring s; + if (!u.str.is_string(str, s)) return; + if (s == "yes") { + TRACE("str", tout << "start finite model test for " << mk_pp(testvar, m) << std::endl;); + ptr_vector & vars = finite_model_test_varlists[testvar]; + for (ptr_vector::iterator it = vars.begin(); it != vars.end(); ++it) { + expr * v = *it; + bool v_has_eqc = false; + get_eqc_value(v, v_has_eqc); + if (v_has_eqc) { + TRACE("str", tout << "variable " << mk_pp(v,m) << " already equivalent to a string constant" << std::endl;); continue; } + // check for any sort of existing length tester we might interfere with + if (m_params.m_UseBinarySearch) { + if (binary_search_len_tester_stack.contains(v) && !binary_search_len_tester_stack[v].empty()) { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } else { + // start binary search as normal + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(binary_search_length_test(v, NULL, ""), m); + assert_implication(implLhs, implRhs); + } + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(v)) { + map_effectively_empty = true; + } + if (!map_effectively_empty) { + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[v]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no existing length testers for " << mk_pp(v, m) << std::endl;); + rational v_len; + rational v_lower_bound; + rational v_upper_bound; + expr_ref vLengthExpr(mk_strlen(v), m); + if (get_len_value(v, v_len)) { + TRACE("str", tout << "length = " << v_len.to_string() << std::endl;); + v_lower_bound = v_len; + v_upper_bound = v_len; + } else { + bool lower_bound_exists = lower_bound(vLengthExpr, v_lower_bound); + bool upper_bound_exists = upper_bound(vLengthExpr, v_upper_bound); + TRACE("str", tout << "bounds = [" << (lower_bound_exists?v_lower_bound.to_string():"?") + << ".." << (upper_bound_exists?v_upper_bound.to_string():"?") << "]" << std::endl;); + + // make sure the bounds are non-negative + if (lower_bound_exists && v_lower_bound.is_neg()) { + v_lower_bound = rational::zero(); + } + if (upper_bound_exists && v_upper_bound.is_neg()) { + v_upper_bound = rational::zero(); + } + + if (lower_bound_exists && upper_bound_exists) { + // easiest case. we will search within these bounds + } else if (upper_bound_exists && !lower_bound_exists) { + // search between 0 and the upper bound + v_lower_bound == rational::zero(); + } else if (lower_bound_exists && !upper_bound_exists) { + // check some finite portion of the search space + v_upper_bound = v_lower_bound + rational(10); + } else { + // no bounds information + v_lower_bound = rational::zero(); + v_upper_bound = v_lower_bound + rational(10); + } + } + // now create a fake length tester over this finite disjunction of lengths + + fvar_len_count_map[v] = 1; + unsigned int testNum = fvar_len_count_map[v]; + + expr_ref indicator(mk_internal_lenTest_var(v, testNum), m); + SASSERT(indicator); + m_trail.push_back(indicator); + + fvar_lenTester_map[v].shrink(0); + fvar_lenTester_map[v].push_back(indicator); + lenTester_fvar_map[indicator] = v; + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + for (rational l = v_lower_bound; l <= v_upper_bound; l += rational::one()) { + zstring lStr = zstring(l.to_string().c_str()); + expr_ref str_indicator(mk_string(lStr), m); + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + expr_ref and_expr(ctx.mk_eq_atom(or_expr, ctx.mk_eq_atom(vLengthExpr, m_autil.mk_numeral(l, true))), m); + andList.push_back(and_expr); + } + andList.push_back(mk_or(orList)); + expr_ref implLhs(ctx.mk_eq_atom(testvar, str), m); + expr_ref implRhs(mk_and(andList), m); + assert_implication(implLhs, implRhs); + } else { + TRACE("str", tout << "already found existing length testers for " << mk_pp(v, m) << std::endl;); + continue; + } + } + } // foreach (v in vars) + } // (s == "yes") + } + + void theory_str::more_len_tests(expr * lenTester, zstring lenTesterValue) { + ast_manager & m = get_manager(); + if (lenTester_fvar_map.contains(lenTester)) { + expr * fVar = lenTester_fvar_map[lenTester]; + expr * toAssert = gen_len_val_options_for_free_var(fVar, lenTester, lenTesterValue); + TRACE("str", tout << "asserting more length tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + void theory_str::more_value_tests(expr * valTester, zstring valTesterValue) { + ast_manager & m = get_manager(); + + expr * fVar = valueTester_fvar_map[valTester]; + if (m_params.m_UseBinarySearch) { + if (!binary_search_len_tester_stack.contains(fVar) || binary_search_len_tester_stack[fVar].empty()) { + TRACE("str", tout << "WARNING: no active length testers for " << mk_pp(fVar, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + expr * effectiveLenInd = binary_search_len_tester_stack[fVar].back(); + bool hasEqcValue; + expr * len_indicator_value = get_eqc_value(effectiveLenInd, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "WARNING: length tester " << mk_pp(effectiveLenInd, m) << " at top of stack for " << mk_pp(fVar, m) << " has no EQC value" << std::endl;); + } else { + // safety check + zstring effectiveLenIndiStr; + u.str.is_string(len_indicator_value, effectiveLenIndiStr); + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "less") { + TRACE("str", tout << "ERROR: illegal state -- requesting 'more value tests' but a length tester is not yet concrete!" << std::endl;); + UNREACHABLE(); + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } else { + int lenTesterCount = fvar_lenTester_map[fVar].size(); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr = ""; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[fVar][i]; bool indicatorHasEqcValue = false; expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); - TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << - " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); if (indicatorHasEqcValue) { zstring len_pIndiStr; u.str.is_string(len_indicator_value, len_pIndiStr); @@ -10281,318 +6974,3623 @@ expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTe effectiveLenIndiStr = len_pIndiStr; break; } - } else { - if (lenTesterInCbEq != len_indicator_pre) { - TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) - << " does not have an equivalence class value." - << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); - if (i > 0) { - effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; - bool effectiveHasEqcValue; - expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); - bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); - TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " - << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; - if (effectiveHasEqcValue) { - tout << "~= " << mk_pp(effective_eqc_value, m); - } else { - tout << "no eqc string constant"; - } - tout << std::endl;); - if (effectiveLenInd == lenTesterInCbEq) { - effectiveLenIndiStr = lenTesterValue; + } + } + expr * valueAssert = gen_free_var_options(fVar, effectiveLenInd, effectiveLenIndiStr, valTester, valTesterValue); + TRACE("str", tout << "asserting more value tests for free variable " << mk_ismt2_pp(fVar, m) << std::endl;); + if (valueAssert != NULL) { + assert_axiom(valueAssert); + } + } + } + + bool theory_str::free_var_attempt(expr * nn1, expr * nn2) { + ast_manager & m = get_manager(); + zstring nn2_str; + if (internal_lenTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + TRACE("str", tout << "acting on equivalence between length tester var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_len_tests(nn1, nn2_str); + return true; + } else if (internal_valTest_vars.contains(nn1) && u.str.is_string(nn2, nn2_str)) { + if (nn2_str == "more") { + TRACE("str", tout << "acting on equivalence between value var " << mk_ismt2_pp(nn1, m) + << " and constant " << mk_ismt2_pp(nn2, m) << std::endl;); + more_value_tests(nn1, nn2_str); + } + return true; + } else if (internal_unrollTest_vars.contains(nn1)) { + return true; + } else { + return false; + } + } + + void theory_str::handle_equality(expr * lhs, expr * rhs) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + // both terms must be of sort String + sort * lhs_sort = m.get_sort(lhs); + sort * rhs_sort = m.get_sort(rhs); + sort * str_sort = u.str.mk_string_sort(); + + if (lhs_sort != str_sort || rhs_sort != str_sort) { + TRACE("str", tout << "skip equality: not String sort" << std::endl;); + return; + } + + /* // temporarily disabled, we are borrowing these testers for something else + if (m_params.m_FiniteOverlapModels && !finite_model_test_varlists.empty()) { + if (finite_model_test_varlists.contains(lhs)) { + finite_model_test(lhs, rhs); return; + } else if (finite_model_test_varlists.contains(rhs)) { + finite_model_test(rhs, lhs); return; + } + } + */ + + if (free_var_attempt(lhs, rhs) || free_var_attempt(rhs, lhs)) { + return; + } + + if (u.str.is_concat(to_app(lhs)) && u.str.is_concat(to_app(rhs))) { + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + expr * nn1_value = get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1_arg0 = to_app(lhs)->get_arg(0); + expr * nn1_arg1 = to_app(lhs)->get_arg(1); + expr * nn2_arg0 = to_app(rhs)->get_arg(0); + expr * nn2_arg1 = to_app(rhs)->get_arg(1); + if (nn1_arg0 == nn2_arg0 && in_same_eqc(nn1_arg1, nn2_arg1)) { + TRACE("str", tout << "skip: lhs arg0 == rhs arg0" << std::endl;); + return; + } + + if (nn1_arg1 == nn2_arg1 && in_same_eqc(nn1_arg0, nn2_arg0)) { + TRACE("str", tout << "skip: lhs arg1 == rhs arg1" << std::endl;); + return; + } + } + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "opt_DeferEQCConsistencyCheck is set; deferring new_eq_check call" << std::endl;); + } else { + // newEqCheck() -- check consistency wrt. existing equivalence classes + if (!new_eq_check(lhs, rhs)) { + return; + } + } + + // BEGIN new_eq_handler() in strTheory + + { + rational nn1Len, nn2Len; + bool nn1Len_exists = get_len_value(lhs, nn1Len); + bool nn2Len_exists = get_len_value(rhs, nn2Len); + expr * emptyStr = mk_string(""); + + if (nn1Len_exists && nn1Len.is_zero()) { + if (!in_same_eqc(lhs, emptyStr) && rhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(lhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(lhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + + if (nn2Len_exists && nn2Len.is_zero()) { + if (!in_same_eqc(rhs, emptyStr) && lhs != emptyStr) { + expr_ref eql(ctx.mk_eq_atom(mk_strlen(rhs), mk_int(0)), m); + expr_ref eqr(ctx.mk_eq_atom(rhs, emptyStr), m); + expr_ref toAssert(ctx.mk_eq_atom(eql, eqr), m); + assert_axiom(toAssert); + } + } + } + + instantiate_str_eq_length_axiom(ctx.get_enode(lhs), ctx.get_enode(rhs)); + + // group terms by equivalence class (groupNodeInEqc()) + + std::set eqc_concat_lhs; + std::set eqc_var_lhs; + std::set eqc_const_lhs; + group_terms_by_eqc(lhs, eqc_concat_lhs, eqc_var_lhs, eqc_const_lhs); + + std::set eqc_concat_rhs; + std::set eqc_var_rhs; + std::set eqc_const_rhs; + group_terms_by_eqc(rhs, eqc_concat_rhs, eqc_var_rhs, eqc_const_rhs); + + TRACE("str", + tout << "lhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_lhs.begin(); it != eqc_concat_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_lhs.begin(); it != eqc_var_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_lhs.begin(); it != eqc_const_lhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + + tout << "rhs eqc:" << std::endl; + tout << "Concats:" << std::endl; + for (std::set::iterator it = eqc_concat_rhs.begin(); it != eqc_concat_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Variables:" << std::endl; + for (std::set::iterator it = eqc_var_rhs.begin(); it != eqc_var_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + tout << "Constants:" << std::endl; + for (std::set::iterator it = eqc_const_rhs.begin(); it != eqc_const_rhs.end(); ++it) { + expr * ex = *it; + tout << mk_ismt2_pp(ex, get_manager()) << std::endl; + } + ); + + // step 1: Concat == Concat + int hasCommon = 0; + if (eqc_concat_lhs.size() != 0 && eqc_concat_rhs.size() != 0) { + std::set::iterator itor1 = eqc_concat_lhs.begin(); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + if (eqc_concat_rhs.find(*itor1) != eqc_concat_rhs.end()) { + hasCommon = 1; + break; + } + } + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + if (eqc_concat_lhs.find(*itor2) != eqc_concat_lhs.end()) { + hasCommon = 1; + break; + } + } + if (hasCommon == 0) { + if (opt_ConcatOverlapAvoid) { + bool found = false; + // check each pair and take the first ones that won't immediately overlap + for (itor1 = eqc_concat_lhs.begin(); itor1 != eqc_concat_lhs.end() && !found; ++itor1) { + expr * concat_lhs = *itor1; + for (itor2 = eqc_concat_rhs.begin(); itor2 != eqc_concat_rhs.end() && !found; ++itor2) { + expr * concat_rhs = *itor2; + if (will_result_in_overlap(concat_lhs, concat_rhs)) { + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " will result in overlap; skipping." << std::endl;); } else { - if (effectiveHasEqcValue) { - u.str.is_string(effective_eqc_value, effectiveLenIndiStr); - } else { - NOT_IMPLEMENTED_YET(); - } + TRACE("str", tout << "Concats " << mk_pp(concat_lhs, m) << " and " + << mk_pp(concat_rhs, m) << " won't overlap. Simplifying here." << std::endl;); + simplify_concat_equality(concat_lhs, concat_rhs); + found = true; + break; } } - break; } - // lenTesterInCbEq == len_indicator_pre - else { - if (lenTesterValue != "more") { - effectiveLenInd = len_indicator_pre; - effectiveLenIndiStr = lenTesterValue; + if (!found) { + TRACE("str", tout << "All pairs of concats expected to overlap, falling back." << std::endl;); + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } else { + // default behaviour + simplify_concat_equality(*(eqc_concat_lhs.begin()), *(eqc_concat_rhs.begin())); + } + } + } + + // step 2: Concat == Constant + + if (eqc_const_lhs.size() != 0) { + expr * conStr = *(eqc_const_lhs.begin()); + std::set::iterator itor2 = eqc_concat_rhs.begin(); + for (; itor2 != eqc_concat_rhs.end(); itor2++) { + solve_concat_eq_str(*itor2, conStr); + } + } else if (eqc_const_rhs.size() != 0) { + expr* conStr = *(eqc_const_rhs.begin()); + std::set::iterator itor1 = eqc_concat_lhs.begin(); + for (; itor1 != eqc_concat_lhs.end(); itor1++) { + solve_concat_eq_str(*itor1, conStr); + } + } + + // simplify parents wrt. the equivalence class of both sides + bool nn1HasEqcValue = false; + bool nn2HasEqcValue = false; + // we want the Z3str2 eqc check here... + expr * nn1_value = z3str2_get_eqc_value(lhs, nn1HasEqcValue); + expr * nn2_value = z3str2_get_eqc_value(rhs, nn2HasEqcValue); + if (nn1HasEqcValue && !nn2HasEqcValue) { + simplify_parent(rhs, nn1_value); + } + + if (!nn1HasEqcValue && nn2HasEqcValue) { + simplify_parent(lhs, nn2_value); + } + + expr * nn1EqConst = NULL; + std::set nn1EqUnrollFuncs; + get_eqc_allUnroll(lhs, nn1EqConst, nn1EqUnrollFuncs); + expr * nn2EqConst = NULL; + std::set nn2EqUnrollFuncs; + get_eqc_allUnroll(rhs, nn2EqConst, nn2EqUnrollFuncs); + + if (nn2EqConst != NULL) { + for (std::set::iterator itor1 = nn1EqUnrollFuncs.begin(); itor1 != nn1EqUnrollFuncs.end(); itor1++) { + process_unroll_eq_const_str(*itor1, nn2EqConst); + } + } + + if (nn1EqConst != NULL) { + for (std::set::iterator itor2 = nn2EqUnrollFuncs.begin(); itor2 != nn2EqUnrollFuncs.end(); itor2++) { + process_unroll_eq_const_str(*itor2, nn1EqConst); + } + } + + } + + void theory_str::set_up_axioms(expr * ex) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + sort * ex_sort = m.get_sort(ex); + sort * str_sort = u.str.mk_string_sort(); + sort * bool_sort = m.mk_bool_sort(); + + family_id m_arith_fid = m.mk_family_id("arith"); + sort * int_sort = m.mk_sort(m_arith_fid, INT_SORT); + + if (ex_sort == str_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort String" << std::endl;); + // set up basic string axioms + enode * n = ctx.get_enode(ex); + SASSERT(n); + m_basicstr_axiom_todo.push_back(n); + TRACE("str", tout << "add " << mk_pp(ex, m) << " to m_basicstr_axiom_todo" << std::endl;); + + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_concat(ap)) { + // if ex is a concat, set up concat axioms later + m_concat_axiom_todo.push_back(n); + // we also want to check whether we can eval this concat, + // in case the rewriter did not totally finish with this term + m_concat_eval_todo.push_back(n); + } else if (u.str.is_length(ap)) { + // if the argument is a variable, + // keep track of this for later, we'll need it during model gen + expr * var = ap->get_arg(0); + app * aVar = to_app(var); + if (aVar->get_num_args() == 0 && !u.str.is_string(aVar)) { + input_var_in_len.insert(var); + } + } else if (u.str.is_at(ap) || u.str.is_extract(ap) || u.str.is_replace(ap)) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_itos(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } else if (ap->get_num_args() == 0 && !u.str.is_string(ap)) { + // if ex is a variable, add it to our list of variables + TRACE("str", tout << "tracking variable " << mk_ismt2_pp(ap, get_manager()) << std::endl;); + variable_set.insert(ex); + ctx.mark_as_relevant(ex); + // this might help?? + theory_var v = mk_var(n); + TRACE("str", tout << "variable " << mk_ismt2_pp(ap, get_manager()) << " is #" << v << std::endl;); + } + } + } else if (ex_sort == bool_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Bool" << std::endl;); + // set up axioms for boolean terms + + ensure_enode(ex); + if (ctx.e_internalized(ex)) { + enode * n = ctx.get_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + if (u.str.is_prefix(ap) || u.str.is_suffix(ap) || u.str.is_contains(ap) || u.str.is_in_re(ap)) { + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "WARNING: Bool term " << mk_ismt2_pp(ex, get_manager()) << " not internalized. Delaying axiom setup to prevent a crash." << std::endl;); + ENSURE(!search_started); // infinite loop prevention + m_delayed_axiom_setup_terms.push_back(ex); + return; + } + } else if (ex_sort == int_sort) { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of sort Int" << std::endl;); + // set up axioms for integer terms + enode * n = ensure_enode(ex); + SASSERT(n); + + if (is_app(ex)) { + app * ap = to_app(ex); + // TODO indexof2/lastindexof + if (u.str.is_index(ap) /* || is_Indexof2(ap) || is_LastIndexof(ap) */) { + m_library_aware_axiom_todo.push_back(n); + } else if (u.str.is_stoi(ap)) { + TRACE("str", tout << "found string-integer conversion term: " << mk_pp(ex, get_manager()) << std::endl;); + string_int_conversion_terms.push_back(ap); + m_library_aware_axiom_todo.push_back(n); + } + } + } else { + TRACE("str", tout << "setting up axioms for " << mk_ismt2_pp(ex, get_manager()) << + ": expr is of wrong sort, ignoring" << std::endl;); + } + + // if expr is an application, recursively inspect all arguments + if (is_app(ex)) { + app * term = (app*)ex; + unsigned num_args = term->get_num_args(); + for (unsigned i = 0; i < num_args; i++) { + set_up_axioms(term->get_arg(i)); + } + } + } + + void theory_str::add_theory_assumptions(expr_ref_vector & assumptions) { + TRACE("str", tout << "add overlap assumption for theory_str" << std::endl;); + symbol strOverlap("!!TheoryStrOverlapAssumption!!"); + seq_util m_sequtil(get_manager()); + sort * s = get_manager().mk_bool_sort(); + m_theoryStrOverlapAssumption_term = expr_ref(get_manager().mk_const(strOverlap, s), get_manager()); + assumptions.push_back(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + } + + lbool theory_str::validate_unsat_core(expr_ref_vector & unsat_core) { + bool assumptionFound = false; + + app * target_term = to_app(get_manager().mk_not(m_theoryStrOverlapAssumption_term)); + get_context().internalize(target_term, false); + for (unsigned i = 0; i < unsat_core.size(); ++i) { + app * core_term = to_app(unsat_core.get(i)); + // not sure if this is the correct way to compare terms in this context + enode * e1; + enode * e2; + e1 = get_context().get_enode(target_term); + e2 = get_context().get_enode(core_term); + if (e1 == e2) { + TRACE("str", tout << "overlap detected in unsat core, changing UNSAT to UNKNOWN" << std::endl;); + assumptionFound = true; + return l_undef; + } + } + + return l_false; + } + + void theory_str::init_search_eh() { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", + tout << "dumping all asserted formulas:" << std::endl; + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? " (rel)" : " (NOT REL)") << std::endl; + } + ); + /* + * Recursive descent through all asserted formulas to set up axioms. + * Note that this is just the input structure and not necessarily things + * that we know to be true or false. We're just doing this to see + * which terms are explicitly mentioned. + */ + unsigned nFormulas = ctx.get_num_asserted_formulas(); + for (unsigned i = 0; i < nFormulas; ++i) { + expr * ex = ctx.get_asserted_formula(i); + set_up_axioms(ex); + } + + /* + * Similar recursive descent, except over all initially assigned terms. + * This is done to find equalities between terms, etc. that we otherwise + * might not get a chance to see. + */ + + /* + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + if (m.is_eq(ex)) { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) << + ": expr is equality" << std::endl;); + app * eq = (app*)ex; + SASSERT(eq->get_num_args() == 2); + expr * lhs = eq->get_arg(0); + expr * rhs = eq->get_arg(1); + + enode * e_lhs = ctx.get_enode(lhs); + enode * e_rhs = ctx.get_enode(rhs); + std::pair eq_pair(e_lhs, e_rhs); + m_str_eq_todo.push_back(eq_pair); + } else { + TRACE("str", tout << "processing assignment " << mk_ismt2_pp(ex, m) + << ": expr ignored" << std::endl;); + } + } + */ + + // this might be cheating but we need to make sure that certain maps are populated + // before the first call to new_eq_eh() + propagate(); + + TRACE("str", tout << "search started" << std::endl;); + search_started = true; + } + + void theory_str::new_eq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new eq: v#" << x << " = v#" << y << std::endl;); + TRACE("str", tout << "new eq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " = " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + + /* + if (m_find.find(x) == m_find.find(y)) { + return; + } + */ + handle_equality(get_enode(x)->get_owner(), get_enode(y)->get_owner()); + + // replicate Z3str2 behaviour: merge eqc **AFTER** handle_equality + m_find.merge(x, y); + } + + void theory_str::new_diseq_eh(theory_var x, theory_var y) { + //TRACE("str", tout << "new diseq: v#" << x << " != v#" << y << std::endl;); + TRACE("str", tout << "new diseq: " << mk_ismt2_pp(get_enode(x)->get_owner(), get_manager()) << " != " << + mk_ismt2_pp(get_enode(y)->get_owner(), get_manager()) << std::endl;); + } + + void theory_str::relevant_eh(app * n) { + TRACE("str", tout << "relevant: " << mk_ismt2_pp(n, get_manager()) << std::endl;); + } + + void theory_str::assign_eh(bool_var v, bool is_true) { + context & ctx = get_context(); + TRACE("str", tout << "assert: v" << v << " #" << ctx.bool_var2expr(v)->get_id() << " is_true: " << is_true << std::endl;); + } + + void theory_str::push_scope_eh() { + theory::push_scope_eh(); + m_trail_stack.push_scope(); + + sLevel += 1; + TRACE("str", tout << "push to " << sLevel << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + } + + void theory_str::recursive_check_variable_scope(expr * ex) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + if (is_app(ex)) { + app * a = to_app(ex); + if (a->get_num_args() == 0) { + // we only care about string variables + sort * s = m.get_sort(ex); + sort * string_sort = u.str.mk_string_sort(); + if (s != string_sort) { + return; + } + // base case: string constant / var + if (u.str.is_string(a)) { + return; + } else { + // assume var + if (variable_set.find(ex) == variable_set.end() + && internal_variable_set.find(ex) == internal_variable_set.end()) { + TRACE("str", tout << "WARNING: possible reference to out-of-scope variable " << mk_pp(ex, m) << std::endl;); + } + } + } else { + for (unsigned i = 0; i < a->get_num_args(); ++i) { + recursive_check_variable_scope(a->get_arg(i)); + } + } + } + } + + void theory_str::check_variable_scope() { + if (!opt_CheckVariableScope) { + return; + } + + if (!is_trace_enabled("t_str_detail")) { + return; + } + + TRACE("str", tout << "checking scopes of variables in the current assignment" << std::endl;); + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + recursive_check_variable_scope(ex); + } + } + + void theory_str::pop_scope_eh(unsigned num_scopes) { + sLevel -= num_scopes; + TRACE("str", tout << "pop " << num_scopes << " to " << sLevel << std::endl;); + context & ctx = get_context(); + ast_manager & m = get_manager(); + + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign_on_scope_change")) { dump_assignments(); }); + + // list of expr* to remove from cut_var_map + ptr_vector cutvarmap_removes; + + obj_map >::iterator varItor = cut_var_map.begin(); + while (varItor != cut_var_map.end()) { + expr * e = varItor->m_key; + std::stack & val = cut_var_map[varItor->m_key]; + while ((val.size() > 0) && (val.top()->level != 0) && (val.top()->level >= sLevel)) { + TRACE("str", tout << "remove cut info for " << mk_pp(e, m) << std::endl; print_cut_var(e, tout);); + T_cut * aCut = val.top(); + val.pop(); + // dealloc(aCut); + } + if (val.size() == 0) { + cutvarmap_removes.insert(varItor->m_key); + } + varItor++; + } + + if (!cutvarmap_removes.empty()) { + ptr_vector::iterator it = cutvarmap_removes.begin(); + for (; it != cutvarmap_removes.end(); ++it) { + expr * ex = *it; + cut_var_map.remove(ex); + } + } + + ptr_vector new_m_basicstr; + for (ptr_vector::iterator it = m_basicstr_axiom_todo.begin(); it != m_basicstr_axiom_todo.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + TRACE("str", tout << "consider deleting " << mk_pp(a, get_manager()) + << ", enode scope level is " << e->get_iscope_lvl() + << std::endl;); + if (e->get_iscope_lvl() <= (unsigned)sLevel) { + new_m_basicstr.push_back(e); + } + } + m_basicstr_axiom_todo.reset(); + m_basicstr_axiom_todo = new_m_basicstr; + + m_trail_stack.pop_scope(num_scopes); + theory::pop_scope_eh(num_scopes); + + //check_variable_scope(); + } + + void theory_str::dump_assignments() { + TRACE_CODE( + ast_manager & m = get_manager(); + context & ctx = get_context(); + tout << "dumping all assignments:" << std::endl; + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + for (expr_ref_vector::iterator i = assignments.begin(); i != assignments.end(); ++i) { + expr * ex = *i; + tout << mk_ismt2_pp(ex, m) << (ctx.is_relevant(ex) ? "" : " (NOT REL)") << std::endl; + } + ); + } + + void theory_str::classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + // check whether the node is a string variable; + // testing set membership here bypasses several expensive checks. + // note that internal variables don't count if they're only length tester / value tester vars. + if (variable_set.find(node) != variable_set.end() + && internal_lenTest_vars.find(node) == internal_lenTest_vars.end() + && internal_valTest_vars.find(node) == internal_valTest_vars.end() + && internal_unrollTest_vars.find(node) == internal_unrollTest_vars.end()) { + if (varMap[node] != 1) { + TRACE("str", tout << "new variable: " << mk_pp(node, get_manager()) << std::endl;); + } + varMap[node] = 1; + } + // check whether the node is a function that we want to inspect + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } else if (u.str.is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + bool arg0HasEq = false; + bool arg1HasEq = false; + expr * arg0Val = get_eqc_value(arg0, arg0HasEq); + expr * arg1Val = get_eqc_value(arg1, arg1HasEq); + + int canskip = 0; + zstring tmp; + u.str.is_string(arg0Val, tmp); + if (arg0HasEq && tmp.empty()) { + canskip = 1; + } + u.str.is_string(arg1Val, tmp); + if (canskip == 0 && arg1HasEq && tmp.empty()) { + canskip = 1; + } + if (canskip == 0 && concatMap.find(node) == concatMap.end()) { + concatMap[node] = 1; + } + } else if (u.re.is_unroll(aNode)) { + // Unroll + if (unrollMap.find(node) == unrollMap.end()) { + unrollMap[node] = 1; + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + classify_ast_by_type(arg, varMap, concatMap, unrollMap); + } + } + } + + // NOTE: this function used to take an argument `Z3_ast node`; + // it was not used and so was removed from the signature + void theory_str::classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap) { + + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + expr * argAst = *it; + // the original code jumped through some hoops to check whether the AST node + // is a function, then checked whether that function is "interesting". + // however, the only thing that's considered "interesting" is an equality predicate. + // so we bypass a huge amount of work by doing the following... + + if (m.is_eq(argAst)) { + TRACE("str", tout + << "eq ast " << mk_pp(argAst, m) << " is between args of sort " + << m.get_sort(to_app(argAst)->get_arg(0))->get_name() + << std::endl;); + classify_ast_by_type(argAst, varMap, concatMap, unrollMap); + } + } + } + + inline expr * theory_str::get_alias_index_ast(std::map & aliasIndexMap, expr * node) { + if (aliasIndexMap.find(node) != aliasIndexMap.end()) + return aliasIndexMap[node]; + else + return node; + } + + inline expr * theory_str::getMostLeftNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgL = aNode->get_arg(0); + return getMostLeftNodeInConcat(concatArgL); + } + } + + inline expr * theory_str::getMostRightNodeInConcat(expr * node) { + app * aNode = to_app(node); + if (!u.str.is_concat(aNode)) { + return node; + } else { + expr * concatArgR = aNode->get_arg(1); + return getMostRightNodeInConcat(concatArgR); + } + } + + void theory_str::trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap) { +#ifdef _TRACE + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + { + tout << "(0) alias: variables" << std::endl; + std::map > aliasSumMap; + std::map::iterator itor0 = aliasIndexMap.begin(); + for (; itor0 != aliasIndexMap.end(); itor0++) { + aliasSumMap[itor0->second][itor0->first] = 1; + } + std::map >::iterator keyItor = aliasSumMap.begin(); + for (; keyItor != aliasSumMap.end(); keyItor++) { + tout << " * "; + tout << mk_pp(keyItor->first, mgr); + tout << " : "; + std::map::iterator innerItor = keyItor->second.begin(); + for (; innerItor != keyItor->second.end(); innerItor++) { + tout << mk_pp(innerItor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(1) var = constStr:" << std::endl; + std::map::iterator itor1 = var_eq_constStr_map.begin(); + for (; itor1 != var_eq_constStr_map.end(); itor1++) { + tout << " * "; + tout << mk_pp(itor1->first, mgr); + tout << " = "; + tout << mk_pp(itor1->second, mgr); + if (!in_same_eqc(itor1->first, itor1->second)) { + tout << " (not true in ctx)"; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(2) var = concat:" << std::endl; + std::map >::iterator itor2 = var_eq_concat_map.begin(); + for (; itor2 != var_eq_concat_map.end(); itor2++) { + tout << " * "; + tout << mk_pp(itor2->first, mgr); + tout << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(3) var = unrollFunc:" << std::endl; + std::map >::iterator itor2 = var_eq_unroll_map.begin(); + for (; itor2 != var_eq_unroll_map.end(); itor2++) { + tout << " * " << mk_pp(itor2->first, mgr) << " = { "; + std::map::iterator i_itor = itor2->second.begin(); + for (; i_itor != itor2->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr) << ", "; + } + tout << " }" << std::endl; + } + tout << std::endl; + } + + { + tout << "(4) concat = constStr:" << std::endl; + std::map::iterator itor3 = concat_eq_constStr_map.begin(); + for (; itor3 != concat_eq_constStr_map.end(); itor3++) { + tout << " * "; + tout << mk_pp(itor3->first, mgr); + tout << " = "; + tout << mk_pp(itor3->second, mgr); + tout << std::endl; + + } + tout << std::endl; + } + + { + tout << "(5) eq concats:" << std::endl; + std::map >::iterator itor4 = concat_eq_concat_map.begin(); + for (; itor4 != concat_eq_concat_map.end(); itor4++) { + if (itor4->second.size() > 1) { + std::map::iterator i_itor = itor4->second.begin(); + tout << " * "; + for (; i_itor != itor4->second.end(); i_itor++) { + tout << mk_pp(i_itor->first, mgr); + tout << " , "; + } + tout << std::endl; + } + } + tout << std::endl; + } + + { + tout << "(6) eq unrolls:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + std::set::iterator i_itor = itor5->second.begin(); + for (; i_itor != itor5->second.end(); i_itor++) { + tout << mk_pp(*i_itor, mgr) << ", "; + } + tout << std::endl; + } + tout << std::endl; + } + + { + tout << "(7) unroll = concats:" << std::endl; + std::map >::iterator itor5 = unrollGroupMap.begin(); + for (; itor5 != unrollGroupMap.end(); itor5++) { + tout << " * "; + expr * unroll = itor5->first; + tout << mk_pp(unroll, mgr) << std::endl; + enode * e_curr = ctx.get_enode(unroll); + enode * e_curr_end = e_curr; + do { + app * curr = e_curr->get_owner(); + if (u.str.is_concat(curr)) { + tout << " >>> " << mk_pp(curr, mgr) << std::endl; + } + e_curr = e_curr->get_next(); + } while (e_curr != e_curr_end); + tout << std::endl; + } + tout << std::endl; + } +#else + return; +#endif // _TRACE + } + + + /* + * Dependence analysis from current context assignment + * - "freeVarMap" contains a set of variables that doesn't constrained by Concats. + * But it's possible that it's bounded by unrolls + * For the case of + * (1) var1 = unroll(r1, t1) + * var1 is in the freeVarMap + * > should unroll r1 for var1 + * (2) var1 = unroll(r1, t1) /\ var1 = Concat(var2, var3) + * var2, var3 are all in freeVar + * > should split the unroll function so that var2 and var3 are bounded by new unrolls + */ + int theory_str::ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map) { + std::map concatMap; + std::map unrollMap; + std::map aliasIndexMap; + std::map var_eq_constStr_map; + std::map concat_eq_constStr_map; + std::map > var_eq_unroll_map; + std::map > concat_eq_concat_map; + std::map > depMap; + + context & ctx = get_context(); + ast_manager & m = get_manager(); + + // note that the old API concatenated these assignments into + // a massive conjunction; we may have the opportunity to avoid that here + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + // Step 1: get variables / concat AST appearing in the context + // the thing we iterate over should just be variable_set - internal_variable_set + // so we avoid computing the set difference (but this might be slower) + for(obj_hashtable::iterator it = variable_set.begin(); it != variable_set.end(); ++it) { + expr* var = *it; + if (internal_variable_set.find(var) == internal_variable_set.end()) { + TRACE("str", tout << "new variable: " << mk_pp(var, m) << std::endl;); + strVarMap[*it] = 1; + } + } + classify_ast_by_type_in_positive_context(strVarMap, concatMap, unrollMap); + + std::map aliasUnrollSet; + std::map::iterator unrollItor = unrollMap.begin(); + for (; unrollItor != unrollMap.end(); ++unrollItor) { + if (aliasUnrollSet.find(unrollItor->first) != aliasUnrollSet.end()) { + continue; + } + expr * aRoot = NULL; + enode * e_currEqc = ctx.get_enode(unrollItor->first); + enode * e_curr = e_currEqc; + do { + app * curr = e_currEqc->get_owner(); + if (u.re.is_unroll(curr)) { + if (aRoot == NULL) { + aRoot = curr; + } + aliasUnrollSet[curr] = aRoot; + } + e_currEqc = e_currEqc->get_next(); + } while (e_currEqc != e_curr); + } + + for (unrollItor = unrollMap.begin(); unrollItor != unrollMap.end(); unrollItor++) { + expr * unrFunc = unrollItor->first; + expr * urKey = aliasUnrollSet[unrFunc]; + unrollGroupMap[urKey].insert(unrFunc); + } + + // Step 2: collect alias relation + // e.g. suppose we have the equivalence class {x, y, z}; + // then we set aliasIndexMap[y] = x + // and aliasIndexMap[z] = x + + std::map::iterator varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + if (aliasIndexMap.find(varItor->first) != aliasIndexMap.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = varItor->first; + do { + if (variable_set.find(curr) != variable_set.end()) { + if (aRoot == NULL) { + aRoot = curr; + } else { + aliasIndexMap[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != varItor->first); + } + + // Step 3: Collect interested cases + + varItor = strVarMap.begin(); + for (; varItor != strVarMap.end(); ++varItor) { + expr * deAliasNode = get_alias_index_ast(aliasIndexMap, varItor->first); + // Case 1: variable = string constant + // e.g. z = "str1" ::= var_eq_constStr_map[z] = "str1" + + if (var_eq_constStr_map.find(deAliasNode) == var_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasNode, nodeHasEqcValue); + if (nodeHasEqcValue) { + var_eq_constStr_map[deAliasNode] = nodeValue; + } + } + + // Case 2: var_eq_concat + // e.g. z = concat("str1", b) ::= var_eq_concat[z][concat(c, "str2")] = 1 + // var_eq_unroll + // e.g. z = unroll(...) ::= var_eq_unroll[z][unroll(...)] = 1 + + if (var_eq_concat_map.find(deAliasNode) == var_eq_concat_map.end()) { + expr * curr = get_eqc_next(deAliasNode); + while (curr != deAliasNode) { + app * aCurr = to_app(curr); + // collect concat + if (u.str.is_concat(aCurr)) { + expr * arg0 = aCurr->get_arg(0); + expr * arg1 = aCurr->get_arg(1); + bool arg0HasEqcValue = false; + bool arg1HasEqcValue = false; + expr * arg0_value = get_eqc_value(arg0, arg0HasEqcValue); + expr * arg1_value = get_eqc_value(arg1, arg1HasEqcValue); + + bool is_arg0_emptyStr = false; + if (arg0HasEqcValue) { + zstring strval; + u.str.is_string(arg0_value, strval); + if (strval.empty()) { + is_arg0_emptyStr = true; + } + } + + bool is_arg1_emptyStr = false; + if (arg1HasEqcValue) { + zstring strval; + u.str.is_string(arg1_value, strval); + if (strval.empty()) { + is_arg1_emptyStr = true; + } + } + + if (!is_arg0_emptyStr && !is_arg1_emptyStr) { + var_eq_concat_map[deAliasNode][curr] = 1; + } + } else if (u.re.is_unroll(to_app(curr))) { + var_eq_unroll_map[deAliasNode][curr] = 1; + } + + curr = get_eqc_next(curr); + } + } + + } // for(varItor in strVarMap) + + // -------------------------------------------------- + // * collect aliasing relation among eq concats + // e.g EQC={concat1, concat2, concat3} + // concats_eq_Index_map[concat2] = concat1 + // concats_eq_Index_map[concat3] = concat1 + // -------------------------------------------------- + + std::map concats_eq_index_map; + std::map::iterator concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + continue; + } + expr * aRoot = NULL; + expr * curr = concatItor->first; + do { + if (u.str.is_concat(to_app(curr))) { + if (aRoot == NULL) { + aRoot = curr; + } else { + concats_eq_index_map[curr] = aRoot; + } + } + curr = get_eqc_next(curr); + } while (curr != concatItor->first); + } + + concatItor = concatMap.begin(); + for(; concatItor != concatMap.end(); ++concatItor) { + expr * deAliasConcat = NULL; + if (concats_eq_index_map.find(concatItor->first) != concats_eq_index_map.end()) { + deAliasConcat = concats_eq_index_map[concatItor->first]; + } else { + deAliasConcat = concatItor->first; + } + + // (3) concat_eq_conststr, e.g. concat(a,b) = "str1" + if (concat_eq_constStr_map.find(deAliasConcat) == concat_eq_constStr_map.end()) { + bool nodeHasEqcValue = false; + expr * nodeValue = get_eqc_value(deAliasConcat, nodeHasEqcValue); + if (nodeHasEqcValue) { + concat_eq_constStr_map[deAliasConcat] = nodeValue; + } + } + + // (4) concat_eq_concat, e.g. + // concat(a,b) = concat("str1", c) AND z = concat(a,b) AND z = concat(e,f) + if (concat_eq_concat_map.find(deAliasConcat) == concat_eq_concat_map.end()) { + expr * curr = deAliasConcat; + do { + if (u.str.is_concat(to_app(curr))) { + // curr cannot be reduced + if (concatMap.find(curr) != concatMap.end()) { + concat_eq_concat_map[deAliasConcat][curr] = 1; + } + } + curr = get_eqc_next(curr); + } while (curr != deAliasConcat); + } + } + + // print some debugging info + TRACE("str", trace_ctx_dep(tout, aliasIndexMap, var_eq_constStr_map, + var_eq_concat_map, var_eq_unroll_map, + concat_eq_constStr_map, concat_eq_concat_map, unrollGroupMap);); + + if (!contain_pair_bool_map.empty()) { + compute_contains(aliasIndexMap, concats_eq_index_map, var_eq_constStr_map, concat_eq_constStr_map, var_eq_concat_map); + } + + // step 4: dependence analysis + + // (1) var = string constant + for (std::map::iterator itor = var_eq_constStr_map.begin(); + itor != var_eq_constStr_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + expr * strAst = itor->second; + depMap[var][strAst] = 1; + } + + // (2) var = concat + for (std::map >::iterator itor = var_eq_concat_map.begin(); + itor != var_eq_concat_map.end(); ++itor) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); ++itor1) { + expr * concat = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concat, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); ++itor2) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[var].find(varInConcat) != depMap[var].end() && depMap[var][varInConcat] == 1)) { + depMap[var][varInConcat] = 2; + } + } + } + } + + for (std::map >::iterator itor = var_eq_unroll_map.begin(); + itor != var_eq_unroll_map.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * unrollFunc = itor1->first; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(unrollFunc, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInFunc = get_alias_index_ast(aliasIndexMap, itor2->first); + + TRACE("str", tout << "var in unroll = " << + mk_ismt2_pp(itor2->first, m) << std::endl + << "dealiased var = " << mk_ismt2_pp(varInFunc, m) << std::endl;); + + // it's possible that we have both (Unroll $$_regVar_0 $$_unr_0) /\ (Unroll abcd $$_unr_0), + // while $$_regVar_0 = "abcd" + // have to exclude such cases + bool varHasValue = false; + get_eqc_value(varInFunc, varHasValue); + if (varHasValue) + continue; + + if (depMap[var].find(varInFunc) == depMap[var].end()) { + depMap[var][varInFunc] = 6; + } + } + } + } + + // (3) concat = string constant + for (std::map::iterator itor = concat_eq_constStr_map.begin(); + itor != concat_eq_constStr_map.end(); itor++) { + expr * concatAst = itor->first; + expr * constStr = itor->second; + std::map inVarMap; + std::map inConcatMap; + std::map inUnrollMap; + classify_ast_by_type(concatAst, inVarMap, inConcatMap, inUnrollMap); + for (std::map::iterator itor2 = inVarMap.begin(); itor2 != inVarMap.end(); itor2++) { + expr * varInConcat = get_alias_index_ast(aliasIndexMap, itor2->first); + if (!(depMap[varInConcat].find(constStr) != depMap[varInConcat].end() && depMap[varInConcat][constStr] == 1)) + depMap[varInConcat][constStr] = 3; + } + } + + // (4) equivalent concats + // - possibility 1 : concat("str", v1) = concat(concat(v2, v3), v4) = concat(v5, v6) + // ==> v2, v5 are constrained by "str" + // - possibility 2 : concat(v1, "str") = concat(v2, v3) = concat(v4, v5) + // ==> v2, v4 are constrained by "str" + //-------------------------------------------------------------- + + std::map mostLeftNodes; + std::map mostRightNodes; + + std::map mLIdxMap; + std::map > mLMap; + std::map mRIdxMap; + std::map > mRMap; + std::set nSet; + + for (std::map >::iterator itor = concat_eq_concat_map.begin(); + itor != concat_eq_concat_map.end(); itor++) { + mostLeftNodes.clear(); + mostRightNodes.clear(); + + expr * mLConst = NULL; + expr * mRConst = NULL; + + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + expr * concatNode = itor1->first; + expr * mLNode = getMostLeftNodeInConcat(concatNode); + zstring strval; + if (u.str.is_string(to_app(mLNode), strval)) { + if (mLConst == NULL && strval.empty()) { + mLConst = mLNode; + } + } else { + mostLeftNodes[mLNode] = concatNode; + } + + expr * mRNode = getMostRightNodeInConcat(concatNode); + if (u.str.is_string(to_app(mRNode), strval)) { + if (mRConst == NULL && strval.empty()) { + mRConst = mRNode; + } + } else { + mostRightNodes[mRNode] = concatNode; + } + } + + if (mLConst != NULL) { + // ------------------------------------------------------------------------------------- + // The left most variable in a concat is constrained by a constant string in eqc concat + // ------------------------------------------------------------------------------------- + // e.g. Concat(x, ...) = Concat("abc", ...) + // ------------------------------------------------------------------------------------- + for (std::map::iterator itor1 = mostLeftNodes.begin(); + itor1 != mostLeftNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mLConst) == depMap[deVar].end() || depMap[deVar][mLConst] != 1) { + depMap[deVar][mLConst] = 4; + } + } + } + + { + // ------------------------------------------------------------------------------------- + // The left most variables in eqc concats are constrained by each other + // ------------------------------------------------------------------------------------- + // e.g. concat(x, ...) = concat(u, ...) = ... + // x and u are constrained by each other + // ------------------------------------------------------------------------------------- + nSet.clear(); + std::map::iterator itl = mostLeftNodes.begin(); + for (; itl != mostLeftNodes.end(); itl++) { + bool lfHasEqcValue = false; + get_eqc_value(itl->first, lfHasEqcValue); + if (lfHasEqcValue) + continue; + expr * deVar = get_alias_index_ast(aliasIndexMap, itl->first); + nSet.insert(deVar); + } + + if (nSet.size() > 1) { + int lId = -1; + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + if (mLIdxMap.find(*itor2) != mLIdxMap.end()) { + lId = mLIdxMap[*itor2]; break; } } - } // !indicatorHasEqcValue - } // for (i : [0..lenTesterCount-1]) - if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { - TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); - expr_ref indicator(m); - unsigned int testNum = 0; - - TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr - << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); - - if (i == lenTesterCount) { - fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; - testNum = fvar_len_count_map[freeVar]; - indicator = mk_internal_lenTest_var(freeVar, testNum); - fvar_lenTester_map[freeVar].push_back(indicator); - lenTester_fvar_map.insert(indicator, freeVar); - } else { - indicator = fvar_lenTester_map[freeVar][i]; - refresh_theory_var(indicator); - testNum = i + 1; + if (lId == -1) + lId = mLMap.size(); + for (std::set::iterator itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool itorHasEqcValue = false; + get_eqc_value(*itor2, itorHasEqcValue); + if (itorHasEqcValue) + continue; + mLIdxMap[*itor2] = lId; + mLMap[lId].insert(*itor2); + } } + } + + if (mRConst != NULL) { + for (std::map::iterator itor1 = mostRightNodes.begin(); + itor1 != mostRightNodes.end(); itor1++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itor1->first); + if (depMap[deVar].find(mRConst) == depMap[deVar].end() || depMap[deVar][mRConst] != 1) { + depMap[deVar][mRConst] = 5; + } + } + } + + { + nSet.clear(); + std::map::iterator itr = mostRightNodes.begin(); + for (; itr != mostRightNodes.end(); itr++) { + expr * deVar = get_alias_index_ast(aliasIndexMap, itr->first); + nSet.insert(deVar); + } + if (nSet.size() > 1) { + int rId = -1; + std::set::iterator itor2 = nSet.begin(); + for (; itor2 != nSet.end(); itor2++) { + if (mRIdxMap.find(*itor2) != mRIdxMap.end()) { + rId = mRIdxMap[*itor2]; + break; + } + } + if (rId == -1) + rId = mRMap.size(); + for (itor2 = nSet.begin(); itor2 != nSet.end(); itor2++) { + bool rHasEqcValue = false; + get_eqc_value(*itor2, rHasEqcValue); + if (rHasEqcValue) + continue; + mRIdxMap[*itor2] = rId; + mRMap[rId].insert(*itor2); + } + } + } + } + + // print the dependence map + TRACE("str", + tout << "Dependence Map" << std::endl; + for(std::map >::iterator itor = depMap.begin(); itor != depMap.end(); itor++) { + tout << mk_pp(itor->first, m); + rational nnLen; + bool nnLen_exists = get_len_value(itor->first, nnLen); + tout << " [len = " << (nnLen_exists ? nnLen.to_string() : "?") << "] \t-->\t"; + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + tout << mk_pp(itor1->first, m) << "(" << itor1->second << "), "; + } + tout << std::endl; + } + ); + + // step, errr, 5: compute free variables based on the dependence map + + // the case dependence map is empty, every var in VarMap is free + //--------------------------------------------------------------- + // remove L/R most var in eq concat since they are constrained with each other + std::map > lrConstrainedMap; + for (std::map >::iterator itor = mLMap.begin(); itor != mLMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + for (std::map >::iterator itor = mRMap.begin(); itor != mRMap.end(); itor++) { + for (std::set::iterator it1 = itor->second.begin(); it1 != itor->second.end(); it1++) { + std::set::iterator it2 = it1; + it2++; + for (; it2 != itor->second.end(); it2++) { + expr * n1 = *it1; + expr * n2 = *it2; + lrConstrainedMap[n1][n2] = 1; + lrConstrainedMap[n2][n1] = 1; + } + } + } + + if (depMap.size() == 0) { + std::map::iterator itor = strVarMap.begin(); + for (; itor != strVarMap.end(); itor++) { + expr * var = get_alias_index_ast(aliasIndexMap, itor->first); + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else { + // if the keys in aliasIndexMap are not contained in keys in depMap, they are free + // e.g., x= y /\ x = z /\ t = "abc" + // aliasIndexMap[y]= x, aliasIndexMap[z] = x + // depMap t ~ "abc"(1) + // x should be free + std::map::iterator itor2 = strVarMap.begin(); + for (; itor2 != strVarMap.end(); itor2++) { + if (aliasIndexMap.find(itor2->first) != aliasIndexMap.end()) { + expr * var = aliasIndexMap[itor2->first]; + if (depMap.find(var) == depMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } else if (aliasIndexMap.find(itor2->first) == aliasIndexMap.end()) { + // if a variable is not in aliasIndexMap and not in depMap, it's free + if (depMap.find(itor2->first) == depMap.end()) { + expr * var = itor2->first; + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + } + } + } + + std::map >::iterator itor = depMap.begin(); + for (; itor != depMap.end(); itor++) { + for (std::map::iterator itor1 = itor->second.begin(); itor1 != itor->second.end(); itor1++) { + if (variable_set.find(itor1->first) != variable_set.end()) { // expr type = var + expr * var = get_alias_index_ast(aliasIndexMap, itor1->first); + // if a var is dep on itself and all dependence are type 2, it's a free variable + // e.g {y --> x(2), y(2), m --> m(2), n(2)} y,m are free + { + if (depMap.find(var) == depMap.end()) { + if (freeVarMap.find(var) == freeVarMap.end()) { + if (lrConstrainedMap.find(var) == lrConstrainedMap.end()) { + freeVarMap[var] = 1; + } else { + int lrConstainted = 0; + std::map::iterator lrit = freeVarMap.begin(); + for (; lrit != freeVarMap.end(); lrit++) { + if (lrConstrainedMap[var].find(lrit->first) != lrConstrainedMap[var].end()) { + lrConstainted = 1; + break; + } + } + if (lrConstainted == 0) { + freeVarMap[var] = 1; + } + } + + } else { + freeVarMap[var] = freeVarMap[var] + 1; + } + } + } + } + } + } + } + + return 0; + } + + // Check agreement between integer and string theories for the term a = (str.to-int S). + // Returns true if axioms were added, and false otherwise. + bool theory_str::finalcheck_str2int(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * S = a->get_arg(0); + + // check integer theory + rational Ival; + bool Ival_exists = get_value(a, Ival); + if (Ival_exists) { + TRACE("str", tout << "integer theory assigns " << mk_pp(a, m) << " = " << Ival.to_string() << std::endl;); + // if that value is not -1, we can assert (str.to-int S) = Ival --> S = "Ival" + if (!Ival.is_minus_one()) { + zstring Ival_str(Ival.to_string().c_str()); + expr_ref premise(ctx.mk_eq_atom(a, m_autil.mk_numeral(Ival, true)), m); + expr_ref conclusion(ctx.mk_eq_atom(S, mk_string(Ival_str)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "integer theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + + return axiomAdd; + } + + bool theory_str::finalcheck_int2str(app * a) { + bool axiomAdd = false; + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr * N = a->get_arg(0); + + // check string theory + bool Sval_expr_exists; + expr * Sval_expr = get_eqc_value(a, Sval_expr_exists); + if (Sval_expr_exists) { + zstring Sval; + u.str.is_string(Sval_expr, Sval); + TRACE("str", tout << "string theory assigns \"" << mk_pp(a, m) << " = " << Sval << "\n";); + // empty string --> integer value < 0 + if (Sval.empty()) { + // ignore this. we should already assert the axiom for what happens when the string is "" + } else { + // nonempty string --> convert to correct integer value, or disallow it + rational convertedRepresentation(0); + rational ten(10); + bool conversionOK = true; + for (unsigned i = 0; i < Sval.length(); ++i) { + char digit = (int)Sval[i]; + if (isdigit((int)digit)) { + std::string sDigit(1, digit); + int val = atoi(sDigit.c_str()); + convertedRepresentation = (ten * convertedRepresentation) + rational(val); + } else { + // not a digit, invalid + TRACE("str", tout << "str.to-int argument contains non-digit character '" << digit << "'" << std::endl;); + conversionOK = false; + break; + } + } + if (conversionOK) { + expr_ref premise(ctx.mk_eq_atom(a, mk_string(Sval)), m); + expr_ref conclusion(ctx.mk_eq_atom(N, m_autil.mk_numeral(convertedRepresentation, true)), m); + expr_ref axiom(rewrite_implication(premise, conclusion), m); + if (!string_int_axioms.contains(axiom)) { + string_int_axioms.insert(axiom); + assert_axiom(axiom); + m_trail_stack.push(insert_obj_trail(string_int_axioms, axiom)); + axiomAdd = true; + } + } else { + expr_ref axiom(m.mk_not(ctx.mk_eq_atom(a, mk_string(Sval))), m); + // always assert this axiom because this is a conflict clause + assert_axiom(axiom); + axiomAdd = true; + } + } + } else { + TRACE("str", tout << "string theory has no assignment for " << mk_pp(a, m) << std::endl;); + NOT_IMPLEMENTED_YET(); + } + return axiomAdd; + } + + void theory_str::collect_var_concat(expr * node, std::set & varSet, std::set & concatSet) { + if (variable_set.find(node) != variable_set.end()) { + if (internal_lenTest_vars.find(node) == internal_lenTest_vars.end()) { + varSet.insert(node); + } + } + else if (is_app(node)) { + app * aNode = to_app(node); + if (u.str.is_length(aNode)) { + // Length + return; + } + if (u.str.is_concat(aNode)) { + expr * arg0 = aNode->get_arg(0); + expr * arg1 = aNode->get_arg(1); + if (concatSet.find(node) == concatSet.end()) { + concatSet.insert(node); + } + } + // recursively visit all arguments + for (unsigned i = 0; i < aNode->get_num_args(); ++i) { + expr * arg = aNode->get_arg(i); + collect_var_concat(arg, varSet, concatSet); + } + } + } + + bool theory_str::propagate_length_within_eqc(expr * var) { + bool res = false; + ast_manager & m = get_manager(); + context & ctx = get_context(); + + TRACE("str", tout << "propagate_length_within_eqc: " << mk_ismt2_pp(var, m) << std::endl ;); + + enode * n_eq_enode = ctx.get_enode(var); + rational varLen; + if (! get_len_value(var, varLen)) { + bool hasLen = false; + expr * nodeWithLen= var; + do { + if (get_len_value(nodeWithLen, varLen)) { + hasLen = true; + break; + } + nodeWithLen = get_eqc_next(nodeWithLen); + } while (nodeWithLen != var); + + if (hasLen) { + // var = nodeWithLen --> |var| = |nodeWithLen| + expr_ref_vector l_items(m); + expr_ref varEqNode(ctx.mk_eq_atom(var, nodeWithLen), m); + l_items.push_back(varEqNode); + + expr_ref nodeWithLenExpr (mk_strlen(nodeWithLen), m); + expr_ref varLenExpr (mk_int(varLen), m); + expr_ref lenEqNum(ctx.mk_eq_atom(nodeWithLenExpr, varLenExpr), m); + l_items.push_back(lenEqNum); + + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref varLen(mk_strlen(var), m); + expr_ref axr(ctx.mk_eq_atom(varLen, mk_int(varLen)), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m);); + res = true; + } + } + return res; + } + + bool theory_str::propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + bool axiomAdded = false; + // collect all concats in context + for (expr_ref_vector::iterator it = assignments.begin(); it != assignments.end(); ++it) { + if (! ctx.is_relevant(*it)) { + continue; + } + if (m.is_eq(*it)) { + collect_var_concat(*it, varSet, concatSet); + } + } + // iterate each concat + // if a concat doesn't have length info, check if the length of all leaf nodes can be resolved + for (std::set::iterator it = concatSet.begin(); it != concatSet.end(); it++) { + expr * concat = *it; + rational lenValue; + expr_ref concatlenExpr (mk_strlen(concat), m) ; + bool allLeafResolved = true; + if (! get_value(concatlenExpr, lenValue)) { + // the length fo concat is unresolved yet + if (get_len_value(concat, lenValue)) { + // but all leaf nodes have length information + TRACE("str", tout << "* length pop-up: " << mk_ismt2_pp(concat, m) << "| = " << lenValue << std::endl;); + std::set leafNodes; + get_unique_non_concat_nodes(concat, leafNodes); + expr_ref_vector l_items(m); + for (std::set::iterator leafIt = leafNodes.begin(); leafIt != leafNodes.end(); ++leafIt) { + rational leafLenValue; + if (get_len_value(*leafIt, leafLenValue)) { + expr_ref leafItLenExpr (mk_strlen(*leafIt), m); + expr_ref leafLenValueExpr (mk_int(leafLenValue), m); + expr_ref lcExpr (ctx.mk_eq_atom(leafItLenExpr, leafLenValueExpr), m); + l_items.push_back(lcExpr); + } else { + allLeafResolved = false; + break; + } + } + if (allLeafResolved) { + expr_ref axl(m.mk_and(l_items.size(), l_items.c_ptr()), m); + expr_ref lenValueExpr (mk_int(lenValue), m); + expr_ref axr(ctx.mk_eq_atom(concatlenExpr, lenValueExpr), m); + assert_implication(axl, axr); + TRACE("str", tout << mk_ismt2_pp(axl, m) << std::endl << " ---> " << std::endl << mk_ismt2_pp(axr, m)<< std::endl;); + axiomAdded = true; + } + } + } + } + // if no concat length is propagated, check the length of variables. + if (! axiomAdded) { + for (std::set::iterator it = varSet.begin(); it != varSet.end(); it++) { + expr * var = *it; + rational lenValue; + expr_ref varlen (mk_strlen(var), m) ; + bool allLeafResolved = true; + if (! get_value(varlen, lenValue)) { + if (propagate_length_within_eqc(var)) { + axiomAdded = true; + } + } + } + + } + return axiomAdded; + } + + void theory_str::get_unique_non_concat_nodes(expr * node, std::set & argSet) { + app * a_node = to_app(node); + if (!u.str.is_concat(a_node)) { + argSet.insert(node); + return; + } else { + SASSERT(a_node->get_num_args() == 2); + expr * leftArg = a_node->get_arg(0); + expr * rightArg = a_node->get_arg(1); + get_unique_non_concat_nodes(leftArg, argSet); + get_unique_non_concat_nodes(rightArg, argSet); + } + } + + final_check_status theory_str::final_check_eh() { + context & ctx = get_context(); + ast_manager & m = get_manager(); + + expr_ref_vector assignments(m); + ctx.get_assignments(assignments); + + if (opt_VerifyFinalCheckProgress) { + finalCheckProgressIndicator = false; + } + + TRACE("str", tout << "final check" << std::endl;); + TRACE_CODE(if (is_trace_enabled("t_str_dump_assign")) { dump_assignments(); }); + check_variable_scope(); + + if (opt_DeferEQCConsistencyCheck) { + TRACE("str", tout << "performing deferred EQC consistency check" << std::endl;); + std::set eqc_roots; + for (ptr_vector::const_iterator it = ctx.begin_enodes(); it != ctx.end_enodes(); ++it) { + enode * e = *it; + enode * root = e->get_root(); + eqc_roots.insert(root); + } + + bool found_inconsistency = false; + + for (std::set::iterator it = eqc_roots.begin(); it != eqc_roots.end(); ++it) { + enode * e = *it; + app * a = e->get_owner(); + if (!(m.get_sort(a) == u.str.mk_string_sort())) { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " not a string term; skipping" << std::endl;); + } else { + TRACE("str", tout << "EQC root " << mk_pp(a, m) << " is a string term. Checking this EQC" << std::endl;); + // first call check_concat_len_in_eqc() on each member of the eqc + enode * e_it = e; + enode * e_root = e_it; + do { + bool status = check_concat_len_in_eqc(e_it->get_owner()); + if (!status) { + TRACE("str", tout << "concat-len check asserted an axiom on " << mk_pp(e_it->get_owner(), m) << std::endl;); + found_inconsistency = true; + } + e_it = e_it->get_next(); + } while (e_it != e_root); + + // now grab any two distinct elements from the EQC and call new_eq_check() on them + enode * e1 = e; + enode * e2 = e1->get_next(); + if (e1 != e2) { + TRACE("str", tout << "deferred new_eq_check() over EQC of " << mk_pp(e1->get_owner(), m) << " and " << mk_pp(e2->get_owner(), m) << std::endl;); + bool result = new_eq_check(e1->get_owner(), e2->get_owner()); + if (!result) { + TRACE("str", tout << "new_eq_check found inconsistencies" << std::endl;); + found_inconsistency = true; + } + } + } + } + + if (found_inconsistency) { + TRACE("str", tout << "Found inconsistency in final check! Returning to search." << std::endl;); + return FC_CONTINUE; + } else { + TRACE("str", tout << "Deferred consistency check passed. Continuing in final check." << std::endl;); + } + } + + // run dependence analysis to find free string variables + std::map varAppearInAssign; + std::map freeVar_map; + std::map > unrollGroup_map; + std::map > var_eq_concat_map; + int conflictInDep = ctx_dep_analysis(varAppearInAssign, freeVar_map, unrollGroup_map, var_eq_concat_map); + if (conflictInDep == -1) { + // return Z3_TRUE; + return FC_DONE; + } + + // enhancement: improved backpropagation of string constants into var=concat terms + bool backpropagation_occurred = false; + for (std::map >::iterator veqc_map_it = var_eq_concat_map.begin(); + veqc_map_it != var_eq_concat_map.end(); ++veqc_map_it) { + expr * var = veqc_map_it->first; + for (std::map::iterator concat_map_it = veqc_map_it->second.begin(); + concat_map_it != veqc_map_it->second.end(); ++concat_map_it) { + app * concat = to_app(concat_map_it->first); + expr * concat_lhs = concat->get_arg(0); + expr * concat_rhs = concat->get_arg(1); + // If the concat LHS and RHS both have a string constant in their EQC, + // but the var does not, then we assert an axiom of the form + // (lhs = "lhs" AND rhs = "rhs") --> (Concat lhs rhs) = "lhsrhs" + bool concat_lhs_haseqc, concat_rhs_haseqc, var_haseqc; + expr * concat_lhs_str = get_eqc_value(concat_lhs, concat_lhs_haseqc); + expr * concat_rhs_str = get_eqc_value(concat_rhs, concat_rhs_haseqc); + expr * var_str = get_eqc_value(var, var_haseqc); + if (concat_lhs_haseqc && concat_rhs_haseqc && !var_haseqc) { + TRACE("str", tout << "backpropagate into " << mk_pp(var, m) << " = " << mk_pp(concat, m) << std::endl + << "LHS ~= " << mk_pp(concat_lhs_str, m) << " RHS ~= " << mk_pp(concat_rhs_str, m) << std::endl;); + zstring lhsString, rhsString; + u.str.is_string(concat_lhs_str, lhsString); + u.str.is_string(concat_rhs_str, rhsString); + zstring concatString = lhsString + rhsString; + expr_ref lhs1(ctx.mk_eq_atom(concat_lhs, concat_lhs_str), m); + expr_ref lhs2(ctx.mk_eq_atom(concat_rhs, concat_rhs_str), m); + expr_ref lhs(m.mk_and(lhs1, lhs2), m); + expr_ref rhs(ctx.mk_eq_atom(concat, mk_string(concatString)), m); + assert_implication(lhs, rhs); + backpropagation_occurred = true; + } + } + } + + if (backpropagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by backpropagation." << std::endl;); + return FC_CONTINUE; + } + + // enhancement: improved backpropagation of length information + { + std::set varSet; + std::set concatSet; + std::map exprLenMap; + + bool length_propagation_occurred = propagate_length(varSet, concatSet, exprLenMap); + if (length_propagation_occurred) { + TRACE("str", tout << "Resuming search due to axioms added by length propagation." << std::endl;); + return FC_CONTINUE; + } + } + + bool needToAssignFreeVars = false; + std::set free_variables; + std::set unused_internal_variables; + { // Z3str2 free variables check + std::map::iterator itor = varAppearInAssign.begin(); + for (; itor != varAppearInAssign.end(); ++itor) { + /* + std::string vName = std::string(Z3_ast_to_string(ctx, itor->first)); + if (vName.length() >= 3 && vName.substr(0, 3) == "$$_") + continue; + */ + if (internal_variable_set.find(itor->first) != internal_variable_set.end() + || regex_variable_set.find(itor->first) != regex_variable_set.end()) { + // this can be ignored, I think + TRACE("str", tout << "free internal variable " << mk_pp(itor->first, m) << " ignored" << std::endl;); + continue; + } + bool hasEqcValue = false; + expr * eqcString = get_eqc_value(itor->first, hasEqcValue); + if (!hasEqcValue) { + TRACE("str", tout << "found free variable " << mk_pp(itor->first, m) << std::endl;); + needToAssignFreeVars = true; + free_variables.insert(itor->first); + // break; + } else { + // debug + TRACE("str", tout << "variable " << mk_pp(itor->first, m) << " = " << mk_pp(eqcString, m) << std::endl;); + } + } + } + + if (!needToAssignFreeVars) { + + // check string-int terms + bool addedStrIntAxioms = false; + for (unsigned i = 0; i < string_int_conversion_terms.size(); ++i) { + app * ex = to_app(string_int_conversion_terms[i].get()); + if (u.str.is_stoi(ex)) { + bool axiomAdd = finalcheck_str2int(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else if (u.str.is_itos(ex)) { + bool axiomAdd = finalcheck_int2str(ex); + if (axiomAdd) { + addedStrIntAxioms = true; + } + } else { + UNREACHABLE(); + } + } + if (addedStrIntAxioms) { + TRACE("str", tout << "Resuming search due to addition of string-integer conversion axioms." << std::endl;); + return FC_CONTINUE; + } + + if (unused_internal_variables.empty()) { + TRACE("str", tout << "All variables are assigned. Done!" << std::endl;); + return FC_DONE; + } else { + TRACE("str", tout << "Assigning decoy values to free internal variables." << std::endl;); + for (std::set::iterator it = unused_internal_variables.begin(); it != unused_internal_variables.end(); ++it) { + expr * var = *it; + expr_ref assignment(m.mk_eq(var, mk_string("**unused**")), m); + assert_axiom(assignment); + } + return FC_CONTINUE; + } + } + + CTRACE("str", needToAssignFreeVars, + tout << "Need to assign values to the following free variables:" << std::endl; + for (std::set::iterator itx = free_variables.begin(); itx != free_variables.end(); ++itx) { + tout << mk_ismt2_pp(*itx, m) << std::endl; + } + tout << "freeVar_map has the following entries:" << std::endl; + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * var = fvIt->first; + tout << mk_ismt2_pp(var, m) << std::endl; + } + ); + + // ----------------------------------------------------------- + // variables in freeVar are those not bounded by Concats + // classify variables in freeVarMap: + // (1) freeVar = unroll(r1, t1) + // (2) vars are not bounded by either concat or unroll + // ----------------------------------------------------------- + std::map > fv_unrolls_map; + std::set tmpSet; + expr * constValue = NULL; + for (std::map::iterator fvIt2 = freeVar_map.begin(); fvIt2 != freeVar_map.end(); fvIt2++) { + expr * var = fvIt2->first; + tmpSet.clear(); + get_eqc_allUnroll(var, constValue, tmpSet); + if (tmpSet.size() > 0) { + fv_unrolls_map[var] = tmpSet; + } + } + // erase var bounded by an unroll function from freeVar_map + for (std::map >::iterator fvIt3 = fv_unrolls_map.begin(); + fvIt3 != fv_unrolls_map.end(); fvIt3++) { + expr * var = fvIt3->first; + TRACE("str", tout << "erase free variable " << mk_pp(var, m) << " from freeVar_map, it is bounded by an Unroll" << std::endl;); + freeVar_map.erase(var); + } + + // collect the case: + // * Concat(X, Y) = unroll(r1, t1) /\ Concat(X, Y) = unroll(r2, t2) + // concatEqUnrollsMap[Concat(X, Y)] = {unroll(r1, t1), unroll(r2, t2)} + + std::map > concatEqUnrollsMap; + for (std::map >::iterator urItor = unrollGroup_map.begin(); + urItor != unrollGroup_map.end(); urItor++) { + expr * unroll = urItor->first; + expr * curr = unroll; + do { + if (u.str.is_concat(to_app(curr))) { + concatEqUnrollsMap[curr].insert(unroll); + concatEqUnrollsMap[curr].insert(unrollGroup_map[unroll].begin(), unrollGroup_map[unroll].end()); + } + enode * e_curr = ctx.get_enode(curr); + curr = e_curr->get_next()->get_owner(); + // curr = get_eqc_next(curr); + } while (curr != unroll); + } + + std::map > concatFreeArgsEqUnrollsMap; + std::set fvUnrollSet; + for (std::map >::iterator concatItor = concatEqUnrollsMap.begin(); + concatItor != concatEqUnrollsMap.end(); concatItor++) { + expr * concat = concatItor->first; + expr * concatArg1 = to_app(concat)->get_arg(0); + expr * concatArg2 = to_app(concat)->get_arg(1); + bool arg1Bounded = false; + bool arg2Bounded = false; + // arg1 + if (variable_set.find(concatArg1) != variable_set.end()) { + if (freeVar_map.find(concatArg1) == freeVar_map.end()) { + arg1Bounded = true; + } else { + fvUnrollSet.insert(concatArg1); + } + } else if (u.str.is_concat(to_app(concatArg1))) { + if (concatEqUnrollsMap.find(concatArg1) == concatEqUnrollsMap.end()) { + arg1Bounded = true; + } + } + // arg2 + if (variable_set.find(concatArg2) != variable_set.end()) { + if (freeVar_map.find(concatArg2) == freeVar_map.end()) { + arg2Bounded = true; + } else { + fvUnrollSet.insert(concatArg2); + } + } else if (u.str.is_concat(to_app(concatArg2))) { + if (concatEqUnrollsMap.find(concatArg2) == concatEqUnrollsMap.end()) { + arg2Bounded = true; + } + } + if (!arg1Bounded && !arg2Bounded) { + concatFreeArgsEqUnrollsMap[concat].insert( + concatEqUnrollsMap[concat].begin(), + concatEqUnrollsMap[concat].end()); + } + } + for (std::set::iterator vItor = fvUnrollSet.begin(); vItor != fvUnrollSet.end(); vItor++) { + TRACE("str", tout << "remove " << mk_pp(*vItor, m) << " from freeVar_map" << std::endl;); + freeVar_map.erase(*vItor); + } + + // Assign free variables + std::set fSimpUnroll; + + constValue = NULL; + + { + TRACE("str", tout << "free var map (#" << freeVar_map.size() << "):" << std::endl; + for (std::map::iterator freeVarItor1 = freeVar_map.begin(); freeVarItor1 != freeVar_map.end(); freeVarItor1++) { + expr * freeVar = freeVarItor1->first; + rational lenValue; + bool lenValue_exists = get_len_value(freeVar, lenValue); + tout << mk_pp(freeVar, m) << " [depCnt = " << freeVarItor1->second << ", length = " + << (lenValue_exists ? lenValue.to_string() : "?") + << "]" << std::endl; + } + ); + } + + for (std::map >::iterator fvIt2 = concatFreeArgsEqUnrollsMap.begin(); + fvIt2 != concatFreeArgsEqUnrollsMap.end(); fvIt2++) { + expr * concat = fvIt2->first; + for (std::set::iterator urItor = fvIt2->second.begin(); urItor != fvIt2->second.end(); urItor++) { + expr * unroll = *urItor; + process_concat_eq_unroll(concat, unroll); + } + } + + // -------- + // experimental free variable assignment - begin + // * special handling for variables that are not used in concat + // -------- + bool testAssign = true; + if (!testAssign) { + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + /* + std::string vName = std::string(Z3_ast_to_string(ctx, freeVar)); + if (vName.length() >= 9 && vName.substr(0, 9) == "$$_regVar") { + continue; + } + */ + expr * toAssert = gen_len_val_options_for_free_var(freeVar, NULL, ""); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } else { + process_free_var(freeVar_map); + } + // experimental free variable assignment - end + + // now deal with removed free variables that are bounded by an unroll + TRACE("str", tout << "fv_unrolls_map (#" << fv_unrolls_map.size() << "):" << std::endl;); + for (std::map >::iterator fvIt1 = fv_unrolls_map.begin(); + fvIt1 != fv_unrolls_map.end(); fvIt1++) { + expr * var = fvIt1->first; + fSimpUnroll.clear(); + get_eqc_simpleUnroll(var, constValue, fSimpUnroll); + if (fSimpUnroll.size() == 0) { + gen_assign_unroll_reg(fv_unrolls_map[var]); + } else { + expr * toAssert = gen_assign_unroll_Str2Reg(var, fSimpUnroll); + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } + } + + if (opt_VerifyFinalCheckProgress && !finalCheckProgressIndicator) { + TRACE("str", tout << "BUG: no progress in final check, giving up!!" << std::endl;); + m.raise_exception("no progress in theory_str final check"); + } + + return FC_CONTINUE; // since by this point we've added axioms + } + + inline zstring int_to_string(int i) { + std::stringstream ss; + ss << i; + std::string str = ss.str(); + return zstring(str.c_str()); + } + + inline std::string longlong_to_string(long long i) { + std::stringstream ss; + ss << i; + return ss.str(); + } + + void theory_str::print_value_tester_list(svector > & testerList) { + ast_manager & m = get_manager(); + TRACE("str", + int ss = testerList.size(); + tout << "valueTesterList = {"; + for (int i = 0; i < ss; ++i) { + if (i % 4 == 0) { + tout << std::endl; + } + tout << "(" << testerList[i].first << ", "; + tout << mk_ismt2_pp(testerList[i].second, m); + tout << "), "; + } + tout << std::endl << "}" << std::endl; + ); + } + + zstring theory_str::gen_val_string(int len, int_vector & encoding) { + SASSERT(charSetSize > 0); + SASSERT(char_set != NULL); + + std::string re(len, char_set[0]); + for (int i = 0; i < (int) encoding.size() - 1; i++) { + int idx = encoding[i]; + re[len - 1 - i] = char_set[idx]; + } + return zstring(re.c_str()); + } + + /* + * The return value indicates whether we covered the search space. + * - If the next encoding is valid, return false + * - Otherwise, return true + */ + bool theory_str::get_next_val_encode(int_vector & base, int_vector & next) { + SASSERT(charSetSize > 0); + + TRACE("str", tout << "base vector: [ "; + for (unsigned i = 0; i < base.size(); ++i) { + tout << base[i] << " "; + } + tout << "]" << std::endl; + ); + + int s = 0; + int carry = 0; + next.reset(); + + for (int i = 0; i < (int) base.size(); i++) { + if (i == 0) { + s = base[i] + 1; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } else { + s = base[i] + carry; + carry = s / charSetSize; + s = s % charSetSize; + next.push_back(s); + } + } + if (next[next.size() - 1] > 0) { + next.reset(); + return true; + } else { + return false; + } + } + + expr * theory_str::gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + int distance = 32; + + // ---------------------------------------------------------------------------------------- + // generate value options encoding + // encoding is a vector of size (len + 1) + // e.g, len = 2, + // encoding {1, 2, 0} means the value option is "charSet[2]"."charSet[1]" + // the last item in the encoding indicates whether the whole space is covered + // for example, if the charSet = {a, b}. All valid encodings are + // {0, 0, 0}, {1, 0, 0}, {0, 1, 0}, {1, 1, 0} + // if add 1 to the last one, we get + // {0, 0, 1} + // the last item "1" shows this is not a valid encoding, and we have covered all space + // ---------------------------------------------------------------------------------------- + int len = atoi(lenStr.encode().c_str()); + bool coverAll = false; + svector options; + int_vector base; + + TRACE("str", tout + << "freeVar = " << mk_ismt2_pp(freeVar, m) << std::endl + << "len_indicator = " << mk_ismt2_pp(len_indicator, m) << std::endl + << "val_indicator = " << mk_ismt2_pp(val_indicator, m) << std::endl + << "lenstr = " << lenStr << "\n" + << "tries = " << tries << "\n"; + if (m_params.m_AggressiveValueTesting) { + tout << "note: aggressive value testing is enabled" << std::endl; + } + ); + + if (tries == 0) { + base = int_vector(len + 1, 0); + coverAll = false; + } else { + expr * lastestValIndi = fvar_valueTester_map[freeVar][len][tries - 1].second; + TRACE("str", tout << "last value tester = " << mk_ismt2_pp(lastestValIndi, m) << std::endl;); + coverAll = get_next_val_encode(val_range_map[lastestValIndi], base); + } + + long long l = (tries) * distance; + long long h = l; + for (int i = 0; i < distance; i++) { + if (coverAll) + break; + options.push_back(base); + h++; + coverAll = get_next_val_encode(options[options.size() - 1], base); + } + val_range_map[val_indicator] = options[options.size() - 1]; + + TRACE("str", + tout << "value tester encoding " << "{" << std::endl; + int_vector vec = val_range_map[val_indicator]; + + for (int_vector::iterator it = vec.begin(); it != vec.end(); ++it) { + tout << *it << std::endl; + } + tout << "}" << std::endl; + ); + + // ---------------------------------------------------------------------------------------- + + ptr_vector orList; + ptr_vector andList; + + for (long long i = l; i < h; i++) { + orList.push_back(m.mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()) )); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string(longlong_to_string(i).c_str()), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + zstring aStr = gen_val_string(len, options[i - l]); + expr * strAst; + if (m_params.m_UseFastValueTesterCache) { + if (!valueTesterCache.find(aStr, strAst)) { + strAst = mk_string(aStr); + valueTesterCache.insert(aStr, strAst); + m_trail.push_back(strAst); + } + } else { + strAst = mk_string(aStr); + } + andList.push_back(m.mk_eq(orList[orList.size() - 1], m.mk_eq(freeVar, strAst))); + } + if (!coverAll) { + orList.push_back(m.mk_eq(val_indicator, mk_string("more"))); + if (m_params.m_AggressiveValueTesting) { + literal l = mk_eq(val_indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + } + + expr ** or_items = alloc_svect(expr*, orList.size()); + expr ** and_items = alloc_svect(expr*, andList.size() + 1); + + for (int i = 0; i < (int) orList.size(); i++) { + or_items[i] = orList[i]; + } + if (orList.size() > 1) + and_items[0] = m.mk_or(orList.size(), or_items); + else + and_items[0] = or_items[0]; + + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i + 1] = andList[i]; + } + expr * valTestAssert = m.mk_and(andList.size() + 1, and_items); + + // --------------------------------------- + // If the new value tester is $$_val_x_16_i + // Should add ($$_len_x_j = 16) /\ ($$_val_x_16_i = "more") + // --------------------------------------- + andList.reset(); + andList.push_back(m.mk_eq(len_indicator, mk_string(lenStr))); + for (int i = 0; i < tries; i++) { + expr * vTester = fvar_valueTester_map[freeVar][len][i].second; + if (vTester != val_indicator) + andList.push_back(m.mk_eq(vTester, mk_string("more"))); + } + expr * assertL = NULL; + if (andList.size() == 1) { + assertL = andList[0]; + } else { + expr ** and_items = alloc_svect(expr*, andList.size()); + for (int i = 0; i < (int) andList.size(); i++) { + and_items[i] = andList[i]; + } + assertL = m.mk_and(andList.size(), and_items); + } + + // (assertL => valTestAssert) <=> (!assertL OR valTestAssert) + valTestAssert = m.mk_or(m.mk_not(assertL), valTestAssert); + return valTestAssert; + } + + expr * theory_str::gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr) { + ast_manager & m = get_manager(); + + int len = atoi(len_valueStr.encode().c_str()); + + // check whether any value tester is actually in scope + TRACE("str", tout << "checking scope of previous value testers" << std::endl;); + bool map_effectively_empty = true; + if (fvar_valueTester_map[freeVar].find(len) != fvar_valueTester_map[freeVar].end()) { + // there's *something* in the map, but check its scope + svector > entries = fvar_valueTester_map[freeVar][len]; + for (svector >::iterator it = entries.begin(); it != entries.end(); ++it) { + std::pair entry = *it; + expr * aTester = entry.second; + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << mk_pp(aTester, m) << " out of scope" << std::endl;); + } else { + TRACE("str", tout << mk_pp(aTester, m) << " in scope" << std::endl;); + map_effectively_empty = false; + break; + } + } + } + + if (map_effectively_empty) { + TRACE("str", tout << "no previous value testers, or none of them were in scope" << std::endl;); + int tries = 0; + expr * val_indicator = mk_internal_valTest_var(freeVar, len, tries); + valueTester_fvar_map[val_indicator] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, val_indicator)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + return gen_val_options(freeVar, len_indicator, val_indicator, len_valueStr, tries); + } else { + TRACE("str", tout << "checking previous value testers" << std::endl;); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + + // go through all previous value testers + // If some doesn't have an eqc value, add its assertion again. + int testerTotal = fvar_valueTester_map[freeVar][len].size(); + int i = 0; + for (; i < testerTotal; i++) { + expr * aTester = fvar_valueTester_map[freeVar][len][i].second; + + // it's probably worth checking scope here, actually + if (internal_variable_set.find(aTester) == internal_variable_set.end()) { + TRACE("str", tout << "value tester " << mk_pp(aTester, m) << " out of scope, skipping" << std::endl;); + continue; + } + + if (aTester == valTesterInCbEq) { + break; + } + + bool anEqcHasValue = false; + // Z3_ast anEqc = get_eqc_value(t, aTester, anEqcHasValue); + expr * aTester_eqc_value = get_eqc_value(aTester, anEqcHasValue); + if (!anEqcHasValue) { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " doesn't have an equivalence class value." << std::endl;); + refresh_theory_var(aTester); + + expr * makeupAssert = gen_val_options(freeVar, len_indicator, aTester, len_valueStr, i); + + TRACE("str", tout << "var: " << mk_ismt2_pp(freeVar, m) << std::endl + << mk_ismt2_pp(makeupAssert, m) << std::endl;); + assert_axiom(makeupAssert); + } else { + TRACE("str", tout << "value tester " << mk_ismt2_pp(aTester, m) + << " == " << mk_ismt2_pp(aTester_eqc_value, m) << std::endl;); + } + } + + if (valTesterValueStr == "more") { + expr * valTester = NULL; + if (i + 1 < testerTotal) { + valTester = fvar_valueTester_map[freeVar][len][i + 1].second; + refresh_theory_var(valTester); + } else { + valTester = mk_internal_valTest_var(freeVar, len, i + 1); + valueTester_fvar_map[valTester] = freeVar; + fvar_valueTester_map[freeVar][len].push_back(std::make_pair(sLevel, valTester)); + print_value_tester_list(fvar_valueTester_map[freeVar][len]); + } + expr * nextAssert = gen_val_options(freeVar, len_indicator, valTester, len_valueStr, i + 1); + return nextAssert; + } + + return NULL; + } + } + + void theory_str::reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "reduce regex " << mk_pp(regex, mgr) << " with respect to variable " << mk_pp(var, mgr) << std::endl;); + + app * regexFuncDecl = to_app(regex); + if (u.re.is_to_re(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Str2Reg(s1) + // ==> + // var = s1 /\ length(var) = length(s1) + // --------------------------------------------------------- + expr * strInside = to_app(regex)->get_arg(0); + items.push_back(ctx.mk_eq_atom(var, strInside)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(strInside))); + return; + } + // RegexUnion + else if (u.re.is_union(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexUnion(r1, r2) + // ==> + // (var = newVar1 \/ var = newVar2) + // (var = newVar1 --> length(var) = length(newVar1)) /\ (var = newVar2 --> length(var) = length(newVar2)) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + items.push_back(mgr.mk_or(ctx.mk_eq_atom(var, newVar1), ctx.mk_eq_atom(var, newVar2))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar1)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar1)))); + items.push_back(mgr.mk_or( + mgr.mk_not(ctx.mk_eq_atom(var, newVar2)), + ctx.mk_eq_atom(mk_strlen(var), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + + return; + } + // RegexConcat + else if (u.re.is_concat(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in RegexConcat(r1, r2) + // ==> + // (var = newVar1 . newVar2) /\ (length(var) = length(vewVar1 . newVar2) ) + // /\ (newVar1 \in r1) /\ (newVar2 \in r2) + // --------------------------------------------------------- + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + items.push_back(ctx.mk_eq_atom(var, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), + m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + + expr * regArg1 = to_app(regex)->get_arg(0); + reduce_virtual_regex_in(newVar1, regArg1, items); + expr * regArg2 = to_app(regex)->get_arg(1); + reduce_virtual_regex_in(newVar2, regArg2, items); + return; + } + // Unroll + else if (u.re.is_star(regexFuncDecl)) { + // --------------------------------------------------------- + // var \in Star(r1) + // ==> + // var = unroll(r1, t1) /\ |var| = |unroll(r1, t1)| + // --------------------------------------------------------- + expr * regArg = to_app(regex)->get_arg(0); + expr_ref unrollCnt(mk_unroll_bound_var(), mgr); + expr_ref unrollFunc(mk_unroll(regArg, unrollCnt), mgr); + items.push_back(ctx.mk_eq_atom(var, unrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(var), mk_strlen(unrollFunc))); + return; + } + // re.range + else if (u.re.is_range(regexFuncDecl)) { + // var in range("a", "z") + // ==> + // (var = "a" or var = "b" or ... or var = "z") + expr_ref lo(regexFuncDecl->get_arg(0), mgr); + expr_ref hi(regexFuncDecl->get_arg(1), mgr); + zstring str_lo, str_hi; + SASSERT(u.str.is_string(lo)); + SASSERT(u.str.is_string(hi)); + u.str.is_string(lo, str_lo); + u.str.is_string(hi, str_hi); + SASSERT(str_lo.length() == 1); + SASSERT(str_hi.length() == 1); + unsigned int c1 = str_lo[0]; + unsigned int c2 = str_hi[0]; + if (c1 > c2) { + // exchange + unsigned int tmp = c1; + c1 = c2; + c2 = tmp; + } + expr_ref_vector range_cases(mgr); + for (unsigned int ch = c1; ch <= c2; ++ch) { + zstring s_ch(ch); + expr_ref rhs(ctx.mk_eq_atom(var, u.str.mk_string(s_ch)), mgr); + range_cases.push_back(rhs); + } + expr_ref rhs(mk_or(range_cases), mgr); + SASSERT(rhs); + assert_axiom(rhs); + return; + } else { + get_manager().raise_exception("unrecognized regex operator"); + UNREACHABLE(); + } + } + + void theory_str::gen_assign_unroll_reg(std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + expr_ref_vector items(mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * unrFunc = *itor; + TRACE("str", tout << "generating assignment for unroll " << mk_pp(unrFunc, mgr) << std::endl;); + + expr * regexInUnr = to_app(unrFunc)->get_arg(0); + expr * cntInUnr = to_app(unrFunc)->get_arg(1); + items.reset(); + + rational low, high; + bool low_exists = lower_bound(cntInUnr, low); + bool high_exists = upper_bound(cntInUnr, high); + + TRACE("str", + tout << "unroll " << mk_pp(unrFunc, mgr) << std::endl; + rational unrLenValue; + bool unrLenValue_exists = get_len_value(unrFunc, unrLenValue); + tout << "unroll length: " << (unrLenValue_exists ? unrLenValue.to_string() : "?") << std::endl; + rational cntInUnrValue; + bool cntHasValue = get_value(cntInUnr, cntInUnrValue); + tout << "unroll count: " << (cntHasValue ? cntInUnrValue.to_string() : "?") + << " low = " + << (low_exists ? low.to_string() : "?") + << " high = " + << (high_exists ? high.to_string() : "?") + << std::endl; + ); + + expr_ref toAssert(mgr); + if (low.is_neg()) { + toAssert = m_autil.mk_ge(cntInUnr, mk_int(0)); + } else { + if (unroll_var_map.find(unrFunc) == unroll_var_map.end()) { + + expr_ref newVar1(mk_regex_rep_var(), mgr); + expr_ref newVar2(mk_regex_rep_var(), mgr); + expr_ref concatAst(mk_concat(newVar1, newVar2), mgr); + expr_ref newCnt(mk_unroll_bound_var(), mgr); + expr_ref newUnrollFunc(mk_unroll(regexInUnr, newCnt), mgr); + + // unroll(r1, t1) = newVar1 . newVar2 + items.push_back(ctx.mk_eq_atom(unrFunc, concatAst)); + items.push_back(ctx.mk_eq_atom(mk_strlen(unrFunc), m_autil.mk_add(mk_strlen(newVar1), mk_strlen(newVar2)))); + // mk_strlen(unrFunc) >= mk_strlen(newVar{1,2}) + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar1))), mk_int(0))); + items.push_back(m_autil.mk_ge(m_autil.mk_add(mk_strlen(unrFunc), m_autil.mk_mul(mk_int(-1), mk_strlen(newVar2))), mk_int(0))); + // newVar1 \in r1 + reduce_virtual_regex_in(newVar1, regexInUnr, items); + items.push_back(ctx.mk_eq_atom(cntInUnr, m_autil.mk_add(newCnt, mk_int(1)))); + items.push_back(ctx.mk_eq_atom(newVar2, newUnrollFunc)); + items.push_back(ctx.mk_eq_atom(mk_strlen(newVar2), mk_strlen(newUnrollFunc))); + toAssert = ctx.mk_eq_atom( + m_autil.mk_ge(cntInUnr, mk_int(1)), + mk_and(items)); + + // option 0 + expr_ref op0(ctx.mk_eq_atom(cntInUnr, mk_int(0)), mgr); + expr_ref ast1(ctx.mk_eq_atom(unrFunc, mk_string("")), mgr); + expr_ref ast2(ctx.mk_eq_atom(mk_strlen(unrFunc), mk_int(0)), mgr); + expr_ref and1(mgr.mk_and(ast1, ast2), mgr); + + // put together + toAssert = mgr.mk_and(ctx.mk_eq_atom(op0, and1), toAssert); + + unroll_var_map[unrFunc] = toAssert; + } else { + toAssert = unroll_var_map[unrFunc]; + } + } + m_trail.push_back(toAssert); + assert_axiom(toAssert); + } + } + + static int computeGCD(int x, int y) { + if (x == 0) { + return y; + } + while (y != 0) { + if (x > y) { + x = x - y; + } else { + y = y - x; + } + } + return x; + } + + static int computeLCM(int a, int b) { + int temp = computeGCD(a, b); + return temp ? (a / temp * b) : 0; + } + + static zstring get_unrolled_string(zstring core, int count) { + zstring res(""); + for (int i = 0; i < count; i++) { + res = res + core; + } + return res; + } + + expr * theory_str::gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int lcm = 1; + int coreValueCount = 0; + expr * oneUnroll = NULL; + zstring oneCoreStr(""); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + if (oneUnroll == NULL) { + oneUnroll = *itor; + oneCoreStr = coreStr; + } + coreValueCount++; + int core1Len = coreStr.length(); + lcm = computeLCM(lcm, core1Len); + } + // + bool canHaveNonEmptyAssign = true; + expr_ref_vector litems(mgr); + zstring lcmStr = get_unrolled_string(oneCoreStr, (lcm / oneCoreStr.length())); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr * str2RegFunc = to_app(*itor)->get_arg(0); + expr * coreVal = to_app(str2RegFunc)->get_arg(0); + zstring coreStr; + u.str.is_string(coreVal, coreStr); + unsigned int core1Len = coreStr.length(); + zstring uStr = get_unrolled_string(coreStr, (lcm / core1Len)); + if (uStr != lcmStr) { + canHaveNonEmptyAssign = false; + } + litems.push_back(ctx.mk_eq_atom(n, *itor)); + } + + if (canHaveNonEmptyAssign) { + return gen_unroll_conditional_options(n, unrolls, lcmStr); + } else { + expr_ref implyL(mk_and(litems), mgr); + expr_ref implyR(ctx.mk_eq_atom(n, mk_string("")), mgr); + // want to return (implyL -> implyR) + expr * final_axiom = rewrite_implication(implyL, implyR); + return final_axiom; + } + } + + expr * theory_str::gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + int dist = opt_LCMUnrollStep; + expr_ref_vector litems(mgr); + expr_ref moreAst(mk_string("more"), mgr); + for (std::set::iterator itor = unrolls.begin(); itor != unrolls.end(); itor++) { + expr_ref item(ctx.mk_eq_atom(var, *itor), mgr); + TRACE("str", tout << "considering unroll " << mk_pp(item, mgr) << std::endl;); + litems.push_back(item); + } + + // handle out-of-scope entries in unroll_tries_map + + ptr_vector outOfScopeTesters; + + for (ptr_vector::iterator it = unroll_tries_map[var][unrolls].begin(); + it != unroll_tries_map[var][unrolls].end(); ++it) { + expr * tester = *it; + bool inScope = (internal_unrollTest_vars.find(tester) != internal_unrollTest_vars.end()); + TRACE("str", tout << "unroll test var " << mk_pp(tester, mgr) + << (inScope ? " in scope" : " out of scope") + << std::endl;); + if (!inScope) { + outOfScopeTesters.push_back(tester); + } + } + + for (ptr_vector::iterator it = outOfScopeTesters.begin(); + it != outOfScopeTesters.end(); ++it) { + unroll_tries_map[var][unrolls].erase(*it); + } + + + if (unroll_tries_map[var][unrolls].size() == 0) { + unroll_tries_map[var][unrolls].push_back(mk_unroll_test_var()); + } + + int tries = unroll_tries_map[var][unrolls].size(); + for (int i = 0; i < tries; i++) { + expr * tester = unroll_tries_map[var][unrolls][i]; + // TESTING + refresh_theory_var(tester); + bool testerHasValue = false; + expr * testerVal = get_eqc_value(tester, testerHasValue); + if (!testerHasValue) { + // generate make-up assertion + int l = i * dist; + int h = (i + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + + SASSERT(lImp); + TRACE("str", tout << "lImp = " << mk_pp(lImp, mgr) << std::endl;); + SASSERT(rImp); + TRACE("str", tout << "rImp = " << mk_pp(rImp, mgr) << std::endl;); + + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Making up assignments for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + + // note: this is how the code looks in Z3str2's strRegex.cpp:genUnrollConditionalOptions. + // the return is in the same place + + // insert [tester = "more"] to litems so that the implyL for next tester is correct + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } else { + zstring testerStr; + u.str.is_string(testerVal, testerStr); + TRACE("str", tout << "Tester [" << mk_pp(tester, mgr) << "] = " << testerStr << "\n";); + if (testerStr == "more") { + litems.push_back(ctx.mk_eq_atom(tester, moreAst)); + } + } + } + expr * tester = mk_unroll_test_var(); + unroll_tries_map[var][unrolls].push_back(tester); + int l = tries * dist; + int h = (tries + 1) * dist; + expr_ref lImp(mk_and(litems), mgr); + expr_ref rImp(gen_unroll_assign(var, lcmStr, tester, l, h), mgr); + SASSERT(lImp); + SASSERT(rImp); + expr_ref toAssert(mgr.mk_or(mgr.mk_not(lImp), rImp), mgr); + SASSERT(toAssert); + TRACE("str", tout << "Generating assignment for variable which is equal to unbounded Unroll" << std::endl;); + m_trail.push_back(toAssert); + return toAssert; + } + + expr * theory_str::gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h) { + context & ctx = get_context(); + ast_manager & mgr = get_manager(); + + TRACE("str", tout << "entry: var = " << mk_pp(var, mgr) << ", lcmStr = " << lcmStr + << ", l = " << l << ", h = " << h << "\n";); + + if (m_params.m_AggressiveUnrollTesting) { + TRACE("str", tout << "note: aggressive unroll testing is active" << std::endl;); + } + + expr_ref_vector orItems(mgr); + expr_ref_vector andItems(mgr); + + for (int i = l; i < h; i++) { + zstring iStr = int_to_string(i); + expr_ref testerEqAst(ctx.mk_eq_atom(testerVar, mk_string(iStr)), mgr); + TRACE("str", tout << "testerEqAst = " << mk_pp(testerEqAst, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string(iStr), false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + orItems.push_back(testerEqAst); + zstring unrollStrInstance = get_unrolled_string(lcmStr, i); + + expr_ref x1(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(var, mk_string(unrollStrInstance))), mgr); + TRACE("str", tout << "x1 = " << mk_pp(x1, mgr) << std::endl;); + andItems.push_back(x1); + + expr_ref x2(ctx.mk_eq_atom(testerEqAst, ctx.mk_eq_atom(mk_strlen(var), mk_int(i * lcmStr.length()))), mgr); + TRACE("str", tout << "x2 = " << mk_pp(x2, mgr) << std::endl;); + andItems.push_back(x2); + } + expr_ref testerEqMore(ctx.mk_eq_atom(testerVar, mk_string("more")), mgr); + TRACE("str", tout << "testerEqMore = " << mk_pp(testerEqMore, mgr) << std::endl;); + if (m_params.m_AggressiveUnrollTesting) { + literal l = mk_eq(testerVar, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + orItems.push_back(testerEqMore); + int nextLowerLenBound = h * lcmStr.length(); + expr_ref more2(ctx.mk_eq_atom(testerEqMore, + //Z3_mk_ge(mk_length(t, var), mk_int(ctx, nextLowerLenBound)) + m_autil.mk_ge(m_autil.mk_add(mk_strlen(var), mk_int(-1 * nextLowerLenBound)), mk_int(0)) + ), mgr); + TRACE("str", tout << "more2 = " << mk_pp(more2, mgr) << std::endl;); + andItems.push_back(more2); + + expr_ref finalOR(mgr.mk_or(orItems.size(), orItems.c_ptr()), mgr); + TRACE("str", tout << "finalOR = " << mk_pp(finalOR, mgr) << std::endl;); + andItems.push_back(mk_or(orItems)); + + expr_ref finalAND(mgr.mk_and(andItems.size(), andItems.c_ptr()), mgr); + TRACE("str", tout << "finalAND = " << mk_pp(finalAND, mgr) << std::endl;); + + // doing the following avoids a segmentation fault + m_trail.push_back(finalAND); + return finalAND; + } + + expr * theory_str::gen_len_test_options(expr * freeVar, expr * indicator, int tries) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + expr_ref freeVarLen(mk_strlen(freeVar), m); + SASSERT(freeVarLen); + + expr_ref_vector orList(m); + expr_ref_vector andList(m); + + int distance = 3; + int l = (tries - 1) * distance; + int h = tries * distance; + + TRACE("str", + tout << "building andList and orList" << std::endl; + if (m_params.m_AggressiveLengthTesting) { + tout << "note: aggressive length testing is active" << std::endl; + } + ); + + // experimental theory-aware case split support + literal_vector case_split_literals; + + for (int i = l; i < h; ++i) { + expr_ref str_indicator(m); + if (m_params.m_UseFastLengthTesterCache) { + rational ri(i); + expr * lookup_val; + if(lengthTesterCache.find(ri, lookup_val)) { + str_indicator = expr_ref(lookup_val, m); + } else { + // no match; create and insert + zstring i_str = int_to_string(i); + expr_ref new_val(mk_string(i_str), m); + lengthTesterCache.insert(ri, new_val); + m_trail.push_back(new_val); + str_indicator = expr_ref(new_val, m); + } + } else { + zstring i_str = int_to_string(i); + str_indicator = expr_ref(mk_string(i_str), m); + } + expr_ref or_expr(ctx.mk_eq_atom(indicator, str_indicator), m); + orList.push_back(or_expr); + + double priority; + // give high priority to small lengths if this is available + if (i <= 5) { + priority = 0.3; + } else { + // prioritize over "more" + priority = 0.2; + } + add_theory_aware_branching_info(or_expr, priority, l_true); + + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, str_indicator, false); + ctx.mark_as_relevant(l); + ctx.force_phase(l); + } + + case_split_literals.insert(mk_eq(freeVarLen, mk_int(i), false)); + + expr_ref and_expr(ctx.mk_eq_atom(orList.get(orList.size() - 1), m.mk_eq(freeVarLen, mk_int(i))), m); + andList.push_back(and_expr); + } + + expr_ref more_option(ctx.mk_eq_atom(indicator, mk_string("more")), m); + orList.push_back(more_option); + // decrease priority of this option + add_theory_aware_branching_info(more_option, -0.1, l_true); + if (m_params.m_AggressiveLengthTesting) { + literal l = mk_eq(indicator, mk_string("more"), false); + ctx.mark_as_relevant(l); + ctx.force_phase(~l); + } + + andList.push_back(ctx.mk_eq_atom(orList.get(orList.size() - 1), m_autil.mk_ge(freeVarLen, mk_int(h)))); + + /* + { // more experimental theory case split support + expr_ref tmp(m_autil.mk_ge(freeVarLen, mk_int(h)), m); + ctx.internalize(m_autil.mk_ge(freeVarLen, mk_int(h)), false); + case_split_literals.push_back(ctx.get_literal(tmp)); + ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + } + */ + + expr_ref_vector or_items(m); + expr_ref_vector and_items(m); + + for (unsigned i = 0; i < orList.size(); ++i) { + or_items.push_back(orList.get(i)); + } + + and_items.push_back(mk_or(or_items)); + for(unsigned i = 0; i < andList.size(); ++i) { + and_items.push_back(andList.get(i)); + } + + TRACE("str", tout << "check: " << mk_pp(mk_and(and_items), m) << std::endl;); + + expr_ref lenTestAssert = mk_and(and_items); + SASSERT(lenTestAssert); + TRACE("str", tout << "crash avoidance lenTestAssert: " << mk_pp(lenTestAssert, m) << std::endl;); + + int testerCount = tries - 1; + if (testerCount > 0) { + expr_ref_vector and_items_LHS(m); + expr_ref moreAst(mk_string("more"), m); + for (int i = 0; i < testerCount; ++i) { + expr * indicator = fvar_lenTester_map[freeVar][i]; + if (internal_variable_set.find(indicator) == internal_variable_set.end()) { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " out of scope; continuing" << std::endl;); + continue; + } else { + TRACE("str", tout << "indicator " << mk_pp(indicator, m) << " in scope" << std::endl;); + and_items_LHS.push_back(ctx.mk_eq_atom(indicator, moreAst)); + } + } + expr_ref assertL(mk_and(and_items_LHS), m); + SASSERT(assertL); + expr * finalAxiom = m.mk_or(m.mk_not(assertL), lenTestAssert.get()); + SASSERT(finalAxiom != NULL); + TRACE("str", tout << "crash avoidance finalAxiom: " << mk_pp(finalAxiom, m) << std::endl;); + return finalAxiom; + } else { + TRACE("str", tout << "crash avoidance lenTestAssert.get(): " << mk_pp(lenTestAssert.get(), m) << std::endl;); + m_trail.push_back(lenTestAssert.get()); + return lenTestAssert.get(); + } + } + + // Return an expression of the form + // (tester = "less" | tester = "N" | tester = "more") & + // (tester = "less" iff len(freeVar) < N) & (tester = "more" iff len(freeVar) > N) & (tester = "N" iff len(freeVar) = N)) + expr_ref theory_str::binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split) { + context & ctx = get_context(); + ast_manager & m = get_manager(); + rational N = bounds.midPoint; + rational N_minus_one = N - rational::one(); + rational N_plus_one = N + rational::one(); + expr_ref lenFreeVar(mk_strlen(freeVar), m); + + TRACE("str", tout << "create case split for free var " << mk_pp(freeVar, m) + << " over " << mk_pp(tester, m) << " with midpoint " << N << std::endl;); + + expr_ref_vector combinedCaseSplit(m); + expr_ref_vector testerCases(m); + + expr_ref caseLess(ctx.mk_eq_atom(tester, mk_string("less")), m); + testerCases.push_back(caseLess); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseLess, m_autil.mk_le(lenFreeVar, m_autil.mk_numeral(N_minus_one, true) ))); + + expr_ref caseMore(ctx.mk_eq_atom(tester, mk_string("more")), m); + testerCases.push_back(caseMore); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseMore, m_autil.mk_ge(lenFreeVar, m_autil.mk_numeral(N_plus_one, true) ))); + + expr_ref caseEq(ctx.mk_eq_atom(tester, mk_string(N.to_string().c_str())), m); + testerCases.push_back(caseEq); + combinedCaseSplit.push_back(ctx.mk_eq_atom(caseEq, ctx.mk_eq_atom(lenFreeVar, m_autil.mk_numeral(N, true)))); + + combinedCaseSplit.push_back(mk_or(testerCases)); + + // force internalization on all terms in testerCases so we can extract literals + for (unsigned i = 0; i < testerCases.size(); ++i) { + expr * testerCase = testerCases.get(i); + if (!ctx.b_internalized(testerCase)) { + ctx.internalize(testerCase, false); + } + literal l = ctx.get_literal(testerCase); + case_split.push_back(l); + } + + expr_ref final_term(mk_and(combinedCaseSplit), m); + SASSERT(final_term); + TRACE("str", tout << "final term: " << mk_pp(final_term, m) << std::endl;); + return final_term; + } + + expr * theory_str::binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue) { + ast_manager & m = get_manager(); + context & ctx = get_context(); + + if (binary_search_len_tester_stack.contains(freeVar) && !binary_search_len_tester_stack[freeVar].empty()) { + TRACE("str", tout << "checking existing length testers for " << mk_pp(freeVar, m) << std::endl; + for (ptr_vector::const_iterator it = binary_search_len_tester_stack[freeVar].begin(); + it != binary_search_len_tester_stack[freeVar].end(); ++it) { + expr * tester = *it; + tout << mk_pp(tester, m) << ": "; + if (binary_search_len_tester_info.contains(tester)) { + binary_search_info & bounds = binary_search_len_tester_info[tester]; + tout << "[" << bounds.lowerBound << " | " << bounds.midPoint << " | " << bounds.upperBound << "]!" << bounds.windowSize; + } else { + tout << "[WARNING: no bounds info available]"; + } + bool hasEqcValue; + expr * testerEqcValue = get_eqc_value(tester, hasEqcValue); + if (hasEqcValue) { + tout << " = " << mk_pp(testerEqcValue, m); + } else { + tout << " [no eqc value]"; + } + tout << std::endl; + } + ); + expr * lastTester = binary_search_len_tester_stack[freeVar].back(); + bool lastTesterHasEqcValue; + expr * lastTesterValue = get_eqc_value(lastTester, lastTesterHasEqcValue); + zstring lastTesterConstant; + if (!lastTesterHasEqcValue) { + TRACE("str", tout << "length tester " << mk_pp(lastTester, m) << " at top of stack doesn't have an EQC value yet" << std::endl;); + // check previousLenTester + if (previousLenTester == lastTester) { + lastTesterConstant = previousLenTesterValue; + TRACE("str", tout << "invoked with previousLenTester info matching top of stack" << std::endl;); + } else { + TRACE("str", tout << "WARNING: unexpected reordering of length testers!" << std::endl;); + UNREACHABLE(); return NULL; + } + } else { + u.str.is_string(lastTesterValue, lastTesterConstant); + } + TRACE("str", tout << "last length tester is assigned \"" << lastTesterConstant << "\"" << "\n";); + if (lastTesterConstant == "more" || lastTesterConstant == "less") { + // use the previous bounds info to generate a new midpoint + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + TRACE("str", tout << "last bounds are [" << lastBounds.lowerBound << " | " << lastBounds.midPoint << " | " << lastBounds.upperBound << "]!" << lastBounds.windowSize << std::endl;); + binary_search_info newBounds; + expr * newTester; + if (lastTesterConstant == "more") { + // special case: if the midpoint, upper bound, and window size are all equal, + // we double the window size and adjust the bounds + if (lastBounds.midPoint == lastBounds.upperBound && lastBounds.upperBound == lastBounds.windowSize) { + TRACE("str", tout << "search hit window size; expanding" << std::endl;); + newBounds.lowerBound = lastBounds.windowSize + rational::one(); + newBounds.windowSize = lastBounds.windowSize * rational(2); + newBounds.upperBound = newBounds.windowSize; + newBounds.calculate_midpoint(); + } else if (false) { + // handle the case where the midpoint can't be increased further + // (e.g. a window like [50 | 50 | 50]!64 and we don't answer "50") + } else { + // general case + newBounds.lowerBound = lastBounds.midPoint + rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.upperBound = lastBounds.upperBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_high.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_high.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } else if (lastTesterConstant == "less") { + if (false) { + // handle the case where the midpoint can't be decreased further + // (e.g. a window like [0 | 0 | 0]!64 and we don't answer "0" + } else { + // general case + newBounds.upperBound = lastBounds.midPoint - rational::one(); + newBounds.windowSize = lastBounds.windowSize; + newBounds.lowerBound = lastBounds.lowerBound; + newBounds.calculate_midpoint(); + } + if (!binary_search_next_var_low.find(lastTester, newTester)) { + newTester = mk_internal_lenTest_var(freeVar, newBounds.midPoint.get_int32()); + binary_search_next_var_low.insert(lastTester, newTester); + } + refresh_theory_var(newTester); + } + TRACE("str", tout << "new bounds are [" << newBounds.lowerBound << " | " << newBounds.midPoint << " | " << newBounds.upperBound << "]!" << newBounds.windowSize << std::endl;); + binary_search_len_tester_stack[freeVar].push_back(newTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_len_tester_info.insert(newTester, newBounds); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, newTester)); + + literal_vector case_split_literals; + expr_ref next_case_split(binary_search_case_split(freeVar, newTester, newBounds, case_split_literals)); + m_trail.push_back(next_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return next_case_split; + } else { // lastTesterConstant is a concrete value + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // defensive check that this length did not converge on a negative value. + binary_search_info lastBounds; + if (!binary_search_len_tester_info.find(lastTester, lastBounds)) { + // unexpected + TRACE("str", tout << "WARNING: no bounds information available for last tester!" << std::endl;); + UNREACHABLE(); + } + if (lastBounds.midPoint.is_neg()) { + TRACE("str", tout << "WARNING: length search converged on a negative value. Negating this constraint." << std::endl;); + expr_ref axiom(m_autil.mk_ge(mk_strlen(freeVar), m_autil.mk_numeral(rational::zero(), true)), m); + return axiom; + } + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, lastTester, lastTesterConstant, NULL, zstring("")); + return valueAssert; + } + } else { + // no length testers yet + TRACE("str", tout << "no length testers for " << mk_pp(freeVar, m) << std::endl;); + binary_search_len_tester_stack.insert(freeVar, ptr_vector()); + + expr * firstTester; + rational lowerBound(0); + rational upperBound(m_params.m_BinarySearchInitialUpperBound); + rational windowSize(upperBound); + rational midPoint(floor(upperBound / rational(2))); + if (!binary_search_starting_len_tester.find(freeVar, firstTester)) { + firstTester = mk_internal_lenTest_var(freeVar, midPoint.get_int32()); + binary_search_starting_len_tester.insert(freeVar, firstTester); + } + refresh_theory_var(firstTester); + + binary_search_len_tester_stack[freeVar].push_back(firstTester); + m_trail_stack.push(binary_search_trail(binary_search_len_tester_stack, freeVar)); + binary_search_info new_info(lowerBound, midPoint, upperBound, windowSize); + binary_search_len_tester_info.insert(firstTester, new_info); + m_trail_stack.push(insert_obj_map(binary_search_len_tester_info, firstTester)); + + literal_vector case_split_literals; + expr_ref initial_case_split(binary_search_case_split(freeVar, firstTester, new_info, case_split_literals)); + m_trail.push_back(initial_case_split); + // ctx.mk_th_case_split(case_split_literals.size(), case_split_literals.c_ptr()); + return initial_case_split; + } + } + + // ----------------------------------------------------------------------------------------------------- + // True branch will be taken in final_check: + // - When we discover a variable is "free" for the first time + // lenTesterInCbEq = NULL + // lenTesterValue = "" + // False branch will be taken when invoked by new_eq_eh(). + // - After we set up length tester for a "free" var in final_check, + // when the tester is assigned to some value (e.g. "more" or "4"), + // lenTesterInCbEq != NULL, and its value will be passed by lenTesterValue + // The difference is that in new_eq_eh(), lenTesterInCbEq and its value have NOT been put into a same eqc + // ----------------------------------------------------------------------------------------------------- + expr * theory_str::gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue) { + + ast_manager & m = get_manager(); + + TRACE("str", tout << "gen for free var " << mk_ismt2_pp(freeVar, m) << std::endl;); + + if (m_params.m_UseBinarySearch) { + TRACE("str", tout << "using binary search heuristic" << std::endl;); + return binary_search_length_test(freeVar, lenTesterInCbEq, lenTesterValue); + } else { + bool map_effectively_empty = false; + if (!fvar_len_count_map.contains(freeVar)) { + TRACE("str", tout << "fvar_len_count_map is empty" << std::endl;); + map_effectively_empty = true; + } + + if (!map_effectively_empty) { + // check whether any entries correspond to variables that went out of scope; + // if every entry is out of scope then the map counts as being empty + + // assume empty and find a counterexample + map_effectively_empty = true; + ptr_vector indicator_set = fvar_lenTester_map[freeVar]; + for (ptr_vector::iterator it = indicator_set.begin(); it != indicator_set.end(); ++it) { + expr * indicator = *it; + if (internal_variable_set.find(indicator) != internal_variable_set.end()) { + TRACE("str", tout <<"found active internal variable " << mk_ismt2_pp(indicator, m) + << " in fvar_lenTester_map[freeVar]" << std::endl;); + map_effectively_empty = false; + break; + } + } + CTRACE("str", map_effectively_empty, tout << "all variables in fvar_lenTester_map[freeVar] out of scope" << std::endl;); + } + + if (map_effectively_empty) { + // no length assertions for this free variable have ever been added. + TRACE("str", tout << "no length assertions yet" << std::endl;); + + fvar_len_count_map.insert(freeVar, 1); + unsigned int testNum = fvar_len_count_map[freeVar]; + + expr_ref indicator(mk_internal_lenTest_var(freeVar, testNum), m); + SASSERT(indicator); + + // since the map is "effectively empty", we can remove those variables that have left scope... + fvar_lenTester_map[freeVar].shrink(0); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); SASSERT(lenTestAssert != NULL); return lenTestAssert; } else { - TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); - // length is fixed - expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); - return valueAssert; + TRACE("str", tout << "found previous in-scope length assertions" << std::endl;); + + expr * effectiveLenInd = NULL; + zstring effectiveLenIndiStr(""); + int lenTesterCount = (int) fvar_lenTester_map[freeVar].size(); + + TRACE("str", + tout << lenTesterCount << " length testers in fvar_lenTester_map[" << mk_pp(freeVar, m) << "]:" << std::endl; + for (int i = 0; i < lenTesterCount; ++i) { + expr * len_indicator = fvar_lenTester_map[freeVar][i]; + tout << mk_pp(len_indicator, m) << ": "; + bool effectiveInScope = (internal_variable_set.find(len_indicator) != internal_variable_set.end()); + tout << (effectiveInScope ? "in scope" : "NOT in scope"); + tout << std::endl; + } + ); + + int i = 0; + for (; i < lenTesterCount; ++i) { + expr * len_indicator_pre = fvar_lenTester_map[freeVar][i]; + // check whether this is in scope as well + if (internal_variable_set.find(len_indicator_pre) == internal_variable_set.end()) { + TRACE("str", tout << "length indicator " << mk_pp(len_indicator_pre, m) << " not in scope" << std::endl;); + continue; + } + + bool indicatorHasEqcValue = false; + expr * len_indicator_value = get_eqc_value(len_indicator_pre, indicatorHasEqcValue); + TRACE("str", tout << "length indicator " << mk_ismt2_pp(len_indicator_pre, m) << + " = " << mk_ismt2_pp(len_indicator_value, m) << std::endl;); + if (indicatorHasEqcValue) { + zstring len_pIndiStr; + u.str.is_string(len_indicator_value, len_pIndiStr); + if (len_pIndiStr != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = len_pIndiStr; + break; + } + } else { + if (lenTesterInCbEq != len_indicator_pre) { + TRACE("str", tout << "WARNING: length indicator " << mk_ismt2_pp(len_indicator_pre, m) + << " does not have an equivalence class value." + << " i = " << i << ", lenTesterCount = " << lenTesterCount << std::endl;); + if (i > 0) { + effectiveLenInd = fvar_lenTester_map[freeVar][i - 1]; + bool effectiveHasEqcValue; + expr * effective_eqc_value = get_eqc_value(effectiveLenInd, effectiveHasEqcValue); + bool effectiveInScope = (internal_variable_set.find(effectiveLenInd) != internal_variable_set.end()); + TRACE("str", tout << "checking effective length indicator " << mk_pp(effectiveLenInd, m) << ": " + << (effectiveInScope ? "in scope" : "NOT in scope") << ", "; + if (effectiveHasEqcValue) { + tout << "~= " << mk_pp(effective_eqc_value, m); + } else { + tout << "no eqc string constant"; + } + tout << std::endl;); + if (effectiveLenInd == lenTesterInCbEq) { + effectiveLenIndiStr = lenTesterValue; + } else { + if (effectiveHasEqcValue) { + u.str.is_string(effective_eqc_value, effectiveLenIndiStr); + } else { + NOT_IMPLEMENTED_YET(); + } + } + } + break; + } + // lenTesterInCbEq == len_indicator_pre + else { + if (lenTesterValue != "more") { + effectiveLenInd = len_indicator_pre; + effectiveLenIndiStr = lenTesterValue; + break; + } + } + } // !indicatorHasEqcValue + } // for (i : [0..lenTesterCount-1]) + if (effectiveLenIndiStr == "more" || effectiveLenIndiStr == "") { + TRACE("str", tout << "length is not fixed; generating length tester options for free var" << std::endl;); + expr_ref indicator(m); + unsigned int testNum = 0; + + TRACE("str", tout << "effectiveLenIndiStr = " << effectiveLenIndiStr + << ", i = " << i << ", lenTesterCount = " << lenTesterCount << "\n";); + + if (i == lenTesterCount) { + fvar_len_count_map[freeVar] = fvar_len_count_map[freeVar] + 1; + testNum = fvar_len_count_map[freeVar]; + indicator = mk_internal_lenTest_var(freeVar, testNum); + fvar_lenTester_map[freeVar].push_back(indicator); + lenTester_fvar_map.insert(indicator, freeVar); + } else { + indicator = fvar_lenTester_map[freeVar][i]; + refresh_theory_var(indicator); + testNum = i + 1; + } + expr * lenTestAssert = gen_len_test_options(freeVar, indicator, testNum); + SASSERT(lenTestAssert != NULL); + return lenTestAssert; + } else { + TRACE("str", tout << "length is fixed; generating models for free var" << std::endl;); + // length is fixed + expr * valueAssert = gen_free_var_options(freeVar, effectiveLenInd, effectiveLenIndiStr, NULL, zstring("")); + return valueAssert; + } + } // fVarLenCountMap.find(...) + + } // !UseBinarySearch + } + + void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { + context & ctx = get_context(); + + expr * eqcNode = n; + do { + if (u.str.is_concat(to_app(eqcNode))) { + concats.insert(eqcNode); } - } // fVarLenCountMap.find(...) + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } - } // !UseBinarySearch -} + void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { + context & ctx = get_context(); -void theory_str::get_concats_in_eqc(expr * n, std::set & concats) { - context & ctx = get_context(); + expr * eqcNode = n; + do { + if (variable_set.find(eqcNode) != variable_set.end()) { + varSet.insert(eqcNode); + } + eqcNode = get_eqc_next(eqcNode); + } while (eqcNode != n); + } - expr * eqcNode = n; - do { - if (u.str.is_concat(to_app(eqcNode))) { - concats.insert(eqcNode); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} + bool cmpvarnames(expr * lhs, expr * rhs) { + symbol lhs_name = to_app(lhs)->get_decl()->get_name(); + symbol rhs_name = to_app(rhs)->get_decl()->get_name(); + return lhs_name.str() < rhs_name.str(); + } -void theory_str::get_var_in_eqc(expr * n, std::set & varSet) { - context & ctx = get_context(); + void theory_str::process_free_var(std::map & freeVar_map) { + context & ctx = get_context(); + ast_manager & m = get_manager(); - expr * eqcNode = n; - do { - if (variable_set.find(eqcNode) != variable_set.end()) { - varSet.insert(eqcNode); - } - eqcNode = get_eqc_next(eqcNode); - } while (eqcNode != n); -} + std::set eqcRepSet; + std::set leafVarSet; + std::map > aloneVars; -bool cmpvarnames(expr * lhs, expr * rhs) { - symbol lhs_name = to_app(lhs)->get_decl()->get_name(); - symbol rhs_name = to_app(rhs)->get_decl()->get_name(); - return lhs_name.str() < rhs_name.str(); -} + for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { + expr * freeVar = fvIt->first; + // skip all regular expression vars + if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { + continue; + } -void theory_str::process_free_var(std::map & freeVar_map) { - context & ctx = get_context(); - ast_manager & m = get_manager(); - - std::set eqcRepSet; - std::set leafVarSet; - std::map > aloneVars; - - for (std::map::iterator fvIt = freeVar_map.begin(); fvIt != freeVar_map.end(); fvIt++) { - expr * freeVar = fvIt->first; - // skip all regular expression vars - if (regex_variable_set.find(freeVar) != regex_variable_set.end()) { - continue; - } - - // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. - // If found, have to filter it out - std::set eqVarSet; - get_var_in_eqc(freeVar, eqVarSet); - bool duplicated = false; - expr * dupVar = NULL; - for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { - if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { - duplicated = true; - dupVar = *itorEqv; - break; - } - } - if (duplicated && dupVar != NULL) { - TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) - << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); - continue; - } else { - eqcRepSet.insert(freeVar); - } - } - - for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { - bool standAlone = true; - expr * freeVar = *fvIt; - // has length constraint initially - if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { - standAlone = false; - } - // iterate parents - if (standAlone) { - // I hope this works! - enode * e_freeVar = ctx.get_enode(freeVar); - enode_vector::iterator it = e_freeVar->begin_parents(); - for (; it != e_freeVar->end_parents(); ++it) { - expr * parentAst = (*it)->get_owner(); - if (u.str.is_concat(to_app(parentAst))) { - standAlone = false; - break; - } - } - } - - if (standAlone) { - rational len_value; - bool len_value_exists = get_len_value(freeVar, len_value); - if (len_value_exists) { - leafVarSet.insert(freeVar); - } else { - aloneVars[-1].insert(freeVar); - } - } else { - leafVarSet.insert(freeVar); - } - } - - for(std::set::iterator itor1 = leafVarSet.begin(); - itor1 != leafVarSet.end(); ++itor1) { - expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); - // gen_len_val_options_for_free_var() can legally return NULL, - // as methods that it calls may assert their own axioms instead. - if (toAssert != NULL) { - assert_axiom(toAssert); - } - } - - for (std::map >::iterator mItor = aloneVars.begin(); - mItor != aloneVars.end(); ++mItor) { - std::set::iterator itor2 = mItor->second.begin(); - for(; itor2 != mItor->second.end(); ++itor2) { - expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); - // same deal with returning a NULL axiom here - if(toAssert != NULL) { - assert_axiom(toAssert); - } - } - } -} - -/* - * Collect all unroll functions - * and constant string in eqc of node n - */ -void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { - constStr = NULL; - unrollFuncSet.clear(); - context & ctx = get_context(); - - expr * curr = n; - do { - if (u.str.is_string(to_app(curr))) { - constStr = curr; - } else if (u.re.is_unroll(to_app(curr))) { - if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { - unrollFuncSet.insert(curr); + // Iterate the EQC of freeVar, its eqc variable should not be in the eqcRepSet. + // If found, have to filter it out + std::set eqVarSet; + get_var_in_eqc(freeVar, eqVarSet); + bool duplicated = false; + expr * dupVar = NULL; + for (std::set::iterator itorEqv = eqVarSet.begin(); itorEqv != eqVarSet.end(); itorEqv++) { + if (eqcRepSet.find(*itorEqv) != eqcRepSet.end()) { + duplicated = true; + dupVar = *itorEqv; + break; + } + } + if (duplicated && dupVar != NULL) { + TRACE("str", tout << "Duplicated free variable found:" << mk_ismt2_pp(freeVar, m) + << " = " << mk_ismt2_pp(dupVar, m) << " (SKIP)" << std::endl;); + continue; + } else { + eqcRepSet.insert(freeVar); } } - curr = get_eqc_next(curr); - } while (curr != n); -} -// Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. -void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { - constStr = NULL; - unrollFuncSet.clear(); - context & ctx = get_context(); + for (std::set::iterator fvIt = eqcRepSet.begin(); fvIt != eqcRepSet.end(); fvIt++) { + bool standAlone = true; + expr * freeVar = *fvIt; + // has length constraint initially + if (input_var_in_len.find(freeVar) != input_var_in_len.end()) { + standAlone = false; + } + // iterate parents + if (standAlone) { + // I hope this works! + enode * e_freeVar = ctx.get_enode(freeVar); + enode_vector::iterator it = e_freeVar->begin_parents(); + for (; it != e_freeVar->end_parents(); ++it) { + expr * parentAst = (*it)->get_owner(); + if (u.str.is_concat(to_app(parentAst))) { + standAlone = false; + break; + } + } + } - expr * curr = n; - do { - if (u.str.is_string(to_app(curr))) { - constStr = curr; - } else if (u.re.is_unroll(to_app(curr))) { - expr * core = to_app(curr)->get_arg(0); - if (u.re.is_to_re(to_app(core))) { - if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { - unrollFuncSet.insert(curr); - } - } - } - curr = get_eqc_next(curr); - } while (curr != n); -} + if (standAlone) { + rational len_value; + bool len_value_exists = get_len_value(freeVar, len_value); + if (len_value_exists) { + leafVarSet.insert(freeVar); + } else { + aloneVars[-1].insert(freeVar); + } + } else { + leafVarSet.insert(freeVar); + } + } -void theory_str::init_model(model_generator & mg) { - //TRACE("str", tout << "initializing model" << std::endl; display(tout);); - m_factory = alloc(str_value_factory, get_manager(), get_family_id()); - mg.register_factory(m_factory); -} + for(std::set::iterator itor1 = leafVarSet.begin(); + itor1 != leafVarSet.end(); ++itor1) { + expr * toAssert = gen_len_val_options_for_free_var(*itor1, NULL, ""); + // gen_len_val_options_for_free_var() can legally return NULL, + // as methods that it calls may assert their own axioms instead. + if (toAssert != NULL) { + assert_axiom(toAssert); + } + } -/* - * Helper function for mk_value(). - * Attempts to resolve the expression 'n' to a string constant. - * Stronger than get_eqc_value() in that it will perform recursive descent - * through every subexpression and attempt to resolve those to concrete values as well. - * Returns the concrete value obtained from this process, - * guaranteed to satisfy m_strutil.is_string(), - * if one could be obtained, - * or else returns NULL if no concrete value was derived. - */ -app * theory_str::mk_value_helper(app * n) { - if (u.str.is_string(n)) { - return n; - } else if (u.str.is_concat(n)) { - // recursively call this function on each argument - SASSERT(n->get_num_args() == 2); - expr * a0 = n->get_arg(0); - expr * a1 = n->get_arg(1); - - app * a0_conststr = mk_value_helper(to_app(a0)); - app * a1_conststr = mk_value_helper(to_app(a1)); - - if (a0_conststr != NULL && a1_conststr != NULL) { - zstring a0_s, a1_s; - u.str.is_string(a0_conststr, a0_s); - u.str.is_string(a1_conststr, a1_s); - zstring result = a0_s + a1_s; - return to_app(mk_string(result)); + for (std::map >::iterator mItor = aloneVars.begin(); + mItor != aloneVars.end(); ++mItor) { + std::set::iterator itor2 = mItor->second.begin(); + for(; itor2 != mItor->second.end(); ++itor2) { + expr * toAssert = gen_len_val_options_for_free_var(*itor2, NULL, ""); + // same deal with returning a NULL axiom here + if(toAssert != NULL) { + assert_axiom(toAssert); + } + } } } - // fallback path - // try to find some constant string, anything, in the equivalence class of n - bool hasEqc = false; - expr * n_eqc = get_eqc_value(n, hasEqc); - if (hasEqc) { - return to_app(n_eqc); - } else { - return NULL; + + /* + * Collect all unroll functions + * and constant string in eqc of node n + */ + void theory_str::get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); + + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + curr = get_eqc_next(curr); + } while (curr != n); } -} -model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { - TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << - " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); - ast_manager & m = get_manager(); - context & ctx = get_context(); - app_ref owner(m); - owner = n->get_owner(); + // Collect simple Unroll functions (whose core is Str2Reg) and constant strings in the EQC of n. + void theory_str::get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet) { + constStr = NULL; + unrollFuncSet.clear(); + context & ctx = get_context(); - // If the owner is not internalized, it doesn't have an enode associated. - SASSERT(ctx.e_internalized(owner)); - - app * val = mk_value_helper(owner); - if (val != NULL) { - return alloc(expr_wrapper_proc, val); - } else { - TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); - return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); + expr * curr = n; + do { + if (u.str.is_string(to_app(curr))) { + constStr = curr; + } else if (u.re.is_unroll(to_app(curr))) { + expr * core = to_app(curr)->get_arg(0); + if (u.re.is_to_re(to_app(core))) { + if (unrollFuncSet.find(curr) == unrollFuncSet.end()) { + unrollFuncSet.insert(curr); + } + } + } + curr = get_eqc_next(curr); + } while (curr != n); } -} -void theory_str::finalize_model(model_generator & mg) {} + void theory_str::init_model(model_generator & mg) { + //TRACE("str", tout << "initializing model" << std::endl; display(tout);); + m_factory = alloc(str_value_factory, get_manager(), get_family_id()); + mg.register_factory(m_factory); + } -void theory_str::display(std::ostream & out) const { - out << "TODO: theory_str display" << std::endl; -} + /* + * Helper function for mk_value(). + * Attempts to resolve the expression 'n' to a string constant. + * Stronger than get_eqc_value() in that it will perform recursive descent + * through every subexpression and attempt to resolve those to concrete values as well. + * Returns the concrete value obtained from this process, + * guaranteed to satisfy m_strutil.is_string(), + * if one could be obtained, + * or else returns NULL if no concrete value was derived. + */ + app * theory_str::mk_value_helper(app * n) { + if (u.str.is_string(n)) { + return n; + } else if (u.str.is_concat(n)) { + // recursively call this function on each argument + SASSERT(n->get_num_args() == 2); + expr * a0 = n->get_arg(0); + expr * a1 = n->get_arg(1); + + app * a0_conststr = mk_value_helper(to_app(a0)); + app * a1_conststr = mk_value_helper(to_app(a1)); + + if (a0_conststr != NULL && a1_conststr != NULL) { + zstring a0_s, a1_s; + u.str.is_string(a0_conststr, a0_s); + u.str.is_string(a1_conststr, a1_s); + zstring result = a0_s + a1_s; + return to_app(mk_string(result)); + } + } + // fallback path + // try to find some constant string, anything, in the equivalence class of n + bool hasEqc = false; + expr * n_eqc = get_eqc_value(n, hasEqc); + if (hasEqc) { + return to_app(n_eqc); + } else { + return NULL; + } + } + + model_value_proc * theory_str::mk_value(enode * n, model_generator & mg) { + TRACE("str", tout << "mk_value for: " << mk_ismt2_pp(n->get_owner(), get_manager()) << + " (sort " << mk_ismt2_pp(get_manager().get_sort(n->get_owner()), get_manager()) << ")" << std::endl;); + ast_manager & m = get_manager(); + context & ctx = get_context(); + app_ref owner(m); + owner = n->get_owner(); + + // If the owner is not internalized, it doesn't have an enode associated. + SASSERT(ctx.e_internalized(owner)); + + app * val = mk_value_helper(owner); + if (val != NULL) { + return alloc(expr_wrapper_proc, val); + } else { + TRACE("str", tout << "WARNING: failed to find a concrete value, falling back" << std::endl;); + return alloc(expr_wrapper_proc, to_app(mk_string("**UNUSED**"))); + } + } + + void theory_str::finalize_model(model_generator & mg) {} + + void theory_str::display(std::ostream & out) const { + out << "TODO: theory_str display" << std::endl; + } }; /* namespace smt */ diff --git a/src/smt/theory_str.h b/src/smt/theory_str.h index 7c2df9e12..2e6d96fa7 100644 --- a/src/smt/theory_str.h +++ b/src/smt/theory_str.h @@ -1,19 +1,19 @@ /*++ -Module Name: + Module Name: - theory_str.h + theory_str.h -Abstract: + Abstract: - String Theory Plugin + String Theory Plugin -Author: + Author: - Murphy Berzish and Yunhui Zheng + Murphy Berzish and Yunhui Zheng -Revision History: + Revision History: ---*/ + --*/ #ifndef _THEORY_STR_H_ #define _THEORY_STR_H_ @@ -33,619 +33,619 @@ Revision History: namespace smt { - typedef hashtable symbol_set; +typedef hashtable symbol_set; - class str_value_factory : public value_factory { - seq_util u; - symbol_set m_strings; - std::string delim; - unsigned m_next; - public: - str_value_factory(ast_manager & m, family_id fid) : - value_factory(m, fid), - u(m), delim("!"), m_next(0) {} - virtual ~str_value_factory() {} - virtual expr * get_some_value(sort * s) { - return u.str.mk_string(symbol("some value")); - } - virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { - v1 = u.str.mk_string(symbol("value 1")); - v2 = u.str.mk_string(symbol("value 2")); - return true; - } - virtual expr * get_fresh_value(sort * s) { - if (u.is_string(s)) { - while (true) { - std::ostringstream strm; - strm << delim << std::hex << (m_next++) << std::dec << delim; - symbol sym(strm.str().c_str()); - if (m_strings.contains(sym)) continue; - m_strings.insert(sym); - return u.str.mk_string(sym); - } +class str_value_factory : public value_factory { + seq_util u; + symbol_set m_strings; + std::string delim; + unsigned m_next; +public: + str_value_factory(ast_manager & m, family_id fid) : + value_factory(m, fid), + u(m), delim("!"), m_next(0) {} + virtual ~str_value_factory() {} + virtual expr * get_some_value(sort * s) { + return u.str.mk_string(symbol("some value")); + } + virtual bool get_some_values(sort * s, expr_ref & v1, expr_ref & v2) { + v1 = u.str.mk_string(symbol("value 1")); + v2 = u.str.mk_string(symbol("value 2")); + return true; + } + virtual expr * get_fresh_value(sort * s) { + if (u.is_string(s)) { + while (true) { + std::ostringstream strm; + strm << delim << std::hex << (m_next++) << std::dec << delim; + symbol sym(strm.str().c_str()); + if (m_strings.contains(sym)) continue; + m_strings.insert(sym); + return u.str.mk_string(sym); } - sort* seq = 0; - if (u.is_re(s, seq)) { - expr* v0 = get_fresh_value(seq); - return u.re.mk_to_re(v0); - } - TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); - UNREACHABLE(); return NULL; } - virtual void register_value(expr * n) { /* Ignore */ } - }; + sort* seq = 0; + if (u.is_re(s, seq)) { + expr* v0 = get_fresh_value(seq); + return u.re.mk_to_re(v0); + } + TRACE("t_str", tout << "unexpected sort in get_fresh_value(): " << mk_pp(s, m_manager) << std::endl;); + UNREACHABLE(); return NULL; + } + virtual void register_value(expr * n) { /* Ignore */ } +}; - // rather than modify obj_pair_map I inherit from it and add my own helper methods - class theory_str_contain_pair_bool_map_t : public obj_pair_map { - public: - expr * operator[](std::pair key) const { - expr * value; - bool found = this->find(key.first, key.second, value); - if (found) { - return value; +// rather than modify obj_pair_map I inherit from it and add my own helper methods +class theory_str_contain_pair_bool_map_t : public obj_pair_map { +public: + expr * operator[](std::pair key) const { + expr * value; + bool found = this->find(key.first, key.second, value); + if (found) { + return value; + } else { + TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); + return NULL; + } + } + + bool contains(std::pair key) const { + expr * unused; + return this->find(key.first, key.second, unused); + } +}; + +template +class binary_search_trail : public trail { + obj_map > & target; + expr * entry; +public: + binary_search_trail(obj_map > & target, expr * entry) : + target(target), entry(entry) {} + virtual ~binary_search_trail() {} + virtual void undo(Ctx & ctx) { + TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); + if (target.contains(entry)) { + if (!target[entry].empty()) { + target[entry].pop_back(); } else { - TRACE("t_str", tout << "WARNING: lookup miss in contain_pair_bool_map!" << std::endl;); - return NULL; + TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); } + } else { + TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); } + } +}; - bool contains(std::pair key) const { - expr * unused; - return this->find(key.first, key.second, unused); + +class nfa { +protected: + bool m_valid; + unsigned m_next_id; + + unsigned next_id() { + unsigned retval = m_next_id; + ++m_next_id; + return retval; + } + + unsigned m_start_state; + unsigned m_end_state; + + std::map > transition_map; + std::map > epsilon_map; + + void make_transition(unsigned start, char symbol, unsigned end) { + transition_map[start][symbol] = end; + } + + void make_epsilon_move(unsigned start, unsigned end) { + epsilon_map[start].insert(end); + } + + // Convert a regular expression to an e-NFA using Thompson's construction + void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); + +public: + nfa(seq_util & u, expr * e) +: m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { + convert_re(e, m_start_state, m_end_state, u); + } + + nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} + + bool is_valid() const { + return m_valid; + } + + void epsilon_closure(unsigned start, std::set & closure); + + bool matches(zstring input); +}; + +class theory_str : public theory { + struct T_cut + { + int level; + std::map vars; + + T_cut() { + level = -100; } }; - template - class binary_search_trail : public trail { - obj_map > & target; - expr * entry; - public: - binary_search_trail(obj_map > & target, expr * entry) : - target(target), entry(entry) {} - virtual ~binary_search_trail() {} - virtual void undo(Ctx & ctx) { - TRACE("t_str_binary_search", tout << "in binary_search_trail::undo()" << std::endl;); - if (target.contains(entry)) { - if (!target[entry].empty()) { - target[entry].pop_back(); - } else { - TRACE("t_str_binary_search", tout << "WARNING: attempt to remove length tester from an empty stack" << std::endl;); - } - } else { - TRACE("t_str_binary_search", tout << "WARNING: attempt to access length tester map via invalid key" << std::endl;); - } + typedef trail_stack th_trail_stack; + typedef union_find th_union_find; + + typedef map, default_eq > rational_map; + struct zstring_hash_proc { + unsigned operator()(zstring const & s) const { + return string_hash(s.encode().c_str(), static_cast(s.length()), 17); } }; + typedef map > string_map; +protected: + theory_str_params const & m_params; - class nfa { - protected: - bool m_valid; - unsigned m_next_id; + /* + * Setting EagerStringConstantLengthAssertions to true allows some methods, + * in particular internalize_term(), to add + * length assertions about relevant string constants. + * Note that currently this should always be set to 'true', or else *no* length assertions + * will be made about string constants. + */ + bool opt_EagerStringConstantLengthAssertions; - unsigned next_id() { - unsigned retval = m_next_id; - ++m_next_id; - return retval; + /* + * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked + * without asserting any new axioms is considered a bug and will throw an exception. + */ + bool opt_VerifyFinalCheckProgress; + + /* + * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. + */ + int opt_LCMUnrollStep; + + /* + * If NoQuickReturn_IntegerTheory is set to true, + * integer theory integration checks that assert axioms + * will not return from the function after asserting their axioms. + * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. + */ + bool opt_NoQuickReturn_IntegerTheory; + + /* + * If DisableIntegerTheoryIntegration is set to true, + * ALL calls to the integer theory integration methods + * (get_value, get_len_value, lower_bound, upper_bound) + * will ignore what the arithmetic solver believes about length terms, + * and will return no information. + * + * This reduces performance significantly, but can be useful to enable + * if it is suspected that string-integer integration, or the arithmetic solver itself, + * might have a bug. + * + * The default behaviour of Z3str2 is to set this to 'false'. + */ + bool opt_DisableIntegerTheoryIntegration; + + /* + * If DeferEQCConsistencyCheck is set to true, + * expensive calls to new_eq_check() will be deferred until final check, + * at which time the consistency of *all* string equivalence classes will be validated. + */ + bool opt_DeferEQCConsistencyCheck; + + /* + * If CheckVariableScope is set to true, + * pop_scope_eh() and final_check_eh() will run extra checks + * to determine whether the current assignment + * contains references to any internal variables that are no longer in scope. + */ + bool opt_CheckVariableScope; + + /* + * If ConcatOverlapAvoid is set to true, + * the check to simplify Concat = Concat in handle_equality() will + * avoid simplifying wrt. pairs of Concat terms that will immediately + * result in an overlap. (false = Z3str2 behaviour) + */ + bool opt_ConcatOverlapAvoid; + + bool search_started; + arith_util m_autil; + seq_util u; + int sLevel; + + bool finalCheckProgressIndicator; + + expr_ref_vector m_trail; // trail for generated terms + + str_value_factory * m_factory; + + // terms we couldn't go through set_up_axioms() with because they weren't internalized + expr_ref_vector m_delayed_axiom_setup_terms; + + ptr_vector m_basicstr_axiom_todo; + svector > m_str_eq_todo; + ptr_vector m_concat_axiom_todo; + ptr_vector m_string_constant_length_todo; + ptr_vector m_concat_eval_todo; + + // enode lists for library-aware/high-level string terms (e.g. substr, contains) + ptr_vector m_library_aware_axiom_todo; + + // hashtable of all exprs for which we've already set up term-specific axioms -- + // this prevents infinite recursive descent with respect to axioms that + // include an occurrence of the term for which axioms are being generated + obj_hashtable axiomatized_terms; + + int tmpStringVarCount; + int tmpXorVarCount; + int tmpLenTestVarCount; + int tmpValTestVarCount; + std::map, std::map > varForBreakConcat; + + bool avoidLoopCut; + bool loopDetected; + obj_map > cut_var_map; + expr_ref m_theoryStrOverlapAssumption_term; + + obj_hashtable variable_set; + obj_hashtable internal_variable_set; + obj_hashtable regex_variable_set; + std::map > internal_variable_scope_levels; + + obj_hashtable internal_lenTest_vars; + obj_hashtable internal_valTest_vars; + obj_hashtable internal_unrollTest_vars; + + obj_hashtable input_var_in_len; + + obj_map fvar_len_count_map; + std::map > fvar_lenTester_map; + obj_map lenTester_fvar_map; + + std::map > > > fvar_valueTester_map; + std::map valueTester_fvar_map; + + std::map val_range_map; + + // This can't be an expr_ref_vector because the constructor is wrong, + // we would need to modify the allocator so we pass in ast_manager + std::map, ptr_vector > > unroll_tries_map; + std::map unroll_var_map; + std::map, expr*> concat_eq_unroll_ast_map; + + expr_ref_vector contains_map; + + theory_str_contain_pair_bool_map_t contain_pair_bool_map; + //obj_map > contain_pair_idx_map; + std::map > > contain_pair_idx_map; + + std::map, expr*> regex_in_bool_map; + std::map > regex_in_var_reg_str_map; + + std::map regex_nfa_cache; // Regex term --> NFA + + char * char_set; + std::map charSetLookupTable; + int charSetSize; + + obj_pair_map concat_astNode_map; + + // all (str.to-int) and (int.to-str) terms + expr_ref_vector string_int_conversion_terms; + obj_hashtable string_int_axioms; + + // used when opt_FastLengthTesterCache is true + rational_map lengthTesterCache; + // used when opt_FastValueTesterCache is true + string_map valueTesterCache; + + string_map stringConstantCache; + unsigned long totalCacheAccessCount; + unsigned long cacheHitCount; + unsigned long cacheMissCount; + + // cache mapping each string S to Length(S) + obj_map length_ast_map; + + th_union_find m_find; + th_trail_stack m_trail_stack; + theory_var get_var(expr * n) const; + expr * get_eqc_next(expr * n); + app * get_ast(theory_var i); + + // binary search heuristic data + struct binary_search_info { + rational lowerBound; + rational midPoint; + rational upperBound; + rational windowSize; + + binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), + upperBound(rational::zero()), windowSize(rational::zero()) {} + binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : + lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} + + void calculate_midpoint() { + midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); } - - unsigned m_start_state; - unsigned m_end_state; - - std::map > transition_map; - std::map > epsilon_map; - - void make_transition(unsigned start, char symbol, unsigned end) { - transition_map[start][symbol] = end; - } - - void make_epsilon_move(unsigned start, unsigned end) { - epsilon_map[start].insert(end); - } - - // Convert a regular expression to an e-NFA using Thompson's construction - void convert_re(expr * e, unsigned & start, unsigned & end, seq_util & u); - - public: - nfa(seq_util & u, expr * e) - : m_valid(true), m_next_id(0), m_start_state(0), m_end_state(0) { - convert_re(e, m_start_state, m_end_state, u); - } - - nfa() : m_valid(false), m_next_id(0), m_start_state(0), m_end_state(0) {} - - bool is_valid() const { - return m_valid; - } - - void epsilon_closure(unsigned start, std::set & closure); - - bool matches(zstring input); }; + // maps a free string var to a stack of active length testers. + // can use binary_search_trail to record changes to this object + obj_map > binary_search_len_tester_stack; + // maps a length tester var to the *active* search window + obj_map binary_search_len_tester_info; + // maps a free string var to the first length tester to be (re)used + obj_map binary_search_starting_len_tester; + // maps a length tester to the next length tester to be (re)used if the split is "low" + obj_map binary_search_next_var_low; + // maps a length tester to the next length tester to be (re)used if the split is "high" + obj_map binary_search_next_var_high; - class theory_str : public theory { - struct T_cut - { - int level; - std::map vars; + // finite model finding data + // maps a finite model tester var to a list of variables that will be tested + obj_map > finite_model_test_varlists; +protected: + void assert_axiom(expr * e); + void assert_implication(expr * premise, expr * conclusion); + expr * rewrite_implication(expr * premise, expr * conclusion); - T_cut() { - level = -100; - } - }; + expr * mk_string(zstring const& str); + expr * mk_string(const char * str); - typedef trail_stack th_trail_stack; - typedef union_find th_union_find; + app * mk_strlen(expr * e); + expr * mk_concat(expr * n1, expr * n2); + expr * mk_concat_const_str(expr * n1, expr * n2); + app * mk_contains(expr * haystack, expr * needle); + app * mk_indexof(expr * haystack, expr * needle); - typedef map, default_eq > rational_map; - struct zstring_hash_proc { - unsigned operator()(zstring const & s) const { - return string_hash(s.encode().c_str(), static_cast(s.length()), 17); - } - }; - typedef map > string_map; + literal mk_literal(expr* _e); + app * mk_int(int n); + app * mk_int(rational & q); - protected: - theory_str_params const & m_params; + void check_and_init_cut_var(expr * node); + void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); + void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); + bool has_self_cut(expr * n1, expr * n2); - /* - * Setting EagerStringConstantLengthAssertions to true allows some methods, - * in particular internalize_term(), to add - * length assertions about relevant string constants. - * Note that currently this should always be set to 'true', or else *no* length assertions - * will be made about string constants. - */ - bool opt_EagerStringConstantLengthAssertions; + // for ConcatOverlapAvoid + bool will_result_in_overlap(expr * lhs, expr * rhs); - /* - * If VerifyFinalCheckProgress is set to true, continuing after final check is invoked - * without asserting any new axioms is considered a bug and will throw an exception. - */ - bool opt_VerifyFinalCheckProgress; + void track_variable_scope(expr * var); + app * mk_str_var(std::string name); + app * mk_int_var(std::string name); + app * mk_nonempty_str_var(); + app * mk_internal_xor_var(); + expr * mk_internal_valTest_var(expr * node, int len, int vTries); + app * mk_regex_rep_var(); + app * mk_unroll_bound_var(); + app * mk_unroll_test_var(); + void add_nonempty_constraint(expr * s); - /* - * This constant controls how eagerly we expand unrolls in unbounded regex membership tests. - */ - int opt_LCMUnrollStep; + void instantiate_concat_axiom(enode * cat); + void try_eval_concat(enode * cat); + void instantiate_basic_string_axioms(enode * str); + void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); - /* - * If NoQuickReturn_IntegerTheory is set to true, - * integer theory integration checks that assert axioms - * will not return from the function after asserting their axioms. - * The default behaviour of Z3str2 is to set this to 'false'. This may be incorrect. - */ - bool opt_NoQuickReturn_IntegerTheory; + void instantiate_axiom_CharAt(enode * e); + void instantiate_axiom_prefixof(enode * e); + void instantiate_axiom_suffixof(enode * e); + void instantiate_axiom_Contains(enode * e); + void instantiate_axiom_Indexof(enode * e); + void instantiate_axiom_Indexof2(enode * e); + void instantiate_axiom_LastIndexof(enode * e); + void instantiate_axiom_Substr(enode * e); + void instantiate_axiom_Replace(enode * e); + void instantiate_axiom_str_to_int(enode * e); + void instantiate_axiom_int_to_str(enode * e); - /* - * If DisableIntegerTheoryIntegration is set to true, - * ALL calls to the integer theory integration methods - * (get_value, get_len_value, lower_bound, upper_bound) - * will ignore what the arithmetic solver believes about length terms, - * and will return no information. - * - * This reduces performance significantly, but can be useful to enable - * if it is suspected that string-integer integration, or the arithmetic solver itself, - * might have a bug. - * - * The default behaviour of Z3str2 is to set this to 'false'. - */ - bool opt_DisableIntegerTheoryIntegration; + expr * mk_RegexIn(expr * str, expr * regexp); + void instantiate_axiom_RegexIn(enode * e); + app * mk_unroll(expr * n, expr * bound); - /* - * If DeferEQCConsistencyCheck is set to true, - * expensive calls to new_eq_check() will be deferred until final check, - * at which time the consistency of *all* string equivalence classes will be validated. - */ - bool opt_DeferEQCConsistencyCheck; + void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); + void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); + void process_concat_eq_unroll(expr * concat, expr * unroll); - /* - * If CheckVariableScope is set to true, - * pop_scope_eh() and final_check_eh() will run extra checks - * to determine whether the current assignment - * contains references to any internal variables that are no longer in scope. - */ - bool opt_CheckVariableScope; + void set_up_axioms(expr * ex); + void handle_equality(expr * lhs, expr * rhs); - /* - * If ConcatOverlapAvoid is set to true, - * the check to simplify Concat = Concat in handle_equality() will - * avoid simplifying wrt. pairs of Concat terms that will immediately - * result in an overlap. (false = Z3str2 behaviour) - */ - bool opt_ConcatOverlapAvoid; + app * mk_value_helper(app * n); + expr * get_eqc_value(expr * n, bool & hasEqcValue); + expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); + bool in_same_eqc(expr * n1, expr * n2); + expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); - bool search_started; - arith_util m_autil; - seq_util u; - int sLevel; + bool get_value(expr* e, rational& val) const; + bool get_len_value(expr* e, rational& val); + bool lower_bound(expr* _e, rational& lo); + bool upper_bound(expr* _e, rational& hi); - bool finalCheckProgressIndicator; - - expr_ref_vector m_trail; // trail for generated terms - - str_value_factory * m_factory; - - // terms we couldn't go through set_up_axioms() with because they weren't internalized - expr_ref_vector m_delayed_axiom_setup_terms; - - ptr_vector m_basicstr_axiom_todo; - svector > m_str_eq_todo; - ptr_vector m_concat_axiom_todo; - ptr_vector m_string_constant_length_todo; - ptr_vector m_concat_eval_todo; - - // enode lists for library-aware/high-level string terms (e.g. substr, contains) - ptr_vector m_library_aware_axiom_todo; - - // hashtable of all exprs for which we've already set up term-specific axioms -- - // this prevents infinite recursive descent with respect to axioms that - // include an occurrence of the term for which axioms are being generated - obj_hashtable axiomatized_terms; - - int tmpStringVarCount; - int tmpXorVarCount; - int tmpLenTestVarCount; - int tmpValTestVarCount; - std::map, std::map > varForBreakConcat; - - bool avoidLoopCut; - bool loopDetected; - obj_map > cut_var_map; - expr_ref m_theoryStrOverlapAssumption_term; - - obj_hashtable variable_set; - obj_hashtable internal_variable_set; - obj_hashtable regex_variable_set; - std::map > internal_variable_scope_levels; - - obj_hashtable internal_lenTest_vars; - obj_hashtable internal_valTest_vars; - obj_hashtable internal_unrollTest_vars; - - obj_hashtable input_var_in_len; - - obj_map fvar_len_count_map; - std::map > fvar_lenTester_map; - obj_map lenTester_fvar_map; - - std::map > > > fvar_valueTester_map; - std::map valueTester_fvar_map; - - std::map val_range_map; - - // This can't be an expr_ref_vector because the constructor is wrong, - // we would need to modify the allocator so we pass in ast_manager - std::map, ptr_vector > > unroll_tries_map; - std::map unroll_var_map; - std::map, expr*> concat_eq_unroll_ast_map; - - expr_ref_vector contains_map; - - theory_str_contain_pair_bool_map_t contain_pair_bool_map; - //obj_map > contain_pair_idx_map; - std::map > > contain_pair_idx_map; - - std::map, expr*> regex_in_bool_map; - std::map > regex_in_var_reg_str_map; - - std::map regex_nfa_cache; // Regex term --> NFA - - char * char_set; - std::map charSetLookupTable; - int charSetSize; - - obj_pair_map concat_astNode_map; - - // all (str.to-int) and (int.to-str) terms - expr_ref_vector string_int_conversion_terms; - obj_hashtable string_int_axioms; - - // used when opt_FastLengthTesterCache is true - rational_map lengthTesterCache; - // used when opt_FastValueTesterCache is true - string_map valueTesterCache; - - string_map stringConstantCache; - unsigned long totalCacheAccessCount; - unsigned long cacheHitCount; - unsigned long cacheMissCount; - - // cache mapping each string S to Length(S) - obj_map length_ast_map; - - th_union_find m_find; - th_trail_stack m_trail_stack; - theory_var get_var(expr * n) const; - expr * get_eqc_next(expr * n); - app * get_ast(theory_var i); - - // binary search heuristic data - struct binary_search_info { - rational lowerBound; - rational midPoint; - rational upperBound; - rational windowSize; - - binary_search_info() : lowerBound(rational::zero()), midPoint(rational::zero()), - upperBound(rational::zero()), windowSize(rational::zero()) {} - binary_search_info(rational lower, rational mid, rational upper, rational windowSize) : - lowerBound(lower), midPoint(mid), upperBound(upper), windowSize(windowSize) {} - - void calculate_midpoint() { - midPoint = floor(lowerBound + ((upperBound - lowerBound) / rational(2)) ); - } - }; - // maps a free string var to a stack of active length testers. - // can use binary_search_trail to record changes to this object - obj_map > binary_search_len_tester_stack; - // maps a length tester var to the *active* search window - obj_map binary_search_len_tester_info; - // maps a free string var to the first length tester to be (re)used - obj_map binary_search_starting_len_tester; - // maps a length tester to the next length tester to be (re)used if the split is "low" - obj_map binary_search_next_var_low; - // maps a length tester to the next length tester to be (re)used if the split is "high" - obj_map binary_search_next_var_high; - - // finite model finding data - // maps a finite model tester var to a list of variables that will be tested - obj_map > finite_model_test_varlists; - protected: - void assert_axiom(expr * e); - void assert_implication(expr * premise, expr * conclusion); - expr * rewrite_implication(expr * premise, expr * conclusion); - - expr * mk_string(zstring const& str); - expr * mk_string(const char * str); - - app * mk_strlen(expr * e); - expr * mk_concat(expr * n1, expr * n2); - expr * mk_concat_const_str(expr * n1, expr * n2); - app * mk_contains(expr * haystack, expr * needle); - app * mk_indexof(expr * haystack, expr * needle); - - literal mk_literal(expr* _e); - app * mk_int(int n); - app * mk_int(rational & q); - - void check_and_init_cut_var(expr * node); - void add_cut_info_one_node(expr * baseNode, int slevel, expr * node); - void add_cut_info_merge(expr * destNode, int slevel, expr * srcNode); - bool has_self_cut(expr * n1, expr * n2); - - // for ConcatOverlapAvoid - bool will_result_in_overlap(expr * lhs, expr * rhs); - - void track_variable_scope(expr * var); - app * mk_str_var(std::string name); - app * mk_int_var(std::string name); - app * mk_nonempty_str_var(); - app * mk_internal_xor_var(); - expr * mk_internal_valTest_var(expr * node, int len, int vTries); - app * mk_regex_rep_var(); - app * mk_unroll_bound_var(); - app * mk_unroll_test_var(); - void add_nonempty_constraint(expr * s); - - void instantiate_concat_axiom(enode * cat); - void try_eval_concat(enode * cat); - void instantiate_basic_string_axioms(enode * str); - void instantiate_str_eq_length_axiom(enode * lhs, enode * rhs); - - void instantiate_axiom_CharAt(enode * e); - void instantiate_axiom_prefixof(enode * e); - void instantiate_axiom_suffixof(enode * e); - void instantiate_axiom_Contains(enode * e); - void instantiate_axiom_Indexof(enode * e); - void instantiate_axiom_Indexof2(enode * e); - void instantiate_axiom_LastIndexof(enode * e); - void instantiate_axiom_Substr(enode * e); - void instantiate_axiom_Replace(enode * e); - void instantiate_axiom_str_to_int(enode * e); - void instantiate_axiom_int_to_str(enode * e); - - expr * mk_RegexIn(expr * str, expr * regexp); - void instantiate_axiom_RegexIn(enode * e); - app * mk_unroll(expr * n, expr * bound); - - void process_unroll_eq_const_str(expr * unrollFunc, expr * constStr); - void unroll_str2reg_constStr(expr * unrollFunc, expr * eqConstStr); - void process_concat_eq_unroll(expr * concat, expr * unroll); - - void set_up_axioms(expr * ex); - void handle_equality(expr * lhs, expr * rhs); - - app * mk_value_helper(app * n); - expr * get_eqc_value(expr * n, bool & hasEqcValue); - expr * z3str2_get_eqc_value(expr * n , bool & hasEqcValue); - bool in_same_eqc(expr * n1, expr * n2); - expr * collect_eq_nodes(expr * n, expr_ref_vector & eqcSet); - - bool get_value(expr* e, rational& val) const; - bool get_len_value(expr* e, rational& val); - bool lower_bound(expr* _e, rational& lo); - bool upper_bound(expr* _e, rational& hi); - - bool can_two_nodes_eq(expr * n1, expr * n2); - bool can_concat_eq_str(expr * concat, zstring& str); - bool can_concat_eq_concat(expr * concat1, expr * concat2); - bool check_concat_len_in_eqc(expr * concat); - bool check_length_consistency(expr * n1, expr * n2); - bool check_length_const_string(expr * n1, expr * constStr); - bool check_length_eq_var_concat(expr * n1, expr * n2); - bool check_length_concat_concat(expr * n1, expr * n2); - bool check_length_concat_var(expr * concat, expr * var); - bool check_length_var_var(expr * var1, expr * var2); - void check_contain_in_new_eq(expr * n1, expr * n2); - void check_contain_by_eqc_val(expr * varNode, expr * constNode); - void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); - void check_contain_by_eq_nodes(expr * n1, expr * n2); - bool in_contain_idx_map(expr * n); - void compute_contains(std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap); - expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); - void get_grounded_concats(expr* node, std::map & varAliasMap, - std::map & concatAliasMap, std::map & varConstMap, - std::map & concatConstMap, std::map > & varEqConcatMap, - std::map, std::set > > & groundedMap); - void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); - void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + bool can_two_nodes_eq(expr * n1, expr * n2); + bool can_concat_eq_str(expr * concat, zstring& str); + bool can_concat_eq_concat(expr * concat1, expr * concat2); + bool check_concat_len_in_eqc(expr * concat); + bool check_length_consistency(expr * n1, expr * n2); + bool check_length_const_string(expr * n1, expr * constStr); + bool check_length_eq_var_concat(expr * n1, expr * n2); + bool check_length_concat_concat(expr * n1, expr * n2); + bool check_length_concat_var(expr * concat, expr * var); + bool check_length_var_var(expr * var1, expr * var2); + void check_contain_in_new_eq(expr * n1, expr * n2); + void check_contain_by_eqc_val(expr * varNode, expr * constNode); + void check_contain_by_substr(expr * varNode, expr_ref_vector & willEqClass); + void check_contain_by_eq_nodes(expr * n1, expr * n2); + bool in_contain_idx_map(expr * n); + void compute_contains(std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap); + expr * dealias_node(expr * node, std::map & varAliasMap, std::map & concatAliasMap); + void get_grounded_concats(expr* node, std::map & varAliasMap, + std::map & concatAliasMap, std::map & varConstMap, + std::map & concatConstMap, std::map > & varEqConcatMap, std::map, std::set > > & groundedMap); - bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); + void print_grounded_concat(expr * node, std::map, std::set > > & groundedMap); + void check_subsequence(expr* str, expr* strDeAlias, expr* subStr, expr* subStrDeAlias, expr* boolVar, + std::map, std::set > > & groundedMap); + bool is_partial_in_grounded_concat(const std::vector & strVec, const std::vector & subStrVec); - void get_nodes_in_concat(expr * node, ptr_vector & nodeList); - expr * simplify_concat(expr * node); + void get_nodes_in_concat(expr * node, ptr_vector & nodeList); + expr * simplify_concat(expr * node); - void simplify_parent(expr * nn, expr * eq_str); + void simplify_parent(expr * nn, expr * eq_str); - void simplify_concat_equality(expr * lhs, expr * rhs); - void solve_concat_eq_str(expr * concat, expr * str); + void simplify_concat_equality(expr * lhs, expr * rhs); + void solve_concat_eq_str(expr * concat, expr * str); - void infer_len_concat_equality(expr * nn1, expr * nn2); - bool infer_len_concat(expr * n, rational & nLen); - void infer_len_concat_arg(expr * n, rational len); + void infer_len_concat_equality(expr * nn1, expr * nn2); + bool infer_len_concat(expr * n, rational & nLen); + void infer_len_concat_arg(expr * n, rational len); - bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); - bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type1(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type2(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type3(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type4(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type5(expr * concatAst1, expr * concatAst2); + bool is_concat_eq_type6(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); - void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type1(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type2(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type3(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type4(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type5(expr * concatAst1, expr * concatAst2); + void process_concat_eq_type6(expr * concatAst1, expr * concatAst2); - void print_cut_var(expr * node, std::ofstream & xout); + void print_cut_var(expr * node, std::ofstream & xout); - void generate_mutual_exclusion(expr_ref_vector & exprs); - void add_theory_aware_branching_info(expr * term, double priority, lbool phase); + void generate_mutual_exclusion(expr_ref_vector & exprs); + void add_theory_aware_branching_info(expr * term, double priority, lbool phase); - bool new_eq_check(expr * lhs, expr * rhs); - void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); + bool new_eq_check(expr * lhs, expr * rhs); + void group_terms_by_eqc(expr * n, std::set & concats, std::set & vars, std::set & consts); - int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, - std::map > & unrollGroupMap, std::map > & var_eq_concat_map); - void trace_ctx_dep(std::ofstream & tout, - std::map & aliasIndexMap, - std::map & var_eq_constStr_map, - std::map > & var_eq_concat_map, - std::map > & var_eq_unroll_map, - std::map & concat_eq_constStr_map, - std::map > & concat_eq_concat_map, - std::map > & unrollGroupMap); + int ctx_dep_analysis(std::map & strVarMap, std::map & freeVarMap, + std::map > & unrollGroupMap, std::map > & var_eq_concat_map); + void trace_ctx_dep(std::ofstream & tout, + std::map & aliasIndexMap, + std::map & var_eq_constStr_map, + std::map > & var_eq_concat_map, + std::map > & var_eq_unroll_map, + std::map & concat_eq_constStr_map, + std::map > & concat_eq_concat_map, + std::map > & unrollGroupMap); - void classify_ast_by_type(expr * node, std::map & varMap, - std::map & concatMap, std::map & unrollMap); - void classify_ast_by_type_in_positive_context(std::map & varMap, - std::map & concatMap, std::map & unrollMap); + void classify_ast_by_type(expr * node, std::map & varMap, + std::map & concatMap, std::map & unrollMap); + void classify_ast_by_type_in_positive_context(std::map & varMap, + std::map & concatMap, std::map & unrollMap); - expr * mk_internal_lenTest_var(expr * node, int lTries); - expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); - void process_free_var(std::map & freeVar_map); - expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); - expr * gen_free_var_options(expr * freeVar, expr * len_indicator, - zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); - expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, - zstring lenStr, int tries); - void print_value_tester_list(svector > & testerList); - bool get_next_val_encode(int_vector & base, int_vector & next); - zstring gen_val_string(int len, int_vector & encoding); + expr * mk_internal_lenTest_var(expr * node, int lTries); + expr * gen_len_val_options_for_free_var(expr * freeVar, expr * lenTesterInCbEq, zstring lenTesterValue); + void process_free_var(std::map & freeVar_map); + expr * gen_len_test_options(expr * freeVar, expr * indicator, int tries); + expr * gen_free_var_options(expr * freeVar, expr * len_indicator, + zstring len_valueStr, expr * valTesterInCbEq, zstring valTesterValueStr); + expr * gen_val_options(expr * freeVar, expr * len_indicator, expr * val_indicator, + zstring lenStr, int tries); + void print_value_tester_list(svector > & testerList); + bool get_next_val_encode(int_vector & base, int_vector & next); + zstring gen_val_string(int len, int_vector & encoding); - // binary search heuristic - expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); - expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); + // binary search heuristic + expr * binary_search_length_test(expr * freeVar, expr * previousLenTester, zstring previousLenTesterValue); + expr_ref binary_search_case_split(expr * freeVar, expr * tester, binary_search_info & bounds, literal_vector & case_split_lits); - bool free_var_attempt(expr * nn1, expr * nn2); - void more_len_tests(expr * lenTester, zstring lenTesterValue); - void more_value_tests(expr * valTester, zstring valTesterValue); + bool free_var_attempt(expr * nn1, expr * nn2); + void more_len_tests(expr * lenTester, zstring lenTesterValue); + void more_value_tests(expr * valTester, zstring valTesterValue); - expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); - expr * getMostLeftNodeInConcat(expr * node); - expr * getMostRightNodeInConcat(expr * node); - void get_var_in_eqc(expr * n, std::set & varSet); - void get_concats_in_eqc(expr * n, std::set & concats); - void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); - expr * eval_concat(expr * n1, expr * n2); + expr * get_alias_index_ast(std::map & aliasIndexMap, expr * node); + expr * getMostLeftNodeInConcat(expr * node); + expr * getMostRightNodeInConcat(expr * node); + void get_var_in_eqc(expr * n, std::set & varSet); + void get_concats_in_eqc(expr * n, std::set & concats); + void get_const_str_asts_in_node(expr * node, expr_ref_vector & constList); + expr * eval_concat(expr * n1, expr * n2); - bool finalcheck_str2int(app * a); - bool finalcheck_int2str(app * a); + bool finalcheck_str2int(app * a); + bool finalcheck_int2str(app * a); - // strRegex + // strRegex - void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); - void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); - void gen_assign_unroll_reg(std::set & unrolls); - expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); - expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); - expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); - void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); - void check_regex_in(expr * nn1, expr * nn2); - zstring get_std_regex_str(expr * r); + void get_eqc_allUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void get_eqc_simpleUnroll(expr * n, expr * &constStr, std::set & unrollFuncSet); + void gen_assign_unroll_reg(std::set & unrolls); + expr * gen_assign_unroll_Str2Reg(expr * n, std::set & unrolls); + expr * gen_unroll_conditional_options(expr * var, std::set & unrolls, zstring lcmStr); + expr * gen_unroll_assign(expr * var, zstring lcmStr, expr * testerVar, int l, int h); + void reduce_virtual_regex_in(expr * var, expr * regex, expr_ref_vector & items); + void check_regex_in(expr * nn1, expr * nn2); + zstring get_std_regex_str(expr * r); - void dump_assignments(); - void initialize_charset(); + void dump_assignments(); + void initialize_charset(); - void check_variable_scope(); - void recursive_check_variable_scope(expr * ex); + void check_variable_scope(); + void recursive_check_variable_scope(expr * ex); - void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); - bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); - void get_unique_non_concat_nodes(expr * node, std::set & argSet); - bool propagate_length_within_eqc(expr * var); + void collect_var_concat(expr * node, std::set & varSet, std::set & concatSet); + bool propagate_length(std::set & varSet, std::set & concatSet, std::map & exprLenMap); + void get_unique_non_concat_nodes(expr * node, std::set & argSet); + bool propagate_length_within_eqc(expr * var); - // TESTING - void refresh_theory_var(expr * e); + // TESTING + void refresh_theory_var(expr * e); - expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); - void finite_model_test(expr * v, expr * c); + expr_ref set_up_finite_model_test(expr * lhs, expr * rhs); + void finite_model_test(expr * v, expr * c); - public: - theory_str(ast_manager & m, theory_str_params const & params); - virtual ~theory_str(); +public: + theory_str(ast_manager & m, theory_str_params const & params); + virtual ~theory_str(); - virtual char const * get_name() const { return "seq"; } - virtual void display(std::ostream & out) const; + virtual char const * get_name() const { return "seq"; } + virtual void display(std::ostream & out) const; - bool overlapping_variables_detected() const { return loopDetected; } + bool overlapping_variables_detected() const { return loopDetected; } - th_trail_stack& get_trail_stack() { return m_trail_stack; } - void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {} - void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { } - void unmerge_eh(theory_var v1, theory_var v2) {} - protected: - virtual bool internalize_atom(app * atom, bool gate_ctx); - virtual bool internalize_term(app * term); - virtual enode* ensure_enode(expr* e); - virtual theory_var mk_var(enode * n); + th_trail_stack& get_trail_stack() { return m_trail_stack; } + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2) {} + void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { } + void unmerge_eh(theory_var v1, theory_var v2) {} +protected: + virtual bool internalize_atom(app * atom, bool gate_ctx); + virtual bool internalize_term(app * term); + virtual enode* ensure_enode(expr* e); + virtual theory_var mk_var(enode * n); - virtual void new_eq_eh(theory_var, theory_var); - virtual void new_diseq_eh(theory_var, theory_var); + virtual void new_eq_eh(theory_var, theory_var); + virtual void new_diseq_eh(theory_var, theory_var); - virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } - virtual void init_search_eh(); - virtual void add_theory_assumptions(expr_ref_vector & assumptions); - virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); - virtual void relevant_eh(app * n); - virtual void assign_eh(bool_var v, bool is_true); - virtual void push_scope_eh(); - virtual void pop_scope_eh(unsigned num_scopes); - virtual void reset_eh(); + virtual theory* mk_fresh(context*) { return alloc(theory_str, get_manager(), m_params); } + virtual void init_search_eh(); + virtual void add_theory_assumptions(expr_ref_vector & assumptions); + virtual lbool validate_unsat_core(expr_ref_vector & unsat_core); + virtual void relevant_eh(app * n); + virtual void assign_eh(bool_var v, bool is_true); + virtual void push_scope_eh(); + virtual void pop_scope_eh(unsigned num_scopes); + virtual void reset_eh(); - virtual bool can_propagate(); - virtual void propagate(); + virtual bool can_propagate(); + virtual void propagate(); - virtual final_check_status final_check_eh(); - virtual void attach_new_th_var(enode * n); + virtual final_check_status final_check_eh(); + virtual void attach_new_th_var(enode * n); - virtual void init_model(model_generator & m); - virtual model_value_proc * mk_value(enode * n, model_generator & mg); - virtual void finalize_model(model_generator & mg); - }; + virtual void init_model(model_generator & m); + virtual model_value_proc * mk_value(enode * n, model_generator & mg); + virtual void finalize_model(model_generator & mg); +}; };