Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bitwuzla: Fix handling of quoted symbol names while parsing SMTLIB formulas #417

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,9 @@
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.sosy_lab.java_smt.api.ArrayFormula;
import org.sosy_lab.java_smt.api.BitvectorFormula;
Expand Down Expand Up @@ -59,8 +56,32 @@
public class BitwuzlaFormulaCreator extends FormulaCreator<Term, Sort, Void, BitwuzlaDeclaration> {
private final TermManager termManager;

/**
* Variable cache, contains terms for all declared symbols
*
* <p>Bitwuzla allows the variables to be declared multiple times. Each of these instances will be
* considered a separate variable, even if the names and types are the same. We fix this by
* keeping track of all declared variables in this cache. If a variable is already defined we
* return it from the cache, otherwise a new Term for the variable is created and added to the
* cache.
*
* <p>It is important to keep this cache synchronized with the variable declarations for the
* parser. This is handled in {@link BitwuzlaFormulaManager#parse(String)}.
*/
private final Table<String, Sort, Term> formulaCache = HashBasedTable.create();

/**
* Remove SMTLIB quotes from a symbol name.
*
* <p>If the symbol is not quoted, just return it as is .
*/
static String removeQuotes(String pSymbol) {
if (pSymbol.startsWith("|") && pSymbol.endsWith("|")) {
return pSymbol.substring(1, pSymbol.length() - 1);
}
return pSymbol;
}

/**
* This mapping stores symbols and their constraints, such as from fp-to-bv casts with their
* defining equation.
Expand Down Expand Up @@ -404,7 +425,7 @@ public <R> R visit(FormulaVisitor<R> visitor, Formula formula, Term f)
return visitor.visitFreeVariable(formula, name);

} else if (f.is_variable()) {
String name = f.symbol();
String name = removeQuotes(f.symbol());
Sort sort = f.sort();
Term originalVar = formulaCache.get(name, sort);
return visitor.visitBoundVariable(encapsulate(getFormulaType(originalVar), originalVar), 0);
Expand All @@ -422,7 +443,7 @@ public <R> R visit(FormulaVisitor<R> visitor, Formula formula, Term f)
for (int i = 0; i < size - 1; i++) {
Term boundVar = children.get(i);
boundVars[i] = boundVar;
String name = boundVar.symbol();
String name = removeQuotes(boundVar.symbol());
PhilippWendler marked this conversation as resolved.
Show resolved Hide resolved
assert name != null;
Sort sort = boundVar.sort();
Term freeVar;
Expand Down Expand Up @@ -555,26 +576,10 @@ public BooleanFormula encapsulateBoolean(Term pTerm) {
return new BitwuzlaBooleanFormula(pTerm);
}

protected Table<String, Sort, Term> getCache() {
Table<String, Sort, Term> getCache() {
return formulaCache;
}

// True if the entered String has an existing variable in the cache.
protected boolean formulaCacheContains(String variable) {
// There is always only 1 type permitted per variable
return formulaCache.containsRow(variable);
}

// Optional that contains the variable to the entered String if there is one.
protected Optional<Term> getFormulaFromCache(String variable) {
Iterator<Entry<Sort, Term>> entrySetIter = formulaCache.row(variable).entrySet().iterator();
if (entrySetIter.hasNext()) {
// If there is a non-empty row for an entry, there is only one entry
return Optional.of(entrySetIter.next().getValue());
}
return Optional.empty();
}

@Override
public Object convertValue(Term term) {
Preconditions.checkArgument(term.is_value(), "Term \"%s\" is not a value.", term);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,7 @@ public Term parseImpl(String formulaStr) throws IllegalArgumentException {
declParser.parse(token, true, false);
Term parsed = declParser.get_declared_funs().get(0);

String symbol = parsed.symbol();
if (symbol.startsWith("|") && symbol.endsWith("|")) {
// Strip quotes from the name
symbol = symbol.substring(1, symbol.length() - 1);
}
String symbol = BitwuzlaFormulaCreator.removeQuotes(parsed.symbol());
Sort sort = parsed.sort();

// Check if the symbol is already defined in the variable cache
Expand Down Expand Up @@ -137,12 +133,13 @@ public Term parseImpl(String formulaStr) throws IllegalArgumentException {
// Process the symbols from the parser
Map_TermTerm subst = new Map_TermTerm();
for (Term term : declared) {
if (cache.containsRow(term.symbol())) {
String symbol = BitwuzlaFormulaCreator.removeQuotes(term.symbol());
if (cache.containsRow(symbol)) {
// Symbol is from the context: add the original term to the substitution map
subst.put(term, cache.get(term.symbol(), term.sort()));
subst.put(term, cache.get(symbol, term.sort()));
} else {
// Symbol is new, add it to the context
cache.put(term.symbol(), term.sort(), term);
cache.put(symbol, term.sort(), term);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,16 @@ private Vector_Term parse(String smt2dump) {
return parser.bitwuzla().get_assertions();
}

private Term parseVariable(String pName) {
return parse(String.format("(declare-const %s Bool)(assert %s)", pName, pName)).get(0);
}

private String dump(Term pTerm) {
Bitwuzla prover = new Bitwuzla(termManager, createOptions());
prover.assert_formula(pTerm);
return prover.print_formula();
}

// Bitwuzla currently REWRITES terms when parsing
@Ignore
@Test
Expand Down Expand Up @@ -770,4 +780,89 @@ public void parserFailTest() {
String badInput = "(declare-const a Bool)(assert (or a b))";
parse(badInput);
}

@Test
public void noVariableCacheTest() {
// Bitwuzla allows us to create the same variable twice
// Here t1 and t2 are treated as different variables, even though they share a name and have
// the same sort
Sort sortBool = termManager.mk_bool_sort();
Term t1 = termManager.mk_const(sortBool, "var");
Term t2 = termManager.mk_const(sortBool, "var");

bitwuzla.assert_formula(termManager.mk_term(Kind.NOT, termManager.mk_term(Kind.IFF, t1, t2)));
assertThat(bitwuzla.check_sat()).isEqualTo(Result.SAT);

boolean r1 = bitwuzla.get_value(t1).is_true();
boolean r2 = bitwuzla.get_value(t2).is_true();
assertThat(r1).isNotEqualTo(r2);
}

@Test
public void quotedSymbolTest() {
// When parsing formulas Bitwuzla will preserve any || quotes in the name.
// The parser still makes sure that "var" and "|var|" can't be declared in the same file as
// both names are identical accoridng to the SMTLIB standard
assertThat(parseVariable("var").symbol()).isEqualTo("var");
assertThat(parseVariable("|var|").symbol()).isEqualTo("|var|");
}

@Test
@Ignore
public void illegalSmtlibTest() {
// Bitwuzla does not put reserved variable names in quotes while printing and will produce
// illegal output
Sort sortBool = termManager.mk_bool_sort();
Term t1 = termManager.mk_const(sortBool, "exit");

String expected =
"(set-logic ALL)\n"
+ "(declare-const |exit| Bool)\n"
+ "(assert |exit|)\n"
+ "(check-sat)\n"
+ "(exit)\n";
assertThat(dump(t1)).isEqualTo(expected);
}

@Test
@Ignore
public void illegalSmtlibParseTest() {
// Even with the quotes added Bitwuzla will not parse keywords
String input =
"(set-logic ALL)\n"
+ "(declare-const |exit| Bool)\n"
+ "(assert |exit|)\n"
+ "(check-sat)\n"
+ "(exit)\n";
assertThat(parse(input).get(0).symbol()).isEqualTo("|exit|");
}

@Test
@Ignore
public void illegalSmtlibNumberTest() {
// Bitwuzla also won't quote variable names that are numbers (like "1" or "1.4") while printing
Sort sortBool = termManager.mk_bool_sort();
Term t1 = termManager.mk_const(sortBool, "1");

String expected =
"(set-logic ALL)\n"
+ "(declare-const |1| Bool)\n"
+ "(assert |1|)\n"
+ "(check-sat)\n"
+ "(exit)\n";
assertThat(dump(t1)).isEqualTo(expected);
}

@Test
@Ignore
public void illegalSmtlibParseNumberTest() {
// Even though it can read back input with quoted numbers as variable names
String input =
"(set-logic ALL)\n"
+ "(declare-const |1| Bool)\n"
+ "(assert |1|)\n"
+ "(check-sat)\n"
+ "(exit)\n";
assertThat(parse(input).get(0).symbol()).isEqualTo("|1|");
}
}