Reviewed By: jeremydubreil Differential Revision: D6411833 fbshipit-source-id: 4609a6bmaster
parent
303b79b6ae
commit
f37344358b
@ -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)
|
@ -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…
Reference in new issue