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.

430 lines
13 KiB

(*
* Copyright (c) 2009-2013, Monoidics ltd.
* 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.
*)
(** The Smallfoot Intermediate Language: Expressions *)
open! IStd
module Hashtbl = Caml.Hashtbl
module F = Format
module L = Logging
(* reverse the natural order on Var *)
type ident_ = Ident.t
let compare_ident_ x y = Ident.compare y x
type closure = {name: Typ.Procname.t; captured_vars: (t * Pvar.t * Typ.t) list}
(** This records information about a [sizeof(typ)] expression.
[nbytes] represents the result of the evaluation of [sizeof(typ)] if it is statically known.
If [typ] is of the form [Tarray elt (Some static_length)], then [dynamic_length] is the number
of elements of type [elt] in the array. The [dynamic_length], tracked by symbolic execution, may
differ from the [static_length] obtained from the type definition, e.g. when an array is
over-allocated.
If [typ] is a struct type, the [dynamic_length] is that of the final extensible array, if any.*)
and sizeof_data = {typ: Typ.t; nbytes: int option; dynamic_length: t option; subtype: Subtype.t}
(** Program expressions. *)
and t =
| Var of ident_ (** Pure variable: it is not an lvalue *)
| UnOp of Unop.t * t * Typ.t option (** Unary operator with type of the result if known *)
| BinOp of Binop.t * t * t (** Binary operator *)
| Exn of t (** Exception *)
| Closure of closure (** Anonymous function *)
| Const of Const.t (** Constants *)
| Cast of Typ.t * t (** Type cast *)
| Lvar of Pvar.t (** The address of a program variable *)
| Lfield of t * Fieldname.t * Typ.t
(** A field offset, the type is the surrounding struct type *)
| Lindex of t * t (** An array index offset: [exp1\[exp2\]] *)
| Sizeof of sizeof_data
[@@deriving compare]
let equal = [%compare.equal: t]
let hash = Hashtbl.hash
module Set = Caml.Set.Make (struct
type nonrec t = t
let compare = compare
end)
module Map = Caml.Map.Make (struct
type nonrec t = t
let compare = compare
end)
module Hash = Hashtbl.Make (struct
type nonrec t = t
let equal = equal
let hash = hash
end)
let is_null_literal = function Const (Cint n) -> IntLit.isnull n | _ -> false
let is_this = function Lvar pvar -> Pvar.is_this pvar | _ -> false
let is_zero = function Const (Cint n) -> IntLit.iszero n | _ -> false
let rec is_const = function Const _ -> true | Cast (_, x) -> is_const x | _ -> false
(** {2 Utility Functions for Expressions} *)
(** Turn an expression representing a type into the type it represents If not a sizeof, return the
default type if given, otherwise raise an exception *)
let texp_to_typ default_opt = function
| Sizeof {typ} ->
typ
| _ ->
Typ.unsome "texp_to_typ" default_opt
(** Return the root of [lexp]. *)
let rec root_of_lexp lexp =
match (lexp : t) with
| Var _ ->
lexp
| Const _ ->
lexp
| Cast (_, e) ->
root_of_lexp e
| UnOp _ | BinOp _ | Exn _ | Closure _ ->
lexp
| Lvar _ ->
lexp
| Lfield (e, _, _) ->
root_of_lexp e
| Lindex (e, _) ->
root_of_lexp e
| Sizeof _ ->
lexp
(** Checks whether an expression denotes a location by pointer arithmetic. Currently, catches
array-indexing expressions such as a[i] only. *)
let rec pointer_arith = function
| Lfield (e, _, _) ->
pointer_arith e
| Lindex _ ->
true
| _ ->
false
let get_undefined footprint =
Var (Ident.create_fresh (if footprint then Ident.kfootprint else Ident.kprimed))
(** returns true if the express operates on address of local variable *)
let rec has_local_addr e =
match (e : t) with
| Lvar pv ->
Pvar.is_local pv
| UnOp (_, e', _) | Cast (_, e') | Lfield (e', _, _) ->
has_local_addr e'
| BinOp (_, e0, e1) | Lindex (e0, e1) ->
has_local_addr e0 || has_local_addr e1
| _ ->
false
(** Create integer constant *)
let int i = Const (Cint i)
(** Create float constant *)
let float v = Const (Cfloat v)
(** Integer constant 0 *)
let zero = int IntLit.zero
(** Null constant *)
let null = int IntLit.null
(** Integer constant 1 *)
let one = int IntLit.one
(** Integer constant -1 *)
let minus_one = int IntLit.minus_one
(** Create integer constant corresponding to the boolean value *)
let bool b = if b then one else zero
let and_2ary e1 e2 = BinOp (LAnd, e1, e2)
(** Create expression [e1 == e2] *)
let eq e1 e2 = BinOp (Eq, e1, e2)
(** Create expression [e1 != e2] *)
let ne e1 e2 = BinOp (Ne, e1, e2)
(** Create expression [e1 <= e2] *)
let le e1 e2 = BinOp (Le, e1, e2)
(** Create expression [e1 < e2] *)
let lt e1 e2 = BinOp (Lt, e1, e2)
let nary_of_2ary op_2ary zero es =
match es with [] -> zero | e :: es -> List.fold ~init:e ~f:op_2ary es
let and_nary = nary_of_2ary and_2ary one
let fold_captured ~f exp acc =
let rec fold_captured_ exp captured_acc =
match exp with
| Cast (_, e) | UnOp (_, e, _) | Lfield (e, _, _) | Exn e | Sizeof {dynamic_length= Some e} ->
fold_captured_ e captured_acc
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
fold_captured_ e1 captured_acc |> fold_captured_ e2
| Closure {captured_vars} ->
List.fold captured_vars
~f:(fun acc (captured_exp, _, _) -> f acc captured_exp)
~init:captured_acc
| Const _ | Lvar _ | Var _ | Sizeof _ ->
captured_acc
in
fold_captured_ exp acc
(** Pretty print an expression. *)
let rec pp_ pe pp_t f e =
let pp_exp = pp_ pe pp_t in
let print_binop_stm_output e1 op e2 =
match (op : Binop.t) with
| Eq | Ne | PlusA _ | Mult _ ->
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe op) pp_exp e1
| Lt ->
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Gt) pp_exp e1
| Gt ->
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Lt) pp_exp e1
| Le ->
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Ge) pp_exp e1
| Ge ->
F.fprintf f "(%a %s %a)" pp_exp e2 (Binop.str pe Le) pp_exp e1
| _ ->
F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2
in
match (e : t) with
| Var id ->
Ident.pp f id
| Const c ->
(Const.pp pe) f c
| Cast (typ, e) ->
F.fprintf f "(%a)%a" pp_t typ pp_exp e
| UnOp (op, e, _) ->
F.fprintf f "%s%a" (Unop.to_string op) pp_exp e
| BinOp (op, Const c, e2) when Config.smt_output ->
print_binop_stm_output (Const c) op e2
| BinOp (op, e1, e2) ->
F.fprintf f "(%a %s %a)" pp_exp e1 (Binop.str pe op) pp_exp e2
| Exn e ->
F.fprintf f "EXN %a" pp_exp e
| Closure {name; captured_vars} ->
if List.is_empty captured_vars then F.fprintf f "(%a)" pp_exp (Const (Cfun name))
else
F.fprintf f "(%a,%a)" pp_exp (Const (Cfun name))
(Pp.comma_seq (pp_captured_var pe pp_t))
captured_vars
| Lvar pv ->
Pvar.pp pe f pv
| Lfield (e, fld, _) ->
F.fprintf f "%a.%a" pp_exp e Fieldname.pp fld
| Lindex (e1, e2) ->
F.fprintf f "%a[%a]" pp_exp e1 pp_exp e2
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" pp_exp) l in
let pp_size f size = Option.iter ~f:(Int.pp f) size in
let pp_if b pp label f v = if b then F.fprintf f ";%s=%a" label pp v in
let pp_if_some pp_opt label f opt = pp_if (Option.is_some opt) pp_opt label f opt in
let subt_s = F.asprintf "%a" Subtype.pp subtype in
F.fprintf f "sizeof(t=%a%a%a%a)" pp_t typ (pp_if_some pp_size "nbytes") nbytes
(pp_if_some pp_len "len") dynamic_length
(pp_if (not (String.equal "" subt_s)) Subtype.pp "sub_t")
subtype
and pp_captured_var pe pp_t f (exp, var, typ) =
match exp with
| Lvar evar when Pvar.equal var evar ->
(Pvar.pp pe) f var
| _ ->
F.fprintf f "(%a %a:%a)" (pp_ pe pp_t) exp (Pvar.pp pe) var (Typ.pp pe) typ
let pp_printenv ~print_types pe f e =
let pp_typ = if print_types then Typ.pp_full else Typ.pp in
pp_ pe (pp_typ pe) f e
let pp f e = pp_printenv ~print_types:false Pp.text f e
let to_string e = F.asprintf "%a" pp e
let color_wrapper ~f = if Config.print_using_diff then Pp.color_wrapper ~f else f
let pp_diff ?(print_types = false) =
color_wrapper ~f:(fun pe f e0 ->
let e =
match pe.Pp.obj_sub with
| Some sub ->
(* apply object substitution to expression *) Obj.obj (sub (Obj.repr e0))
| None ->
e0
in
if not (equal e0 e) then match e with Lvar pvar -> Pvar.pp_value f pvar | _ -> assert false
else pp_printenv ~print_types pe f e )
(** dump an expression. *)
let d_exp (e : t) = L.d_pp_with_pe pp_diff e
(** Pretty print a list of expressions. *)
let pp_list pe f expl = Pp.seq (pp_diff pe) f expl
(** dump a list of expressions. *)
let d_list (el : t list) = L.d_pp_with_pe pp_list el
let pp_texp pe f = function
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_diff pe)) l in
let pp_size f size = Option.iter ~f:(Int.pp f) size in
F.fprintf f "%a%a%a%a" (Typ.pp pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp subtype
| e ->
pp_diff pe f e
(** Pretty print a type with all the details. *)
let pp_texp_full pe f = function
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
let pp_len f l = Option.iter ~f:(F.fprintf f "[%a]" (pp_diff pe)) l in
let pp_size f size = Option.iter ~f:(Int.pp f) size in
F.fprintf f "%a%a%a%a" (Typ.pp_full pe) typ pp_size nbytes pp_len dynamic_length Subtype.pp
subtype
| e ->
pp_diff ~print_types:true pe f e
(** Dump a type expression with all the details. *)
let d_texp_full (te : t) = L.d_pp_with_pe pp_texp_full te
let is_objc_block_closure = function
| Closure {name} ->
Typ.Procname.is_objc_block name
| _ ->
false
let rec gen_free_vars =
let open Sequence.Generator in
function
| Var id ->
yield id
| Cast (_, e) | Exn e | Lfield (e, _, _) | Sizeof {dynamic_length= Some e} | UnOp (_, e, _) ->
gen_free_vars e
| Closure {captured_vars} ->
ISequence.gen_sequence_list captured_vars ~f:(fun (e, _, _) -> gen_free_vars e)
| Const (Cint _ | Cfun _ | Cstr _ | Cfloat _ | Cclass _) | Lvar _ | Sizeof {dynamic_length= None}
->
return ()
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
gen_free_vars e1 >>= fun () -> gen_free_vars e2
let free_vars e = Sequence.Generator.run (gen_free_vars e)
let ident_mem e id = free_vars e |> Sequence.exists ~f:(Ident.equal id)
let rec gen_program_vars =
let open Sequence.Generator in
function
| Lvar name ->
yield name
| Const _ | Var _ | Sizeof {dynamic_length= None} ->
return ()
| Cast (_, e) | Exn e | Lfield (e, _, _) | Sizeof {dynamic_length= Some e} | UnOp (_, e, _) ->
gen_program_vars e
| BinOp (_, e1, e2) | Lindex (e1, e2) ->
gen_program_vars e1 >>= fun () -> gen_program_vars e2
| Closure {captured_vars} ->
ISequence.gen_sequence_list captured_vars ~f:(fun (e, _, _) -> gen_program_vars e)
let program_vars e = Sequence.Generator.run (gen_program_vars e)
let rec rename_pvars ~(f : string -> string) : t -> t =
let re e = rename_pvars ~f e in
let rv v = Pvar.rename ~f v in
function
| UnOp (op, e, t) ->
UnOp (op, re e, t)
| BinOp (op, e1, e2) ->
BinOp (op, re e1, re e2)
| Exn e ->
Exn (re e)
| Closure {name; captured_vars} ->
let captured_vars = List.map ~f:(function e, v, t -> (re e, rv v, t)) captured_vars in
Closure {name; captured_vars}
| Cast (t, e) ->
Cast (t, re e)
| Lvar v ->
Lvar (rv v)
| Lfield (e, fld, t) ->
Lfield (re e, fld, t)
| Lindex (e1, e2) ->
Lindex (re e1, re e2)
| Sizeof {typ; nbytes; dynamic_length; subtype} ->
let dynamic_length = Option.map ~f:re dynamic_length in
Sizeof {typ; nbytes; dynamic_length; subtype}
| e (* Should have only cases without subexpressions. *) ->
e
let zero_of_type typ =
match typ.Typ.desc with
| Typ.Tint _ ->
Some (Const (Cint IntLit.zero))
| Typ.Tfloat _ ->
Some (Const (Cfloat 0.0))
| Typ.Tptr _ ->
Some (Const (Cint IntLit.null))
| _ ->
None
let zero_of_type_exn typ = Option.value_exn (zero_of_type typ)
let rec ignore_cast e = match e with Cast (_, e) -> ignore_cast e | _ -> e
let rec ignore_integer_cast e =
match e with Cast (t, e) when Typ.is_int t -> ignore_integer_cast e | _ -> e
let rec get_java_class_initializer tenv = function
| Lfield (Lvar pvar, fn, typ) when Pvar.is_global pvar -> (
match Struct.get_field_type_and_annotation ~lookup:(Tenv.lookup tenv) fn typ with
| Some (field_typ, annot) when Annot.Item.is_final annot ->
let java_class = Typ.JavaClass (Pvar.get_name pvar) in
Some
( Typ.Procname.Java (Typ.Procname.Java.get_class_initializer java_class)
, pvar
, fn
, field_typ )
| _ ->
None )
| Cast (_, e) | Lfield (e, _, _) ->
get_java_class_initializer tenv e
| Lvar _ | Var _ | UnOp _ | BinOp _ | Exn _ | Closure _ | Const _ | Lindex _ | Sizeof _ ->
None