You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
13 KiB
388 lines
13 KiB
(*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*)
|
|
|
|
open! IStd
|
|
module F = Format
|
|
module L = Logging
|
|
module AbstractValue = PulseAbstractValue
|
|
module CItv = PulseCItv
|
|
module Formula = PulseFormula
|
|
module ValueHistory = PulseValueHistory
|
|
|
|
module BoItvs = struct
|
|
include PrettyPrintable.MakePPMonoMap (AbstractValue) (Itv.ItvPure)
|
|
|
|
let find_or_default (v : AbstractValue.t) bo_itvs =
|
|
match find_opt v bo_itvs with
|
|
| Some bo_itv ->
|
|
bo_itv
|
|
| None ->
|
|
Itv.ItvPure.of_foreign_id (v :> int)
|
|
end
|
|
|
|
module CItvs = PrettyPrintable.MakePPMonoMap (AbstractValue) (CItv)
|
|
|
|
(** A mash-up of several arithmetic domains. At the moment they are independent, i.e. we don't use
|
|
facts deduced by one domain to inform another. *)
|
|
type t =
|
|
{ is_unsat: bool (** if [true] then the other components of the record can be arbitrary *)
|
|
; bo_itvs: BoItvs.t
|
|
; citvs: CItvs.t
|
|
; formula: Formula.t }
|
|
|
|
let pp fmt {is_unsat; bo_itvs; citvs; formula} =
|
|
F.fprintf fmt "@[<hv>unsat:%b,@;bo: @[%a@],@;citv: @[%a@],@;formula: @[%a@]@]" is_unsat BoItvs.pp
|
|
bo_itvs CItvs.pp citvs Formula.pp formula
|
|
|
|
|
|
let true_ = {is_unsat= false; bo_itvs= BoItvs.empty; citvs= CItvs.empty; formula= Formula.ttrue}
|
|
|
|
let false_ = {is_unsat= true; bo_itvs= BoItvs.empty; citvs= CItvs.empty; formula= Formula.ttrue}
|
|
|
|
let map_sat phi f = if phi.is_unsat then phi else f phi
|
|
|
|
let ( let+ ) phi f = map_sat phi f
|
|
|
|
let map_formula_sat (x : 'a Formula.normalized) f = match x with Unsat -> false_ | Sat x' -> f x'
|
|
|
|
let ( let+| ) x f = map_formula_sat x f
|
|
|
|
let and_nonnegative v phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula =
|
|
Formula.and_less_equal (LiteralOperand IntLit.zero) (AbstractValueOperand v) formula
|
|
in
|
|
{ is_unsat
|
|
; bo_itvs= BoItvs.add v Itv.ItvPure.nat bo_itvs
|
|
; citvs= CItvs.add v CItv.zero_inf citvs
|
|
; formula }
|
|
|
|
|
|
let and_positive v phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula =
|
|
Formula.and_less_than (LiteralOperand IntLit.zero) (AbstractValueOperand v) formula
|
|
in
|
|
{ is_unsat
|
|
; bo_itvs= BoItvs.add v Itv.ItvPure.pos bo_itvs
|
|
; citvs= CItvs.add v (CItv.ge_to IntLit.one) citvs
|
|
; formula }
|
|
|
|
|
|
let and_eq_int v i phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula = Formula.and_equal (AbstractValueOperand v) (LiteralOperand i) formula in
|
|
{ is_unsat
|
|
; bo_itvs= BoItvs.add v (Itv.ItvPure.of_int_lit i) bo_itvs
|
|
; citvs= CItvs.add v (CItv.equal_to i) citvs
|
|
; formula }
|
|
|
|
|
|
let simplify ~keep phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula = Formula.simplify ~keep formula in
|
|
let is_in_keep v _ = AbstractValue.Set.mem v keep in
|
|
{ is_unsat
|
|
; bo_itvs= BoItvs.filter is_in_keep bo_itvs
|
|
; citvs= CItvs.filter is_in_keep citvs
|
|
; formula }
|
|
|
|
|
|
let subst_find_or_new subst addr_callee =
|
|
match AbstractValue.Map.find_opt addr_callee subst with
|
|
| None ->
|
|
let addr_hist_fresh = (AbstractValue.mk_fresh (), []) in
|
|
(AbstractValue.Map.add addr_callee addr_hist_fresh subst, fst addr_hist_fresh)
|
|
| Some addr_hist_caller ->
|
|
(subst, fst addr_hist_caller)
|
|
|
|
|
|
let eval_sym_of_subst bo_itvs subst s bound_end =
|
|
let v = Symb.Symbol.get_foreign_id_exn s |> AbstractValue.of_id in
|
|
match AbstractValue.Map.find_opt v !subst with
|
|
| Some (v', _) ->
|
|
Itv.ItvPure.get_bound (BoItvs.find_or_default v' bo_itvs) bound_end
|
|
| None ->
|
|
let v' = AbstractValue.mk_fresh () in
|
|
subst := AbstractValue.Map.add v (v', []) !subst ;
|
|
Bounds.Bound.of_foreign_id (v' :> int)
|
|
|
|
|
|
exception Contradiction
|
|
|
|
(* TODO: this doesn't actually do "and" (it doesn't even take the caller interval into account) *)
|
|
let and_bo_itv_callee bo_itvs subst_ref itv_callee =
|
|
match
|
|
Itv.ItvPure.subst itv_callee (fun symb bound ->
|
|
AbstractDomain.Types.NonBottom (eval_sym_of_subst bo_itvs subst_ref symb bound) )
|
|
with
|
|
| NonBottom itv' ->
|
|
itv'
|
|
| Bottom ->
|
|
raise Contradiction
|
|
|
|
|
|
let and_bo_itvs_callee subst bo_itvs_caller bo_itvs_callee =
|
|
(* first translate callee keys into caller values *)
|
|
let subst, bo_itvs_callee_renamed =
|
|
BoItvs.fold
|
|
(fun v_callee bo_itv (subst, bo_itvs) ->
|
|
let subst, v_caller = subst_find_or_new subst v_callee in
|
|
(* TODO: it could be that the same value already had a binding; in that case we want to
|
|
"and" the intervals *)
|
|
let bo_itvs = BoItvs.add v_caller bo_itv bo_itvs in
|
|
(subst, bo_itvs) )
|
|
bo_itvs_callee (subst, BoItvs.empty)
|
|
in
|
|
let subst_ref = ref subst in
|
|
let bo_itvs' =
|
|
BoItvs.merge
|
|
(fun _v_caller bo_itv bo_itv_callee ->
|
|
match (bo_itv, bo_itv_callee) with
|
|
| None, None ->
|
|
None
|
|
| Some _, None ->
|
|
bo_itv
|
|
| _, Some bo_itv_callee ->
|
|
Some (and_bo_itv_callee bo_itvs_caller subst_ref bo_itv_callee) )
|
|
bo_itvs_caller bo_itvs_callee_renamed
|
|
in
|
|
(!subst_ref, bo_itvs')
|
|
|
|
|
|
let and_citv_callee citv_caller citv_callee =
|
|
match CItv.abduce_binop_is_true ~negated:false Eq (Some citv_caller) (Some citv_callee) with
|
|
| Unsatisfiable ->
|
|
raise Contradiction
|
|
| Satisfiable (Some abduce_caller, _abduce_callee) ->
|
|
abduce_caller
|
|
| Satisfiable (None, _) ->
|
|
citv_caller
|
|
|
|
|
|
let and_citvs_callee subst citvs_caller citvs_callee =
|
|
let subst, citvs_callee_renamed =
|
|
CItvs.fold
|
|
(fun v_callee citv (subst, citvs) ->
|
|
let subst, v_caller = subst_find_or_new subst v_callee in
|
|
(* TODO: it could be that the same value already had a binding if several variables from the
|
|
callee map to the same caller variable; in that case we want to "and" the intervals *)
|
|
let citvs = CItvs.add v_caller citv citvs in
|
|
(subst, citvs) )
|
|
citvs_callee (subst, CItvs.empty)
|
|
in
|
|
let citvs' =
|
|
CItvs.union
|
|
(fun _v citv citv_callee -> Some (and_citv_callee citv citv_callee))
|
|
citvs_caller citvs_callee_renamed
|
|
in
|
|
(subst, citvs')
|
|
|
|
|
|
let and_formula_callee subst formula_caller ~callee:formula_callee =
|
|
(* need to translate callee variables to make sense for the caller, thereby possibly extending
|
|
the current substitution *)
|
|
Formula.and_fold_subst_variables formula_caller ~up_to_f:formula_callee ~f:subst_find_or_new
|
|
~init:subst
|
|
|
|
|
|
let and_callee subst phi ~callee:phi_callee =
|
|
if phi.is_unsat || phi_callee.is_unsat then (subst, false_)
|
|
else
|
|
match and_bo_itvs_callee subst phi.bo_itvs phi_callee.bo_itvs with
|
|
| exception Contradiction ->
|
|
L.d_printfln "contradiction found by inferbo intervals" ;
|
|
(subst, false_)
|
|
| subst, bo_itvs' -> (
|
|
match and_citvs_callee subst phi.citvs phi_callee.citvs with
|
|
| exception Contradiction ->
|
|
L.d_printfln "contradiction found by concrete intervals" ;
|
|
(subst, false_)
|
|
| subst, citvs' -> (
|
|
match and_formula_callee subst phi.formula ~callee:phi_callee.formula with
|
|
| Unsat ->
|
|
L.d_printfln "contradiction found by formulas" ;
|
|
(subst, false_)
|
|
| Sat (subst, formula') ->
|
|
(* TODO: normalize here? *)
|
|
L.d_printfln "conjoined formula post call: %a@\n" Formula.pp formula' ;
|
|
(subst, {is_unsat= false; bo_itvs= bo_itvs'; citvs= citvs'; formula= formula'}) ) )
|
|
|
|
|
|
(** {2 Operations} *)
|
|
|
|
type operand = Formula.operand =
|
|
| LiteralOperand of IntLit.t
|
|
| AbstractValueOperand of AbstractValue.t
|
|
|
|
let eval_citv_binop binop_addr bop op_lhs op_rhs citvs =
|
|
let citv_of_op op citvs =
|
|
match op with
|
|
| LiteralOperand i ->
|
|
Some (CItv.equal_to i)
|
|
| AbstractValueOperand v ->
|
|
CItvs.find_opt v citvs
|
|
in
|
|
match
|
|
Option.both (citv_of_op op_lhs citvs) (citv_of_op op_rhs citvs)
|
|
|> Option.bind ~f:(fun (addr_lhs, addr_rhs) -> CItv.binop bop addr_lhs addr_rhs)
|
|
with
|
|
| None ->
|
|
citvs
|
|
| Some binop_a ->
|
|
CItvs.add binop_addr binop_a citvs
|
|
|
|
|
|
let eval_bo_itv_binop binop_addr bop op_lhs op_rhs bo_itvs =
|
|
let bo_itv_of_op op bo_itvs =
|
|
match op with
|
|
| LiteralOperand i ->
|
|
Itv.ItvPure.of_int_lit i
|
|
| AbstractValueOperand v ->
|
|
BoItvs.find_or_default v bo_itvs
|
|
in
|
|
let bo_itv =
|
|
Itv.ItvPure.arith_binop bop (bo_itv_of_op op_lhs bo_itvs) (bo_itv_of_op op_rhs bo_itvs)
|
|
in
|
|
BoItvs.add binop_addr bo_itv bo_itvs
|
|
|
|
|
|
let eval_binop binop_addr binop op_lhs op_rhs phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula = Formula.and_equal_binop binop_addr binop op_lhs op_rhs formula in
|
|
{ is_unsat
|
|
; bo_itvs= eval_bo_itv_binop binop_addr binop op_lhs op_rhs bo_itvs
|
|
; citvs= eval_citv_binop binop_addr binop op_lhs op_rhs citvs
|
|
; formula }
|
|
|
|
|
|
let eval_citv_unop unop_addr unop operand_addr citvs =
|
|
match CItvs.find_opt operand_addr citvs |> Option.bind ~f:(fun a -> CItv.unop unop a) with
|
|
| None ->
|
|
citvs
|
|
| Some unop_a ->
|
|
CItvs.add unop_addr unop_a citvs
|
|
|
|
|
|
let eval_bo_itv_unop unop_addr unop operand_addr bo_itvs =
|
|
let op_itv = BoItvs.find_or_default operand_addr bo_itvs in
|
|
match Itv.ItvPure.arith_unop unop op_itv with
|
|
| None ->
|
|
bo_itvs
|
|
| Some itv ->
|
|
BoItvs.add unop_addr itv bo_itvs
|
|
|
|
|
|
let eval_unop unop_addr unop addr phi =
|
|
let+ {is_unsat; bo_itvs; citvs; formula} = phi in
|
|
let+| formula = Formula.and_equal_unop unop_addr unop (AbstractValueOperand addr) formula in
|
|
{ is_unsat
|
|
; bo_itvs= eval_bo_itv_unop unop_addr unop addr bo_itvs
|
|
; citvs= eval_citv_unop unop_addr unop addr citvs
|
|
; formula }
|
|
|
|
|
|
let prune_bo_with_bop ~negated v_opt arith bop arith' phi =
|
|
match
|
|
Option.both v_opt (if negated then Binop.negate bop else Some bop)
|
|
|> Option.map ~f:(fun (v, positive_bop) ->
|
|
(v, Itv.ItvPure.prune_binop positive_bop arith arith') )
|
|
with
|
|
| None ->
|
|
phi
|
|
| Some (_, Bottom) ->
|
|
{phi with is_unsat= true}
|
|
| Some (v, NonBottom arith_pruned) ->
|
|
{phi with bo_itvs= BoItvs.add v arith_pruned phi.bo_itvs}
|
|
|
|
|
|
let eval_operand phi = function
|
|
| LiteralOperand i ->
|
|
(None, Some (CItv.equal_to i), Itv.ItvPure.of_int_lit i)
|
|
| AbstractValueOperand v ->
|
|
(Some v, CItvs.find_opt v phi.citvs, BoItvs.find_or_default v phi.bo_itvs)
|
|
|
|
|
|
let record_citv_abduced addr_opt arith_opt citvs =
|
|
match Option.both addr_opt arith_opt with
|
|
| None ->
|
|
citvs
|
|
| Some (addr, arith) ->
|
|
CItvs.add addr arith citvs
|
|
|
|
|
|
let prune_binop ~negated bop lhs_op rhs_op ({is_unsat; bo_itvs= _; citvs; formula} as phi) =
|
|
if is_unsat then phi
|
|
else
|
|
let value_lhs_opt, arith_lhs_opt, bo_itv_lhs = eval_operand phi lhs_op in
|
|
let value_rhs_opt, arith_rhs_opt, bo_itv_rhs = eval_operand phi rhs_op in
|
|
match CItv.abduce_binop_is_true ~negated bop arith_lhs_opt arith_rhs_opt with
|
|
| Unsatisfiable ->
|
|
L.d_printfln "contradiction detected by concrete intervals" ;
|
|
false_
|
|
| Satisfiable (abduced_lhs, abduced_rhs) -> (
|
|
let phi =
|
|
let citvs =
|
|
record_citv_abduced value_lhs_opt abduced_lhs citvs
|
|
|> record_citv_abduced value_rhs_opt abduced_rhs
|
|
in
|
|
{phi with citvs}
|
|
in
|
|
let is_unsat =
|
|
match Itv.ItvPure.arith_binop bop bo_itv_lhs bo_itv_rhs |> Itv.ItvPure.to_boolean with
|
|
| False ->
|
|
not negated
|
|
| True ->
|
|
negated
|
|
| Top ->
|
|
false
|
|
| Bottom ->
|
|
true
|
|
in
|
|
if is_unsat then L.d_printfln "contradiction detected by inferbo intervals" ;
|
|
let+ phi = {phi with is_unsat} in
|
|
let+ phi = prune_bo_with_bop ~negated value_lhs_opt bo_itv_lhs bop bo_itv_rhs phi in
|
|
let+ phi =
|
|
Option.value_map (Binop.symmetric bop) ~default:phi ~f:(fun bop' ->
|
|
prune_bo_with_bop ~negated value_rhs_opt bo_itv_rhs bop' bo_itv_lhs phi )
|
|
in
|
|
match Formula.prune_binop ~negated bop lhs_op rhs_op formula with
|
|
| Unsat ->
|
|
L.d_printfln "contradiction detected by formulas" ;
|
|
false_
|
|
| Sat formula ->
|
|
{phi with is_unsat; formula} )
|
|
|
|
|
|
(** {2 Queries} *)
|
|
|
|
let is_known_zero phi v =
|
|
CItvs.find_opt v phi.citvs |> Option.value_map ~default:false ~f:CItv.is_equal_to_zero
|
|
|| BoItvs.find_opt v phi.bo_itvs |> Option.value_map ~default:false ~f:Itv.ItvPure.is_zero
|
|
|| Formula.is_known_zero phi.formula v
|
|
|
|
|
|
let is_unsat_cheap phi = phi.is_unsat
|
|
|
|
let is_unsat_expensive phi =
|
|
(* note: contradictions are detected eagerly for all sub-domains except formula, so just
|
|
evaluate that one *)
|
|
if is_unsat_cheap phi then (phi, true)
|
|
else
|
|
match Formula.normalize phi.formula with
|
|
| Unsat ->
|
|
(false_, true)
|
|
| Sat formula ->
|
|
({phi with formula}, false)
|
|
|
|
|
|
let as_int phi v =
|
|
(* TODO(rgrigore): Ask BoItvs too. *)
|
|
IList.force_until_first_some
|
|
[ lazy (CItvs.find_opt v phi.citvs |> Option.value_map ~default:None ~f:CItv.as_int)
|
|
; lazy (Formula.as_int phi.formula v) ]
|