[experimental] interprocedural should_update analysis

Reviewed By: jeremydubreil

Differential Revision: D6411833

fbshipit-source-id: 4609a6b
master
Sam Blackshear 7 years ago committed by Facebook Github Bot
parent 303b79b6ae
commit f37344358b

@ -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

@ -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

@ -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 }

@ -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 *)

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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)

@ -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

@ -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

@ -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);
}
}

@ -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()]
Loading…
Cancel
Save