Reviewed By: jeremydubreil Differential Revision: D6363711 fbshipit-source-id: 4aed9famaster
parent
63183f94f1
commit
ca3f71a8b3
@ -0,0 +1,119 @@
|
||||
(*
|
||||
* 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 issue_id = IssueType.graphql_field_access.unique_id in
|
||||
let exn = Exceptions.Checkers (issue_id, 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
|
||||
|
@ -0,0 +1,22 @@
|
||||
(*
|
||||
* 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)
|
Loading…
Reference in new issue