diff --git a/infer/src/IR/AccessPath.ml b/infer/src/IR/AccessPath.ml index 92a78eb9f..7f1ff0379 100644 --- a/infer/src/IR/AccessPath.ml +++ b/infer/src/IR/AccessPath.ml @@ -137,6 +137,10 @@ module Raw = struct 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) = (* [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 = diff --git a/infer/src/IR/AccessPath.mli b/infer/src/IR/AccessPath.mli index 8482bf870..55d1f53be 100644 --- a/infer/src/IR/AccessPath.mli +++ b/infer/src/IR/AccessPath.mli @@ -44,6 +44,9 @@ val of_pvar : Pvar.t -> Typ.t -> t val of_id : Ident.t -> Typ.t -> t (** 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 : 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 diff --git a/infer/src/checkers/GraphQLFieldAccess.ml b/infer/src/checkers/GraphQLFieldAccess.ml index 781f76da8..056d3ddf0 100644 --- a/infer/src/checkers/GraphQLFieldAccess.ml +++ b/infer/src/checkers/GraphQLFieldAccess.ml @@ -54,7 +54,8 @@ module LithoContext = struct let ltr = [Errlog.make_trace_element 0 loc message []] in Reporting.log_error summary ~loc ~ltr IssueType.graphql_field_access message 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" diff --git a/infer/src/checkers/LithoDomain.ml b/infer/src/checkers/LithoDomain.ml index e7dc308d2..1e86d34ec 100644 --- a/infer/src/checkers/LithoDomain.ml +++ b/infer/src/checkers/LithoDomain.ml @@ -19,6 +19,8 @@ module LocalAccessPath = struct 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 = match FormalMap.get_formal_index base formal_map with | Some formal_index -> @@ -30,6 +32,8 @@ module LocalAccessPath = struct let pp fmt t = AccessPath.pp fmt t.access_path end +module LocalAccessPathSet = PrettyPrintable.MakePPSet (LocalAccessPath) + let suffixes = String.Set.of_list ["Attr"; "Dip"; "Px"; "Res"; "Sp"] module MethodCall = struct @@ -78,39 +82,97 @@ module OldDomain = AbstractDomain.Map (LocalAccessPath) (CallSet) module NewDomain = struct module CreatedLocation = struct - type t = {location: Location.t [@ignore]; typ_name: Typ.name} [@@deriving compare] - - let pp fmt {location; typ_name} = - F.fprintf fmt "Created at %a with type %a" Location.pp location Typ.Name.pp typ_name + type t = + | ByCreateMethod of {location: Location.t [@ignore]; typ_name: Typ.name} + | ByParameter of LocalAccessPath.t + [@@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 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 include AbstractDomain.Map (LocalAccessPath) (CreatedLocations) 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 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) - 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 - (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 = 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 let join x 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 } @@ -118,17 +180,24 @@ module NewDomain = struct { is_build_method_called= IsBuildMethodCalled.widen ~prev:prev.is_build_method_called ~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 } - 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) = 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 to_string_set method_calls = @@ -144,11 +213,13 @@ module NewDomain = struct let check_required_props ~check_on_string_set parent_typename - {is_build_method_called; method_calls} = - if is_build_method_called then + ({is_build_method_called; is_checked; method_calls} as x) = + if is_build_method_called && not is_checked then ( let prop_set = to_string_set 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 module MethodCalled = struct @@ -183,10 +254,43 @@ module NewDomain = struct let check_required_props ~check_on_string_set x = - let f CreatedLocation.{typ_name} method_calls = - MethodCalls.check_required_props ~check_on_string_set typ_name method_calls + let f created_location 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 - 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 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 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) = {x with created= Created.add lhs (Created.lookup rhs created) created} 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 ; 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 } - let check_required_props ~check_on_string_set {method_called} = - MethodCalled.check_required_props ~check_on_string_set method_called + let check_required_props ~check_on_string_set ({method_called} as x) = + {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 include struct @@ -245,14 +408,14 @@ include struct 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_new f (o, n) = (o, f n) 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 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 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 let substitute ~(f_sub : LocalAccessPath.t -> LocalAccessPath.t option) ((_, new_astate) as astate) diff --git a/infer/src/checkers/LithoDomain.mli b/infer/src/checkers/LithoDomain.mli index 2bbc4f418..186bdaedc 100644 --- a/infer/src/checkers/LithoDomain.mli +++ b/infer/src/checkers/LithoDomain.mli @@ -16,6 +16,8 @@ module LocalAccessPath : sig 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 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 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) 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 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 *) 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 (** Substitute each access path in the domain using [f_sub]. If [f_sub] returns None, the original diff --git a/infer/src/checkers/LithoFramework.ml b/infer/src/checkers/LithoFramework.ml index e766ba8b5..5781cf35f 100644 --- a/infer/src/checkers/LithoFramework.ml +++ b/infer/src/checkers/LithoFramework.ml @@ -88,7 +88,7 @@ module type LithoContext = sig 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 end @@ -105,32 +105,32 @@ struct type nonrec extras = extras - let apply_callee_summary summary_opt caller_pname ret_id_typ actuals ((_, new_domain) as astate) = - let old_domain', _ = - 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 - in - (old_domain', new_domain) + let apply_callee_summary summary_opt ~caller_pname ~callee_pname ret_id_typ formals actuals + ((_, new_domain) as astate) = + Option.value_map summary_opt ~default:astate ~f:(fun 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 + let astate_old, _ = Domain.substitute ~f_sub summary |> Domain.join astate in + let astate_new = + Domain.NewDomain.subst ~formals ~actuals ~ret_id_typ ~caller_pname ~callee_pname + ~caller:new_domain ~callee:(snd summary) + in + (astate_old, astate_new) ) let exec_instr astate ProcData.{summary; tenv; extras= {get_proc_summary_and_formals}} _ @@ -183,11 +183,15 @@ struct else astate else (* 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, _, _) -> 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 - 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, _) -> (* 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; @@ -229,11 +233,16 @@ module MakeAnalyzer (LithoContext : LithoContext with type t = Domain.t) = struc 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 - 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 -> - 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 f_sub access_path = Domain.LocalAccessPath.to_formal_option access_path formal_map in Domain.substitute ~f_sub astate diff --git a/infer/src/checkers/RequiredProps.ml b/infer/src/checkers/RequiredProps.ml index d3084ac73..0c9194173 100644 --- a/infer/src/checkers/RequiredProps.ml +++ b/infer/src/checkers/RequiredProps.ml @@ -192,7 +192,9 @@ module LithoContext = struct if Config.new_litho_domain then 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 - 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" diff --git a/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java b/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java index 1ec914f16..a2cedfb6c 100644 --- a/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java +++ b/infer/tests/codetoanalyze/java/litho-required-props/RequiredProps.java @@ -68,7 +68,7 @@ public class RequiredProps { 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(); } @@ -203,7 +203,7 @@ public class RequiredProps { } // should report here; forgot prop 1 - public Component callBuildSuffixWithoutRequiredBad_FN() { + public Component callBuildSuffixWithoutRequiredBad() { return buildSuffix(mMyComponent.create()); } @@ -317,7 +317,7 @@ public class RequiredProps { return layoutBuilder.build(); } - public void castImpossibleOk(Object o1) { + public void castImpossibleOk_FP(Object o1) { Component.Builder builder = mMyLithoComponent.create(); if (builder instanceof MyComponent.Builder) ((MyComponent.Builder) builder) @@ -334,7 +334,7 @@ public class RequiredProps { return mMyLithoComponent.create(); } - void castMissingOneBad_FN(Object o1) { + void castMissingOneBad(Object o1) { Component.Builder builder = createBuilder(); if (builder instanceof MyLithoComponent.Builder) ((MyLithoComponent.Builder) builder).prop2(new Object()).build(); // this branch will be taken diff --git a/infer/tests/codetoanalyze/java/litho-required-props/issues.exp b/infer/tests/codetoanalyze/java/litho-required-props/issues.exp index a0bb0700c..fd814a789 100644 --- a/infer/tests/codetoanalyze/java/litho-required-props/issues.exp +++ b/infer/tests/codetoanalyze/java/litho-required-props/issues.exp @@ -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.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.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.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 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.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.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)]