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.

356 lines
12 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.
*)
(** Interval abstract domain *)
open Apron
let equal_apron_typ =
(* Apron.Texpr1.typ is a sum of nullary constructors *)
Poly.equal
(** Apron-managed map from variables to intervals *)
type t = Box.t Abstract1.t
let man = lazy (Box.manager_alloc ())
let join l r = Some (Abstract1.join (Lazy.force man) l r)
let equal l r = Abstract1.is_eq (Lazy.force man) l r
let is_false x = Abstract1.is_bottom (Lazy.force man) x
let bindings (itv : t) =
let itv = Abstract1.minimize_environment (Lazy.force man) itv in
let box = Abstract1.to_box (Lazy.force man) itv in
let vars =
Environment.vars box.box1_env |> fun (i, r) -> Array.append i r
in
Array.zip_exn vars box.interval_array
let sexp_of_t (itv : t) =
let sexps =
Array.fold (bindings itv) ~init:[] ~f:(fun acc (v, {inf; sup}) ->
Sexp.List
[ Sexp.Atom (Var.to_string v)
; Sexp.Atom (Scalar.to_string inf)
; Sexp.Atom (Scalar.to_string sup) ]
:: acc )
in
Sexp.List sexps
let pp fs =
let pp_pair a_pp b_pp fs (a, b) =
Format.fprintf fs "@[(%a@,%a)@]" a_pp a b_pp b
in
bindings >> Array.pp "@," (pp_pair Var.print Interval.print) fs
let report_fmt_thunk = Fn.flip pp
let init _gs = Abstract1.top (Lazy.force man) (Environment.make [||] [||])
let apron_var_of_name = (fun nm -> "%" ^ nm) >> Apron.Var.of_string
let apron_var_of_reg = Llair.Reg.name >> apron_var_of_name
let rec apron_typ_of_llair_typ : Llair.Typ.t -> Texpr1.typ option = function
| Pointer {elt= _} -> apron_typ_of_llair_typ Llair.Typ.siz
| Integer {bits= _} -> Some Texpr1.Int
| Float {bits= 32; enc= `IEEE} -> Some Texpr1.Single
| Float {bits= 64; enc= `IEEE} -> Some Texpr1.Double
| Float {bits= 80; enc= `Extended} -> Some Texpr1.Extended
| Float {bits= 128; enc= `IEEE} -> Some Texpr1.Quad
| t ->
warn "No corresponding apron type for llair type %a " Llair.Typ.pp t
() ;
None
let apron_of_q = Q.to_float >> fun fp -> Texpr1.Cst (Coeff.s_of_float fp)
let rec pow base typ = function
| 1 -> base
| z ->
Texpr1.Binop (Texpr1.Mul, base, pow base typ (z - 1), typ, Texpr0.Rnd)
(* An n-ary term with [subtms] {(q0, e0), ..., (qn, en)} is interpreted as:
* e*q (when [op] is [Texpr1.Add])
* e^q (when [op] is [Texpr1.Mul])
* (See sledge/src/llair/term.ml functions assert_(mono|poly)mial for details)
*)
let rec texpr_of_nary_term subtms typ q op =
assert (Term.Qset.length subtms >= 2) ;
let term_to_texpr (tm, coeff) =
let* base = apron_texpr_of_llair_term tm q typ in
match op with
| Texpr1.Add ->
Some
(Texpr1.Binop (Texpr1.Mul, base, apron_of_q coeff, typ, Texpr0.Rnd))
| Texpr1.Mul
(* only handle positive integer exponents *)
when Z.equal Z.one (Q.den coeff) && Q.sign coeff = 1 ->
Some (pow base typ (Q.to_int coeff))
| _ -> None
in
match Term.Qset.to_list subtms with
| hd :: tl ->
List.fold tl ~init:(term_to_texpr hd) ~f:(fun acc curr ->
let* c = term_to_texpr curr in
let+ a = acc in
Texpr1.Binop (op, c, a, typ, Texpr0.Rnd) )
| _ -> assert false
and apron_texpr_of_llair_term tm q typ =
match (tm : Term.t) with
| Add terms -> texpr_of_nary_term terms typ q Texpr1.Add
| Mul terms -> texpr_of_nary_term terms typ q Texpr1.Mul
| Var {name} -> Some (Texpr1.Var (apron_var_of_name name))
| Integer {data} -> Some (Texpr1.Cst (Coeff.s_of_int (Z.to_int data)))
| Float {data} ->
let f =
try Float.of_string data
with Invalid_argument _ -> failwith "malformed float: %s"
in
Some (Texpr1.Cst (Coeff.s_of_float f))
[sledge] Simplify type conversions Summary: The treatment of type conversions is too complicated, non-uniform, etc. This diff attempts to simplify things by separating integer to integer conversions, which are interpreted, from others, which are essentially just uninterpreted functions. Integer conversions are now handled using two expression and term forms: Signed and Unsigned. These each interpret their argument as either a signed or unsigned number of a given bitwidth: ``` | Signed of {bits: int} (** [Ap1 (Signed {bits= n}, dst, arg)] is [arg] interpreted as an [n]-bit signed integer and injected into the [dst] type. That is, it two's-complement--decodes the low [n] bits of the infinite two's-complement encoding of [arg]. The injection into [dst] is a no-op, so [dst] must be an integer type with bitwidth at least [n]. *) | Unsigned of {bits: int} (** [Ap1 (Unsigned {bits= n}, dst, arg)] is [arg] interpreted as an [n]-bit unsigned integer and injected into the [dst] type. That is, it unsigned-binary--decodes the low [n] bits of the infinite two's-complement encoding of [arg]. The injection into [dst] is a no-op, so [dst] must be an integer type with bitwidth greater than [n]. *) | Convert of {src: Typ.t} (** [Ap1 (Convert {src}, dst, arg)] is [arg] converted from type [src] to type [dst], possibly with loss of information. The [src] and [dst] types must be [Typ.convertible] and must not both be [Integer] types. *) ``` Reviewed By: ngorogiannis Differential Revision: D18298140 fbshipit-source-id: 690f065b4
5 years ago
| Ap1 (Convert {dst; src}, t) -> (
match (apron_typ_of_llair_typ dst, apron_typ_of_llair_typ src) with
| None, _ | _, None -> None
| Some dst, Some src ->
let subtm = apron_texpr_of_llair_term t q src in
if equal_apron_typ src dst then subtm
else
let+ t = subtm in
Texpr1.Unop (Texpr1.Cast, t, dst, Texpr0.Rnd) )
(* extraction to unsigned 1-bit int is llvm encoding of C boolean;
restrict to [0,1] *)
[sledge] Simplify type conversions Summary: The treatment of type conversions is too complicated, non-uniform, etc. This diff attempts to simplify things by separating integer to integer conversions, which are interpreted, from others, which are essentially just uninterpreted functions. Integer conversions are now handled using two expression and term forms: Signed and Unsigned. These each interpret their argument as either a signed or unsigned number of a given bitwidth: ``` | Signed of {bits: int} (** [Ap1 (Signed {bits= n}, dst, arg)] is [arg] interpreted as an [n]-bit signed integer and injected into the [dst] type. That is, it two's-complement--decodes the low [n] bits of the infinite two's-complement encoding of [arg]. The injection into [dst] is a no-op, so [dst] must be an integer type with bitwidth at least [n]. *) | Unsigned of {bits: int} (** [Ap1 (Unsigned {bits= n}, dst, arg)] is [arg] interpreted as an [n]-bit unsigned integer and injected into the [dst] type. That is, it unsigned-binary--decodes the low [n] bits of the infinite two's-complement encoding of [arg]. The injection into [dst] is a no-op, so [dst] must be an integer type with bitwidth greater than [n]. *) | Convert of {src: Typ.t} (** [Ap1 (Convert {src}, dst, arg)] is [arg] converted from type [src] to type [dst], possibly with loss of information. The [src] and [dst] types must be [Typ.convertible] and must not both be [Integer] types. *) ``` Reviewed By: ngorogiannis Differential Revision: D18298140 fbshipit-source-id: 690f065b4
5 years ago
| Ap1 (Unsigned {bits= 1}, _t) -> Some (Texpr1.Cst (Coeff.i_of_int 0 1))
(* "t xor true" and "true xor t" are negation *)
| Ap2 (Xor, t, Integer {data}) when Z.is_true data ->
let+ t = apron_texpr_of_llair_term t q typ in
Texpr1.Unop (Texpr1.Neg, t, typ, Texpr0.Rnd)
| Ap2 (Xor, Integer {data}, t) when Z.is_true data ->
let+ t = apron_texpr_of_llair_term t q typ in
Texpr1.Unop (Texpr1.Neg, t, typ, Texpr0.Rnd)
(* query apron for abstract evaluation of binary operations*)
| Ap2 (op, t1, t2) ->
let* f =
match op with
| Rem -> Some (mk_arith_binop typ Texpr0.Mod)
| Div -> Some (mk_arith_binop typ Texpr0.Div)
| Eq -> Some (mk_bool_binop typ q Tcons0.EQ)
| Dq -> Some (mk_bool_binop typ q Tcons0.DISEQ)
| Lt -> Some (Fn.flip (mk_bool_binop typ q Tcons0.SUP))
| Le -> Some (Fn.flip (mk_bool_binop typ q Tcons0.SUPEQ))
| _ -> None
in
let* te1 = apron_texpr_of_llair_term t1 q typ in
let+ te2 = apron_texpr_of_llair_term t2 q typ in
f te1 te2
| x ->
[%Trace.info
"No corresponding apron term for llair term: %a" Term.pp x] ;
None
and mk_arith_binop typ op te1 te2 =
Texpr1.Binop (op, te1, te2, typ, Texpr0.Rnd)
(** abstract evaluation of boolean binary operation [te1 op te2] at [q] by
translation to [te1 - te2 op 0] and intersection with [q]*)
and mk_bool_binop typ q op te1 te2 =
let env = Abstract1.env q in
let lhs = Texpr1.Binop (Texpr1.Sub, te1, te2, typ, Texpr0.Rnd) in
let tcons = Tcons1.make (Texpr1.of_expr env lhs) op in
let ea =
Tcons1.array_make env 1 $> fun ea -> Tcons1.array_set ea 0 tcons
in
(* if meet of q with tree constraint encoding of binop is: (bottom ->
binop definitely false); (unchanged from q -> binop definitely true);
(else -> binop may be true or false) *)
let meet = Abstract1.meet_tcons_array (Lazy.force man) q ea in
if is_false meet then Texpr1.Cst (Coeff.s_of_int 0)
else if equal meet q then Texpr1.Cst (Coeff.s_of_int (-1))
else Texpr1.Cst (Coeff.i_of_int (-1) 0)
let assign reg exp q =
[%Trace.call fun {pf} ->
pf "{%a}@\n%a := %a" pp q Llair.Reg.pp reg Llair.Exp.pp exp]
;
let lval = apron_var_of_reg reg in
( match
Option.bind
~f:(apron_texpr_of_llair_term (Term.of_exp exp) q)
(apron_typ_of_llair_typ (Llair.Reg.typ reg))
with
| Some e ->
let env = Abstract1.env q in
let new_env =
match
( Environment.mem_var env lval
, apron_typ_of_llair_typ (Llair.Reg.typ reg) )
with
| true, _ -> env
| false, Some Texpr1.Int -> Environment.add env [|lval|] [||]
| false, _ -> Environment.add env [||] [|lval|]
in
let man = Lazy.force man in
let q = Abstract1.change_environment man q new_env true in
Abstract1.assign_texpr man q lval (Texpr1.of_expr new_env e) None
| _ -> q )
|>
[%Trace.retn fun {pf} r -> pf "{%a}" pp r]
(** block if [e] is known to be false; skip otherwise *)
let exec_assume q e =
match apron_texpr_of_llair_term (Term.of_exp e) q Texpr1.Int with
| Some e ->
let cond =
Abstract1.bound_texpr (Lazy.force man) q (Texpr1.of_expr q.env e)
in
if Interval.is_zero cond then None else Some q
| _ -> Some q
(** existentially quantify killed register [r] out of state [q] *)
let exec_kill q r =
let apron_v = apron_var_of_reg r in
if Environment.mem_var (Abstract1.env q) apron_v then
Abstract1.forget_array (Lazy.force man) q [|apron_v|] false
else q
(** perform a series [move_vec] of reg:=exp moves at state [q] *)
let exec_move q move_vec =
let defs, uses =
IArray.fold move_vec ~init:(Llair.Reg.Set.empty, Llair.Reg.Set.empty)
~f:(fun (defs, uses) (r, e) ->
( Llair.Reg.Set.add defs r
, Llair.Exp.fold_regs e ~init:uses ~f:Llair.Reg.Set.add ) )
in
assert (Llair.Reg.Set.disjoint defs uses) ;
IArray.fold move_vec ~init:q ~f:(fun a (r, e) -> assign r e a)
let exec_inst q i =
match (i : Llair.inst) with
| Move {reg_exps; loc= _} -> Some (exec_move q reg_exps)
| Store {ptr; exp; len= _; loc= _} -> (
match Llair.Reg.of_exp ptr with
| Some reg -> Some (assign reg exp q)
| None -> Some q )
| Load {reg; ptr; len= _; loc= _} -> Some (assign reg ptr q)
| Nondet {reg= Some reg; msg= _; loc= _} -> Some (exec_kill q reg)
| Nondet {reg= None; msg= _; loc= _}
|Alloc _ | Memset _ | Memcpy _ | Memmov _ | Free _ ->
Some q
| Abort _ -> None
(** Treat any intrinsic function as havoc on the return register [aret] *)
let exec_intrinsic ~skip_throw:_ pre aret i _ =
let name = Llair.Reg.name i in
if
List.exists
[ "malloc"
; "aligned_alloc"
; "calloc"
; "posix_memalign"
; "realloc"
; "mallocx"
; "rallocx"
; "xallocx"
; "sallocx"
; "dallocx"
; "sdallocx"
; "nallocx"
; "malloc_usable_size"
; "mallctl"
; "mallctlnametomib"
; "mallctlbymib"
; "malloc_stats_print"
; "strlen"
; "__cxa_allocate_exception"
; "_ZN5folly13usingJEMallocEv" ]
~f:(String.equal name)
then Option.map ~f:(Option.some << exec_kill pre) aret
else None
type from_call = {areturn: Llair.Reg.t option; caller_q: t}
[@@deriving sexp_of]
let recursion_beyond_bound = `prune
(** existentially quantify locals *)
let post locals _ (q : t) =
let locals =
Llair.Reg.Set.fold locals ~init:[] ~f:(fun a r ->
let v = apron_var_of_reg r in
if Environment.mem_var q.env v then v :: a else a )
|> Array.of_list
in
Abstract1.forget_array (Lazy.force man) q locals false
(** drop caller-local variables, add returned value to caller state *)
let retn _ freturn {areturn; caller_q} callee_q =
match (areturn, freturn) with
| Some aret, Some fret ->
let env_fret_only =
match apron_typ_of_llair_typ (Llair.Reg.typ fret) with
| None -> Environment.make [||] [||]
| Some Texpr1.Int -> Environment.make [|apron_var_of_reg fret|] [||]
| _ -> Environment.make [||] [|apron_var_of_reg fret|]
in
let env = Environment.lce env_fret_only (Abstract1.env caller_q) in
let man = Lazy.force man in
let callee_fret =
(* drop all callee vars, scope to (caller + freturn) env *)
Abstract1.change_environment man callee_q env_fret_only false
|> fun q -> Abstract1.change_environment man q env false
in
let caller_q = Abstract1.change_environment man caller_q env false in
let result = Abstract1.meet man callee_fret caller_q in
Abstract1.rename_array man result
[|apron_var_of_reg fret|]
[|apron_var_of_reg aret|]
| Some aret, None -> exec_kill caller_q aret
| None, _ -> caller_q
(** map actuals to formals (via temporary registers), stash constraints on
caller-local variables. Note that this exploits the non-relational-ness
of Box to ignore all variables other than the formal/actual params/
returns; this will not be possible if extended to a relational domain *)
let call ~summaries ~globals:_ ~actuals ~areturn ~formals ~freturn:_
~locals:_ q =
if summaries then
todo "Summaries not yet implemented for interval analysis" ()
else
let mangle r =
Llair.Reg.program (Llair.Reg.typ r) ("__tmp__" ^ Llair.Reg.name r)
in
let args = List.zip_exn formals actuals in
let q' =
List.fold args ~init:q ~f:(fun q (f, a) -> assign (mangle f) a q)
in
let callee_env =
List.fold formals ~init:([], []) ~f:(fun (is, fs) f ->
match apron_typ_of_llair_typ (Llair.Reg.typ f) with
| None -> (is, fs)
| Some Texpr1.Int -> (apron_var_of_reg (mangle f) :: is, fs)
| _ -> (is, apron_var_of_reg (mangle f) :: fs) )
|> fun (is, fs) ->
Environment.make (Array.of_list is) (Array.of_list fs)
in
let man = Lazy.force man in
let q'' = Abstract1.change_environment man q' callee_env false in
let q''' =
Abstract1.rename_array man q''
(Array.of_list_map ~f:(mangle >> apron_var_of_reg) formals)
(Array.of_list_map ~f:apron_var_of_reg formals)
in
(q''', {areturn; caller_q= q})
let dnf q = [q]
let resolve_callee lookup ptr q =
match Llair.Reg.of_exp ptr with
| Some callee -> (lookup (Llair.Reg.name callee), q)
| None -> ([], q)
type summary = t
let pp_summary = pp
let apply_summary _ _ = None
let create_summary ~locals:_ ~formals:_ q = (q, q)