summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndres Noetzli <noetzli@stanford.edu>2019-06-05 13:01:34 -0700
committerGitHub <noreply@github.com>2019-06-05 13:01:34 -0700
commit6b01e8740111e69219e5d733e1123955f8cd2ea7 (patch)
tree78dfded555702242e0f44aa98924ecce541b7599
parent9af5e9653582a18b1871dfc3774ab50dd24463ce (diff)
Prevent letification from shadowing variables (#3042)
Fixes #3005. When printing nodes, we introduce `let` expressions on the fly. However, when doing that, we have to be careful that we don't shadow existing variables with the same name. When quantifiers are involved, we do not descend into the quantifiers to avoid letifying terms with bound variables that then go out of scope (see #1863). Thus, to avoid shadowing variables appearing in quantifiers, we have to collect all the variables appearing in that term to make sure that the let does not shadow them. In #3005, the issue was caused by a `let` that was introduced outside of a quantifier and then was shadowed in the body of the quantifier by another `let` introduced for that body.
-rw-r--r--src/expr/node_algorithm.cpp32
-rw-r--r--src/expr/node_algorithm.h8
-rw-r--r--src/printer/dagification_visitor.cpp54
-rw-r--r--src/printer/dagification_visitor.h7
-rw-r--r--test/regress/CMakeLists.txt1
-rw-r--r--test/regress/regress0/printer/let_shadowing.smt215
6 files changed, 104 insertions, 13 deletions
diff --git a/src/expr/node_algorithm.cpp b/src/expr/node_algorithm.cpp
index 25ffb0778..841f9ea28 100644
--- a/src/expr/node_algorithm.cpp
+++ b/src/expr/node_algorithm.cpp
@@ -236,6 +236,38 @@ bool getFreeVariables(TNode n,
return !fvs.empty();
}
+bool getVariables(TNode n, std::unordered_set<TNode, TNodeHashFunction>& vs)
+{
+ std::unordered_set<TNode, TNodeHashFunction> visited;
+ std::vector<TNode> visit;
+ TNode cur;
+ visit.push_back(n);
+ do
+ {
+ cur = visit.back();
+ visit.pop_back();
+ std::unordered_set<TNode, TNodeHashFunction>::iterator itv =
+ visited.find(cur);
+ if (itv == visited.end())
+ {
+ if (cur.isVar())
+ {
+ vs.insert(cur);
+ }
+ else
+ {
+ for (const TNode& cn : cur)
+ {
+ visit.push_back(cn);
+ }
+ }
+ visited.insert(cur);
+ }
+ } while (!visit.empty());
+
+ return !vs.empty();
+}
+
void getSymbols(TNode n, std::unordered_set<Node, NodeHashFunction>& syms)
{
std::unordered_set<TNode, TNodeHashFunction> visited;
diff --git a/src/expr/node_algorithm.h b/src/expr/node_algorithm.h
index 656f162ae..727f5ba75 100644
--- a/src/expr/node_algorithm.h
+++ b/src/expr/node_algorithm.h
@@ -73,6 +73,14 @@ bool getFreeVariables(TNode n,
bool computeFv = true);
/**
+ * Get all variables in n.
+ * @param n The node under investigation
+ * @param vs The set which free variables are added to
+ * @return true iff this node contains a free variable.
+ */
+bool getVariables(TNode n, std::unordered_set<TNode, TNodeHashFunction>& vs);
+
+/**
* For term n, this function collects the symbols that occur as a subterms
* of n. A symbol is a variable that does not have kind BOUND_VARIABLE.
* @param n The node under investigation
diff --git a/src/printer/dagification_visitor.cpp b/src/printer/dagification_visitor.cpp
index cf5f35457..a024c97a7 100644
--- a/src/printer/dagification_visitor.cpp
+++ b/src/printer/dagification_visitor.cpp
@@ -17,6 +17,8 @@
#include "printer/dagification_visitor.h"
#include "context/context.h"
+#include "expr/node_algorithm.h"
+#include "expr/node_manager_attributes.h"
#include "theory/substitutions.h"
#include <sstream>
@@ -24,18 +26,20 @@
namespace CVC4 {
namespace printer {
-DagificationVisitor::DagificationVisitor(unsigned threshold, std::string letVarPrefix) :
- d_threshold(threshold),
- d_letVarPrefix(letVarPrefix),
- d_nodeCount(),
- d_top(),
- d_context(new context::Context()),
- d_substitutions(new theory::SubstitutionMap(d_context)),
- d_letVar(0),
- d_done(false),
- d_uniqueParent(),
- d_substNodes() {
-
+DagificationVisitor::DagificationVisitor(unsigned threshold,
+ std::string letVarPrefix)
+ : d_threshold(threshold),
+ d_letVarPrefix(letVarPrefix),
+ d_nodeCount(),
+ d_reservedLetNames(),
+ d_top(),
+ d_context(new context::Context()),
+ d_substitutions(new theory::SubstitutionMap(d_context)),
+ d_letVar(0),
+ d_done(false),
+ d_uniqueParent(),
+ d_substNodes()
+{
// 0 doesn't make sense
AlwaysAssertArgument(threshold > 0, threshold);
}
@@ -51,8 +55,28 @@ bool DagificationVisitor::alreadyVisited(TNode current, TNode parent) {
{
// for quantifiers, we visit them but we don't recurse on them
visit(current, parent);
+
+ // search for variables that start with the let prefix
+ std::unordered_set<TNode, TNodeHashFunction> vs;
+ expr::getVariables(current, vs);
+ for (const TNode v : vs)
+ {
+ const std::string name = v.getAttribute(expr::VarNameAttr());
+ if (name.compare(0, d_letVarPrefix.size(), d_letVarPrefix) == 0)
+ {
+ d_reservedLetNames.insert(name);
+ }
+ }
return true;
}
+ else if (current.isVar())
+ {
+ const std::string name = current.getAttribute(expr::VarNameAttr());
+ if (name.compare(0, d_letVarPrefix.size(), d_letVarPrefix) == 0)
+ {
+ d_reservedLetNames.insert(name);
+ }
+ }
// don't visit variables, constants, or those exprs that we've
// already seen more than the threshold: if we've increased
// the count beyond the threshold already, we've done the same
@@ -137,7 +161,11 @@ void DagificationVisitor::done(TNode node) {
// construct the let binder
std::stringstream ss;
- ss << d_letVarPrefix << d_letVar++;
+ do
+ {
+ ss.str("");
+ ss << d_letVarPrefix << d_letVar++;
+ } while (d_reservedLetNames.find(ss.str()) != d_reservedLetNames.end());
Node letvar = NodeManager::currentNM()->mkSkolem(ss.str(), (*i).getType(), "dagification", NodeManager::SKOLEM_NO_NOTIFY | NodeManager::SKOLEM_EXACT_NAME);
// apply previous substitutions to the rhs, enabling cascading LETs
diff --git a/src/printer/dagification_visitor.h b/src/printer/dagification_visitor.h
index 18f31e662..6df5f32b4 100644
--- a/src/printer/dagification_visitor.h
+++ b/src/printer/dagification_visitor.h
@@ -21,6 +21,7 @@
#include <string>
#include <unordered_map>
+#include <unordered_set>
#include <vector>
#include "expr/node.h"
@@ -69,6 +70,12 @@ class DagificationVisitor {
std::unordered_map<TNode, unsigned, TNodeHashFunction> d_nodeCount;
/**
+ * The set of variable names with the let prefix that appear in the
+ * expression.
+ */
+ std::unordered_set<std::string> d_reservedLetNames;
+
+ /**
* The top-most node we are visiting.
*/
TNode d_top;
diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt
index b63930569..0a5f0a972 100644
--- a/test/regress/CMakeLists.txt
+++ b/test/regress/CMakeLists.txt
@@ -586,6 +586,7 @@ set(regress_0_tests
regress0/printer/bv_consts_bin.smt2
regress0/printer/bv_consts_dec.smt2
regress0/printer/empty_symbol_name.smt2
+ regress0/printer/let_shadowing.smt2
regress0/printer/tuples_and_records.cvc
regress0/push-pop/boolean/fuzz_12.smt2
regress0/push-pop/boolean/fuzz_13.smt2
diff --git a/test/regress/regress0/printer/let_shadowing.smt2 b/test/regress/regress0/printer/let_shadowing.smt2
new file mode 100644
index 000000000..3251e4e38
--- /dev/null
+++ b/test/regress/regress0/printer/let_shadowing.smt2
@@ -0,0 +1,15 @@
+; COMMAND-LINE: --dump raw-benchmark --preprocess-only
+; SCRUBBER: grep assert
+; EXPECT: (assert (let ((_let_1 (* x y))) (= _let_1 _let_1 _let_0)))
+; EXPECT: (assert (let ((_let_1 (and a b))) (= _let_1 _let_1 (forall ((_let_0 Int)) (= 0 _let_0) ))))
+; EXPECT: (assert (let ((_let_1 (and a b))) (= _let_1 _let_1 (forall ((x Int)) (forall ((y Int)) (let ((_let_2 (and b a))) (and _let_1 _let_2 _let_2 (= 0 _let_0))) ) ))))
+(set-logic NIA)
+(declare-const _let_0 Int)
+(declare-const x Int)
+(declare-const y Int)
+(declare-const a Bool)
+(declare-const b Bool)
+(assert (= (* x y) (* x y) _let_0))
+(assert (= (and a b) (and a b) (forall ((_let_0 Int)) (= 0 _let_0))))
+(assert (= (and a b) (and a b) (forall ((x Int)) (forall ((y Int)) (and (and a b) (and b a) (and b a) (= 0 _let_0))))))
+(check-sat)
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback