[litho] Add substitution at function calls

Summary: This diff adds a substitution at function calls.

Reviewed By: ezgicicek

Differential Revision: D18878045

fbshipit-source-id: e081d1500
master
Sungkeun Cho 5 years ago committed by Facebook Github Bot
parent 251d648f0c
commit 2835468df9

@ -137,6 +137,10 @@ module Raw = struct
let of_id id typ = (base_of_id id typ, []) let of_id id typ = (base_of_id id typ, [])
let of_var var typ =
match var with Var.LogicalVar id -> of_id id typ | Var.ProgramVar pvar -> of_pvar pvar typ
let of_exp ~include_array_indexes exp0 typ0 ~(f_resolve_id : Var.t -> t option) = let of_exp ~include_array_indexes exp0 typ0 ~(f_resolve_id : Var.t -> t option) =
(* [typ] is the type of the last element of the access path (e.g., typeof(g) for x.f.g) *) (* [typ] is the type of the last element of the access path (e.g., typeof(g) for x.f.g) *)
let rec of_exp_ exp typ accesses acc = let rec of_exp_ exp typ accesses acc =

@ -44,6 +44,9 @@ val of_pvar : Pvar.t -> Typ.t -> t
val of_id : Ident.t -> Typ.t -> t val of_id : Ident.t -> Typ.t -> t
(** create an access path from an ident *) (** create an access path from an ident *)
val of_var : Var.t -> Typ.t -> t
(** create an access path from a var *)
val of_exp : val of_exp :
include_array_indexes:bool -> Exp.t -> Typ.t -> f_resolve_id:(Var.t -> t option) -> t list include_array_indexes:bool -> Exp.t -> Typ.t -> f_resolve_id:(Var.t -> t option) -> t list
(** extract the access paths that occur in [exp], resolving identifiers using [f_resolve_id]. don't (** extract the access paths that occur in [exp], resolving identifiers using [f_resolve_id]. don't

@ -54,7 +54,8 @@ module LithoContext = struct
let ltr = [Errlog.make_trace_element 0 loc message []] in let ltr = [Errlog.make_trace_element 0 loc message []] in
Reporting.log_error summary ~loc ~ltr IssueType.graphql_field_access message Reporting.log_error summary ~loc ~ltr IssueType.graphql_field_access message
in in
Domain.iter_call_chains ~f:report_graphql_getter astate Domain.iter_call_chains ~f:report_graphql_getter astate ;
astate
let session_name = "litho graphql field access" let session_name = "litho graphql field access"

@ -19,6 +19,8 @@ module LocalAccessPath = struct
make (HilExp.AccessExpression.to_access_path ae) parent make (HilExp.AccessExpression.to_access_path ae) parent
let make_from_pvar pvar typ parent = make (AccessPath.of_pvar pvar typ) parent
let to_formal_option {access_path= ((_, base_typ) as base), accesses; parent} formal_map = let to_formal_option {access_path= ((_, base_typ) as base), accesses; parent} formal_map =
match FormalMap.get_formal_index base formal_map with match FormalMap.get_formal_index base formal_map with
| Some formal_index -> | Some formal_index ->
@ -30,6 +32,8 @@ module LocalAccessPath = struct
let pp fmt t = AccessPath.pp fmt t.access_path let pp fmt t = AccessPath.pp fmt t.access_path
end end
module LocalAccessPathSet = PrettyPrintable.MakePPSet (LocalAccessPath)
let suffixes = String.Set.of_list ["Attr"; "Dip"; "Px"; "Res"; "Sp"] let suffixes = String.Set.of_list ["Attr"; "Dip"; "Px"; "Res"; "Sp"]
module MethodCall = struct module MethodCall = struct
@ -78,39 +82,97 @@ module OldDomain = AbstractDomain.Map (LocalAccessPath) (CallSet)
module NewDomain = struct module NewDomain = struct
module CreatedLocation = struct module CreatedLocation = struct
type t = {location: Location.t [@ignore]; typ_name: Typ.name} [@@deriving compare] type t =
| ByCreateMethod of {location: Location.t [@ignore]; typ_name: Typ.name}
let pp fmt {location; typ_name} = | ByParameter of LocalAccessPath.t
F.fprintf fmt "Created at %a with type %a" Location.pp location Typ.Name.pp typ_name [@@deriving compare]
let pp fmt = function
| ByCreateMethod {location; typ_name} ->
F.fprintf fmt "Created at %a with type %a" Location.pp location Typ.Name.pp typ_name
| ByParameter path ->
F.fprintf fmt "Given by parameter %a" LocalAccessPath.pp path
end end
module CreatedLocations = AbstractDomain.FiniteSet (CreatedLocation) module CreatedLocations = AbstractDomain.FiniteSet (CreatedLocation)
(** Map from access paths of callee parameters and return variable to caller's corresponding
access paths *)
module SubstPathMap = struct
include PrettyPrintable.MakePPMonoMap (LocalAccessPath) (LocalAccessPath)
let make ~formals ~actuals ~caller_return ~callee_return =
let map = singleton callee_return caller_return in
match
List.fold2 formals actuals ~init:map ~f:(fun acc formal actual ->
Option.fold actual ~init:acc ~f:(fun acc actual -> add formal actual acc) )
with
| Ok map ->
map
| Unequal_lengths ->
map
end
module Created = struct module Created = struct
include AbstractDomain.Map (LocalAccessPath) (CreatedLocations) include AbstractDomain.Map (LocalAccessPath) (CreatedLocations)
let lookup k x = Option.value (find_opt k x) ~default:CreatedLocations.empty let lookup k x = Option.value (find_opt k x) ~default:CreatedLocations.empty
let append path locations x =
let append_created_locations = function
| None ->
Some locations
| Some pre_locations ->
Some (CreatedLocations.join pre_locations locations)
in
update path append_created_locations x
let append_one path location x = append path (CreatedLocations.singleton location) x
let subst map ~caller_return ~callee_return ~callee ~caller =
Option.fold (find_opt callee_return callee) ~init:caller ~f:(fun acc callee_returns ->
let accum_subst callee_return acc =
match callee_return with
| CreatedLocation.ByCreateMethod _ ->
append_one caller_return callee_return acc
| CreatedLocation.ByParameter path ->
let caller_path = SubstPathMap.find path map in
Option.value_map (find_opt caller_path caller) ~default:acc
~f:(fun caller_created -> append caller_return caller_created acc)
in
CreatedLocations.fold accum_subst callee_returns acc )
end end
module MethodCalls = struct module MethodCalls = struct
module IsBuildMethodCalled = AbstractDomain.BooleanAnd module IsBuildMethodCalled = AbstractDomain.BooleanOr
(** if the build method has been called on the builder object *)
module IsChecked = AbstractDomain.BooleanOr
(** if the method calls are checked and reported *)
module S = AbstractDomain.InvertedSet (MethodCallPrefix) module S = AbstractDomain.InvertedSet (MethodCallPrefix)
type t = {is_build_method_called: IsBuildMethodCalled.t; method_calls: S.t} type t =
{is_build_method_called: IsBuildMethodCalled.t; is_checked: IsChecked.t; method_calls: S.t}
let pp fmt {is_build_method_called; method_calls} = let pp fmt {is_build_method_called; is_checked; method_calls} =
F.fprintf fmt "%a%s" S.pp method_calls F.fprintf fmt "%a%s" S.pp method_calls
(if is_build_method_called then " then build() called" else "") ( if is_checked then " checked"
else if is_build_method_called then " then build() called"
else "" )
let leq ~lhs ~rhs = let leq ~lhs ~rhs =
IsBuildMethodCalled.leq ~lhs:lhs.is_build_method_called ~rhs:rhs.is_build_method_called IsBuildMethodCalled.leq ~lhs:lhs.is_build_method_called ~rhs:rhs.is_build_method_called
&& IsChecked.leq ~lhs:lhs.is_checked ~rhs:rhs.is_checked
&& S.leq ~lhs:lhs.method_calls ~rhs:rhs.method_calls && S.leq ~lhs:lhs.method_calls ~rhs:rhs.method_calls
let join x y = let join x y =
{ is_build_method_called= { is_build_method_called=
IsBuildMethodCalled.join x.is_build_method_called y.is_build_method_called IsBuildMethodCalled.join x.is_build_method_called y.is_build_method_called
; is_checked= IsChecked.join x.is_checked y.is_checked
; method_calls= S.join x.method_calls y.method_calls } ; method_calls= S.join x.method_calls y.method_calls }
@ -118,17 +180,24 @@ module NewDomain = struct
{ is_build_method_called= { is_build_method_called=
IsBuildMethodCalled.widen ~prev:prev.is_build_method_called IsBuildMethodCalled.widen ~prev:prev.is_build_method_called
~next:next.is_build_method_called ~num_iters ~next:next.is_build_method_called ~num_iters
; is_checked= IsChecked.widen ~prev:prev.is_checked ~next:next.is_checked ~num_iters
; method_calls= S.widen ~prev:prev.method_calls ~next:next.method_calls ~num_iters } ; method_calls= S.widen ~prev:prev.method_calls ~next:next.method_calls ~num_iters }
let empty = {is_build_method_called= false; method_calls= S.empty} let empty = {is_build_method_called= false; is_checked= false; method_calls= S.empty}
let singleton e = {is_build_method_called= false; method_calls= S.singleton e} let singleton e = {is_build_method_called= false; is_checked= false; method_calls= S.singleton e}
let add e ({is_build_method_called; method_calls} as x) = let add e ({is_build_method_called; method_calls} as x) =
if is_build_method_called then x else {x with method_calls= S.add e method_calls} if is_build_method_called then x else {x with method_calls= S.add e method_calls}
let merge x y =
{ is_build_method_called= x.is_build_method_called || y.is_build_method_called
; is_checked= x.is_checked || y.is_checked
; method_calls= S.union x.method_calls y.method_calls }
let set_build_method_called x = {x with is_build_method_called= true} let set_build_method_called x = {x with is_build_method_called= true}
let to_string_set method_calls = let to_string_set method_calls =
@ -144,11 +213,13 @@ module NewDomain = struct
let check_required_props ~check_on_string_set parent_typename let check_required_props ~check_on_string_set parent_typename
{is_build_method_called; method_calls} = ({is_build_method_called; is_checked; method_calls} as x) =
if is_build_method_called then if is_build_method_called && not is_checked then (
let prop_set = to_string_set method_calls in let prop_set = to_string_set method_calls in
let call_chain = get_call_chain method_calls in let call_chain = get_call_chain method_calls in
check_on_string_set parent_typename call_chain prop_set check_on_string_set parent_typename call_chain prop_set ;
{x with is_checked= true} )
else x
end end
module MethodCalled = struct module MethodCalled = struct
@ -183,10 +254,43 @@ module NewDomain = struct
let check_required_props ~check_on_string_set x = let check_required_props ~check_on_string_set x =
let f CreatedLocation.{typ_name} method_calls = let f created_location method_calls =
MethodCalls.check_required_props ~check_on_string_set typ_name method_calls match created_location with
| CreatedLocation.ByCreateMethod {typ_name} ->
MethodCalls.check_required_props ~check_on_string_set typ_name method_calls
| CreatedLocation.ByParameter _ ->
method_calls
in in
iter f x mapi f x
let subst ~is_reachable map ~find_caller_created ~caller ~callee =
let accum_substed created_location callee_method_calls acc =
let merge_method_calls caller_created acc =
let method_calls =
Option.value_map (find_opt caller_created caller) ~default:callee_method_calls
~f:(fun caller_method_calls ->
MethodCalls.merge caller_method_calls callee_method_calls )
in
update caller_created
(function
| None ->
Some method_calls
| Some acc_method_calls ->
Some (MethodCalls.merge acc_method_calls method_calls) )
acc
in
match created_location with
| CreatedLocation.ByCreateMethod _ ->
if is_reachable created_location then merge_method_calls created_location acc else acc
| CreatedLocation.ByParameter path ->
Option.value_map (SubstPathMap.find_opt path map) ~default:acc ~f:(fun caller_path ->
Option.value_map (find_caller_created caller_path) ~default:acc
~f:(fun caller_created ->
CreatedLocations.fold merge_method_calls caller_created acc ) )
in
let caller' = fold accum_substed callee empty in
merge (fun _ v v' -> match v' with Some _ -> v' | None -> v) caller caller'
end end
type t = {created: Created.t; method_called: MethodCalled.t} type t = {created: Created.t; method_called: MethodCalled.t}
@ -214,12 +318,30 @@ module NewDomain = struct
let empty = {created= Created.empty; method_called= MethodCalled.empty} let empty = {created= Created.empty; method_called= MethodCalled.empty}
let init tenv pname formals =
List.fold formals ~init:empty ~f:(fun ({created; method_called} as acc) (pvar, ptr_typ) ->
match ptr_typ with
| Typ.{desc= Tptr (typ, _)} -> (
match Typ.name typ with
| Some typ_name
when PatternMatch.is_subtype_of_str tenv typ_name "com.facebook.litho.Component$Builder"
->
let formal_ae = LocalAccessPath.make_from_pvar pvar ptr_typ pname in
let created_location = CreatedLocation.ByParameter formal_ae in
{ created= Created.add formal_ae (CreatedLocations.singleton created_location) created
; method_called= MethodCalled.add created_location MethodCalls.empty method_called }
| _ ->
acc )
| _ ->
acc )
let assign ~lhs ~rhs ({created} as x) = let assign ~lhs ~rhs ({created} as x) =
{x with created= Created.add lhs (Created.lookup rhs created) created} {x with created= Created.add lhs (Created.lookup rhs created) created}
let call_create lhs typ_name location ({created} as x) = let call_create lhs typ_name location ({created} as x) =
let created_location = CreatedLocation.{location; typ_name} in let created_location = CreatedLocation.ByCreateMethod {location; typ_name} in
{ created= Created.add lhs (CreatedLocations.singleton created_location) created { created= Created.add lhs (CreatedLocations.singleton created_location) created
; method_called= MethodCalled.add created_location MethodCalls.empty x.method_called } ; method_called= MethodCalled.add created_location MethodCalls.empty x.method_called }
@ -236,8 +358,49 @@ module NewDomain = struct
; method_called= MethodCalled.build_method_called created_locations method_called } ; method_called= MethodCalled.build_method_called created_locations method_called }
let check_required_props ~check_on_string_set {method_called} = let check_required_props ~check_on_string_set ({method_called} as x) =
MethodCalled.check_required_props ~check_on_string_set method_called {x with method_called= MethodCalled.check_required_props ~check_on_string_set method_called}
let subst ~formals ~actuals ~ret_id_typ:(ret_var, ret_typ) ~caller_pname ~callee_pname ~caller
~callee =
let callee_return =
LocalAccessPath.make_from_pvar (Pvar.get_ret_pvar callee_pname) ret_typ callee_pname
in
let caller_return = LocalAccessPath.make (AccessPath.of_var ret_var ret_typ) caller_pname in
let formals =
List.map formals ~f:(fun (pvar, typ) -> LocalAccessPath.make_from_pvar pvar typ callee_pname)
in
let actuals =
List.map actuals ~f:(function
| HilExp.AccessExpression actual ->
Some (LocalAccessPath.make_from_access_expression actual caller_pname)
| _ ->
None )
in
let map = SubstPathMap.make ~formals ~actuals ~caller_return ~callee_return in
let created =
Created.subst map ~caller_return ~callee_return ~caller:caller.created ~callee:callee.created
in
let is_reachable =
let reachable_paths =
LocalAccessPathSet.of_list formals |> LocalAccessPathSet.add callee_return
in
let reachable_locations =
let accum_reachable_location path locations acc =
if LocalAccessPathSet.mem path reachable_paths then CreatedLocations.union acc locations
else acc
in
Created.fold accum_reachable_location callee.created CreatedLocations.empty
in
fun created_location -> CreatedLocations.mem created_location reachable_locations
in
let method_called =
let find_caller_created path = Created.find_opt path caller.created in
MethodCalled.subst ~is_reachable map ~find_caller_created ~caller:caller.method_called
~callee:callee.method_called
in
{created; method_called}
end end
include struct include struct
@ -245,14 +408,14 @@ include struct
let lift_old f (o, _) = f o let lift_old f (o, _) = f o
let lift_new f (_, n) = f n
let map_old f (o, n) = (f o, n) let map_old f (o, n) = (f o, n)
let map_new f (o, n) = (o, f n) let map_new f (o, n) = (o, f n)
let empty = (OldDomain.empty, NewDomain.empty) let empty = (OldDomain.empty, NewDomain.empty)
let init tenv pname formals = (OldDomain.empty, NewDomain.init tenv pname formals)
let add k v = map_old (OldDomain.add k v) let add k v = map_old (OldDomain.add k v)
let remove k = map_old (OldDomain.remove k) let remove k = map_old (OldDomain.remove k)
@ -276,7 +439,7 @@ include struct
let call_build_method ~ret ~receiver = map_new (NewDomain.call_build_method ~ret ~receiver) let call_build_method ~ret ~receiver = map_new (NewDomain.call_build_method ~ret ~receiver)
let check_required_props ~check_on_string_set = let check_required_props ~check_on_string_set =
lift_new (NewDomain.check_required_props ~check_on_string_set) map_new (NewDomain.check_required_props ~check_on_string_set)
end end
let substitute ~(f_sub : LocalAccessPath.t -> LocalAccessPath.t option) ((_, new_astate) as astate) let substitute ~(f_sub : LocalAccessPath.t -> LocalAccessPath.t option) ((_, new_astate) as astate)

