From 1e0783f1687b43bcb084ec476e5f52005297974e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= <mikael.mayer@epfl.ch> Date: Thu, 14 Apr 2016 16:49:54 +0200 Subject: [PATCH] Cleaned up input coverage which now fully works. Evaluating counter-examples again to faster cover other flags. --- .../disambiguation/InputCoverage.scala | 99 +++++++++++-------- .../solvers/InputCoverageSuite.scala | 62 +++++++++++- 2 files changed, 118 insertions(+), 43 deletions(-) diff --git a/src/main/scala/leon/synthesis/disambiguation/InputCoverage.scala b/src/main/scala/leon/synthesis/disambiguation/InputCoverage.scala index f3ca01928..775ed59d8 100644 --- a/src/main/scala/leon/synthesis/disambiguation/InputCoverage.scala +++ b/src/main/scala/leon/synthesis/disambiguation/InputCoverage.scala @@ -25,6 +25,7 @@ import leon.solvers._ import scala.concurrent.duration._ import leon.verification.VCStatus import leon.verification.VCResult +import leon.evaluators.AbstractEvaluator /** * @author Mikael @@ -34,7 +35,9 @@ import leon.verification.VCResult * @param fd The calling function */ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Program) { - var numToCoverForEachExample: Int = 1 + + /** If set, performs a cleaning up step to cover the whole function */ + var minimizeExamples: Boolean = true /** If the sub-branches contain identifiers, it returns them unchanged. Else it creates a new boolean indicating this branch. */ @@ -59,10 +62,14 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr (e._1, e_2) } + /** Returns true if there are some branching to monitor in the expression */ def hasConditionals(e: Expr) = { - ExprOps.exists{ case i:IfExpr => true case m: MatchExpr => true case f: FunctionInvocation => true case _ => false}(e) + ExprOps.exists{ case i:IfExpr => true case m: MatchExpr => true case f: FunctionInvocation if fds(f.tfd.fd) || f.tfd.fd == fd => true case _ => false}(e) } + /** Merges two set of identifiers. + * None means that the attached expression is the original one, + * Some(ids) means that it has been augmented with booleans and ids are the "monitoring" boolean flags. */ def merge(a: Option[Seq[Identifier]], b: Option[Seq[Identifier]]) = { (a, b) match { case (None, None) => None @@ -75,7 +82,6 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr /** For each branch in the expression, adds a boolean variable such that the new type of the expression is (previousType, Boolean) * If no variable is output, then the type of the expression is not changed. * If the expression is augmented with a boolean, returns the list of boolean variables which appear in the expression */ - // All functions now return a boolean along with their original return type. def markBranches(e: Expr): (Expr, Option[Seq[Identifier]]) = if(!hasConditionals(e)) (e, None) else e match { case IfExpr(cond, thenn, elze) => @@ -126,7 +132,7 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr val finalExpr = tupleWrap(Seq(Variable(res_id), or(finalIds.map(Variable(_)): _*))).copiedFrom(e) val funCall = letTuple(Seq(res_id, res_b), FunctionInvocation(TypedFunDef(new_fd, targs), children).copiedFrom(e), finalExpr).copiedFrom(e) - (exprBuilder(funCall), Some(finalIds)) + (exprBuilder(funCall), merge(Some(Seq()), ids)) } case _ => tmpIds match { @@ -139,12 +145,13 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr } } - // The cache of transforming functions. - var cache = Map[FunDef, FunDef]() + /** The cache of transforming functions.*/ + private var cache = Map[FunDef, FunDef]() - // Records all boolean serving to identify which part of the code should be executed. - var booleanFlags = Seq[Identifier]() + /** Records all booleans serving to identify which part of the code should be executed.*/ + private var booleanFlags = Seq[Identifier]() + /** Augment function definitions which should have boolean markers, leave others untouched. */ def wrapFunDef(fd: FunDef): FunDef = { if(!(cache contains fd)) { if(fds(fd)) { @@ -161,24 +168,22 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr cache(fd) } + /** Returns true if the expression is a function call with a new function already. */ def isNewFunCall(e: Expr): Boolean = e match { case FunctionInvocation(TypedFunDef(fd, targs), args) => cache.values.exists { f => f == fd } case _ => false } - /** The number of expressions is the same as the number of arguments. */ + /** Returns a stream of covering inputs for the function `f`, + * such that if `f` is evaluated on each of these inputs, all parts of `{ f } U fds` will have been executed at least once. + * + * The number of expressions is the same as the number of arguments of `f` */ def result(): Stream[Seq[Expr]] = { - /* Algorithm: - * def f(x: Int, z: Boolean): Int = - * x match { - * case 0 | 1 => - * if(z) f(if(z) x else -x, z) else 1 - * case _ => - * (if(z) x - * else f(x-1,!z)+f(x-2,!z))*f(x-1,!z) - * } - * 2) In innermost branches, replace each result by (result, bi) where bi is a boolean described later. + cache = Map() + booleanFlags = Seq() + /* Approximative algorithm Algorithm: + * In innermost branches, replace each result by (result, bi) where bi is a boolean described later. * def f(x: Int, z: Boolean): (Int, Boolean) = * x match { * case 0 | 1 => @@ -191,23 +196,7 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr * else (f(x-1,!z)+f(x-2,!z), b3) * (res*f(x-1,!z), b) * } - * 3) If the function is recursive, recover the previous values and left-OR them with the returning bi. - * def f(x: Int, z: Boolean): (Int, Boolean) = - * x match { - * case 0 | 1 => (1, b1) - * case _ => - * if(z) { - * val (r, bf) = f(x-1,!z) - * (x*r, b2 || bf)} - * else { - * val (r, bf) = f(x-1,!z) - * val (r2, bf2) = f(x-2,!z) - * (r+r2, b3 || bf || bf2) - * } - * } - * 4) Add the post-condition - * ensuring { res => !res._2 } - * 5) Let B be the set of bi. + * Let B be the set of bi. * For each b in B * Set all b' in B to false except b to true * Find a counter-example. @@ -235,8 +224,11 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr val start_fd = fdMap.getOrElse(fd, fd) - // For each boolean flag, set it to true, - val covering_examples = for(bvar <- booleanFlags.toStream) yield { + var coveredBooleans = Set[Identifier]() + // For each boolean flag, set it to true, and find a counter-example which should reach this line. + // For each new counter-example, abstract evaluate the original function to remove booleans which have been reached. + val covering_examples = + for(bvar <- booleanFlags.toStream if !coveredBooleans(bvar)) yield { val (program2, idMap2, fdMap2, cdMap2) = DefOps.replaceFunDefs(program)({ (f: FunDef) => if(ExprOps.exists(e => e match { case Variable(id) => booleanFlags contains id case _ => false })(f.fullBody)) { @@ -252,18 +244,41 @@ class InputCoverage(fd: FunDef, fds: Set[FunDef])(implicit c: LeonContext, p: Pr } else None }) val start_fd2 = fdMap2.getOrElse(start_fd, start_fd) - val tfactory = SolverFactory.getFromSettings(c, program2).withTimeout(5.seconds) val vctx = new VerificationContext(c, program2, tfactory) val vcs = VerificationPhase.generateVCs(vctx, Seq(start_fd2)) VerificationPhase.checkVCs(vctx, vcs).results(vcs(0)) match { - case None => Seq() case Some(VCResult(VCStatus.Invalid(model), _, _)) => - fd.paramIds.map(model) + val finalExprs = fd.paramIds.map(model) + val whoIsEvaluated = functionInvocation(start_fd, finalExprs) + val ae = new AbstractEvaluator(c, p) + val coveredBooleansForCE = ae.eval(whoIsEvaluated).result match { + case Some((Tuple(Seq(_, booleans)), _)) => + val targettedIds = ExprOps.collect(e => e match { case Variable(id) => Set(id) case _ => Set[Identifier]() })(booleans) + coveredBooleans ++= targettedIds + targettedIds + case _ => + Set(bvar) + } + finalExprs -> coveredBooleansForCE + case _ => Seq() -> Set(bvar) } } - covering_examples filter (_.nonEmpty) + val covering_examples2 = if(minimizeExamples) { + // Remove examples whose coverage set is included in some other example. + for((covering_example, flags_met) <- covering_examples + if !covering_examples.exists{ + case (other_example, other_flags) => + other_example != covering_example && + flags_met.subsetOf(other_flags) + } + ) yield covering_example + } else { + covering_examples.map(_._1) + } + + covering_examples2 filter (_.nonEmpty) } } \ No newline at end of file diff --git a/src/test/scala/leon/integration/solvers/InputCoverageSuite.scala b/src/test/scala/leon/integration/solvers/InputCoverageSuite.scala index 165509a10..c1c29acbb 100644 --- a/src/test/scala/leon/integration/solvers/InputCoverageSuite.scala +++ b/src/test/scala/leon/integration/solvers/InputCoverageSuite.scala @@ -75,7 +75,7 @@ class InputCoverageSuite extends LeonTestSuiteWithProgram with Matchers with Sca | | sealed abstract class A | case class B() extends A - | case class C(a: Int, tail: A) extends A + | case class C(a: String, tail: A) extends A | case class D(a: String, tail: A, b: String) extends A | | def withMatch(a: A): String = { @@ -255,4 +255,64 @@ class InputCoverageSuite extends LeonTestSuiteWithProgram with Matchers with Sca val coverage = new InputCoverage(withIf, Set(withIf)) coverage.result().toSet should equal (Set(Seq(b(true)), Seq(b(false)))) } + + test("input coverage for ADT should find all of them") { ctxprogram => + implicit val (c, p) = ctxprogram + val withMatch = funDef("InputCoverageSuite.withMatch") + val coverage = new InputCoverage(withMatch, Set(withMatch)) + coverage.minimizeExamples = false + val res = coverage.result().toSet + res should have size 4 + + coverage.minimizeExamples = true + val res2 = coverage.result().toSet + res2 should have size 3 + } + + test("input coverage for combined functions should find all of them") { ctxprogram => + implicit val (c, p) = ctxprogram + val withCoveredFun1 = funDef("InputCoverageSuite.withCoveredFun1") + val withCoveredFun2 = funDef("InputCoverageSuite.withCoveredFun2") + val coverage = new InputCoverage(withCoveredFun1, Set(withCoveredFun1, withCoveredFun2)) + val res = coverage.result().toSet.toSeq + if(res.size == 1) { + val Seq(IntLiteral(a)) = res(0) + withClue(s"a=$a") { + assert(a + 5 > 0 || a - 5 > 0, "At least one of the two examples should cover the positive case") + assert(a + 5 <= 0 || a - 5 <= 0, "At least one of the two examples should cover the negative case") + } + } else { + res should have size 2 + val Seq(IntLiteral(a)) = res(0) + val Seq(IntLiteral(b)) = res(1) + withClue(s"a=$a, b=$b") { + assert(a + 5 > 0 || b + 5 > 0 || a - 5 > 0 || b - 5 > 0 , "At least one of the two examples should cover the positive case") + assert(a + 5 <= 0 || b + 5 <= 0 || a - 5 <= 0 || b - 5 <= 0 , "At least one of the two examples should cover the negative case") + } + } + } + + test("input coverage for composed functions should find all of them") { ctxprogram => + implicit val (c, p) = ctxprogram + val withCoveredFun3 = funDef("InputCoverageSuite.withCoveredFun3") + val withCoveredFun2 = funDef("InputCoverageSuite.withCoveredFun2") + val coverage = new InputCoverage(withCoveredFun3, Set(withCoveredFun3, withCoveredFun2)) + val res = coverage.result().toSet.toSeq + def withCoveredFun2Impl(i: Int) = if(i > 0) 2 else 0 + if(res.size == 1) { + val Seq(IntLiteral(a)) = res(0) + withClue(s"a=$a") { + assert(a + 5 > 0 || withCoveredFun2Impl(a + 5) > 0, "At least one of the two examples should cover the positive case") + assert(a + 5 <= 0 || withCoveredFun2Impl(a + 5) <= 0, "At least one of the two examples should cover the negative case") + } + } else { + res should have size 2 + val Seq(IntLiteral(a)) = res(0) + val Seq(IntLiteral(b)) = res(1) + withClue(s"a=$a, b=$b") { + assert(a + 5 > 0 || withCoveredFun2Impl(a + 5) > 0 || b + 5 > 0 || withCoveredFun2Impl(b + 5) > 0, "At least one of the two examples should cover the positive case") + assert(a + 5 <= 0 || withCoveredFun2Impl(a + 5) <= 0 || b + 5 <= 0 || withCoveredFun2Impl(b + 5) <= 0, "At least one of the two examples should cover the negative case") + } + } + } } \ No newline at end of file -- GitLab