diff --git a/infer/src/checkers/LithoFramework.ml b/infer/src/checkers/LithoFramework.ml deleted file mode 100644 index 172c9cee6..000000000 --- a/infer/src/checkers/LithoFramework.ml +++ /dev/null @@ -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 diff --git a/infer/src/checkers/RequiredProps.ml b/infer/src/checkers/RequiredProps.ml index 1d1397dcd..2b48b4a6f 100644 --- a/infer/src/checkers/RequiredProps.ml +++ b/infer/src/checkers/RequiredProps.ml @@ -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