From 965264c32ced7a8154670a7590bcada1ae5e6e8b Mon Sep 17 00:00:00 2001 From: Etienne Kneuss <ekneuss@gmail.com> Date: Mon, 7 Jan 2013 18:57:03 +0100 Subject: [PATCH] Allow onSuccess to fail. This is necessary to prevent soundness issues. The synthesizer used to generate wrong programs by generating inductive programs with an impossible base-case. onSuccess on inductive rules now prevents this by checking that sufficiently many cases have precondition == true. Otherwise, onSuccess fails. This last-moment failure is now handled correctly. Strenghten precondition --- .../scala/leon/synthesis/Heuristics.scala | 4 +- src/main/scala/leon/synthesis/Rules.scala | 16 ++++---- src/main/scala/leon/synthesis/Solution.scala | 5 --- .../synthesis/heuristics/ADTInduction.scala | 24 +++++++---- .../synthesis/heuristics/IntInduction.scala | 40 +++++++++++-------- .../scala/leon/synthesis/rules/ADTSplit.scala | 4 +- .../scala/leon/synthesis/rules/Assert.scala | 4 +- .../leon/synthesis/rules/CaseSplit.scala | 6 +-- .../leon/synthesis/rules/EqualitySplit.scala | 6 +-- .../synthesis/rules/IntegerEquation.scala | 18 +++++---- .../synthesis/rules/IntegerInequalities.scala | 11 ++--- .../scala/leon/synthesis/rules/OnePoint.scala | 10 +++-- .../synthesis/rules/UnconstrainedOutput.scala | 6 +-- .../leon/synthesis/search/AndOrGraph.scala | 14 +++++-- 14 files changed, 97 insertions(+), 71 deletions(-) diff --git a/src/main/scala/leon/synthesis/Heuristics.scala b/src/main/scala/leon/synthesis/Heuristics.scala index a80831ec4..837c08d3b 100644 --- a/src/main/scala/leon/synthesis/Heuristics.scala +++ b/src/main/scala/leon/synthesis/Heuristics.scala @@ -37,10 +37,10 @@ object HeuristicInstantiation { Some(s) } - def apply(problem: Problem, rule: Rule, subProblems: List[Problem], onSuccess: List[Solution] => Solution) = { + def apply(problem: Problem, rule: Rule, subProblems: List[Problem], onSuccess: List[Solution] => Option[Solution]): RuleInstantiation = { val builder = new SolutionBuilder(subProblems.size) { def apply(sols: List[Solution]) = { - Some(onSuccess(sols)) + onSuccess(sols) } } diff --git a/src/main/scala/leon/synthesis/Rules.scala b/src/main/scala/leon/synthesis/Rules.scala index 0ea37eac2..548d8fb30 100644 --- a/src/main/scala/leon/synthesis/Rules.scala +++ b/src/main/scala/leon/synthesis/Rules.scala @@ -31,10 +31,10 @@ abstract class SolutionBuilder(val arity: Int) { def apply(sols: List[Solution]): Option[Solution] } -class SolutionCombiner(arity: Int, f: List[Solution] => Solution) extends SolutionBuilder(arity) { +class SolutionCombiner(arity: Int, f: List[Solution] => Option[Solution]) extends SolutionBuilder(arity) { def apply(sols: List[Solution]) = { assert(sols.size == arity) - Some(f(sols)) + f(sols) } } @@ -62,14 +62,14 @@ case class RuleDecomposed(sub: List[Problem]) extends RuleApplicationResult case object RuleApplicationImpossible extends RuleApplicationResult object RuleInstantiation { - def immediateDecomp(problem: Problem, rule: Rule, sub: List[Problem], onSuccess: List[Solution] => Solution) = { + def immediateDecomp(problem: Problem, rule: Rule, sub: List[Problem], onSuccess: List[Solution] => Option[Solution]) = { new RuleInstantiation(problem, rule, new SolutionCombiner(sub.size, onSuccess)) { def apply(sctx: SynthesisContext) = RuleDecomposed(sub) } } def immediateSuccess(problem: Problem, rule: Rule, solution: Solution) = { - new RuleInstantiation(problem, rule, new SolutionCombiner(0, ls => solution)) { + new RuleInstantiation(problem, rule, new SolutionCombiner(0, ls => Some(solution))) { def apply(sctx: SynthesisContext) = RuleSuccess(solution) } } @@ -81,9 +81,11 @@ abstract class Rule(val name: String) { def subst(what: Tuple2[Identifier, Expr], in: Expr): Expr = replace(Map(Variable(what._1) -> what._2), in) def substAll(what: Map[Identifier, Expr], in: Expr): Expr = replace(what.map(w => Variable(w._1) -> w._2), in) - val forward: List[Solution] => Solution = { - case List(s) => Solution(s.pre, s.defs, s.term) - case _ => Solution.none + val forward: List[Solution] => Option[Solution] = { + case List(s) => + Some(Solution(s.pre, s.defs, s.term)) + case _ => + None } override def toString = "R: "+name diff --git a/src/main/scala/leon/synthesis/Solution.scala b/src/main/scala/leon/synthesis/Solution.scala index 945e9796b..07c37915b 100644 --- a/src/main/scala/leon/synthesis/Solution.scala +++ b/src/main/scala/leon/synthesis/Solution.scala @@ -28,7 +28,6 @@ class Solution(val pre: Expr, val defs: Set[FunDef], val term: Expr) { } object Solution { - def simplify(e: Expr) = simplifyLets(e) def apply(pre: Expr, defs: Set[FunDef], term: Expr) = { @@ -49,8 +48,4 @@ object Solution { def simplest: Solution = { new Solution(BooleanLiteral(true), Set(), BooleanLiteral(true)) } - - def none: Solution = { - throw new Exception("Unexpected failure to construct solution") - } } diff --git a/src/main/scala/leon/synthesis/heuristics/ADTInduction.scala b/src/main/scala/leon/synthesis/heuristics/ADTInduction.scala index c9e5af133..b1f95c726 100644 --- a/src/main/scala/leon/synthesis/heuristics/ADTInduction.scala +++ b/src/main/scala/leon/synthesis/heuristics/ADTInduction.scala @@ -73,7 +73,7 @@ case object ADTInduction extends Rule("ADT Induction") with Heuristic { sys.error("Woops, non case-class as descendent") } - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case sols => var globalPre = List[Expr]() @@ -86,15 +86,25 @@ case object ADTInduction extends Rule("ADT Induction") with Heuristic { SimpleCase(caze, calls.foldLeft(sol.term){ case (t, (binders, callargs)) => LetTuple(binders, FunctionInvocation(newFun, callargs), t) }) } - val funPre = subst(origId -> Variable(inductOn), Or(globalPre)) - val outerPre = Or(globalPre) + if (sols.exists(_.pre != BooleanLiteral(true))) { + // Required to avoid an impossible cases, which suffices to + // allow invalid programs. This might be too strong though: we + // might only have to enforce it on solutions of base cases. + None + } else { + val funPre = subst(origId -> Variable(inductOn), Or(globalPre)) + val outerPre = Or(globalPre) - newFun.precondition = Some(funPre) - newFun.postcondition = Some(LetTuple(p.xs.toSeq, ResultVariable().setType(resType), p.phi)) + newFun.precondition = Some(funPre) + newFun.postcondition = Some(LetTuple(p.xs.toSeq, ResultVariable().setType(resType), p.phi)) - newFun.body = Some(MatchExpr(Variable(inductOn), cases)) + newFun.body = Some(MatchExpr(Variable(inductOn), cases)) - Solution(Or(globalPre), sols.flatMap(_.defs).toSet+newFun, FunctionInvocation(newFun, Variable(origId) :: oas.map(Variable(_)))) + Some(Solution(Or(globalPre), + sols.flatMap(_.defs).toSet+newFun, + FunctionInvocation(newFun, Variable(origId) :: oas.map(Variable(_))) + )) + } } Some(HeuristicInstantiation(p, this, subProblemsInfo.map(_._1).toList, onSuccess)) diff --git a/src/main/scala/leon/synthesis/heuristics/IntInduction.scala b/src/main/scala/leon/synthesis/heuristics/IntInduction.scala index 8151341a3..e59150394 100644 --- a/src/main/scala/leon/synthesis/heuristics/IntInduction.scala +++ b/src/main/scala/leon/synthesis/heuristics/IntInduction.scala @@ -29,29 +29,35 @@ case object IntInduction extends Rule("Int Induction") with Heuristic { val subGT = Problem(inductOn :: postXs, And(Seq(GreaterThan(Variable(inductOn), IntLiteral(0)), postCondGT, p.pc)), newPhi, p.xs) val subLT = Problem(inductOn :: postXs, And(Seq(LessThan(Variable(inductOn), IntLiteral(0)), postCondLT, p.pc)), newPhi, p.xs) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case List(base, gt, lt) => - val preIn = Or(Seq(And(Equals(Variable(inductOn), IntLiteral(0)), base.pre), - And(GreaterThan(Variable(inductOn), IntLiteral(0)), gt.pre), - And(LessThan(Variable(inductOn), IntLiteral(0)), lt.pre))) - val preOut = subst(inductOn -> Variable(origId), preIn) + if (base.pre != BooleanLiteral(true) || (gt.pre != BooleanLiteral(true) && lt.pre != BooleanLiteral(true))) { + // Required to avoid an impossible base-case, which suffices to + // allow invalid programs. + None + } else { + val preIn = Or(Seq(And(Equals(Variable(inductOn), IntLiteral(0)), base.pre), + And(GreaterThan(Variable(inductOn), IntLiteral(0)), gt.pre), + And(LessThan(Variable(inductOn), IntLiteral(0)), lt.pre))) + val preOut = subst(inductOn -> Variable(origId), preIn) - val newFun = new FunDef(FreshIdentifier("rec", true), tpe, Seq(VarDecl(inductOn, inductOn.getType))) - newFun.precondition = Some(preIn) - newFun.postcondition = Some(LetTuple(p.xs.toSeq, ResultVariable().setType(tpe), p.phi)) + val newFun = new FunDef(FreshIdentifier("rec", true), tpe, Seq(VarDecl(inductOn, inductOn.getType))) + newFun.precondition = Some(preIn) + newFun.postcondition = Some(LetTuple(p.xs.toSeq, ResultVariable().setType(tpe), p.phi)) - newFun.body = Some( - IfExpr(Equals(Variable(inductOn), IntLiteral(0)), - base.toExpr, - IfExpr(GreaterThan(Variable(inductOn), IntLiteral(0)), - LetTuple(postXs, FunctionInvocation(newFun, Seq(Minus(Variable(inductOn), IntLiteral(1)))), gt.toExpr) - , LetTuple(postXs, FunctionInvocation(newFun, Seq(Plus(Variable(inductOn), IntLiteral(1)))), lt.toExpr))) - ) + newFun.body = Some( + IfExpr(Equals(Variable(inductOn), IntLiteral(0)), + base.toExpr, + IfExpr(GreaterThan(Variable(inductOn), IntLiteral(0)), + LetTuple(postXs, FunctionInvocation(newFun, Seq(Minus(Variable(inductOn), IntLiteral(1)))), gt.toExpr) + , LetTuple(postXs, FunctionInvocation(newFun, Seq(Plus(Variable(inductOn), IntLiteral(1)))), lt.toExpr))) + ) - Solution(preOut, base.defs++gt.defs++lt.defs+newFun, FunctionInvocation(newFun, Seq(Variable(origId)))) + Some(Solution(preOut, base.defs++gt.defs++lt.defs+newFun, FunctionInvocation(newFun, Seq(Variable(origId))))) + } case _ => - Solution.none + None } Some(HeuristicInstantiation(p, this, List(subBase, subGT, subLT), onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/ADTSplit.scala b/src/main/scala/leon/synthesis/rules/ADTSplit.scala index 7235f9bb4..07347e95b 100644 --- a/src/main/scala/leon/synthesis/rules/ADTSplit.scala +++ b/src/main/scala/leon/synthesis/rules/ADTSplit.scala @@ -56,7 +56,7 @@ case object ADTSplit extends Rule("ADT Split.") { } - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case sols => var globalPre = List[Expr]() @@ -66,7 +66,7 @@ case object ADTSplit extends Rule("ADT Split.") { SimpleCase(pattern, sol.term) } - Solution(Or(globalPre), sols.flatMap(_.defs).toSet, MatchExpr(Variable(id), cases)) + Some(Solution(Or(globalPre), sols.flatMap(_.defs).toSet, MatchExpr(Variable(id), cases))) } Some(RuleInstantiation.immediateDecomp(p, this, subInfo.map(_._2).toList, onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/Assert.scala b/src/main/scala/leon/synthesis/rules/Assert.scala index d45d08903..898f1ea05 100644 --- a/src/main/scala/leon/synthesis/rules/Assert.scala +++ b/src/main/scala/leon/synthesis/rules/Assert.scala @@ -21,8 +21,8 @@ case object Assert extends Rule("Assert") { val sub = p.copy(pc = And(p.pc +: exprsA), phi = And(others)) List(RuleInstantiation.immediateDecomp(p, this, List(sub), { - case Solution(pre, defs, term) :: Nil => Solution(And(exprsA :+ pre), defs, term) - case _ => Solution.none + case Solution(pre, defs, term) :: Nil => Some(Solution(And(exprsA :+ pre), defs, term)) + case _ => None })) } } else { diff --git a/src/main/scala/leon/synthesis/rules/CaseSplit.scala b/src/main/scala/leon/synthesis/rules/CaseSplit.scala index 9b3e56315..1e1ac7940 100644 --- a/src/main/scala/leon/synthesis/rules/CaseSplit.scala +++ b/src/main/scala/leon/synthesis/rules/CaseSplit.scala @@ -19,7 +19,7 @@ case object CaseSplit extends Rule("Case-Split") { def split(alts: Seq[Expr], p: Problem): RuleInstantiation = { val subs = alts.map(a => Problem(p.as, p.pc, a, p.xs)).toList - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case sols if sols.size == subs.size => val pre = Or(sols.map(_.pre)) val defs = sols.map(_.defs).reduceLeft(_ ++ _) @@ -28,10 +28,10 @@ case object CaseSplit extends Rule("Case-Split") { val term = prefix.foldRight(last.term) { (s, t) => IfExpr(s.pre, s.term, t) } - Solution(pre, defs, term) + Some(Solution(pre, defs, term)) case _ => - Solution.none + None } RuleInstantiation.immediateDecomp(p, this, subs, onSuccess) diff --git a/src/main/scala/leon/synthesis/rules/EqualitySplit.scala b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala index 603a48a08..205440cf3 100644 --- a/src/main/scala/leon/synthesis/rules/EqualitySplit.scala +++ b/src/main/scala/leon/synthesis/rules/EqualitySplit.scala @@ -41,11 +41,11 @@ case object EqualitySplit extends Rule("Eq. Split.") { val sub1 = p.copy(pc = And(Equals(Variable(a1), Variable(a2)), p.pc)) val sub2 = p.copy(pc = And(Not(Equals(Variable(a1), Variable(a2))), p.pc)) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case List(s1, s2) => - Solution(Or(s1.pre, s2.pre), s1.defs++s2.defs, IfExpr(Equals(Variable(a1), Variable(a2)), s1.term, s2.term)) + Some(Solution(Or(s1.pre, s2.pre), s1.defs++s2.defs, IfExpr(Equals(Variable(a1), Variable(a2)), s1.term, s2.term))) case _ => - Solution.none + None } Some(RuleInstantiation.immediateDecomp(p, this, List(sub1, sub2), onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/IntegerEquation.scala b/src/main/scala/leon/synthesis/rules/IntegerEquation.scala index f74e4478e..1d3579907 100644 --- a/src/main/scala/leon/synthesis/rules/IntegerEquation.scala +++ b/src/main/scala/leon/synthesis/rules/IntegerEquation.scala @@ -53,11 +53,11 @@ case object IntegerEquation extends Rule("Integer Equation") { val eqPre = Equals(normalizedEq.head, IntLiteral(0)) val newProblem = Problem(problem.as, And(eqPre, problem.pc), And(allOthers), problem.xs) - val onSuccess: List[Solution] => Solution = { - case List(Solution(pre, defs, term)) => { - Solution(And(eqPre, pre), defs, term) - } - case _ => Solution.none + val onSuccess: List[Solution] => Option[Solution] = { + case List(Solution(pre, defs, term)) => + Some(Solution(And(eqPre, pre), defs, term)) + case _ => + None } List(RuleInstantiation.immediateDecomp(problem, this, List(newProblem), onSuccess)) @@ -90,7 +90,7 @@ case object IntegerEquation extends Rule("Integer Equation") { val newProblem = Problem(problem.as ++ freshInputVariables, And(eqPre, problem.pc), freshFormula, subproblemxs) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case List(Solution(pre, defs, term)) => { val freshPre = replace(equivalenceConstraints, pre) val freshTerm = replace(equivalenceConstraints, term) @@ -98,9 +98,11 @@ case object IntegerEquation extends Rule("Integer Equation") { val id2res: Map[Expr, Expr] = freshsubxs.zip(subproblemxs).map{case (id1, id2) => (Variable(id1), Variable(id2))}.toMap ++ neqxs.map(id => (Variable(id), eqSubstMap(Variable(id)))).toMap - Solution(And(eqPre, freshPre), defs, simplifyArithmetic(simplifyLets(LetTuple(subproblemxs, freshTerm, replace(id2res, Tuple(problem.xs.map(Variable(_)))))))) + Some(Solution(And(eqPre, freshPre), defs, simplifyArithmetic(simplifyLets(LetTuple(subproblemxs, freshTerm, replace(id2res, Tuple(problem.xs.map(Variable(_))))))))) } - case _ => Solution.none + + case _ => + None } List(RuleInstantiation.immediateDecomp(problem, this, List(newProblem), onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/IntegerInequalities.scala b/src/main/scala/leon/synthesis/rules/IntegerInequalities.scala index 7fbc67fd6..540dd6b94 100644 --- a/src/main/scala/leon/synthesis/rules/IntegerInequalities.scala +++ b/src/main/scala/leon/synthesis/rules/IntegerInequalities.scala @@ -163,13 +163,13 @@ case object IntegerInequalities extends Rule("Integer Inequalities") { val subProblem = Problem(problem.as ++ remainderIds, problem.pc, subProblemFormula, subProblemxs) - def onSuccess(sols: List[Solution]): Solution = sols match { + def onSuccess(sols: List[Solution]): Option[Solution] = sols match { case List(Solution(pre, defs, term)) => { if(remainderIds.isEmpty) { - Solution(And(newPre, pre), defs, + Some(Solution(And(newPre, pre), defs, LetTuple(subProblemxs, term, Let(processedVar, witness, - Tuple(problem.xs.map(Variable(_)))))) + Tuple(problem.xs.map(Variable(_))))))) } else if(remainderIds.size > 1) { sys.error("TODO") } else { @@ -194,10 +194,11 @@ case object IntegerInequalities extends Rule("Integer Inequalities") { )) funDef.body = Some(funBody) - Solution(And(newPre, pre), defs + funDef, FunctionInvocation(funDef, Seq(IntLiteral(L-1)))) + Some(Solution(And(newPre, pre), defs + funDef, FunctionInvocation(funDef, Seq(IntLiteral(L-1))))) } } - case _ => Solution.none + case _ => + None } List(RuleInstantiation.immediateDecomp(problem, this, List(subProblem), onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/OnePoint.scala b/src/main/scala/leon/synthesis/rules/OnePoint.scala index 7f4fae9da..42c931f59 100644 --- a/src/main/scala/leon/synthesis/rules/OnePoint.scala +++ b/src/main/scala/leon/synthesis/rules/OnePoint.scala @@ -25,14 +25,16 @@ case object OnePoint extends Rule("One-point") { val newProblem = Problem(p.as, p.pc, subst(x -> e, And(others)), oxs) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case List(Solution(pre, defs, term)) => if (oxs.isEmpty) { - Solution(pre, defs, Tuple(e :: Nil)) + Some(Solution(pre, defs, Tuple(e :: Nil))) } else { - Solution(pre, defs, LetTuple(oxs, term, subst(x -> e, Tuple(p.xs.map(Variable(_)))))) + Some(Solution(pre, defs, LetTuple(oxs, term, subst(x -> e, Tuple(p.xs.map(Variable(_))))))) } - case _ => Solution.none + + case _ => + None } List(RuleInstantiation.immediateDecomp(p, this, List(newProblem), onSuccess)) diff --git a/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala index 381f73f6b..c5ddd02ef 100644 --- a/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala +++ b/src/main/scala/leon/synthesis/rules/UnconstrainedOutput.scala @@ -13,11 +13,11 @@ case object UnconstrainedOutput extends Rule("Unconstr.Output") { if (!unconstr.isEmpty) { val sub = p.copy(xs = p.xs.filterNot(unconstr)) - val onSuccess: List[Solution] => Solution = { + val onSuccess: List[Solution] => Option[Solution] = { case List(s) => - Solution(s.pre, s.defs, LetTuple(sub.xs, s.term, Tuple(p.xs.map(id => if (unconstr(id)) simplestValue(Variable(id)) else Variable(id))))) + Some(Solution(s.pre, s.defs, LetTuple(sub.xs, s.term, Tuple(p.xs.map(id => if (unconstr(id)) simplestValue(Variable(id)) else Variable(id)))))) case _ => - Solution.none + None } List(RuleInstantiation.immediateDecomp(p, this, List(sub), onSuccess)) diff --git a/src/main/scala/leon/synthesis/search/AndOrGraph.scala b/src/main/scala/leon/synthesis/search/AndOrGraph.scala index 251734160..8227a0e9f 100644 --- a/src/main/scala/leon/synthesis/search/AndOrGraph.scala +++ b/src/main/scala/leon/synthesis/search/AndOrGraph.scala @@ -84,10 +84,18 @@ class AndOrGraph[AT <: AOAndTask[S], OT <: AOOrTask[S], S](val root: OT, val cos subSolutions += sub.task -> sol if (subSolutions.size == subProblems.size) { - solution = task.composeSolution(subTasks.map(subSolutions)) - updateMin() + task.composeSolution(subTasks.map(subSolutions)) match { + case Some(sol) => + solution = Some(sol) + updateMin() + + notifyParent(sol) - notifyParent(solution.get) + case None => + if (solution.isEmpty) { + unsolvable(sub) + } + } } else { updateMin() } -- GitLab