Summary: This checker is for internal usage. Reviewed By: ezgicicek Differential Revision: D26453750 fbshipit-source-id: a9397f2e4master
							parent
							
								
									c6a253b875
								
							
						
					
					
						commit
						7e6654cd25
					
				| @ -0,0 +1,239 @@ | |||||||
|  | (* | ||||||
|  |  * 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 ConfigName = FbGKInteraction.ConfigName | ||||||
|  | 
 | ||||||
|  | module Branch = struct | ||||||
|  |   type t = True | False | Top | ||||||
|  | 
 | ||||||
|  |   let pp f = function | ||||||
|  |     | True -> | ||||||
|  |         F.pp_print_string f "true branch" | ||||||
|  |     | False -> | ||||||
|  |         F.pp_print_string f "false branch" | ||||||
|  |     | Top -> | ||||||
|  |         AbstractDomain.TopLiftedUtils.pp_top f | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let leq ~lhs ~rhs = | ||||||
|  |     match (lhs, rhs) with True, True | False, False | _, Top -> true | _, _ -> false | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let join x y = match (x, y) with True, True -> True | False, False -> False | _, _ -> Top | ||||||
|  | 
 | ||||||
|  |   let widen ~prev ~next ~num_iters:_ = join prev next | ||||||
|  | 
 | ||||||
|  |   let top = Top | ||||||
|  | 
 | ||||||
|  |   let is_top = function Top -> true | True | False -> false | ||||||
|  | 
 | ||||||
|  |   let neg = function True -> False | False -> True | Top -> Top | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module ConfigChecks = AbstractDomain.SafeInvertedMap (ConfigName) (Branch) | ||||||
|  | 
 | ||||||
|  | module UncheckedCallee = struct | ||||||
|  |   type t = {callee: Procname.t; location: Location.t} [@@deriving compare] | ||||||
|  | 
 | ||||||
|  |   let pp f {callee; location} = | ||||||
|  |     F.fprintf f "%a is called at %a" Procname.pp callee Location.pp location | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let get_location {location} = location | ||||||
|  | 
 | ||||||
|  |   let report {InterproceduralAnalysis.proc_desc; err_log} x = | ||||||
|  |     let desc = F.asprintf "%a without config check" pp x in | ||||||
|  |     let trace = | ||||||
|  |       (* TODO *) | ||||||
|  |       [] | ||||||
|  |     in | ||||||
|  |     Reporting.log_issue proc_desc err_log ~loc:(get_location x) ~ltr:trace ConfigImpactAnalysis | ||||||
|  |       IssueType.config_impact_analysis desc | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module UncheckedCallees = struct | ||||||
|  |   include AbstractDomain.FiniteSet (UncheckedCallee) | ||||||
|  | 
 | ||||||
|  |   let report analysis_data unchecked_callees = | ||||||
|  |     iter (UncheckedCallee.report analysis_data) unchecked_callees | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Loc = struct | ||||||
|  |   type t = Ident of Ident.t | Pvar of Pvar.t [@@deriving compare] | ||||||
|  | 
 | ||||||
|  |   let pp f = function Ident id -> Ident.pp f id | Pvar pvar -> Pvar.pp Pp.text f pvar | ||||||
|  | 
 | ||||||
|  |   let of_id id = Ident id | ||||||
|  | 
 | ||||||
|  |   let of_pvar pvar = Pvar pvar | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module ConfigLifted = AbstractDomain.Flat (ConfigName) | ||||||
|  | 
 | ||||||
|  | module Val = struct | ||||||
|  |   type t = {config: ConfigLifted.t} | ||||||
|  | 
 | ||||||
|  |   let pp f {config} = F.fprintf f "@[config:@,%a@]" ConfigLifted.pp config | ||||||
|  | 
 | ||||||
|  |   let leq ~lhs ~rhs = ConfigLifted.leq ~lhs:lhs.config ~rhs:rhs.config | ||||||
|  | 
 | ||||||
|  |   let join x y = {config= ConfigLifted.join x.config y.config} | ||||||
|  | 
 | ||||||
|  |   let widen ~prev ~next ~num_iters = | ||||||
|  |     {config= ConfigLifted.widen ~prev:prev.config ~next:next.config ~num_iters} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let bottom = {config= ConfigLifted.bottom} | ||||||
|  | 
 | ||||||
|  |   let of_config config = {config= ConfigLifted.v config} | ||||||
|  | 
 | ||||||
|  |   let get_config_opt {config} = ConfigLifted.get config | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Mem = struct | ||||||
|  |   include AbstractDomain.Map (Loc) (Val) | ||||||
|  | 
 | ||||||
|  |   let lookup loc mem = find_opt loc mem |> Option.value ~default:Val.bottom | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Summary = struct | ||||||
|  |   type t = {unchecked_callees: UncheckedCallees.t} | ||||||
|  | 
 | ||||||
|  |   let pp f {unchecked_callees} = | ||||||
|  |     F.fprintf f "@[unchecked callees:@,%a@]" UncheckedCallees.pp unchecked_callees | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Dom = struct | ||||||
|  |   type t = {config_checks: ConfigChecks.t; unchecked_callees: UncheckedCallees.t; mem: Mem.t} | ||||||
|  | 
 | ||||||
|  |   let pp f {config_checks; unchecked_callees; mem} = | ||||||
|  |     F.fprintf f "@[@[config checks:@,%a@]@ @[unchecked callees:@,%a@]@ @[mem:%,%a@]@]" | ||||||
|  |       ConfigChecks.pp config_checks UncheckedCallees.pp unchecked_callees Mem.pp mem | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let leq ~lhs ~rhs = | ||||||
|  |     ConfigChecks.leq ~lhs:lhs.config_checks ~rhs:rhs.config_checks | ||||||
|  |     && UncheckedCallees.leq ~lhs:lhs.unchecked_callees ~rhs:rhs.unchecked_callees | ||||||
|  |     && Mem.leq ~lhs:lhs.mem ~rhs:rhs.mem | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let join x y = | ||||||
|  |     { config_checks= ConfigChecks.join x.config_checks y.config_checks | ||||||
|  |     ; unchecked_callees= UncheckedCallees.join x.unchecked_callees y.unchecked_callees | ||||||
|  |     ; mem= Mem.join x.mem y.mem } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let widen ~prev ~next ~num_iters = | ||||||
|  |     { config_checks= ConfigChecks.widen ~prev:prev.config_checks ~next:next.config_checks ~num_iters | ||||||
|  |     ; unchecked_callees= | ||||||
|  |         UncheckedCallees.widen ~prev:prev.unchecked_callees ~next:next.unchecked_callees ~num_iters | ||||||
|  |     ; mem= Mem.widen ~prev:prev.mem ~next:next.mem ~num_iters } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let to_summary {unchecked_callees} = {Summary.unchecked_callees} | ||||||
|  | 
 | ||||||
|  |   let init = | ||||||
|  |     {config_checks= ConfigChecks.top; unchecked_callees= UncheckedCallees.bottom; mem= Mem.bottom} | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let add_mem loc v ({mem} as astate) = {astate with mem= Mem.add loc v mem} | ||||||
|  | 
 | ||||||
|  |   let copy_mem ~tgt ~src ({mem} as astate) = add_mem tgt (Mem.lookup src mem) astate | ||||||
|  | 
 | ||||||
|  |   let call_config_check ret config astate = add_mem (Loc.of_id ret) (Val.of_config config) astate | ||||||
|  | 
 | ||||||
|  |   let load_config id pvar astate = copy_mem ~tgt:(Loc.of_id id) ~src:(Loc.of_pvar pvar) astate | ||||||
|  | 
 | ||||||
|  |   let store_config pvar id astate = copy_mem ~tgt:(Loc.of_pvar pvar) ~src:(Loc.of_id id) astate | ||||||
|  | 
 | ||||||
|  |   let boolean_value id_tgt id_src astate = | ||||||
|  |     copy_mem ~tgt:(Loc.of_id id_tgt) ~src:(Loc.of_id id_src) astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let neg_branch res = Option.map ~f:(fun (config, branch) -> (config, Branch.neg branch)) res | ||||||
|  | 
 | ||||||
|  |   let rec get_config_check_prune e mem = | ||||||
|  |     match (e : Exp.t) with | ||||||
|  |     | Var id -> | ||||||
|  |         Mem.lookup (Loc.of_id id) mem | ||||||
|  |         |> Val.get_config_opt | ||||||
|  |         |> Option.map ~f:(fun config -> (config, Branch.True)) | ||||||
|  |     | UnOp (LNot, e, _) -> | ||||||
|  |         get_config_check_prune e mem |> neg_branch | ||||||
|  |     | BinOp ((Eq | Ne), Const _, Const _) -> | ||||||
|  |         None | ||||||
|  |     | (BinOp (Eq, e, (Const _ as const)) | BinOp (Eq, (Const _ as const), e)) when Exp.is_zero const | ||||||
|  |       -> | ||||||
|  |         get_config_check_prune e mem |> neg_branch | ||||||
|  |     | (BinOp (Ne, e, (Const _ as const)) | BinOp (Ne, (Const _ as const), e)) when Exp.is_zero const | ||||||
|  |       -> | ||||||
|  |         get_config_check_prune e mem | ||||||
|  |     | _ -> | ||||||
|  |         None | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let prune e ({config_checks; mem} as astate) = | ||||||
|  |     get_config_check_prune e mem | ||||||
|  |     |> Option.value_map ~default:astate ~f:(fun (config, branch) -> | ||||||
|  |            {astate with config_checks= ConfigChecks.add config branch config_checks} ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let call callee location ({config_checks; unchecked_callees} as astate) = | ||||||
|  |     if ConfigChecks.is_top config_checks then | ||||||
|  |       let unchecked_callee = UncheckedCallee.{callee; location} in | ||||||
|  |       {astate with unchecked_callees= UncheckedCallees.add unchecked_callee unchecked_callees} | ||||||
|  |     else astate | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module TransferFunctions = struct | ||||||
|  |   module CFG = ProcCfg.NormalOneInstrPerNode | ||||||
|  |   module Domain = Dom | ||||||
|  | 
 | ||||||
|  |   type analysis_data = Summary.t InterproceduralAnalysis.t | ||||||
|  | 
 | ||||||
|  |   let is_java_boolean_value_method pname = | ||||||
|  |     Procname.get_class_name pname |> Option.exists ~f:(String.equal "java.lang.Boolean") | ||||||
|  |     && Procname.get_method pname |> String.equal "booleanValue" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let exec_instr astate {InterproceduralAnalysis.tenv} _node instr = | ||||||
|  |     match (instr : Sil.instr) with | ||||||
|  |     | Load {id; e= Lvar pvar} -> | ||||||
|  |         Dom.load_config id pvar astate | ||||||
|  |     | Store {e1= Lvar pvar; e2= Var id} -> | ||||||
|  |         Dom.store_config pvar id astate | ||||||
|  |     | Call ((ret, _), Const (Cfun callee), [(Var id, _)], _, _) | ||||||
|  |       when is_java_boolean_value_method callee -> | ||||||
|  |         Dom.boolean_value ret id astate | ||||||
|  |     | Call ((ret, _), Const (Cfun callee), args, location, _) -> ( | ||||||
|  |       match FbGKInteraction.get_config_check tenv callee args with | ||||||
|  |       | Some (`Config config) -> | ||||||
|  |           Dom.call_config_check ret config astate | ||||||
|  |       | Some (`Exp _) -> | ||||||
|  |           astate | ||||||
|  |       | None -> | ||||||
|  |           (* normal function calls *) | ||||||
|  |           Dom.call callee location astate ) | ||||||
|  |     | Prune (e, _, _, _) -> | ||||||
|  |         Dom.prune e astate | ||||||
|  |     | _ -> | ||||||
|  |         astate | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   let pp_session_name node fmt = | ||||||
|  |     F.fprintf fmt "Config impact function calls %a" CFG.Node.pp_id (CFG.Node.id node) | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | module Analyzer = AbstractInterpreter.MakeWTO (TransferFunctions) | ||||||
|  | 
 | ||||||
|  | let checker ({InterproceduralAnalysis.proc_desc} as analysis_data) = | ||||||
|  |   Option.map (Analyzer.compute_post analysis_data ~initial:Dom.init proc_desc) | ||||||
|  |     ~f:(fun ({Dom.unchecked_callees} as astate) -> | ||||||
|  |       UncheckedCallees.report analysis_data unchecked_callees ; | ||||||
|  |       Dom.to_summary astate ) | ||||||
| @ -0,0 +1,16 @@ | |||||||
|  | (* | ||||||
|  |  * 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 Summary : sig | ||||||
|  |   type t | ||||||
|  | 
 | ||||||
|  |   val pp : Format.formatter -> t -> unit | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | val checker : Summary.t InterproceduralAnalysis.t -> Summary.t option | ||||||
| @ -0,0 +1,13 @@ | |||||||
|  | # 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.
 | ||||||
|  | 
 | ||||||
|  | TESTS_DIR = ../../.. | ||||||
|  | 
 | ||||||
|  | INFER_OPTIONS = --config-impact-analysis-only --debug-exceptions \
 | ||||||
|  |   --report-force-relative-path | ||||||
|  | INFERPRINT_OPTIONS = --issues-tests | ||||||
|  | SOURCES = $(wildcard *.java) | ||||||
|  | 
 | ||||||
|  | include $(TESTS_DIR)/javac.make | ||||||
					Loading…
					
					
				
		Reference in new issue