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.

158 lines
5.4 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 L = Logging
(** value domain, with the following concretization function [gamma]:
{[
gamma(VDom.top) = { any value }
gamma(VDom.v Closure) = { a closure }
gamma(VDom.bot) = emptyset
]} *)
module ExpClosure = struct
type t = Exp.closure
let pp fmt closure = Exp.pp fmt (Exp.Closure closure)
let equal closure1 closure2 = Exp.equal (Exp.Closure closure1) (Exp.Closure closure2)
end
module VDom = AbstractDomain.Flat (ExpClosure)
module CFG = ProcCfg.Normal
module Domain = AbstractDomain.SafeInvertedMap (Var) (VDom)
let get_var (astate : Domain.t) (v : Var.t) =
match Domain.find_opt v astate with Some c -> c | None -> VDom.top
let rec eval_expr (astate : Domain.t) (expr : Exp.t) =
match expr with
| Var id ->
get_var astate (Var.of_id id)
| Closure c when Exp.is_objc_block_closure expr ->
VDom.v c
| Closure _ (* TODO: implement for C++ lambdas *) ->
VDom.top
| Cast (_, e) ->
eval_expr astate e
| Lvar v ->
get_var astate (Var.of_pvar v)
| UnOp _ | BinOp _ | Exn _ | Const _ | Lfield _ | Lindex _ | Sizeof _ ->
VDom.top
let eval_instr (astate : Domain.t) (instr : Sil.instr) : Domain.t =
match instr with
| Load {id} when Ident.is_none id ->
astate
| Load {id; e} ->
let aval = eval_expr astate e in
Domain.add (Var.of_id id) aval astate
| Store {e1= Lvar pvar; e2} ->
let aval = eval_expr astate e2 in
Domain.add (Var.of_pvar pvar) aval astate
| Store _ | Prune _ | Metadata _ | Call _ ->
astate
module TransferFunctions = struct
module CFG = CFG
module Domain = Domain
type analysis_data = unit
let exec_instr astate _ _node _ instr = eval_instr astate instr
let pp_session_name node fmt =
Format.fprintf fmt "Closures Substitution %a" CFG.Node.pp_id (CFG.Node.id node)
end
module Analyzer = AbstractInterpreter.MakeRPO (TransferFunctions)
let get_invariant_at_node (map : Analyzer.invariant_map) node =
Analyzer.InvariantMap.find_opt (Procdesc.Node.get_id node) map
|> Option.value_map ~default:Domain.top ~f:(fun abstate -> abstate.AbstractInterpreter.State.pre)
let replace_closure_call node (astate : Domain.t) (instr : Sil.instr) : Sil.instr =
let kind = `ExecNode in
let pp_name fmt = Format.pp_print_string fmt "Closure Call Substitution" in
NodePrinter.with_session (CFG.Node.underlying_node node) ~kind ~pp_name ~f:(fun () ->
match instr with
| Call (ret_id_typ, Var id, actual_params, loc, call_flags) -> (
L.d_printfln "call %a " (Sil.pp_instr Pp.text ~print_types:true) instr ;
match eval_expr astate (Var id) |> VDom.get with
| None ->
L.d_printfln "(no closure found)" ;
instr
| Some c ->
L.d_printfln "found closure %a for variable %a\n" Exp.pp (Exp.Closure c) Ident.pp id ;
let captured_values =
List.map ~f:(fun (id_exp, _, typ, _) -> (id_exp, typ)) c.captured_vars
in
let actual_params = captured_values @ actual_params in
let new_instr =
Sil.Call (ret_id_typ, Const (Cfun c.name), actual_params, loc, call_flags)
in
L.d_printfln "replaced by call %a " (Sil.pp_instr Pp.text ~print_types:true) new_instr ;
new_instr )
| _ ->
instr )
(** [replace_closure_param] propagates closures to function parameters, so that more functions are
specialized by [CCallSpecializedWithClosures.process]. Note that unlike [replace_closure_call]
running at the analysis phase, [replace_closure_param] should run before
[CCallSpecializedWithClosures.process] at the capture phase. *)
let replace_closure_param node (astate : Domain.t) (instr : Sil.instr) : Sil.instr =
let kind = `ExecNode in
let pp_name fmt = Format.pp_print_string fmt "Closure Param Substitution" in
let replace () =
match instr with
| Sil.Call (ret, func, actual_params, loc, flags) ->
let modified = ref false in
let replace_param ((exp, typ) as param) =
match exp with
| Exp.Closure _ ->
param
| _ -> (
match eval_expr astate exp |> VDom.get with
| None ->
param
| Some c ->
L.d_printfln "found closure %a for parameter %a\n" Exp.pp_closure c Exp.pp exp ;
modified := true ;
(Exp.Closure c, typ) )
in
let actual_params' = List.map actual_params ~f:replace_param in
if !modified then Sil.Call (ret, func, actual_params', loc, flags) else instr
| _ ->
instr
in
NodePrinter.with_session (CFG.Node.underlying_node node) ~kind ~pp_name ~f:replace
let process_common replace_instr pdesc =
let node_cfg = CFG.from_pdesc pdesc in
let map = Analyzer.exec_cfg node_cfg ~initial:Domain.empty () in
let update_context = eval_instr in
let context_at_node node = get_invariant_at_node map node in
let _has_changed : bool =
Procdesc.replace_instrs_using_context pdesc ~f:replace_instr ~update_context ~context_at_node
in
()
let process_closure_call summary =
let pdesc = Summary.get_proc_desc summary in
process_common replace_closure_call pdesc
let process_closure_param pdesc = process_common replace_closure_param pdesc