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.
190 lines
6.7 KiB
190 lines
6.7 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 Domain = LithoDomain
|
|
|
|
(** return true if this function is part of the Litho framework code rather than client code *)
|
|
let is_function = function
|
|
| Typ.Procname.Java java_procname -> (
|
|
match Typ.Procname.Java.get_package java_procname with
|
|
| Some "com.facebook.litho" ->
|
|
true
|
|
| _ ->
|
|
false )
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let is_component_builder procname tenv =
|
|
match procname with
|
|
| Typ.Procname.Java java_procname ->
|
|
PatternMatch.is_subtype_of_str tenv
|
|
(Typ.Procname.Java.get_class_type_name java_procname)
|
|
"com.facebook.litho.Component$Builder"
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let is_component procname tenv =
|
|
match procname with
|
|
| Typ.Procname.Java java_procname ->
|
|
PatternMatch.is_subtype_of_str tenv
|
|
(Typ.Procname.Java.get_class_type_name java_procname)
|
|
"com.facebook.litho.Component"
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let is_component_build_method procname tenv =
|
|
match Typ.Procname.get_method procname with
|
|
| "build" ->
|
|
is_component_builder procname tenv
|
|
| _ ->
|
|
false
|
|
|
|
|
|
let is_component_create_method procname tenv =
|
|
match Typ.Procname.get_method procname with "create" -> is_component procname tenv | _ -> false
|
|
|
|
|
|
let is_on_create_layout = function
|
|
| Typ.Procname.Java java_pname -> (
|
|
match Typ.Procname.Java.get_method java_pname with "onCreateLayout" -> true | _ -> false )
|
|
| _ ->
|
|
false
|
|
|
|
|
|
module type LithoContext = sig
|
|
type t
|
|
|
|
val field : (Payloads.t, t option) Field.t
|
|
|
|
val check_callee : callee_pname:Typ.Procname.t -> tenv:Tenv.t -> t option -> bool
|
|
|
|
val satisfies_heuristic :
|
|
callee_pname:Typ.Procname.t -> callee_summary_opt:t option -> Tenv.t -> bool
|
|
|
|
val should_report : Procdesc.t -> Tenv.t -> bool
|
|
|
|
val report : t -> Tenv.t -> Summary.t -> unit
|
|
|
|
val session_name : string
|
|
end
|
|
|
|
module TransferFunctions (CFG : ProcCfg.S) (LithoContext : LithoContext with type t = Domain.t) =
|
|
struct
|
|
module CFG = CFG
|
|
module Domain = Domain
|
|
module Payload = SummaryPayload.Make (LithoContext)
|
|
|
|
type extras = ProcData.no_extras
|
|
|
|
let apply_callee_summary summary_opt caller_pname ret_id_typ actuals astate =
|
|
match summary_opt with
|
|
| Some summary ->
|
|
(* TODO: append paths if the footprint access path is an actual path instead of a var *)
|
|
let f_sub {Domain.LocalAccessPath.access_path= (var, _), _} =
|
|
match Var.get_footprint_index var with
|
|
| Some footprint_index -> (
|
|
match List.nth actuals footprint_index with
|
|
| Some (HilExp.AccessExpression actual_access_expr) ->
|
|
Some
|
|
(Domain.LocalAccessPath.make
|
|
(HilExp.AccessExpression.to_access_path actual_access_expr)
|
|
caller_pname)
|
|
| _ ->
|
|
None )
|
|
| None ->
|
|
if Var.is_return var then
|
|
Some (Domain.LocalAccessPath.make (ret_id_typ, []) caller_pname)
|
|
else None
|
|
in
|
|
Domain.substitute ~f_sub summary |> Domain.join astate
|
|
| None ->
|
|
astate
|
|
|
|
|
|
let exec_instr astate ProcData.{summary; tenv} _ (instr : HilInstr.t) : Domain.t =
|
|
let caller_pname = Summary.get_proc_name summary in
|
|
match instr with
|
|
| Call
|
|
( return_base
|
|
, Direct callee_pname
|
|
, (HilExp.AccessExpression receiver_ae :: _ as actuals)
|
|
, _
|
|
, location ) ->
|
|
let callee_summary_opt = Payload.read ~caller_summary:summary ~callee_pname in
|
|
let receiver =
|
|
Domain.LocalAccessPath.make
|
|
(HilExp.AccessExpression.to_access_path receiver_ae)
|
|
caller_pname
|
|
in
|
|
if
|
|
( LithoContext.check_callee ~callee_pname ~tenv callee_summary_opt
|
|
|| (* track callee in order to report respective errors *)
|
|
Domain.mem receiver astate
|
|
(* track anything called on a receiver we're already tracking *) )
|
|
&& LithoContext.satisfies_heuristic ~callee_pname ~callee_summary_opt tenv
|
|
then
|
|
let return_access_path = Domain.LocalAccessPath.make (return_base, []) caller_pname in
|
|
let return_calls =
|
|
(try Domain.find return_access_path astate with Caml.Not_found -> Domain.CallSet.empty)
|
|
|> Domain.CallSet.add (Domain.MethodCall.make receiver callee_pname location)
|
|
in
|
|
Domain.add return_access_path return_calls astate
|
|
else
|
|
(* treat it like a normal call *)
|
|
apply_callee_summary callee_summary_opt caller_pname return_base actuals astate
|
|
| Call (ret_id_typ, Direct callee_procname, actuals, _, _) ->
|
|
let callee_summary_opt =
|
|
Payload.read ~caller_summary:summary ~callee_pname:callee_procname
|
|
in
|
|
apply_callee_summary callee_summary_opt caller_pname ret_id_typ actuals astate
|
|
| Assign (lhs_ae, HilExp.AccessExpression rhs_ae, _) -> (
|
|
(* creating an alias for the rhs binding; assume all reads will now occur through the
|
|
alias. this helps us keep track of chains in cases like tmp = getFoo(); x = tmp;
|
|
tmp.getBar() *)
|
|
let lhs_access_path =
|
|
Domain.LocalAccessPath.make (HilExp.AccessExpression.to_access_path lhs_ae) caller_pname
|
|
in
|
|
let rhs_access_path =
|
|
Domain.LocalAccessPath.make (HilExp.AccessExpression.to_access_path rhs_ae) caller_pname
|
|
in
|
|
try
|
|
let call_set = Domain.find rhs_access_path astate in
|
|
Domain.remove rhs_access_path astate |> Domain.add lhs_access_path call_set
|
|
with Caml.Not_found -> astate )
|
|
| _ ->
|
|
astate
|
|
|
|
|
|
let pp_session_name _node fmt = F.pp_print_string fmt LithoContext.session_name
|
|
end
|
|
|
|
module MakeAnalyzer (LithoContext : LithoContext with type t = Domain.t) = struct
|
|
module TF = TransferFunctions (ProcCfg.Exceptional) (LithoContext)
|
|
module A = LowerHil.MakeAbstractInterpreter (TF)
|
|
|
|
let checker {Callbacks.summary; exe_env} =
|
|
let proc_desc = Summary.get_proc_desc summary in
|
|
let tenv = Exe_env.get_tenv exe_env (Summary.get_proc_name summary) in
|
|
let proc_data = ProcData.make_default summary tenv in
|
|
match A.compute_post proc_data ~initial:Domain.empty with
|
|
| Some post ->
|
|
if LithoContext.should_report proc_desc tenv then LithoContext.report post tenv summary ;
|
|
let postprocess astate formal_map : Domain.t =
|
|
let f_sub access_path = Domain.LocalAccessPath.to_formal_option access_path formal_map in
|
|
Domain.substitute ~f_sub astate
|
|
in
|
|
let payload = postprocess post (FormalMap.make proc_desc) in
|
|
TF.Payload.update_summary payload summary
|
|
| None ->
|
|
summary
|
|
end
|