From f37344358b54b6dc61c108cc17fc526b3d8813a1 Mon Sep 17 00:00:00 2001 From: Sam Blackshear Date: Thu, 14 Dec 2017 15:36:27 -0800 Subject: [PATCH] [experimental] interprocedural should_update analysis Reviewed By: jeremydubreil Differential Revision: D6411833 fbshipit-source-id: 4609a6b --- infer/src/absint/Var.ml | 4 + infer/src/absint/Var.mli | 2 + infer/src/backend/specs.ml | 20 +- infer/src/backend/specs.mli | 15 +- infer/src/base/Config.ml | 12 +- infer/src/base/Config.mli | 4 +- infer/src/checkers/Litho.ml | 178 ++++++++++++++++++ infer/src/checkers/LithoDomain.ml | 67 +++++++ infer/src/checkers/ShouldUpdate.ml | 117 ------------ infer/src/checkers/ShouldUpdateDomain.ml | 22 --- infer/src/checkers/registerCheckers.ml | 5 +- infer/tests/codetoanalyze/java/litho/Makefile | 15 ++ .../java/litho/ShouldUpdate.java | 100 ++++++++++ .../tests/codetoanalyze/java/litho/issues.exp | 27 +++ 14 files changed, 419 insertions(+), 169 deletions(-) create mode 100644 infer/src/checkers/Litho.ml create mode 100644 infer/src/checkers/LithoDomain.ml delete mode 100644 infer/src/checkers/ShouldUpdate.ml delete mode 100644 infer/src/checkers/ShouldUpdateDomain.ml create mode 100644 infer/tests/codetoanalyze/java/litho/Makefile create mode 100644 infer/tests/codetoanalyze/java/litho/ShouldUpdate.java create mode 100644 infer/tests/codetoanalyze/java/litho/issues.exp diff --git a/infer/src/absint/Var.ml b/infer/src/absint/Var.ml index 7d393507b..69432f056 100644 --- a/infer/src/absint/Var.ml +++ b/infer/src/absint/Var.ml @@ -31,6 +31,10 @@ let is_footprint = function ProgramVar _ -> false | LogicalVar id -> Ident.is_fo let pp fmt = function ProgramVar pv -> Pvar.pp Pp.text fmt pv | LogicalVar id -> Ident.pp fmt id +let get_footprint_index t = + match t with LogicalVar id when is_footprint t -> Some (Ident.get_stamp id) | _ -> None + + module Map = PrettyPrintable.MakePPMap (struct type nonrec t = t diff --git a/infer/src/absint/Var.mli b/infer/src/absint/Var.mli index cf5ccfc43..5fc33c581 100644 --- a/infer/src/absint/Var.mli +++ b/infer/src/absint/Var.mli @@ -30,6 +30,8 @@ val is_return : t -> bool val is_footprint : t -> bool +val get_footprint_index : t -> int option + val pp : Format.formatter -> t -> unit module Map : PrettyPrintable.PPMap with type key = t diff --git a/infer/src/backend/specs.ml b/infer/src/backend/specs.ml index a8a67a0f6..e07bd7ee8 100644 --- a/infer/src/backend/specs.ml +++ b/infer/src/backend/specs.ml @@ -343,17 +343,16 @@ let equal_phase = [%compare.equal : phase] (** Payload: results of some analysis *) type payload = - { preposts: NormSpec.t list option (** list of specs *) - ; typestate: unit TypeState.t option (** final typestate *) - ; annot_map: AnnotReachabilityDomain.astate option + { annot_map: AnnotReachabilityDomain.astate option + ; buffer_overrun: BufferOverrunDomain.Summary.t option ; crashcontext_frame: Stacktree_t.stacktree option - (** Proc location and blame_range info for crashcontext analysis *) + ; litho: LithoDomain.astate option + ; preposts: NormSpec.t list option ; quandary: QuandarySummary.t option + ; racerd: RacerDDomain.summary option ; resources: ResourceLeakDomain.summary option ; siof: SiofDomain.astate option - ; racerd: RacerDDomain.summary option - ; should_update: ShouldUpdateDomain.astate option - ; buffer_overrun: BufferOverrunDomain.Summary.t option + ; typestate: unit TypeState.t option ; uninit: UninitDomain.summary option } type summary = @@ -493,6 +492,7 @@ let pp_payload pe fmt ; quandary ; siof ; racerd + ; litho ; buffer_overrun ; annot_map ; uninit } = @@ -502,7 +502,7 @@ let pp_payload pe fmt | None -> () in - F.fprintf fmt "%a%a%a%a%a%a%a%a%a@\n" + F.fprintf fmt "%a%a%a%a%a%a%a%a%a%a@\n" (pp_opt "PrePosts" (pp_specs pe)) (Option.map ~f:NormSpec.tospecs preposts) (pp_opt "TypeState" (TypeState.pp TypeState.unit_ext)) @@ -512,7 +512,7 @@ let pp_payload pe fmt (pp_opt "Quandary" QuandarySummary.pp) quandary (pp_opt "Siof" SiofDomain.pp) siof (pp_opt "RacerD" RacerDDomain.pp_summary) - racerd + racerd (pp_opt "Litho" LithoDomain.pp) litho (pp_opt "BufferOverrun" BufferOverrunDomain.Summary.pp) buffer_overrun (pp_opt "AnnotationReachability" AnnotReachabilityDomain.pp) @@ -721,7 +721,7 @@ let empty_payload = ; resources= None ; siof= None ; racerd= None - ; should_update= None + ; litho= None ; buffer_overrun= None ; uninit= None } diff --git a/infer/src/backend/specs.mli b/infer/src/backend/specs.mli index 5a55b6672..ff6c1d0cc 100644 --- a/infer/src/backend/specs.mli +++ b/infer/src/backend/specs.mli @@ -128,17 +128,16 @@ val equal_phase : phase -> phase -> bool (** Payload: results of some analysis *) type payload = - { preposts: NormSpec.t list option (** list of specs *) - ; typestate: unit TypeState.t option (** final typestate *) - ; annot_map: AnnotReachabilityDomain.astate option (** list of calls of the form (call, loc) *) - ; crashcontext_frame: Stacktree_j.stacktree option - (** Procedure location and blame_range info for crashcontext analysis *) + { annot_map: AnnotReachabilityDomain.astate option + ; buffer_overrun: BufferOverrunDomain.Summary.t option + ; crashcontext_frame: Stacktree_t.stacktree option + ; litho: LithoDomain.astate option + ; preposts: NormSpec.t list option ; quandary: QuandarySummary.t option + ; racerd: RacerDDomain.summary option ; resources: ResourceLeakDomain.summary option ; siof: SiofDomain.astate option - ; racerd: RacerDDomain.summary option - ; should_update: ShouldUpdateDomain.astate option - ; buffer_overrun: BufferOverrunDomain.Summary.t option + ; typestate: unit TypeState.t option ; uninit: UninitDomain.summary option } (** Procedure summary *) diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 47b4ae6f0..2c9fafdee 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -701,12 +701,12 @@ and ( annotation_reachability , fragment_retains_view , immutable_cast , linters + , litho , liveness , printf_args , quandary , racerd , resource_leak - , should_update , siof , suggest_nullable , uninit ) = @@ -739,6 +739,7 @@ and ( annotation_reachability mk_checker ~long:"immutable-cast" ~default:true "the detection of object cast from immutable type to mutable type. For instance, it will detect cast from ImmutableList to List, ImmutableMap to Map, and ImmutableSet to Set." and linters = mk_checker ~long:"linters" ~default:true "syntactic linters" + and litho = mk_checker ~long:"litho" "Experimental checkers supporting the Litho framework" and liveness = mk_checker ~long:"liveness" ~default:true "the detection of dead stores and unused variables" and printf_args = @@ -749,9 +750,6 @@ and ( annotation_reachability mk_checker ~long:"racerd" ~deprecated:["-threadsafety"] ~default:true "the RacerD thread safety analysis" and resource_leak = mk_checker ~long:"resource-leak" "" - and should_update = - mk_checker ~long:"should-update" - "Experimental checker for identifying GraphQL dependencies of a Litho component" and siof = mk_checker ~long:"siof" ~default:true "the Static Initialization Order Fiasco analysis (C++ only)" @@ -804,12 +802,12 @@ and ( annotation_reachability , fragment_retains_view , immutable_cast , linters + , litho , liveness , printf_args , quandary , racerd , resource_leak - , should_update , siof , suggest_nullable , uninit ) @@ -2503,6 +2501,8 @@ and linters_ignore_clang_failures = !linters_ignore_clang_failures and linters_validate_syntax_only = !linters_validate_syntax_only +and litho = !litho + and liveness = !liveness and load_average = @@ -2623,8 +2623,6 @@ and seconds_per_iteration = !seconds_per_iteration and select = !select -and should_update = !should_update - and show_buckets = !print_buckets and show_progress_bar = !progress_bar diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 2285d6220..79ae9876d 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -515,6 +515,8 @@ val linters_ignore_clang_failures : bool val linters_validate_syntax_only : bool +val litho : bool + val liveness : bool val load_analysis_results : string option @@ -614,8 +616,6 @@ val seconds_per_iteration : float option val select : int option -val should_update : bool - val show_buckets : bool val show_progress_bar : bool diff --git a/infer/src/checkers/Litho.ml b/infer/src/checkers/Litho.ml new file mode 100644 index 000000000..3bb44f06f --- /dev/null +++ b/infer/src/checkers/Litho.ml @@ -0,0 +1,178 @@ +(* + * Copyright (c) 2017 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +module F = Format +module L = Logging +module Domain = LithoDomain + +module Summary = Summary.Make (struct + type payload = Domain.astate + + let update_payload astate (summary: Specs.summary) = + {summary with payload= {summary.payload with litho= Some astate}} + + + let read_payload (summary: Specs.summary) = summary.payload.litho +end) + +module TransferFunctions (CFG : ProcCfg.S) = struct + module CFG = CFG + module Domain = Domain + + type extras = ProcData.no_extras + + let is_graphql_getter procname summary = + Option.is_none summary + (* we skip analysis of all GraphQL procs *) + && + match procname with + | Typ.Procname.Java java_procname + -> ( + PatternMatch.is_getter java_procname + && + match Typ.Procname.java_get_package java_procname with + | Some package -> + String.is_prefix ~prefix:"com.facebook.graphql.model" package + | None -> + false ) + | _ -> + false + + + let apply_callee_summary summary_opt caller_pname ret_opt actuals astate = + match summary_opt with + | Some summary -> + (* TODO: append paths if the footprint access path is an actual path instead of a var *) + let f_sub {Domain.LocalAccessPath.access_path= (var, _), _} = + match Var.get_footprint_index var with + | Some footprint_index -> ( + match List.nth actuals footprint_index with + | Some HilExp.AccessPath actual_access_path -> + Some (Domain.LocalAccessPath.make actual_access_path caller_pname) + | _ -> + None ) + | None -> + if Var.is_return var then + match ret_opt with + | Some ret -> + Some (Domain.LocalAccessPath.make (ret, []) caller_pname) + | None -> + assert false + else None + in + Domain.substitute ~f_sub summary + | None -> + astate + + + let exec_instr astate (proc_data: extras ProcData.t) _ (instr: HilInstr.t) : Domain.astate = + let caller_pname = Procdesc.get_proc_name proc_data.pdesc in + match instr with + | Call + ( (Some return_base as ret_opt) + , Direct callee_procname + , ((HilExp.AccessPath receiver_ap) :: _ as actuals) + , _ + , _ ) -> + let summary = Summary.read_summary proc_data.pdesc callee_procname in + (* track the call if the callee is a graphql getter *or* the receiver is already tracked *) + (* TODO: we should probably track all formals as well *) + let receiver = Domain.LocalAccessPath.make receiver_ap caller_pname in + if is_graphql_getter callee_procname summary || Domain.mem receiver astate then + let receiver = Domain.LocalAccessPath.make receiver_ap caller_pname in + let return_access_path = Domain.LocalAccessPath.make (return_base, []) caller_pname in + let return_calls = + (try Domain.find return_access_path astate with Not_found -> Domain.CallSet.empty) + |> Domain.CallSet.add {receiver; procname= callee_procname} + in + Domain.add return_access_path return_calls astate + else + (* treat it like a normal call *) + apply_callee_summary summary caller_pname ret_opt actuals astate + | Call (ret_opt, Direct callee_procname, actuals, _, _) -> + let summary = Summary.read_summary proc_data.pdesc callee_procname in + apply_callee_summary summary caller_pname ret_opt actuals astate + | Assign (lhs_ap, HilExp.AccessPath rhs_ap, _) + -> ( + (* 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 lhs_ap caller_pname in + let rhs_access_path = Domain.LocalAccessPath.make rhs_ap caller_pname in + try + let call_set = Domain.find rhs_access_path astate in + Domain.remove rhs_access_path astate |> Domain.add lhs_access_path call_set + with Not_found -> astate ) + | _ -> + astate + +end + +module Analyzer = LowerHil.MakeAbstractInterpreter (ProcCfg.Exceptional) (TransferFunctions) + +let report access_path call_chain summary = + let call_strings = List.map ~f:(Typ.Procname.to_simplified_string ~withclass:false) call_chain in + let call_string = String.concat ~sep:"." call_strings in + let message = F.asprintf "%a.%s" AccessPath.pp access_path call_string in + let exn = Exceptions.Checkers (IssueType.graphql_field_access, Localise.verbatim_desc message) in + let loc = Specs.get_loc summary in + let ltr = [Errlog.make_trace_element 0 loc message []] in + Reporting.log_error summary ~loc ~ltr exn + + +let unroll_call call astate summary = + let max_depth = Domain.cardinal astate in + let rec unroll_call_ ({receiver; procname}: Domain.MethodCall.t) (acc, depth) = + let acc' = procname :: acc in + let depth' = depth + 1 in + let is_cycle access_path = + (* detect direct cycles and cycles due to mutual recursion *) + Domain.LocalAccessPath.equal access_path receiver || depth' > max_depth + in + try + let calls' = Domain.find receiver astate in + Domain.CallSet.iter + (fun call -> + if not (is_cycle call.receiver) then unroll_call_ call (acc', depth') + else report receiver.access_path acc' summary) + calls' + with Not_found -> report receiver.access_path acc' summary + in + unroll_call_ call ([], 0) + + +let should_report proc_desc = + match Procdesc.get_proc_name proc_desc with + | Typ.Procname.Java java_pname -> ( + match Typ.Procname.java_get_method java_pname with "onCreateLayout" -> true | _ -> false ) + | _ -> + false + + +let report_call_chains post summary = + Domain.iter + (fun _ call_set -> Domain.CallSet.iter (fun call -> unroll_call call post summary) call_set) + post + + +let postprocess astate proc_desc : Domain.astate = + let formal_map = FormalMap.make proc_desc in + let f_sub access_path = Domain.LocalAccessPath.to_formal_option access_path formal_map in + Domain.substitute ~f_sub astate + + +let checker {Callbacks.summary; proc_desc; tenv} = + let proc_data = ProcData.make_default proc_desc tenv in + match Analyzer.compute_post proc_data ~initial:Domain.empty with + | Some post -> + if should_report proc_desc then report_call_chains post summary ; + let payload = postprocess post proc_desc in + Summary.update_summary payload summary + | None -> + summary diff --git a/infer/src/checkers/LithoDomain.ml b/infer/src/checkers/LithoDomain.ml new file mode 100644 index 000000000..4bf3204e4 --- /dev/null +++ b/infer/src/checkers/LithoDomain.ml @@ -0,0 +1,67 @@ +(* + * Copyright (c) 2017 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + *) + +module F = Format +module L = Logging + +(** Access path + its parent procedure *) +module LocalAccessPath = struct + type t = {access_path: AccessPath.t; parent: Typ.Procname.t} [@@deriving compare] + + let equal = [%compare.equal : t] + + let make access_path parent = {access_path; parent} + + let is_rooted_in_footprint {access_path= (base_var, _), _} = Var.is_footprint base_var + + 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 -> + Some (make ((Var.of_formal_index formal_index, base_typ), accesses) parent) + | None -> + None + + + let pp fmt t = AccessPath.pp fmt t.access_path +end + +(** Called procedure + it's receiver *) +module MethodCall = struct + type t = {receiver: LocalAccessPath.t; procname: Typ.Procname.t} [@@deriving compare] + + let pp fmt {receiver; procname} = + F.fprintf fmt "%a.%a" LocalAccessPath.pp receiver Typ.Procname.pp procname + +end + +module CallSet = AbstractDomain.FiniteSet (MethodCall) +include AbstractDomain.Map (LocalAccessPath) (CallSet) + +let substitute ~(f_sub: LocalAccessPath.t -> LocalAccessPath.t option) astate = + fold + (fun original_access_path call_set acc -> + let access_path' = + match f_sub original_access_path with + | Some access_path -> + access_path + | None -> + original_access_path + in + let call_set' = + CallSet.fold + (fun ({procname} as call) call_set_acc -> + let receiver = + match f_sub call.receiver with Some receiver' -> receiver' | None -> call.receiver + in + CallSet.add {receiver; procname} call_set_acc) + call_set CallSet.empty + in + add access_path' call_set' acc) + astate empty + diff --git a/infer/src/checkers/ShouldUpdate.ml b/infer/src/checkers/ShouldUpdate.ml deleted file mode 100644 index 1037a4387..000000000 --- a/infer/src/checkers/ShouldUpdate.ml +++ /dev/null @@ -1,117 +0,0 @@ -(* - * Copyright (c) 2017 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - *) - -module F = Format -module L = Logging -module Domain = ShouldUpdateDomain - -module Summary = Summary.Make (struct - type payload = Domain.astate - - let update_payload astate (summary: Specs.summary) = - {summary with payload= {summary.payload with should_update= Some astate}} - - - let read_payload (summary: Specs.summary) = summary.payload.should_update -end) - -module TransferFunctions (CFG : ProcCfg.S) = struct - module CFG = CFG - module Domain = Domain - - type extras = ProcData.no_extras - - let is_getter = function - | Typ.Procname.Java procname -> - PatternMatch.is_getter procname - | _ -> - false - - - let exec_instr astate _ _ (instr: HilInstr.t) : Domain.astate = - match instr with - | Call (Some return_base, Direct procname, (HilExp.AccessPath receiver) :: _, _, _) - when is_getter procname -> - let return_access_path = (return_base, []) in - let return_calls = - (try Domain.find return_access_path astate with Not_found -> Domain.CallSet.empty) - |> Domain.CallSet.add {receiver; procname} - in - Domain.add return_access_path return_calls astate - | Call _ -> - (* TODO: interprocedural analysis - (1) add caller actuals to their callee call set (according to the summary) - (2) bind the caller return value to its callee call set (according to the summary - *) - astate - | Assign (lhs_access_path, HilExp.AccessPath rhs_access_path, _) -> ( - try - (* 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 call_set = Domain.find rhs_access_path astate in - Domain.remove rhs_access_path astate |> Domain.add lhs_access_path call_set - with Not_found -> astate ) - | _ -> - astate - -end - -module Analyzer = LowerHil.MakeAbstractInterpreter (ProcCfg.Exceptional) (TransferFunctions) - -let report receiver call_chain summary = - let call_strings = List.map ~f:(Typ.Procname.to_simplified_string ~withclass:false) call_chain in - let call_string = String.concat ~sep:"." call_strings in - let message = F.asprintf "GraphQL getter chain %a.%s" AccessPath.pp receiver call_string in - let exn = Exceptions.Checkers (IssueType.graphql_field_access, Localise.verbatim_desc message) in - let loc = Specs.get_loc summary in - Reporting.log_error summary ~loc exn - - -let unroll_call call astate summary = - let max_depth = Domain.cardinal astate in - let rec unroll_call_ ({receiver; procname}: Domain.CallWithReceiver.t) (acc, depth) = - let acc' = procname :: acc in - let depth' = depth + 1 in - let is_cycle access_path = - (* detect direct cycles and cycles due to mutual recursion *) - AccessPath.equal access_path receiver || depth' > max_depth - in - try - let calls' = Domain.find receiver astate in - Domain.CallSet.iter - (fun call -> if not (is_cycle call.receiver) then unroll_call_ call (acc', depth')) - calls' - with Not_found -> report receiver acc' summary - in - unroll_call_ call ([], 0) - - -let should_report proc_desc = - match Procdesc.get_proc_name proc_desc with - | Typ.Procname.Java java_pname -> ( - match Typ.Procname.java_get_method java_pname with "onCreateLayout" -> true | _ -> false ) - | _ -> - false - - -let report_call_chains post summary = - Domain.iter - (fun _ call_set -> Domain.CallSet.iter (fun call -> unroll_call call post summary) call_set) - post - - -let checker {Callbacks.summary; proc_desc; tenv} = - let proc_data = ProcData.make_default proc_desc tenv in - match Analyzer.compute_post proc_data ~initial:Domain.empty with - | Some post -> - if should_report proc_desc then report_call_chains post summary ; - summary - | None -> - summary diff --git a/infer/src/checkers/ShouldUpdateDomain.ml b/infer/src/checkers/ShouldUpdateDomain.ml deleted file mode 100644 index 0cd8f86ed..000000000 --- a/infer/src/checkers/ShouldUpdateDomain.ml +++ /dev/null @@ -1,22 +0,0 @@ -(* - * Copyright (c) 2017 - present Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - *) - -module F = Format -module L = Logging - -module CallWithReceiver = struct - type t = {receiver: AccessPath.t; procname: Typ.Procname.t} [@@deriving compare] - - let pp fmt {receiver; procname} = - F.fprintf fmt "%a.%a" AccessPath.pp receiver Typ.Procname.pp procname - -end - -module CallSet = AbstractDomain.FiniteSet (CallWithReceiver) -include AbstractDomain.Map (AccessPath) (CallSet) diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml index c2a378d42..5f28341d8 100644 --- a/infer/src/checkers/registerCheckers.ml +++ b/infer/src/checkers/registerCheckers.ml @@ -89,9 +89,7 @@ let all_checkers = interprocedural later on *) Procedure ResourceLeaks.checker , Config.Java ) ] } - ; { name= "should update" - ; active= Config.should_update - ; callbacks= [(Procedure ShouldUpdate.checker, Config.Java)] } + ; {name= "litho"; active= Config.litho; callbacks= [(Procedure Litho.checker, Config.Java)]} ; {name= "SIOF"; active= Config.siof; callbacks= [(Procedure Siof.checker, Config.Clang)]} ; { name= "uninitialized variables" ; active= Config.uninit @@ -134,3 +132,4 @@ let pp_checker fmt {name; callbacks} = F.fprintf fmt "%s (%a)" name (Pp.seq ~sep:", " (Pp.to_string ~f:Config.string_of_language)) langs_of_callbacks + diff --git a/infer/tests/codetoanalyze/java/litho/Makefile b/infer/tests/codetoanalyze/java/litho/Makefile new file mode 100644 index 000000000..fda568703 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho/Makefile @@ -0,0 +1,15 @@ +# Copyright (c) 2016 - present Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the BSD style license found in the +# LICENSE file in the root directory of this source tree. An additional grant +# of patent rights can be found in the PATENTS file in the same directory. + +TESTS_DIR = ../../.. + +ANALYZER = checkers +INFER_OPTIONS = --litho-only --debug-exceptions +INFERPRINT_OPTIONS = --issues-tests +SOURCES = $(wildcard *.java) + +include $(TESTS_DIR)/javac.make diff --git a/infer/tests/codetoanalyze/java/litho/ShouldUpdate.java b/infer/tests/codetoanalyze/java/litho/ShouldUpdate.java new file mode 100644 index 000000000..de1e9735b --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho/ShouldUpdate.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 - present Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +// makes it easier to mock graphql types +package com.facebook.graphql.model; + +import java.util.List; + +abstract class A { + abstract A getA(); + abstract B getB(); + abstract C getC(); +} + +abstract class B { + abstract C getC(); +} + +abstract class C { + abstract D getD(); +} + +class D {} + +abstract class GraphQLStory { + + public abstract List getActors(); + +} + +class LithoTest { + + void /*basic chain*/onCreateLayout(A a) { + a.getB().getC().getD(); + } + + void /*sibling chain*/onCreateLayout(A a, int i) { + a.getB().getC().getD(); + a.getC().getD(); + } + + void /*split chain*/onCreateLayout(A a, int i1, int i2) { + B b = a.getB(); + C c = b.getC(); + c.getD(); + } + + + void chainFromActual1(B b) { + b.getC().getD(); + } + + void chainFromActual2(C c) { + c.getD(); + } + + void /*chain rooted in actual*/onCreateLayout(A a, boolean b) { + chainFromActual1(a.getB()); + } + + void /*local chain + interproc chain*/onCreateLayout(A a, char ch) { + C c = a.getB().getC(); + chainFromActual2(c); + } + + // conditional getters + static GraphQLStory getPrimaryActor(GraphQLStory story) { + List actors = story.getActors(); + return actors != null && actors.size() > 0 ? (GraphQLStory) actors.get(0) : null; + } + + void /*conditional getters on formal*/onCreateLayout(GraphQLStory story) { + getPrimaryActor(story).toString(); + } + + native static GraphQLStory getStory(); + + void /*conditional getters on local*/onCreateLayout() { + GraphQLStory story = getStory(); + getPrimaryActor(story).toString(); + } + + void /*cycle*/onCreateLayout(A a, float f) { + a = a.getA(); + } + + void cycle(A a) { + a = a.getA(); + } + + void /*interprocedural cycle*/onCreateLayout(A a, double d) { + cycle(a); + } + +} diff --git a/infer/tests/codetoanalyze/java/litho/issues.exp b/infer/tests/codetoanalyze/java/litho/issues.exp new file mode 100644 index 000000000..474bbe8b6 --- /dev/null +++ b/infer/tests/codetoanalyze/java/litho/issues.exp @@ -0,0 +1,27 @@ +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().get(...)] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().get(...).toString()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().size()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A), 0, GRAPHQL_FIELD_ACCESS, [&a.getB()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,boolean), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,boolean), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,boolean), 0, GRAPHQL_FIELD_ACCESS, [&a.getB()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,char), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,char), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,char), 0, GRAPHQL_FIELD_ACCESS, [&a.getB()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,double), 0, GRAPHQL_FIELD_ACCESS, [&a.getA()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,float), 0, GRAPHQL_FIELD_ACCESS, [&a.getA()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC().getD()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB().getC()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(A,int,int), 0, GRAPHQL_FIELD_ACCESS, [&a.getB()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(GraphQLStory), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().get(...).toString()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(GraphQLStory), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors()] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(GraphQLStory), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().get(...)] +codetoanalyze/java/litho/ShouldUpdate.java, void LithoTest.onCreateLayout(GraphQLStory), 0, GRAPHQL_FIELD_ACCESS, [&story.getActors().size()]