@ -16,6 +16,8 @@ module LocalAccessPath : sig
val make_from_access_expression : HilExp.AccessExpression.t -> Typ.Procname.t -> t val make_from_access_expression : HilExp.AccessExpression.t -> Typ.Procname.t -> t
val make_from_pvar : Pvar.t -> Typ.t -> Typ.Procname.t -> t
val to_formal_option : t -> FormalMap.t -> t option val to_formal_option : t -> FormalMap.t -> t option
val pp : F.formatter -> t -> unit val pp : F.formatter -> t -> unit
@ -51,12 +53,26 @@ module CallSet : module type of AbstractDomain.FiniteSet (MethodCall)
module OldDomain : module type of AbstractDomain.Map (LocalAccessPath) (CallSet) module OldDomain : module type of AbstractDomain.Map (LocalAccessPath) (CallSet)
module NewDomain : AbstractDomain.S module NewDomain : sig
include AbstractDomain.S
val subst :
formals:(Pvar.t * Typ.t) list
-> actuals:HilExp.t list
-> ret_id_typ:AccessPath.base
-> caller_pname:Typ.Procname.t
-> callee_pname:Typ.Procname.t
-> caller:t
-> callee:t
-> t
end
include module type of AbstractDomain.Pair (OldDomain) (NewDomain) include module type of AbstractDomain.Pair (OldDomain) (NewDomain)
val empty : t val empty : t
val init : Tenv.t -> Typ.Procname.t -> (Pvar.t * Typ.t) list -> t
val add : LocalAccessPath.t -> CallSet.t -> t -> t val add : LocalAccessPath.t -> CallSet.t -> t -> t
val remove : LocalAccessPath.t -> t -> t val remove : LocalAccessPath.t -> t -> t
@ -80,7 +96,7 @@ val call_build_method : ret:LocalAccessPath.t -> receiver:LocalAccessPath.t -> t
(** Semantics of builder's final build method *) (** Semantics of builder's final build method *)
val check_required_props : val check_required_props :
check_on_string_set:(Typ.name -> MethodCallPrefix.t list -> String.Set.t -> unit) -> t -> unit check_on_string_set:(Typ.name -> MethodCallPrefix.t list -> String.Set.t -> unit) -> t -> t
val substitute : f_sub:(LocalAccessPath.t -> LocalAccessPath.t option) -> t -> t val substitute : f_sub:(LocalAccessPath.t -> LocalAccessPath.t option) -> t -> t
(** Substitute each access path in the domain using [f_sub]. If [f_sub] returns None, the original (** Substitute each access path in the domain using [f_sub]. If [f_sub] returns None, the original

@ -88,7 +88,7 @@ module type LithoContext = sig
val should_report : Procdesc.t -> Tenv.t -> bool val should_report : Procdesc.t -> Tenv.t -> bool
val report : t -> Tenv.t -> Summary.t -> unit val report : t -> Tenv.t -> Summary.t -> t
val session_name : string val session_name : string
end end
@ -105,32 +105,32 @@ struct
type nonrec extras = extras type nonrec extras = extras
let apply_callee_summary summary_opt caller_pname ret_id_typ actuals ((_, new_domain) as astate) = let apply_callee_summary summary_opt ~caller_pname ~callee_pname ret_id_typ formals actuals
let old_domain', _ = ((_, new_domain) as astate) =
match summary_opt with Option.value_map summary_opt ~default:astate ~f:(fun summary ->
| Some summary -> (* TODO: append paths if the footprint access path is an actual path instead of a var *)
(* TODO: append paths if the footprint access path is an actual path instead of a var *) let f_sub {Domain.LocalAccessPath.access_path= (var, _), _} =
let f_sub {Domain.LocalAccessPath.access_path= (var, _), _} = match Var.get_footprint_index var with
match Var.get_footprint_index var with | Some footprint_index -> (
| Some footprint_index -> ( match List.nth actuals footprint_index with
match List.nth actuals footprint_index with | Some (HilExp.AccessExpression actual_access_expr) ->
| Some (HilExp.AccessExpression actual_access_expr) -> Some
Some (Domain.LocalAccessPath.make
(Domain.LocalAccessPath.make (HilExp.AccessExpression.to_access_path actual_access_expr)
(HilExp.AccessExpression.to_access_path actual_access_expr) caller_pname)
caller_pname) | _ ->
| _ -> None )
None ) | None ->
| None -> if Var.is_return var then
if Var.is_return var then Some (Domain.LocalAccessPath.make (ret_id_typ, []) caller_pname)
Some (Domain.LocalAccessPath.make (ret_id_typ, []) caller_pname) else None
else None in
in let astate_old, _ = Domain.substitute ~f_sub summary |> Domain.join astate in
Domain.substitute ~f_sub summary |> Domain.join astate let astate_new =
| None -> Domain.NewDomain.subst ~formals ~actuals ~ret_id_typ ~caller_pname ~callee_pname
astate ~caller:new_domain ~callee:(snd summary)
in in
(old_domain', new_domain) (astate_old, astate_new) )
let exec_instr astate ProcData.{summary; tenv; extras= {get_proc_summary_and_formals}} _ let exec_instr astate ProcData.{summary; tenv; extras= {get_proc_summary_and_formals}} _
@ -183,11 +183,15 @@ struct
else astate else astate
else else
(* treat it like a normal call *) (* treat it like a normal call *)
apply_callee_summary callee_summary_opt caller_pname return_base actuals astate 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, _, _) -> | 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_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 callee_summary_opt = Option.map callee_summary_and_formals_opt ~f:fst in
apply_callee_summary callee_summary_opt caller_pname ret_id_typ actuals astate 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, HilExp.AccessExpression rhs_ae, _) -> | Assign (lhs_ae, HilExp.AccessExpression rhs_ae, _) ->
(* creating an alias for the rhs binding; assume all reads will now occur through the (* 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; alias. this helps us keep track of chains in cases like tmp = getFoo(); x = tmp;
@ -229,11 +233,16 @@ module MakeAnalyzer (LithoContext : LithoContext with type t = Domain.t) = struc
let checker {Callbacks.summary; exe_env} = let checker {Callbacks.summary; exe_env} =
let proc_desc = Summary.get_proc_desc summary in 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 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 proc_data = ProcData.make summary tenv (init_extras summary) in
match A.compute_post proc_data ~initial:Domain.empty with let initial = Domain.init tenv proc_name (Procdesc.get_pvar_formals proc_desc) in
match A.compute_post proc_data ~initial with
| Some post -> | Some post ->
if LithoContext.should_report proc_desc tenv then LithoContext.report post tenv summary ; let post =
if LithoContext.should_report proc_desc tenv then LithoContext.report post tenv summary
else post
in
let postprocess astate formal_map : Domain.t = let postprocess astate formal_map : Domain.t =
let f_sub access_path = Domain.LocalAccessPath.to_formal_option access_path formal_map in let f_sub access_path = Domain.LocalAccessPath.to_formal_option access_path formal_map in
Domain.substitute ~f_sub astate Domain.substitute ~f_sub astate

@ -192,7 +192,9 @@ module LithoContext = struct
if Config.new_litho_domain then if Config.new_litho_domain then
let check_on_string_set_prefix = check_on_string_set_prefix tenv summary in let check_on_string_set_prefix = check_on_string_set_prefix tenv summary in
Domain.check_required_props ~check_on_string_set:check_on_string_set_prefix astate Domain.check_required_props ~check_on_string_set:check_on_string_set_prefix astate
else Domain.iter_call_chains ~f:check_required_prop_chain astate else (
Domain.iter_call_chains ~f:check_required_prop_chain astate ;
astate )
let session_name = "litho required props" let session_name = "litho required props"

@ -68,7 +68,7 @@ public class RequiredProps {
return setProp3(mMyComponent.create().prop1(new Object()).prop2(new Object())).build(); return setProp3(mMyComponent.create().prop1(new Object()).prop2(new Object())).build();
} }
public Component setProp3InCalleeButForgetProp1Bad_FN() { public Component setProp3InCalleeButForgetProp1Bad() {
return setProp3(mMyComponent.create()).prop2(new Object()).build(); return setProp3(mMyComponent.create()).prop2(new Object()).build();
} }
@ -203,7 +203,7 @@ public class RequiredProps {
} }
// should report here; forgot prop 1 // should report here; forgot prop 1
public Component callBuildSuffixWithoutRequiredBad_FN() { public Component callBuildSuffixWithoutRequiredBad() {
return buildSuffix(mMyComponent.create()); return buildSuffix(mMyComponent.create());
} }
@ -317,7 +317,7 @@ public class RequiredProps {
return layoutBuilder.build(); return layoutBuilder.build();
} }
public void castImpossibleOk(Object o1) { public void castImpossibleOk_FP(Object o1) {
Component.Builder builder = mMyLithoComponent.create(); Component.Builder builder = mMyLithoComponent.create();
if (builder instanceof MyComponent.Builder) if (builder instanceof MyComponent.Builder)
((MyComponent.Builder) builder) ((MyComponent.Builder) builder)
@ -334,7 +334,7 @@ public class RequiredProps {
return mMyLithoComponent.create(); return mMyLithoComponent.create();
} }
void castMissingOneBad_FN(Object o1) { void castMissingOneBad(Object o1) {
Component.Builder builder = createBuilder(); Component.Builder builder = createBuilder();
if (builder instanceof MyLithoComponent.Builder) if (builder instanceof MyLithoComponent.Builder)
((MyLithoComponent.Builder) builder).prop2(new Object()).build(); // this branch will be taken ((MyLithoComponent.Builder) builder).prop2(new Object()).build(); // this branch will be taken

@ -12,12 +12,18 @@ codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.l
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithColumnChildBad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithColumnChildBad():void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout1Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout1Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout3Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.buildWithout3Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.callBuildSuffixWithoutRequiredBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castImpossibleOk_FP(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castImpossibleOk_FP(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castMissingOneBad(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.castMissingOneBad(java.lang.Object):void, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build()]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.doubleSetMissingBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls Component$Builder Component$Builder.commonProp(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.doubleSetMissingBad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls Component$Builder Component$Builder.commonProp(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.generalTypeForgot3Bad():java.lang.Object, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.generalTypeForgot3Bad():java.lang.Object, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp1InBothBranchesBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp1InBothBranchesBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp1InBothBranchesBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop2(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp1InBothBranchesBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop2(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp2InOneBranchBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp2InOneBranchBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop2 is required for component com.facebook.litho.MyLithoComponent, but is not set before the call to build(),calls MyLithoComponent$Builder MyLithoComponent$Builder.prop1(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp3InOneBranchBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.missingProp3InOneBranchBeforeBuildBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setProp3InCalleeButForgetProp1Bad():com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnBothBranchesMissingProp3Bad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnBothBranchesMissingProp3Bad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop3 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop1(Object),calls MyComponent$Builder MyComponent$Builder.prop2(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]
codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchEffectfulBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)] codetoanalyze/java/litho-required-props/RequiredProps.java, codetoanalyze.java.litho.RequiredProps.setRequiredOnOneBranchEffectfulBad(boolean):com.facebook.litho.Component, 0, MISSING_REQUIRED_PROP, no_bucket, ERROR, [@Prop prop1 is required for component codetoanalyze.java.litho.MyComponent, but is not set before the call to build(),calls MyComponent$Builder MyComponent$Builder.prop2(Object),calls MyComponent$Builder MyComponent$Builder.prop3(Object)]

Loading…
Cancel
Save