(* * Copyright (c) 2009-2013, Monoidics ltd. * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) open! IStd (** Attribute manipulation in Propositions (i.e., Symbolic Heaps) *) (** Check whether an atom is used to mark an attribute *) let is_pred atom = match atom with Sil.Apred _ | Anpred _ -> true | _ -> false (** Add an attribute associated to the argument expressions *) let add tenv ?(footprint = false) ?(polarity = true) prop attr args = Prop.prop_atom_and tenv ~footprint prop (if polarity then Sil.Apred (attr, args) else Sil.Anpred (attr, args)) let attributes_in_same_category attr1 attr2 = let cat1 = PredSymb.to_category attr1 in let cat2 = PredSymb.to_category attr2 in PredSymb.equal_category cat1 cat2 (** Replace an attribute associated to the expression *) let add_or_replace_check_changed tenv check_attribute_change prop atom = match atom with | Sil.Apred (att0, (_ :: _ as exps0)) | Anpred (att0, (_ :: _ as exps0)) -> let pairs = List.map ~f:(fun e -> (e, Prop.exp_normalize_prop tenv prop e)) exps0 in let _, nexp = List.hd_exn pairs in (* len exps0 > 0 by match *) let atom_map = function | (Sil.Apred (att, exp :: _) | Anpred (att, exp :: _)) when Exp.equal nexp exp && attributes_in_same_category att att0 -> check_attribute_change att att0 ; atom | atom' -> atom' in let pi = prop.Prop.pi in let pi' = IList.map_changed ~equal:Sil.equal_atom ~f:atom_map pi in if phys_equal pi pi' then Prop.prop_atom_and tenv prop atom else Prop.normalize tenv (Prop.set prop ~pi:pi') | _ -> prop let add_or_replace tenv prop atom = (* wrapper for the most common case: do nothing *) let check_attr_changed _ _ = () in add_or_replace_check_changed tenv check_attr_changed prop atom (** Get all the attributes of the prop *) let get_all (prop : 'a Prop.t) = let res = ref [] in let do_atom a = if is_pred a then res := a :: !res in List.iter ~f:do_atom prop.pi ; List.rev !res (** Get the attribute associated to the expression, if any *) let get_for_exp tenv (prop : 'a Prop.t) exp = let nexp = Prop.exp_normalize_prop tenv prop exp in let atom_get_attr attributes atom = match atom with | (Sil.Apred (_, es) | Anpred (_, es)) when List.mem ~equal:Exp.equal es nexp -> atom :: attributes | _ -> attributes in List.fold ~f:atom_get_attr ~init:[] prop.pi let get tenv prop exp category = let atts = get_for_exp tenv prop exp in List.find ~f:(function | Sil.Apred (att, _) | Anpred (att, _) -> PredSymb.equal_category (PredSymb.to_category att) category | _ -> false ) atts let get_undef tenv prop exp = get tenv prop exp ACundef let get_resource tenv prop exp = get tenv prop exp ACresource let get_objc_null tenv prop exp = get tenv prop exp ACobjc_null let get_observer tenv prop exp = get tenv prop exp ACobserver let get_wontleak tenv prop exp = get tenv prop exp ACwontleak let has_dangling_uninit tenv prop exp = let la = get_for_exp tenv prop exp in List.exists ~f:(function Sil.Apred (a, _) -> PredSymb.equal a (Adangling DAuninit) | _ -> false) la let filter_atoms tenv ~f prop = let pi0 = prop.Prop.pi in let pi1 = IList.filter_changed ~f pi0 in if phys_equal pi1 pi0 then prop else Prop.normalize tenv (Prop.set prop ~pi:pi1) let remove tenv prop atom = if is_pred atom then let natom = Prop.atom_normalize_prop tenv prop atom in let f a = not (Sil.equal_atom natom a) in filter_atoms tenv ~f prop else prop (** Remove an attribute from all the atoms in the heap *) let remove_for_attr tenv prop att0 = let f = function | Sil.Apred (att, _) | Anpred (att, _) -> not (PredSymb.equal att0 att) | _ -> true in filter_atoms tenv ~f prop let remove_resource tenv ra_kind ra_res = let f = function | Sil.Apred (Aresource res_action, _) -> PredSymb.compare_res_act_kind res_action.ra_kind ra_kind <> 0 || PredSymb.compare_resource res_action.ra_res ra_res <> 0 | _ -> true in filter_atoms tenv ~f (** Apply f to every resource attribute in the prop *) let map_resource tenv prop f = let attribute_map e = function | PredSymb.Aresource ra -> PredSymb.Aresource (f e ra) | att -> att in let atom_map = function | Sil.Apred (att, ([e] as es)) -> Sil.Apred (attribute_map e att, es) | Sil.Anpred (att, ([e] as es)) -> Sil.Anpred (attribute_map e att, es) | atom -> atom in let pi0 = prop.Prop.pi in let pi1 = IList.map_changed ~equal:Sil.equal_atom ~f:atom_map pi0 in if phys_equal pi1 pi0 then prop else Prop.normalize tenv (Prop.set prop ~pi:pi1) (* Replace an attribute OBJC_NULL($n1) with OBJC_NULL(var) when var = $n1, and also sets $n1 = 0 *) let replace_objc_null tenv prop lhs_exp rhs_exp = match (get_objc_null tenv prop rhs_exp, rhs_exp) with | Some atom, Exp.Var _ -> let prop = remove tenv prop atom in let prop = Prop.conjoin_eq tenv rhs_exp Exp.zero prop in let natom = Sil.atom_replace_exp [(rhs_exp, lhs_exp)] atom in add_or_replace tenv prop natom | _ -> prop let rec nullify_exp_with_objc_null tenv prop exp = match exp with | Exp.BinOp (_, exp1, exp2) -> let prop' = nullify_exp_with_objc_null tenv prop exp1 in nullify_exp_with_objc_null tenv prop' exp2 | Exp.UnOp (_, exp, _) -> nullify_exp_with_objc_null tenv prop exp | Exp.Var _ -> ( match get_objc_null tenv prop exp with | Some atom -> let prop' = remove tenv prop atom in Prop.conjoin_eq tenv exp Exp.zero prop' | _ -> prop ) | _ -> prop (** mark Exp.Var's or Exp.Lvar's as undefined The annotations of the return type of the method get propagated to the return id, with the exception of when the return type is a struct, and we translate it as passing a reference to the method. *) let mark_vars_as_undefined tenv prop ~ret_exp ~undefined_actuals_by_ref callee_pname ret_annots loc path_pos = let mark_var_as_undefined ~annot exp prop = match exp with | Exp.Var _ | Lvar _ -> let att_undef = PredSymb.Aundef (callee_pname, annot, loc, path_pos) in add_or_replace tenv prop (Apred (att_undef, [exp])) | _ -> prop in let prop_with_ret_attr = mark_var_as_undefined ~annot:ret_annots ret_exp prop in List.fold ~f:(fun prop id -> mark_var_as_undefined ~annot:[] id prop) ~init:prop_with_ret_attr undefined_actuals_by_ref (** type for arithmetic problems *) type arith_problem = (* division by zero *) | Div0 of Exp.t (* unary minus of unsigned type applied to the given expression *) | UminusUnsigned of Exp.t * Typ.t (** Look for an arithmetic problem in [exp] *) let find_arithmetic_problem tenv proc_node_session prop exp = let exps_divided = ref [] in let uminus_unsigned = ref [] in let res = ref prop in let check_zero e = match Prop.exp_normalize_prop tenv prop e with | Exp.Const c when Const.iszero_int_float c -> true | _ -> res := add_or_replace tenv !res (Apred (Adiv0 proc_node_session, [e])) ; false in let rec walk = function | Exp.Var _ -> () | Exp.UnOp ( Unop.Neg , e , Some ( {Typ.desc= Tint (Typ.IUChar | Typ.IUInt | Typ.IUShort | Typ.IULong | Typ.IULongLong)} as typ ) ) -> uminus_unsigned := (e, typ) :: !uminus_unsigned | Exp.UnOp (_, e, _) -> walk e | Exp.BinOp (op, e1, e2) -> if Binop.equal op Binop.Div || Binop.equal op Binop.Mod then exps_divided := e2 :: !exps_divided ; walk e1 ; walk e2 | Exp.Exn _ -> () | Exp.Closure _ -> () | Exp.Const _ -> () | Exp.Cast (_, e) -> walk e | Exp.Lvar _ -> () | Exp.Lfield (e, _, _) -> walk e | Exp.Lindex (e1, e2) -> walk e1 ; walk e2 | Exp.Sizeof {dynamic_length= None} -> () | Exp.Sizeof {dynamic_length= Some len} -> walk len in walk exp ; let problem_opt = match (List.find ~f:check_zero !exps_divided, !uminus_unsigned) with | Some e, _ -> Some (Div0 e) | None, (e, t) :: _ -> Some (UminusUnsigned (e, t)) | None, [] -> None in (problem_opt, !res) (** Deallocate the stack variables in [pvars], and replace them by normal variables. Return the list of stack variables whose address was still present after deallocation. *) let deallocate_stack_vars tenv (p : 'a Prop.t) pvars = let filter = function | Sil.Hpointsto (Exp.Lvar v, _, _) -> List.exists ~f:(Pvar.equal v) pvars | _ -> false in let sigma_stack, sigma_other = List.partition_tf ~f:filter p.sigma in let fresh_address_vars = ref [] in (* fresh vars substituted for the address of stack vars *) let stack_vars_address_in_post = ref [] in (* stack vars whose address is still present *) let exp_replace = List.map ~f:(function | Sil.Hpointsto (Exp.Lvar v, _, _) -> let freshv = Ident.create_fresh Ident.kprimed in fresh_address_vars := (v, freshv) :: !fresh_address_vars ; (Exp.Lvar v, Exp.Var freshv) | _ -> assert false ) sigma_stack in let pi1 = List.map ~f:(fun (id, e) -> Sil.Aeq (Exp.Var id, e)) (Sil.sub_to_list p.sub) in let pi = List.map ~f:(Sil.atom_replace_exp exp_replace) (p.pi @ pi1) in let p' = Prop.normalize tenv (Prop.set p ~sub:Sil.sub_empty ~sigma:(Prop.sigma_replace_exp tenv exp_replace sigma_other)) in let p'' = let res = ref p' in let p'_fav = Prop.free_vars p' |> Ident.set_of_sequence in let do_var (v, freshv) = (* static locals are not stack-allocated *) if not (Pvar.is_static_local v) then (* the address of a de-allocated stack var in in the post *) if Ident.Set.mem freshv p'_fav then ( stack_vars_address_in_post := v :: !stack_vars_address_in_post ; let pred = Sil.Apred (Adangling DAaddr_stack_var, [Exp.Var freshv]) in res := add_or_replace tenv !res pred ) in List.iter ~f:do_var !fresh_address_vars ; !res in (* Filter out local addresses in p'' *) let filtered_pi, changed = List.fold_right p''.pi ~init:([], false) ~f:(fun a (filtered, changed) -> if Sil.atom_has_local_addr a then (filtered, true) else (a :: filtered, changed) ) in (* Avoid normalization when p'' does not change *) let p''' = if changed then Prop.normalize tenv (Prop.set p'' ~pi:filtered_pi) else p'' in (!stack_vars_address_in_post, List.fold ~f:(Prop.prop_atom_and tenv) ~init:p''' pi) (** Input of this method is an exp in a prop. Output is a formal variable or path from a formal variable that is equal to the expression, or the OBJC_NULL attribute of the expression. *) let find_equal_formal_path tenv e prop = let rec find_in_sigma e seen_hpreds = List.fold_right ~f:(fun hpred res -> if List.mem ~equal:Sil.equal_hpred seen_hpreds hpred then None else let seen_hpreds = hpred :: seen_hpreds in match res with | Some _ -> res | None -> ( match hpred with | Sil.Hpointsto (Exp.Lvar pvar1, Sil.Eexp (exp2, Sil.Iformal (_, _)), _) when Exp.equal exp2 e && (Pvar.is_local pvar1 || Pvar.is_seed pvar1) -> Some (Exp.Lvar pvar1) | Sil.Hpointsto (exp1, Sil.Estruct (fields, _), _) -> List.fold_right ~f:(fun (field, strexp) res -> match res with | Some _ -> res | None -> ( match strexp with | Sil.Eexp (exp2, _) when Exp.equal exp2 e -> ( match find_in_sigma exp1 seen_hpreds with | Some vfs -> Some (Exp.Lfield (vfs, field, Typ.mk Tvoid)) | None -> None ) | _ -> None ) ) fields ~init:None | _ -> None ) ) prop.Prop.sigma ~init:None in match find_in_sigma e [] with | Some vfs -> Some vfs | None -> ( match get_objc_null tenv prop e with | Some (Apred (Aobjc_null, [_; vfs])) -> Some vfs | _ -> None )