From 373c4aba199175a5c1e54de66d418a8c475e7357 Mon Sep 17 00:00:00 2001 From: Regis Blanc <regwblanc@gmail.com> Date: Wed, 4 Feb 2015 20:39:38 +0100 Subject: [PATCH] Refactor FiniteArray to an implicit representation FiniteArray now takes an Expr for the length, a default Expr, and a Map of defined Expr for some of its elements. Adapt the rest of Leon to the new trees. The PrettyPrinter tries to be a little bit smart about how to print the array, only printing a few elements when the array gets too big. The regression test produces an huge array counter-example that used to lead to a crash of Leon with the previous implementation of Array (fully represented in memory). --- .../scala/leon/codegen/CodeGeneration.scala | 7 +-- .../scala/leon/codegen/CompilationUnit.scala | 4 +- .../leon/evaluators/RecursiveEvaluator.scala | 28 ++++++------ .../frontends/scalac/CodeExtraction.scala | 2 +- .../scala/leon/purescala/Extractors.scala | 21 +++++++-- .../scala/leon/purescala/PrettyPrinter.scala | 32 ++++++++++++-- .../scala/leon/purescala/ScalaPrinter.scala | 28 +++++++++++- src/main/scala/leon/purescala/TreeOps.scala | 2 +- src/main/scala/leon/purescala/Trees.scala | 12 +++++- .../solvers/smtlib/SMTLIBCVC4Target.scala | 4 +- .../leon/solvers/smtlib/SMTLIBTarget.scala | 12 +++++- .../leon/solvers/smtlib/SMTLIBZ3Target.scala | 7 +-- .../leon/solvers/z3/AbstractZ3Solver.scala | 30 ++++++------- .../verification/VerificationReport.scala | 1 - .../purescala/invalid/BigArray.scala | 11 +++++ .../evaluators/DefaultEvaluatorTests.scala | 43 +++++++++++++++++++ 16 files changed, 185 insertions(+), 59 deletions(-) create mode 100644 src/test/resources/regression/verification/purescala/invalid/BigArray.scala diff --git a/src/main/scala/leon/codegen/CodeGeneration.scala b/src/main/scala/leon/codegen/CodeGeneration.scala index f83c724db..ff969c68b 100644 --- a/src/main/scala/leon/codegen/CodeGeneration.scala +++ b/src/main/scala/leon/codegen/CodeGeneration.scala @@ -650,8 +650,9 @@ trait CodeGeneration { ch << storeInstr //returns targetArray - case a @ FiniteArray(es) => - ch << Ldc(es.size) + case a @ FiniteArray(elems, default, length) => + val IntLiteral(l) = length + ch << Ldc(l) val storeInstr = a.getType match { case ArrayType(CharType) => ch << NewArray.primitive("T_CHAR"); CASTORE case ArrayType(Int32Type) => ch << NewArray.primitive("T_INT"); IASTORE @@ -659,7 +660,7 @@ trait CodeGeneration { case ArrayType(other) => ch << NewArray(typeToJVM(other)); AASTORE case other => throw CompilationException("Cannot compile finite array expression whose type is %s.".format(other)) } - for((e,i) <- es.zipWithIndex) { + for((i, e) <- elems) { ch << DUP << Ldc(i) mkExpr(e, ch) ch << storeInstr diff --git a/src/main/scala/leon/codegen/CompilationUnit.scala b/src/main/scala/leon/codegen/CompilationUnit.scala index 20e41e421..64851adca 100644 --- a/src/main/scala/leon/codegen/CompilationUnit.scala +++ b/src/main/scala/leon/codegen/CompilationUnit.scala @@ -146,8 +146,8 @@ class CompilationUnit(val ctx: LeonContext, // For now, we only treat boolean arrays separately. // We have a use for these, mind you. - case f @ FiniteArray(exprs) if f.getType == ArrayType(BooleanType) => - exprs.map(e => exprToJVM(e).asInstanceOf[java.lang.Boolean].booleanValue).toArray + //case f @ FiniteArray(exprs) if f.getType == ArrayType(BooleanType) => + // exprs.map(e => exprToJVM(e).asInstanceOf[java.lang.Boolean].booleanValue).toArray case s @ FiniteSet(els) => val s = new leon.codegen.runtime.Set() diff --git a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala index 6eb4323b4..fa0148b5d 100644 --- a/src/main/scala/leon/evaluators/RecursiveEvaluator.scala +++ b/src/main/scala/leon/evaluators/RecursiveEvaluator.scala @@ -336,17 +336,9 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int case u @ UnitLiteral() => u case l @ Lambda(_, _) => l - case f @ ArrayFill(length, default) => - val rDefault = e(default) - val rLength = e(length) - val IntLiteral(iLength) = rLength - FiniteArray((1 to iLength).map(_ => rDefault).toSeq).setType(ArrayType(rDefault.getType)) - case ArrayLength(a) => - var ra = e(a) - while(!ra.isInstanceOf[FiniteArray]) - ra = ra.asInstanceOf[ArrayUpdated].array - IntLiteral(ra.asInstanceOf[FiniteArray].exprs.size) + var FiniteArray(elems, default, IntLiteral(length)) = e(a) + IntLiteral(length) case ArrayUpdated(a, i, v) => val ra = e(a) @@ -354,20 +346,24 @@ abstract class RecursiveEvaluator(ctx: LeonContext, prog: Program, maxSteps: Int val rv = e(v) val IntLiteral(index) = ri - val FiniteArray(exprs) = ra - FiniteArray(exprs.updated(index, rv)).setType(ra.getType) + val FiniteArray(elems, default, length) = ra + FiniteArray(elems.updated(index, rv), default, length).setType(ra.getType) case ArraySelect(a, i) => val IntLiteral(index) = e(i) - val FiniteArray(exprs) = e(a) + val FiniteArray(elems, default, length) = e(a) try { - exprs(index) + elems.get(index).orElse(default).get } catch { case e : IndexOutOfBoundsException => throw RuntimeError(e.getMessage) } - case FiniteArray(exprs) => - FiniteArray(exprs.map(ex => e(ex))).setType(expr.getType) + case FiniteArray(elems, default, length) => + FiniteArray( + elems.map(el => (el._1, e(el._2))), + default.map(e), + e(length) + ).setType(expr.getType) case f @ FiniteMap(ss) => FiniteMap(ss.map{ case (k, v) => (e(k), e(v)) }.distinct).setType(f.getType) case g @ MapGet(m,k) => (e(m), e(k)) match { diff --git a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala index bbfffc373..254fd8f57 100644 --- a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala +++ b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala @@ -1365,7 +1365,7 @@ trait CodeExtraction extends ASTExtractors { val underlying = extractType(baseType) val lengthRec = extractTree(length) val defaultValueRec = extractTree(defaultValue) - ArrayFill(lengthRec, defaultValueRec) + FiniteArray(Map(), Some(defaultValueRec), lengthRec).setType(ArrayType(underlying)) case ExIfThenElse(t1,t2,t3) => val r1 = extractTree(t1) diff --git a/src/main/scala/leon/purescala/Extractors.scala b/src/main/scala/leon/purescala/Extractors.scala index a5c591bdd..d8ba07c13 100644 --- a/src/main/scala/leon/purescala/Extractors.scala +++ b/src/main/scala/leon/purescala/Extractors.scala @@ -65,7 +65,6 @@ object Extractors { case MapUnion(t1,t2) => Some((t1,t2,MapUnion)) case MapDifference(t1,t2) => Some((t1,t2,MapDifference)) case MapIsDefinedAt(t1,t2) => Some((t1,t2, MapIsDefinedAt)) - case ArrayFill(t1, t2) => Some((t1, t2, ArrayFill)) case ArraySelect(t1, t2) => Some((t1, t2, ArraySelect)) case Let(binders, e, body) => Some((e, body, (e: Expr, b: Expr) => Let(binders, e, b))) case LetTuple(binders, e, body) => Some((e, body, (e: Expr, b: Expr) => LetTuple(binders, e, b))) @@ -120,10 +119,24 @@ object Extractors { } case FiniteMultiset(args) => Some((args, FiniteMultiset)) case ArrayUpdated(t1, t2, t3) => Some((Seq(t1,t2,t3), (as: Seq[Expr]) => ArrayUpdated(as(0), as(1), as(2)))) - case FiniteArray(args) => Some((args, { (as: Seq[Expr]) => - val tpe = leastUpperBound(as.map(_.getType)).map(ArrayType(_)).getOrElse(expr.getType) - FiniteArray(as).setType(tpe) + case FiniteArray(elems, default, length) => { + val fixedElems: Seq[(Int, Expr)] = elems.toSeq + val all: Seq[Expr] = fixedElems.map(_._2) ++ default ++ Seq(length) + Some((all, (as: Seq[Expr]) => { + val tpe = leastUpperBound(as.map(_.getType)) + .map(ArrayType(_)) + .getOrElse(expr.getType) + val (newElems, newDefault, newSize) = default match { + case None => (as.init, None, as.last) + case Some(_) => (as.init.init, Some(as.init.last), as.last) + } + FiniteArray( + fixedElems.zip(newElems).map(p => (p._1._1, p._2)).toMap, + newDefault, + newSize).setType(tpe) })) + } + case Tuple(args) => Some((args, Tuple)) case IfExpr(cond, thenn, elze) => Some((Seq(cond, thenn, elze), (as: Seq[Expr]) => IfExpr(as(0), as(1), as(2)))) case MatchLike(scrut, cases, builder) => Some(( diff --git a/src/main/scala/leon/purescala/PrettyPrinter.scala b/src/main/scala/leon/purescala/PrettyPrinter.scala index de7052e22..43d98b758 100644 --- a/src/main/scala/leon/purescala/PrettyPrinter.scala +++ b/src/main/scala/leon/purescala/PrettyPrinter.scala @@ -13,7 +13,7 @@ import utils._ import java.lang.StringBuffer import PrinterHelpers._ -import TreeOps.{isStringLiteral, isListLiteral} +import TreeOps.{isStringLiteral, isListLiteral, simplestValue} import TypeTreeOps.leastUpperBound import synthesis.Witnesses._ @@ -374,10 +374,36 @@ class PrettyPrinter(opts: PrinterOptions, val sb: StringBuffer = new StringBuffe case MapGet(m,k) => p"$m($k)" case MapIsDefinedAt(m,k) => p"$m.isDefinedAt($k)" case ArrayLength(a) => p"$a.length" - case ArrayFill(size, v) => p"Array.fill($size)($v)" case ArraySelect(a, i) => p"$a($i)" case ArrayUpdated(a, i, v) => p"$a.updated($i, $v)" - case FiniteArray(exprs) => p"Array($exprs)" + case a@FiniteArray(es, d, s) => { + val ArrayType(underlying) = a.getType + val default = d.getOrElse(simplestValue(underlying)) + def ppBigArray(): Unit = { + if(es.isEmpty) { + p"Array($default, $default, $default, ..., $default) (of size $s)" + } else { + p"Array(_) (of size $s)" + } + } + s match { + case IntLiteral(length) => { + if(es.size == length) { + val orderedElements = es.toSeq.sortWith((e1, e2) => e1._1 < e2._1) + p"Array($orderedElements)" + } else if(length < 10) { + val elems = (0 until length).map(i => + es.find(el => el._1 == i).map(el => el._2).getOrElse(d.get) + ) + p"Array($elems)" + } else { + ppBigArray() + } + } + case _ => ppBigArray() + } + } + case Not(expr) => p"\u00AC$expr" case ValDef(id, tpe) => p"${typed(id)}" case This(_) => p"this" diff --git a/src/main/scala/leon/purescala/ScalaPrinter.scala b/src/main/scala/leon/purescala/ScalaPrinter.scala index d9fd8e296..82ddb9cb8 100644 --- a/src/main/scala/leon/purescala/ScalaPrinter.scala +++ b/src/main/scala/leon/purescala/ScalaPrinter.scala @@ -49,7 +49,33 @@ class ScalaPrinter(opts: PrinterOptions, sb: StringBuffer = new StringBuffer) ex case SetDifference(l,r) => p"$l -- $r" case SetIntersection(l,r) => p"$l & $r" case SetCardinality(s) => p"$s.size" - case FiniteArray(exprs) => p"Array($exprs)" + + case a@FiniteArray(elems, oDef, size) => { + import TreeOps._ + val ArrayType(underlying) = a.getType + val default = oDef.getOrElse(simplestValue(underlying)) + size match { + case IntLiteral(s) => { + val explicitArray = Array.fill(s)(default) + for((id, el) <- elems) + explicitArray(id) = el + val lit = explicitArray.toList + p"Array($lit)" + } + case size => { + p"""|{ + | val tmp = Array.fill($size)($default) + |""" + for((id, el) <- elems) + p""""| tmp($id) = $el + |""" + p"""| tmp + |}""" + + } + } + } + case Not(expr) => p"!$expr" case _ => super.pp(tree) diff --git a/src/main/scala/leon/purescala/TreeOps.scala b/src/main/scala/leon/purescala/TreeOps.scala index 801469a6b..2789fb43a 100644 --- a/src/main/scala/leon/purescala/TreeOps.scala +++ b/src/main/scala/leon/purescala/TreeOps.scala @@ -1016,7 +1016,7 @@ object TreeOps { case SetType(baseType) => FiniteSet(Set()).setType(tpe) case MapType(fromType, toType) => FiniteMap(Seq()).setType(tpe) case TupleType(tpes) => Tuple(tpes.map(simplestValue)) - case ArrayType(tpe) => ArrayFill(IntLiteral(0), simplestValue(tpe)) + case ArrayType(tpe) => FiniteArray(Map(), Some(simplestValue(tpe)), IntLiteral(0)).setType(ArrayType(tpe)) case act @ AbstractClassType(acd, tpe) => val children = acd.knownChildren diff --git a/src/main/scala/leon/purescala/Trees.scala b/src/main/scala/leon/purescala/Trees.scala index 052dd2305..89a23828b 100644 --- a/src/main/scala/leon/purescala/Trees.scala +++ b/src/main/scala/leon/purescala/Trees.scala @@ -430,8 +430,8 @@ object Trees { val getType = BooleanType } - /* Array operations */ + /* Array operations */ case class ArraySelect(array: Expr, index: Expr) extends Expr { def getType = array.getType match { case ArrayType(base) => @@ -454,7 +454,15 @@ object Trees { val getType = Int32Type } - case class FiniteArray(exprs: Seq[Expr]) extends Expr with MutableTyped + case class FiniteArray(elems: Map[Int, Expr], default: Option[Expr], length: Expr) extends Expr with MutableTyped + + object FiniteArray { + def apply(elems: Seq[Expr]): FiniteArray = { + val res = FiniteArray(elems.zipWithIndex.map(_.swap).toMap, None, IntLiteral(elems.size)) + elems.headOption.foreach(e => res.setType(ArrayType(e.getType))) + res + } + } /* Special trees */ diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala index 074fffce4..796b49647 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBCVC4Target.scala @@ -98,13 +98,13 @@ trait SMTLIBCVC4Target extends SMTLIBTarget { } override def toSMT(e: Expr)(implicit bindings: Map[Identifier, Term]) = e match { - case a @ FiniteArray(elems) => + case a @ FiniteArray(elems, default, size) => val tpe @ ArrayType(base) = normalizeType(a.getType) declareSort(tpe) var ar: Term = declareVariable(FreshIdentifier("arrayconst").setType(RawArrayType(Int32Type, base))) - for ((e, i) <- elems.zipWithIndex) { + for ((i, e) <- elems) { ar = FunctionApplication(SSymbol("store"), Seq(ar, toSMT(IntLiteral(i)), toSMT(e))) } diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala index eb59fd5b9..4d222d4f7 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBTarget.scala @@ -526,9 +526,17 @@ trait SMTLIBTarget { val IntLiteral(size) = fromSMT(args(0), Int32Type) val RawArrayValue(_, elems, default) = fromSMT(args(1), RawArrayType(Int32Type, at.base)) - val entries = for (i <- 0 to size-1) yield elems.getOrElse(IntLiteral(i), default) + if(size > 10) { + val definedElements = elems.collect{ + case (IntLiteral(i), value) => (i, value) + }.toMap + FiniteArray(definedElements, Some(default), IntLiteral(size)).setType(at) - FiniteArray(entries).setType(at) + } else { + val entries = for (i <- 0 to size-1) yield elems.getOrElse(IntLiteral(i), default) + + FiniteArray(entries).setType(at) + } case t => unsupported("Woot? structural type that is non-structural: "+t) diff --git a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala index 91e205c6a..7206a276c 100644 --- a/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala +++ b/src/main/scala/leon/solvers/smtlib/SMTLIBZ3Target.scala @@ -85,14 +85,15 @@ trait SMTLIBZ3Target extends SMTLIBTarget { } override def toSMT(e: Expr)(implicit bindings: Map[Identifier, Term]): Term = e match { - case a @ FiniteArray(elems) => + case a @ FiniteArray(elems, oDef, size) => val tpe @ ArrayType(base) = normalizeType(a.getType) declareSort(tpe) + val default: Expr = oDef.getOrElse(simplestValue(base)) - var ar: Term = ArrayConst(declareSort(RawArrayType(Int32Type, base)), toSMT(simplestValue(base))) + var ar: Term = ArrayConst(declareSort(RawArrayType(Int32Type, base)), toSMT(default)) - for ((e, i) <- elems.zipWithIndex) { + for ((i, e) <- elems) { ar = ArraysEx.Store(ar, toSMT(IntLiteral(i)), toSMT(e)) } diff --git a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala index 02b6683c4..03201a3eb 100644 --- a/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala +++ b/src/main/scala/leon/solvers/z3/AbstractZ3Solver.scala @@ -633,14 +633,6 @@ trait AbstractZ3Solver case MapType(ft, tt) => z3.mkDistinct(z3.mkSelect(rec(m), rec(k)), mapRangeNoneConstructors(tt)()) case errorType => scala.sys.error("Unexpected type for map: " + (ex, errorType)) } - case fill @ ArrayFill(length, default) => - val at @ ArrayType(base) = fill.getType - typeToSort(at) - val meta = arrayMetaDecls(normalizeType(at)) - - val ar = z3.mkConstArray(typeToSort(Int32Type), rec(default)) - val res = meta.cons(ar, rec(length)) - res case ArraySelect(a, index) => typeToSort(a.getType) @@ -665,13 +657,17 @@ trait AbstractZ3Solver val res = meta.length(ar) res - case arr @ FiniteArray(exprs) => { - val ArrayType(innerType) = arr.getType - val arrayType = arr.getType - val a: Expr = ArrayFill(IntLiteral(exprs.length), simplestValue(innerType)) - val u = exprs.zipWithIndex.foldLeft(a)((array, expI) => ArrayUpdated(array, IntLiteral(expI._2), expI._1)) - rec(u) - } + case arr @ FiniteArray(elems, oDefault, length) => + val at @ ArrayType(base) = arr.getType + typeToSort(at) + val meta = arrayMetaDecls(normalizeType(at)) + + val default = oDefault.getOrElse(simplestValue(base)) + + val ar = z3.mkConstArray(typeToSort(base), rec(default)) + val u = elems.foldLeft(ar)((array, el) => + z3.mkStore(array, rec(IntLiteral(el._1)), rec(el._2))) + meta.cons(u, rec(length)) case gv @ GenericValue(tp, id) => z3.mkApp(genericValueToDecl(gv)) @@ -739,9 +735,7 @@ trait AbstractZ3Solver (index -> rec(v)) } - FiniteArray(for (i <- 1 to length) yield { - valuesMap.getOrElse(i, elseValue) - }).setType(at) + FiniteArray(valuesMap, Some(elseValue), IntLiteral(length)).setType(at) } case LeonType(tpe @ MapType(kt, vt)) => diff --git a/src/main/scala/leon/verification/VerificationReport.scala b/src/main/scala/leon/verification/VerificationReport.scala index bec5a07bf..1b39389f6 100644 --- a/src/main/scala/leon/verification/VerificationReport.scala +++ b/src/main/scala/leon/verification/VerificationReport.scala @@ -4,7 +4,6 @@ package leon package verification import purescala.Definitions.FunDef -import purescala.ScalaPrinter class VerificationReport(val fvcs: Map[FunDef, List[VerificationCondition]]) { import scala.math.Ordering.Implicits._ diff --git a/src/test/resources/regression/verification/purescala/invalid/BigArray.scala b/src/test/resources/regression/verification/purescala/invalid/BigArray.scala new file mode 100644 index 000000000..f75e88970 --- /dev/null +++ b/src/test/resources/regression/verification/purescala/invalid/BigArray.scala @@ -0,0 +1,11 @@ +import leon.annotation._ +import leon.lang._ + +object BigArray { + + def big(a: Array[Int]): Int = { + require(a.length >= 10 && a(7) == 42) + a.length + } ensuring(_ <= 1000000000) + +} diff --git a/src/test/scala/leon/test/evaluators/DefaultEvaluatorTests.scala b/src/test/scala/leon/test/evaluators/DefaultEvaluatorTests.scala index 8b0946d8b..0dd8fe1d3 100644 --- a/src/test/scala/leon/test/evaluators/DefaultEvaluatorTests.scala +++ b/src/test/scala/leon/test/evaluators/DefaultEvaluatorTests.scala @@ -103,4 +103,47 @@ class DefaultEvaluatorTests extends leon.test.LeonTestSuite { defaultEvaluator.eval(Let(id, IntLiteral(42), Variable(id))), IntLiteral(42)) } + + + test("eval literal array ops") { + expectSuccessful( + defaultEvaluator.eval(FiniteArray(Map(), Some(IntLiteral(12)), IntLiteral(7)).setType(ArrayType(Int32Type))), + FiniteArray(Map(), Some(IntLiteral(12)), IntLiteral(7))) + expectSuccessful( + defaultEvaluator.eval( + ArrayLength(FiniteArray(Map(), Some(IntLiteral(12)), IntLiteral(7)).setType(ArrayType(Int32Type)))), + IntLiteral(7)) + expectSuccessful( + defaultEvaluator.eval(ArraySelect( + FiniteArray(Seq(IntLiteral(2), IntLiteral(4), IntLiteral(7))), + IntLiteral(1))), + IntLiteral(4)) + expectSuccessful( + defaultEvaluator.eval( + ArrayUpdated( + FiniteArray(Seq(IntLiteral(2), IntLiteral(4), IntLiteral(7))), + IntLiteral(1), + IntLiteral(42))), + FiniteArray(Seq(IntLiteral(2), IntLiteral(42), IntLiteral(7)))) + } + + test("eval variable length of array") { + val id = FreshIdentifier("id").setType(Int32Type) + expectSuccessful( + defaultEvaluator.eval( + ArrayLength( + FiniteArray(Map(), Some(IntLiteral(12)), Variable(id)) + .setType(ArrayType(Int32Type))), + Map(id -> IntLiteral(27))), + IntLiteral(27)) + } + + test("eval variable default value of array") { + val id = FreshIdentifier("id").setType(Int32Type) + expectSuccessful( + defaultEvaluator.eval( + FiniteArray(Map(), Some(Variable(id)), IntLiteral(7)).setType(ArrayType(Int32Type)), + Map(id -> IntLiteral(27))), + FiniteArray(Map(), Some(IntLiteral(27)), IntLiteral(7))) + } } -- GitLab