liquid-fixpoint-0.1.0.0: external/fixpoint/counterexample.ml
(*
* Copyright © 2009-12 The Regents of the University of California. All rights reserved.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, provided that the
* above copyright notice and the following two paragraphs appear in
* all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
* FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
* IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION
* TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
*)
(***************************************************************)
(* Counterexample Generation (cf. Lahiri-Vanegue, VMCAI 2011) **)
(***************************************************************)
module F = Format
module BS = BNstats
module C = FixConstraint
module Misc = FixMisc
module IM = Misc.IntMap
module IS = Misc.IntSet
module A = Ast
module Sy = A.Symbol
module SM = Sy.SMap
module SS = Sy.SSet
module P = A.Predicate
module Su = A.Subst
module Q = Qualifier
open Misc.Ops
let mydebug = false
(***************************************************************************)
(************** Type Aliases ***********************************************)
(***************************************************************************)
type step = int
type kvar = Sy.t
type fact = Abs of kvar * Qualifier.t | Conc of C.id
type cause = step * C.id * ((Sy.t * fact) list)
(* [k |-> [(i0, [q_i0_1...]),...]]
* where q_i0_1... are killed at timestep i0 for kvar k sorted by step *)
type lifespan = (step * Q.t list) list SM.t
(* [id |-> i0,...]
* where the constraint id is selected at steps i0... by solver *)
type ctrace = step list IM.t
(* [((x1,k1,q1), c1);...;((xn,kn,qn),cn)]
* where each k_i+1, q_i+1, c_i+1 is the "cause" for why k_i, q_i is killed *)
(* type cex = (Sy.t * fact * C.id) list *)
type cex = Cause of Sy.t * fact * C.id * cex list
(****************************************************************************)
(******************** Printing Counterexamples ******************************)
(****************************************************************************)
let print_fact ppf = function
| Abs (k, q) -> F.fprintf ppf "(%a/%a)" Sy.print k Q.print_args q
| Conc i -> F.fprintf ppf "(id %d)" i
let print_step ppf (x, f, cid) =
F.fprintf ppf "%a: %a @@ %d" Sy.print x print_fact f cid
(*
let print_cex = Misc.pprint_many_box true "" "---> " "" print_step
*)
let rec print_cex spaces ppf (Cause (x, f, cid, cs)) =
F.fprintf ppf "%s `-> %a\n%a"
(String.concat "" (Misc.clone " " spaces))
print_step (x, f, cid)
(Misc.pprint_many false "\n\n" (print_cex (spaces + 4))) cs
let print_cex = print_cex 0
let print_fact_causes n ppf (f, xfs) =
F.fprintf ppf "fact %a killed at %d by: %a \n"
print_fact f
n
(Misc.pprint_many_brackets false (Misc.pprint_tuple Sy.print print_fact)) xfs
(****************************************************************************)
(******************** Instance Type *****************************************)
(****************************************************************************)
let compare_fact f1 f2 =
compare (Misc.fsprintf print_fact f1) (Misc.fsprintf print_fact f2)
module FactMap = Misc.EMap (struct
type t = fact
let compare = compare_fact
let print = print_fact
end)
type t = { tpc : ProverArch.prover
; n : int (* number of solver iters *)
; s : FixConstraint.soln
; cm : FixConstraint.t IM.t
; ctrace : ctrace
; lifespan : lifespan (* builds soln at n *)
; fsm : step FactMap.t (* fact |-> step at which killed *)
; scm : int IM.t (* step |-> constr at step *)
}
let scm_of_ctrace ctrace =
ctrace
|> IM.to_list
|> Misc.flap (fun (cid, is) -> List.map (fun i -> (i, cid)) is)
|> Misc.fsort fst
|> IM.of_list
let fsm_of_lifespan lifetime =
SM.fold begin fun k sqs fsm ->
List.fold_left begin fun fsm (step, qs) ->
List.fold_left begin fun fsm q ->
FactMap.add (Abs (k, q)) step fsm
end fsm qs
end fsm sqs
end lifetime FactMap.empty
(************************************************************************)
(*********** Helpers to Reconstitute Solutions and Candidates ***********)
(************************************************************************)
let constrOfId me cid =
IM.safeFind cid me.cm "Cex.constrOfId"
let solutionAt me n k =
SM.safeFind k me.lifespan "solutionAt: bad kvar"
|> List.filter (fun (m,_) -> n <= m)
|> Misc.flap snd
|> Misc.map Q.pred_of_t
|> (++) (me.s k)
let isUnsatAt me c n =
let s = solutionAt me n in
let rhsp = A.pAnd <| C.preds_of_reft s (C.rhs_of_t c) in
let query = A.pAnd <| (A.pNot rhsp) :: (C.preds_of_lhs s c) in
not <| me.tpc#is_contra (C.senv_of_t c) query
let prevStep_conc me c : int =
let _ = asserts (C.is_conc_rhs c) in
let no = Misc.find_first_true (isUnsatAt me c) 0 me.n in
Misc.maybe_apply (+) no (-1)
let prevStep_abs me cid n : int =
let rec go n = function
| m1 :: _ when m1 = n -> (-1)
| m1 :: (m2 :: _ as rest) -> if n = m2 then m1 else go n rest
| _ -> assertf "prevStep with bad ctrace"
in go n (IM.safeFind cid me.ctrace "prevStep: bad cid")
let prevStep me c n =
if C.is_conc_rhs c then
prevStep_conc me c
else
prevStep_abs me (C.id_of_t c) n
let killstep_of_fact me f =
FactMap.safeFind f me.fsm "Cex.killstep_of_fact"
let delta me c n k : fact list =
let _n = prevStep me c n in
SM.safeFind k me.lifespan "delta: bad kvar"
|> List.filter (fun (m,_) -> _n <= m && m < n)
|> Misc.flap snd
|> Misc.map (fun q -> Abs (k, q))
(************************************************************************)
(************************************************************************)
(************************************************************************)
let killerCands me c n : (int * (((Sy.t * fact) * A.pred)) list) list =
foreach (C.kbindings_of_lhs c) begin fun (x, (vv, t, ras)) ->
foreach ras begin function C.Kvar (su, k) ->
foreach (delta me c n k) begin function (Abs (_, q) as f) ->
let su' = Su.extend su (vv, A.eVar x) in
let p' = A.substs_pred (Q.pred_of_t q) su' in
(x, f), p'
end
end |> Misc.flatten
end |> Misc.flatten
|> Misc.kgroupby (fst <+> snd <+> killstep_of_fact me)
(************************************************************************)
(******************** Lazy Explanations *********************************)
(************************************************************************)
let killedPred me c f =
match f, C.rhs_of_t c with
| Abs (k, q), (_,_, [C.Kvar (su, k')])
when k = k'
-> A.substs_pred (Q.pred_of_t q) su
| Conc cid, (_,_,[C.Conc p])
when C.id_of_t c = cid
-> p
| _ -> failwith "Counterexample.killed"
let getKillStep me c bgp iks =
let iks = Misc.fsort fst iks in
let ps = iks |>: (snd <+> List.map snd <+> A.pAnd) in
match me.tpc#unsat_suffix (C.senv_of_t c) bgp ps with
| Some j when 0 <= j && j < List.length iks
-> List.nth iks j
| io -> let _ = F.printf
"getKillStep failure: (cid = %d) (|iks| = %d) (io = %a)\n bgp = %a\nps = %a\n"
(C.id_of_t c)
(List.length iks)
(Misc.pprint_maybe Misc.pprint_int) io
P.print bgp
(Misc.pprint_many_brackets true P.print) ps
in assertf "getKillStep"
let killinfo me = function
| Conc cid -> me.n, cid
| f -> let n = killstep_of_fact me f in
(n, IM.safeFind n me.scm "Cex.killinfo")
(* {{{ ORIGINAL: simply use unsat core.
let is_bot_killer = function
| (f, p) when P.is_contra p -> Some f
| _ -> None
let getKillers_cands me c bgp cands =
match cands, Misc.exists_maybe is_bot_killer cands with
| [], _ ->
None
| _, Some g ->
Some g
| _, _ ->
TP.unsat_core me.tpc (C.senv_of_t c) bgp cands
|> Misc.do_catch "ERROR: empty unsat core" List.hd
|> some
let getKillers_fact (me: t) (f: fact) =
let n, cid = killinfo me f in
let c = IM.safeFind cid me.cm "Cex.getKillers 3" in
match killerCands me c n with [] -> (cid, None) | iks ->
let bgps = C.preds_of_lhs (solutionAt me n) c
|> (++) [A.pNot (killedPred me c f)] in
let (j, cands) = getKillStep me c (A.pAnd bgps) iks in
let bgps' = iks
|> List.filter (fun (i,_) -> j < i)
|> Misc.flap (snd <+> List.map snd) in
(cid, getKillers_cands me c (A.pAnd (bgps ++ bgps')) cands)
let maxCubeSize = 1
let underApproxCubes me (p:pred) (q:pred) (rs: ('a * pred) list) : 'a option =
Misc.exists_maybe begin fun (f, fp) ->
if SAT (p /\ fp) && UNSAT (p /\ fp /\ q)
then Some f
else None
end rs
}}} *)
let getKillers_cands me c p q rs =
let env = C.senv_of_t c in
let contra = fun p -> me.tpc#is_contra env p in
Misc.map_partial begin fun (f, fp) ->
if contra (A.pAnd [p; fp; q]) && not (contra (A.pAnd [p; fp]))
then Some f
else None
end rs
let getKillers_fact (me: t) (f: fact) =
let n, cid = killinfo me f in
let c = IM.safeFind cid me.cm "Cex.getKillers 3" in
match killerCands me c n with [] -> (cid, []) | iks ->
let bgps = C.preds_of_lhs (solutionAt me n) c in
let killedp = A.pNot (killedPred me c f) in
let (j, cands) = getKillStep me c (A.pAnd bgps) iks in
let bgps' = iks
|> List.filter (fun (i,_) -> j < i)
|> Misc.flap (snd <+> List.map snd) in
(cid, getKillers_cands me c (A.pAnd (bgps ++ bgps')) killedp cands)
let rec explain me f =
let cid, xfs = getKillers_fact me f in
List.map (fun (x',f') -> Cause (x', f', cid, explain me f')) xfs
(********************************************************************)
(*********************** API ****************************************)
(********************************************************************)
(* API *)
let create tpc s cs ctrace lifespan =
let scm = scm_of_ctrace ctrace in
{ tpc = tpc
; s = s
; cm = cs |>: Misc.pad_fst C.id_of_t |> IM.of_list
; n = 1 + Misc.list_max 0 (IM.domain scm)
; ctrace = IM.map Misc.sort_and_compact ctrace
; lifespan = lifespan
; fsm = fsm_of_lifespan lifespan
; scm = scm
}
(* API *)
let explain me c =
let cid0 = C.id_of_t c in
let f0 = Conc cid0 in
Cause (Sy.of_string "ERROR", f0, cid0, explain me f0)