[litho] Remove functor that is instantiated once

Reviewed By: skcho

Differential Revision: D18961821

fbshipit-source-id: b90901751
master
Ezgi Çiçek 5 years ago committed by Facebook Github Bot
parent 9ed9363207
commit 1e1d40b460

@ -1,213 +0,0 @@
(*
* 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 get_component_create_typ_opt procname tenv =
match procname with
| Typ.Procname.Java java_pname when is_component_create_method procname tenv ->
Some (Typ.Procname.Java.get_class_type_name java_pname)
| _ ->
None
module type LithoContext = sig
type t
type summary
val field : (Payloads.t, summary option) Field.t
val check_callee : callee_pname:Typ.Procname.t -> tenv:Tenv.t -> summary option -> bool
val satisfies_heuristic :
callee_pname:Typ.Procname.t -> callee_summary_opt:summary option -> Tenv.t -> bool
val should_report : Procdesc.t -> Tenv.t -> bool
val report : summary -> Tenv.t -> Summary.t -> summary
val session_name : string
end
type get_proc_summary_and_formals =
Typ.Procname.t -> (Domain.summary * (Pvar.t * Typ.t) list) option
type extras = {get_proc_summary_and_formals: get_proc_summary_and_formals}
module TransferFunctions
(CFG : ProcCfg.S)
(LithoContext : LithoContext with type summary = Domain.summary) =
struct
module CFG = CFG
module Domain = Domain
module Payload = SummaryPayload.Make (struct
type t = LithoContext.summary
let field = LithoContext.field
end)
type nonrec extras = extras
let apply_callee_summary summary_opt ~caller_pname ~callee_pname ret_id_typ formals actuals astate
=
Option.value_map summary_opt ~default:astate ~f:(fun callee_summary ->
Domain.subst ~formals ~actuals ~ret_id_typ ~caller_pname ~callee_pname ~caller:astate
~callee:callee_summary )
let exec_instr astate ProcData.{summary; tenv; extras= {get_proc_summary_and_formals}} _
(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_and_formals_opt = get_proc_summary_and_formals callee_pname in
let callee_summary_opt = Option.map callee_summary_and_formals_opt ~f:fst in
let receiver =
Domain.LocalAccessPath.make_from_access_expression receiver_ae caller_pname
in
if
LithoContext.check_callee ~callee_pname ~tenv callee_summary_opt
(* track callee in order to report respective errors *)
&& LithoContext.satisfies_heuristic ~callee_pname ~callee_summary_opt tenv
then
let return_access_path = Domain.LocalAccessPath.make (return_base, []) caller_pname in
match get_component_create_typ_opt callee_pname tenv with
| Some create_typ ->
Domain.call_create return_access_path create_typ location astate
| None ->
if is_component_build_method callee_pname tenv then
Domain.call_build_method ~ret:return_access_path ~receiver astate
else if is_component_builder callee_pname tenv then
let callee_prefix = Domain.MethodCallPrefix.make callee_pname location in
Domain.call_builder ~ret:return_access_path ~receiver callee_prefix astate
else astate
else
(* treat it like a normal call *)
Option.value_map callee_summary_and_formals_opt ~default:astate ~f:(fun (_, formals) ->
apply_callee_summary callee_summary_opt ~caller_pname ~callee_pname return_base
formals actuals astate )
| Call (ret_id_typ, Direct callee_pname, actuals, _, _) ->
let callee_summary_and_formals_opt = get_proc_summary_and_formals callee_pname in
let callee_summary_opt = Option.map callee_summary_and_formals_opt ~f:fst in
Option.value_map callee_summary_and_formals_opt ~default:astate ~f:(fun (_, formals) ->
apply_callee_summary callee_summary_opt ~caller_pname ~callee_pname ret_id_typ formals
actuals astate )
| Assign (lhs_ae, rhs, _) ->
let astate =
match rhs with
| 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
Domain.assign ~lhs:lhs_access_path ~rhs:rhs_access_path astate
| _ ->
astate
in
if HilExp.AccessExpression.is_return_var lhs_ae then Domain.call_return astate else astate
| _ ->
astate
let pp_session_name _node fmt = F.pp_print_string fmt LithoContext.session_name
end
module MakeAnalyzer (LithoContext : LithoContext with type summary = Domain.summary) = struct
module TF = TransferFunctions (ProcCfg.Normal) (LithoContext)
module A = LowerHil.MakeAbstractInterpreter (TF)
let init_extras summary =
let get_proc_summary_and_formals callee_pname =
Ondemand.analyze_proc_name ~caller_summary:summary callee_pname
|> Option.bind ~f:(fun summary ->
TF.Payload.of_summary summary
|> Option.map ~f:(fun payload ->
(payload, Summary.get_proc_desc summary |> Procdesc.get_pvar_formals) ) )
in
{get_proc_summary_and_formals}
let checker {Callbacks.summary; exe_env} =
let proc_desc = Summary.get_proc_desc summary in
let proc_name = Summary.get_proc_name summary in
let tenv = Exe_env.get_tenv exe_env (Summary.get_proc_name summary) in
let proc_data = ProcData.make summary tenv (init_extras summary) in
let initial = Domain.init tenv proc_name (Procdesc.get_pvar_formals proc_desc) in
match A.compute_post proc_data ~initial with
| Some post ->
let is_void_func = Procdesc.get_ret_type proc_desc |> Typ.is_void in
let post = Domain.get_summary ~is_void_func post in
let post =
if LithoContext.should_report proc_desc tenv then LithoContext.report post tenv summary
else post
in
TF.Payload.update_summary post summary
| None ->
summary
end

@ -9,6 +9,12 @@ open! IStd
module F = Format
module Domain = LithoDomain
module Payload = SummaryPayload.Make (struct
type t = Domain.summary
let field = Payloads.Fields.litho_required_props
end)
(* VarProp is only for props that have a varArg parameter like
@Prop(varArg = "var_prop") whereas Prop is for everything except. *)
type required_prop = Prop of string | VarProp of {prop: string; var_prop: string}
@ -98,56 +104,204 @@ let has_prop prop_set prop =
check var_prop || check prop
module LithoContext = struct
type t = Domain.t
(** return true if this function is part of the Litho framework code rather than client code *)
let is_litho_function = function
| Typ.Procname.Java java_procname -> (
match Typ.Procname.Java.get_package java_procname with
| Some "com.facebook.litho" ->
true
| _ ->
false )
| _ ->
false
type summary = Domain.summary
let check_callee ~callee_pname ~tenv _ =
LithoFramework.is_component_builder callee_pname tenv
|| LithoFramework.is_component_create_method callee_pname tenv
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 satisfies_heuristic ~callee_pname ~callee_summary_opt tenv =
(* If the method is build() or create() itself or doesn't contain a build() in
its summary, we want to track it in the domain. *)
let build_exists_in_callees =
Option.value_map ~default:false callee_summary_opt ~f:(fun sum ->
LithoDomain.Mem.contains_build sum )
in
LithoFramework.is_component_build_method callee_pname tenv
|| LithoFramework.is_component_create_method callee_pname tenv
||
match callee_pname with
| Typ.Procname.Java java_callee_procname ->
not (Typ.Procname.Java.is_static java_callee_procname || build_exists_in_callees)
| _ ->
not build_exists_in_callees
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 field = Payloads.Fields.litho_required_props
let is_component_build_method procname tenv =
match Typ.Procname.get_method procname with
| "build" ->
is_component_builder procname tenv
| _ ->
false
let should_report proc_desc tenv =
let pname = Procdesc.get_proc_name proc_desc in
(not (LithoFramework.is_function pname))
&& (not (LithoFramework.is_component_build_method pname tenv))
&& Procdesc.get_access proc_desc <> PredSymb.Private
let is_component_create_method procname tenv =
match Typ.Procname.get_method procname with "create" -> is_component procname tenv | _ -> false
let report astate tenv summary =
let check_on_string_set parent_typename call_chain prop_set =
let required_props = get_required_props parent_typename tenv in
List.iter required_props ~f:(fun required_prop ->
if not (has_prop prop_set required_prop) then
report_missing_required_prop summary required_prop parent_typename
(Summary.get_loc summary) call_chain )
in
Domain.check_required_props ~check_on_string_set astate
let get_component_create_typ_opt procname tenv =
match procname with
| Typ.Procname.Java java_pname when is_component_create_method procname tenv ->
Some (Typ.Procname.Java.get_class_type_name java_pname)
| _ ->
None
let session_name = "litho required props"
let satisfies_heuristic ~callee_pname ~callee_summary_opt tenv =
(* If the method is build() or create() itself or doesn't contain a build() in
its summary, we want to track it in the domain. *)
let build_exists_in_callees =
Option.value_map ~default:false callee_summary_opt ~f:(fun sum ->
LithoDomain.Mem.contains_build sum )
in
is_component_build_method callee_pname tenv
|| is_component_create_method callee_pname tenv
||
match callee_pname with
| Typ.Procname.Java java_callee_procname ->
not (Typ.Procname.Java.is_static java_callee_procname || build_exists_in_callees)
| _ ->
not build_exists_in_callees
let should_report proc_desc tenv =
let pname = Procdesc.get_proc_name proc_desc in
(not (is_litho_function pname))
&& (not (is_component_build_method pname tenv))
&& Procdesc.get_access proc_desc <> PredSymb.Private
let report astate tenv summary =
let check_on_string_set parent_typename call_chain prop_set =
let required_props = get_required_props parent_typename tenv in
List.iter required_props ~f:(fun required_prop ->
if not (has_prop prop_set required_prop) then
report_missing_required_prop summary required_prop parent_typename
(Summary.get_loc summary) call_chain )
in
Domain.check_required_props ~check_on_string_set astate
type get_proc_summary_and_formals =
Typ.Procname.t -> (Domain.summary * (Pvar.t * Typ.t) list) option
module TransferFunctions = struct
module CFG = ProcCfg.Normal
module Domain = LithoDomain
type extras = {get_proc_summary_and_formals: get_proc_summary_and_formals}
let apply_callee_summary summary_opt ~caller_pname ~callee_pname ret_id_typ formals actuals astate
=
Option.value_map summary_opt ~default:astate ~f:(fun callee_summary ->
Domain.subst ~formals ~actuals ~ret_id_typ ~caller_pname ~callee_pname ~caller:astate
~callee:callee_summary )
let exec_instr astate ProcData.{summary; tenv; extras= {get_proc_summary_and_formals}} _
(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_and_formals_opt = get_proc_summary_and_formals callee_pname in
let callee_summary_opt = Option.map callee_summary_and_formals_opt ~f:fst in
let receiver =
Domain.LocalAccessPath.make_from_access_expression receiver_ae caller_pname
in
if
(is_component_builder callee_pname tenv || is_component_create_method callee_pname tenv)
(* track callee in order to report respective errors *)
&& satisfies_heuristic ~callee_pname ~callee_summary_opt tenv
then
let return_access_path = Domain.LocalAccessPath.make (return_base, []) caller_pname in
match get_component_create_typ_opt callee_pname tenv with
| Some create_typ ->
Domain.call_create return_access_path create_typ location astate
| None ->
if is_component_build_method callee_pname tenv then
Domain.call_build_method ~ret:return_access_path ~receiver astate
else if is_component_builder callee_pname tenv then
let callee_prefix = Domain.MethodCallPrefix.make callee_pname location in
Domain.call_builder ~ret:return_access_path ~receiver callee_prefix astate
else astate
else
(* treat it like a normal call *)
Option.value_map callee_summary_and_formals_opt ~default:astate ~f:(fun (_, formals) ->
apply_callee_summary callee_summary_opt ~caller_pname ~callee_pname return_base
formals actuals astate )
| Call (ret_id_typ, Direct callee_pname, actuals, _, _) ->
let callee_summary_and_formals_opt = get_proc_summary_and_formals callee_pname in
let callee_summary_opt = Option.map callee_summary_and_formals_opt ~f:fst in
Option.value_map callee_summary_and_formals_opt ~default:astate ~f:(fun (_, formals) ->
apply_callee_summary callee_summary_opt ~caller_pname ~callee_pname ret_id_typ formals
actuals astate )
| Assign (lhs_ae, rhs, _) ->
let astate =
match rhs with
| 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
Domain.assign ~lhs:lhs_access_path ~rhs:rhs_access_path astate
| _ ->
astate
in
if HilExp.AccessExpression.is_return_var lhs_ae then Domain.call_return astate else astate
| _ ->
astate
let pp_session_name _node fmt = F.pp_print_string fmt "litho required props"
end
module Analyzer = LithoFramework.MakeAnalyzer (LithoContext)
module Analyzer = LowerHil.MakeAbstractInterpreter (TransferFunctions)
let checker callback = Analyzer.checker callback
let init_extras summary =
let get_proc_summary_and_formals callee_pname =
Ondemand.analyze_proc_name ~caller_summary:summary callee_pname
|> Option.bind ~f:(fun summary ->
Payload.of_summary summary
|> Option.map ~f:(fun payload ->
(payload, Summary.get_proc_desc summary |> Procdesc.get_pvar_formals) ) )
in
TransferFunctions.{get_proc_summary_and_formals}
let checker {Callbacks.summary; exe_env} =
let proc_desc = Summary.get_proc_desc summary in
let proc_name = Summary.get_proc_name summary in
let tenv = Exe_env.get_tenv exe_env (Summary.get_proc_name summary) in
let proc_data = ProcData.make summary tenv (init_extras summary) in
let initial = Domain.init tenv proc_name (Procdesc.get_pvar_formals proc_desc) in
match Analyzer.compute_post proc_data ~initial with
| Some post ->
let is_void_func = Procdesc.get_ret_type proc_desc |> Typ.is_void in
let post = Domain.get_summary ~is_void_func post in
let post = if should_report proc_desc tenv then report post tenv summary else post in
Payload.update_summary post summary
| None ->
summary

Loading…
Cancel
Save