From 49e582fa49ff6a455a4eee77f9cf87dfbaf9d20b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ezgi=20=C3=87i=C3=A7ek?= Date: Sat, 25 Aug 2018 02:17:38 -0700 Subject: [PATCH] [Loop-hoisting] Add a new checker for hoisting invariant function calls Reviewed By: mbouaziz Differential Revision: D9460455 fbshipit-source-id: 7488c1a69 --- Makefile | 2 +- infer/src/absint/ProcCfg.ml | 27 ++++ infer/src/absint/ProcCfg.mli | 26 ++++ infer/src/base/Config.ml | 5 + infer/src/base/Config.mli | 2 + infer/src/base/IssueType.ml | 2 + infer/src/base/IssueType.mli | 2 + infer/src/checkers/dominators.ml | 33 ++++ infer/src/checkers/hoisting.ml | 104 +++++++++++++ infer/src/checkers/loopInvariant.ml | 43 +++--- infer/src/checkers/loop_control.ml | 16 +- infer/src/checkers/registerCheckers.ml | 5 + infer/src/dune.common.in | 1 + .../codetoanalyze/java/hoisting/Hoist.java | 143 ++++++++++++++++++ .../codetoanalyze/java/hoisting/Makefile | 13 ++ .../codetoanalyze/java/hoisting/issues.exp | 9 ++ opam | 1 + opam.lock | 1 + scripts/toplevel_init | 1 + 19 files changed, 410 insertions(+), 26 deletions(-) create mode 100644 infer/src/checkers/dominators.ml create mode 100644 infer/src/checkers/hoisting.ml create mode 100644 infer/tests/codetoanalyze/java/hoisting/Hoist.java create mode 100644 infer/tests/codetoanalyze/java/hoisting/Makefile create mode 100644 infer/tests/codetoanalyze/java/hoisting/issues.exp diff --git a/Makefile b/Makefile index d42558ab8..53f42a6ef 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ BUILD_SYSTEMS_TESTS += \ DIRECT_TESTS += \ java_checkers java_eradicate java_infer java_lab java_tracing java_quandary \ - java_racerd java_stability java_crashcontext java_starvation java_performance + java_racerd java_stability java_crashcontext java_hoisting java_starvation java_performance ifneq ($(ANT),no) BUILD_SYSTEMS_TESTS += ant endif diff --git a/infer/src/absint/ProcCfg.ml b/infer/src/absint/ProcCfg.ml index 61e6f6697..24e7287c1 100644 --- a/infer/src/absint/ProcCfg.ml +++ b/infer/src/absint/ProcCfg.ml @@ -373,3 +373,30 @@ end = struct end module NormalOneInstrPerNode = OneInstrPerNode (Normal) + +(* Make ProcCfg compatible with ocamlgraph *) +module MakeOcamlGraph (Base : S) = struct + type t = Base.t + + module V = struct + type t = Base.Node.t + + let compare n1 n2 = Base.Node.compare_id (Base.Node.id n1) (Base.Node.id n2) + + let equal = [%compare.equal : t] + + let hash = Base.Node.hash + end + + let pred g = IContainer.to_rev_list ~fold:(Base.fold_normal_preds g) + + let succ g = IContainer.to_rev_list ~fold:(Base.fold_normal_succs g) + + let iter_succ f g node = Container.iter ~fold:(Base.fold_normal_succs g) ~f node + + let fold_vertex f g init = Base.fold_nodes ~init ~f:(Fn.flip f) g + + let iter_vertex f g = Container.iter ~fold:Base.fold_nodes g ~f + + let nb_vertex = Container.length ~fold:Base.fold_nodes +end diff --git a/infer/src/absint/ProcCfg.mli b/infer/src/absint/ProcCfg.mli index 9761e8605..fefc7ad01 100644 --- a/infer/src/absint/ProcCfg.mli +++ b/infer/src/absint/ProcCfg.mli @@ -116,3 +116,29 @@ module OneInstrPerNode (Base : S with module Node = DefaultNode) : sig end module NormalOneInstrPerNode : module type of OneInstrPerNode (Normal) + +module MakeOcamlGraph (Base : S) : sig + type t = Base.t + + module V : sig + type t = Base.Node.t + + val equal : t -> t -> bool + + val compare : t -> t -> int + + val hash : t -> int + end + + val pred : t -> Base.Node.t -> Base.Node.t list + + val succ : t -> Base.Node.t -> Base.Node.t list + + val fold_vertex : (Base.Node.t -> 'a -> 'a) -> t -> 'a -> 'a + + val iter_vertex : (Base.Node.t -> unit) -> t -> unit + + val iter_succ : (Base.Node.t -> unit) -> t -> Base.Node.t -> unit + + val nb_vertex : t -> int +end diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 46e5eb19e..8a0315457 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -665,6 +665,7 @@ and ( annotation_reachability , linters , litho , liveness + , loop_hoisting , ownership , printf_args , quandary @@ -713,6 +714,7 @@ and ( annotation_reachability 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 loop_hoisting = mk_checker ~long:"loop-hoisting" ~default:false "checker for loop-hoisting" and ownership = mk_checker ~long:"ownership" ~default:true "the detection of C++ lifetime bugs" and printf_args = mk_checker ~long:"printf-args" ~default:true @@ -779,6 +781,7 @@ and ( annotation_reachability , linters , litho , liveness + , loop_hoisting , ownership , printf_args , quandary @@ -2695,6 +2698,8 @@ and log_events = !log_events and log_file = !log_file +and loop_hoisting = !loop_hoisting + and max_nesting = !max_nesting and method_decls_info = !method_decls_info diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index 4999a284b..e7ce6ae19 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -458,6 +458,8 @@ val log_events : bool val log_file : string +val loop_hoisting : bool + val max_nesting : int option val method_decls_info : string option diff --git a/infer/src/base/IssueType.ml b/infer/src/base/IssueType.ml index 309f4f772..82d1ea1d6 100644 --- a/infer/src/base/IssueType.ml +++ b/infer/src/base/IssueType.ml @@ -256,6 +256,8 @@ let interface_not_thread_safe = from_string "INTERFACE_NOT_THREAD_SAFE" let internal_error = from_string "Internal_error" +let invariant_call = from_string "INVARIANT_CALL" + let javascript_injection = from_string "JAVASCRIPT_INJECTION" let leak_after_array_abstraction = from_string "Leak_after_array_abstraction" diff --git a/infer/src/base/IssueType.mli b/infer/src/base/IssueType.mli index efc0880a5..e18705813 100644 --- a/infer/src/base/IssueType.mli +++ b/infer/src/base/IssueType.mli @@ -173,6 +173,8 @@ val interface_not_thread_safe : t val internal_error : t +val invariant_call : t + val javascript_injection : t val leak_after_array_abstraction : t diff --git a/infer/src/checkers/dominators.ml b/infer/src/checkers/dominators.ml new file mode 100644 index 000000000..62db7afa3 --- /dev/null +++ b/infer/src/checkers/dominators.ml @@ -0,0 +1,33 @@ +(* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) +open! IStd +open Graph +module L = Logging +module CFG = ProcCfg.Normal +(* Use ocamlgraph's dominators functor to get the dominators *) +module GDoms = Dominator.Make (ProcCfg.MakeOcamlGraph (CFG)) + +let print_dominators cfg idom = + L.(debug Analysis Medium) "@\n ----- **** Dominators TABLE **** -------@\n" ; + Procdesc.get_nodes cfg + |> List.iter ~f:(fun n -> + L.(debug Analysis Medium) "@\n Node: %a Dominators:\n" Procdesc.Node.pp n ; + List.iter + ~f:(L.(debug Analysis Medium) "%a;" Procdesc.Node.pp) + (GDoms.idom_to_dominators idom n) ) + + +(* Computes the dominator tree, using ocamlgraph's Lengauer-Tarjan algorithm. + [compute_idom cfg node] returns a function [idom : Procdesc.Node.t -> Procdesc.Node.t] s.t. + [idom n] returns the immediate dominator of [n]. *) +let get_idoms pdesc = + let idom = GDoms.compute_idom pdesc (ProcCfg.Normal.start_node pdesc) in + print_dominators pdesc idom ; idom + + +(* make each node to be dominated by itself, i.e reflexive, unlike ocamlgraph *) +let dominates idom x y = GDoms.idom_to_dom idom x y || Procdesc.Node.equal x y diff --git a/infer/src/checkers/hoisting.ml b/infer/src/checkers/hoisting.ml new file mode 100644 index 000000000..80fbd7428 --- /dev/null +++ b/infer/src/checkers/hoisting.ml @@ -0,0 +1,104 @@ +(* + * Copyright (c) 2018-present, Facebook, Inc. + * + * 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 InstrCFG = ProcCfg.NormalOneInstrPerNode + +module Call = struct + type t = {pname: Typ.Procname.t; loc: Location.t} [@@deriving compare] + + let pp fmt {pname; loc} = + F.fprintf fmt "loop-invariant call to %a, at %a " Typ.Procname.pp pname Location.pp loc +end + +module LoopNodes = AbstractDomain.FiniteSet (Procdesc.Node) +module HoistCalls = AbstractDomain.FiniteSet (Call) + +(** Map loop_header -> instrs that can be hoisted out of the loop *) +module LoopHeadToHoistInstrs = +Procdesc.NodeMap + +(* A loop-invariant function call C(args) at node N can be hoisted out of the loop if + * + * 1. C is guaranteed to execute, i.e. N dominates all loop sources + * 2. args are loop invariant *) + +let add_if_hoistable inv_vars instr node source_nodes idom hoistable_calls = + match instr with + | Sil.Call ((ret_id, _), Exp.Const (Const.Cfun pname), _, loc, _) + when (* Check condition (1); N dominates all loop sources *) + List.for_all ~f:(fun source -> Dominators.dominates idom node source) source_nodes + && (* Check condition (2); id should be invariant already *) + LoopInvariant.InvariantVars.mem (Var.of_id ret_id) inv_vars -> + HoistCalls.add {pname; loc} hoistable_calls + | _ -> + hoistable_calls + + +let get_hoistable_calls inv_vars loop_nodes source_nodes idom = + LoopNodes.fold + (fun node hoist_calls -> + let instr_in_node = Procdesc.Node.get_instrs node in + Instrs.fold ~init:hoist_calls + ~f:(fun acc instr -> add_if_hoistable inv_vars instr node source_nodes idom acc) + instr_in_node ) + loop_nodes HoistCalls.empty + + +let get_hoist_inv_map tenv reaching_defs_invariant_map loop_head_to_source_nodes idom = + Procdesc.NodeMap.fold + (fun loop_head source_nodes inv_map -> + (* get all the nodes in the loop *) + let loop_nodes = Loop_control.get_all_nodes_upwards_until loop_head source_nodes in + let inv_vars_in_loop = + LoopInvariant.get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes + ~is_inv_by_default:true + in + let hoist_instrs = get_hoistable_calls inv_vars_in_loop loop_nodes source_nodes idom in + LoopHeadToHoistInstrs.add loop_head hoist_instrs inv_map ) + loop_head_to_source_nodes LoopHeadToHoistInstrs.empty + + +let do_report summary Call.({pname; loc}) loop_head_loc = + let exp_desc = + F.asprintf "Loop-invariant call to %a at %a" Typ.Procname.pp pname Location.pp loc + in + let ltr = [Errlog.make_trace_element 0 loc exp_desc []] in + let exn = + let message = + F.asprintf "%s can be moved out of the loop at %a." exp_desc Location.pp loop_head_loc + in + Exceptions.Checkers (IssueType.invariant_call, Localise.verbatim_desc message) + in + Reporting.log_error summary ~loc ~ltr exn + + +let checker {Callbacks.tenv; summary; proc_desc} : Summary.t = + let cfg = InstrCFG.from_pdesc proc_desc in + let proc_data = ProcData.make_default proc_desc tenv in + (* computes reaching defs: node -> (var -> node set) *) + let reaching_defs_invariant_map = + ReachingDefs.Analyzer.exec_cfg cfg proc_data + ~initial:(ReachingDefs.init_reaching_defs_with_formals proc_desc) + ~debug:false + in + (* get dominators *) + let idom = Dominators.get_idoms proc_desc in + let loop_head_to_source_nodes = Loop_control.get_loop_head_to_source_nodes cfg in + (* get a map, loop head -> instrs that can be hoisted out of the loop *) + let loop_head_to_inv_instrs = + get_hoist_inv_map tenv reaching_defs_invariant_map loop_head_to_source_nodes idom + in + (* report function calls to hoist (per loop) *) + (* Note: we report the innermost loop for hoisting out. TODO: Future + optimization, take out as further up as possible.*) + LoopHeadToHoistInstrs.iter + (fun loop_head inv_instrs -> + let loop_head_loc = Procdesc.Node.get_loc loop_head in + HoistCalls.iter (fun call -> do_report summary call loop_head_loc) inv_instrs ) + loop_head_to_inv_instrs ; + summary diff --git a/infer/src/checkers/loopInvariant.ml b/infer/src/checkers/loopInvariant.ml index 72837b6e1..73c190f39 100644 --- a/infer/src/checkers/loopInvariant.ml +++ b/infer/src/checkers/loopInvariant.ml @@ -19,34 +19,40 @@ let is_defined_outside loop_nodes reaching_defs var = |> Option.value ~default:true -(* check if the def of var is unique and satisfies function f_exp *) -let is_def_unique_and_satisfy tenv var (loop_nodes: LoopNodes.t) f_exp = +let is_fun_call_invariant tenv ~is_exp_invariant ~is_inv_by_default callee_pname params = + List.for_all ~f:(fun (exp, _) -> is_exp_invariant exp) params + && + match + (* Take into account invariance behavior of modeled functions *) + Models.Call.dispatch tenv callee_pname params + with + | Some inv -> + InvariantModels.is_invariant inv + | None -> + is_inv_by_default + + +(* check if the def of var is unique and invariant *) +let is_def_unique_and_satisfy tenv var (loop_nodes: LoopNodes.t) ~is_inv_by_default + is_exp_invariant = let equals_var id = Var.equal var (Var.of_id id) in (* Use O(1) is_singleton check *) (* tedious parameter wrangling to make IContainer's fold interface happy *) IContainer.is_singleton - ~fold:(fun s ~init ~f -> LoopNodes.fold (fun acc x -> f x acc) s init) + ~fold:(fun s ~init ~f -> LoopNodes.fold (fun node acc -> f acc node) s init) loop_nodes && LoopNodes.for_all (fun node -> Procdesc.Node.get_instrs node |> Instrs.exists ~f:(function - | Sil.Load (id, exp_rhs, _, _) when equals_var id && f_exp exp_rhs -> + | Sil.Load (id, exp_rhs, _, _) when equals_var id && is_exp_invariant exp_rhs -> true | Sil.Store (exp_lhs, _, exp_rhs, _) - when Exp.equal exp_lhs (Var.to_exp var) && f_exp exp_rhs -> + when Exp.equal exp_lhs (Var.to_exp var) && is_exp_invariant exp_rhs -> true - | Sil.Call ((id, _), Const (Cfun callee_pname), params, _, _) when equals_var id -> ( - match - (* Take into account invariance behavior of modeled - functions *) - Models.Call.dispatch tenv callee_pname params - with - | Some inv -> - InvariantModels.is_invariant inv - && List.for_all ~f:(fun (exp, _) -> f_exp exp) params - | None -> - Config.cost_invariant_by_default ) + | Sil.Call ((id, _), Const (Cfun callee_pname), params, _, _) when equals_var id -> + is_fun_call_invariant tenv ~is_exp_invariant ~is_inv_by_default callee_pname + params | _ -> false ) ) loop_nodes @@ -84,7 +90,7 @@ let get_vars_in_loop loop_nodes = (* A variable is invariant if - its reaching definition is outside of the loop - o.w. its definition is constant or invariant itself *) -let get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes = +let get_inv_vars_in_loop tenv reaching_defs_invariant_map ~is_inv_by_default loop_head loop_nodes = let process_var_once var inv_vars = (* if a variable is marked invariant once, it can't be invalidated (i.e. invariance is monotonic) *) @@ -100,7 +106,7 @@ let get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes = if LoopNodes.is_empty in_loop_defs then (InvariantVars.add var inv_vars, true) else if (* its definition is unique and invariant *) - is_def_unique_and_satisfy tenv var def_nodes + is_def_unique_and_satisfy tenv var def_nodes ~is_inv_by_default (is_exp_invariant inv_vars loop_nodes reaching_defs) then (InvariantVars.add var inv_vars, true) else (inv_vars, false) ) @@ -132,6 +138,7 @@ let get_loop_inv_var_map tenv reaching_defs_invariant_map loop_head_to_loop_node (fun loop_head loop_nodes inv_map -> let inv_vars_in_loop = get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes + ~is_inv_by_default:Config.cost_invariant_by_default in L.(debug Analysis Medium) "@\n>>> loop head: %a --> inv vars: %a @\n" Procdesc.Node.pp loop_head InvariantVars.pp diff --git a/infer/src/checkers/loop_control.ml b/infer/src/checkers/loop_control.ml index 621461842..323cb5295 100644 --- a/infer/src/checkers/loop_control.ml +++ b/infer/src/checkers/loop_control.ml @@ -108,6 +108,14 @@ let remove_prune_node_pairs exit_nodes guard_nodes = |> Control.GuardNodes.union exit_nodes +let get_loop_head_to_source_nodes cfg = + get_back_edges cfg + |> List.fold ~init:Procdesc.NodeMap.empty ~f:(fun loop_head_to_source_list {source; target} -> + Procdesc.NodeMap.update target + (function Some source_list -> Some (source :: source_list) | None -> Some [source]) + loop_head_to_source_list ) + + (* Get a pair of maps (exit_map, loop_head_to_guard_map) where exit_map : exit_node -> loop_head set (i.e. target of the back edges) loop_head_to_guard_map : loop_head -> guard_nodes and @@ -117,13 +125,7 @@ let get_control_maps cfg = (* Since there could be multiple back-edges per loop, collect all source nodes per loop head *) (* loop_head (target of back-edges) --> source nodes *) - let loop_head_to_source_nodes_map = - get_back_edges cfg - |> List.fold ~init:Procdesc.NodeMap.empty ~f:(fun loop_head_to_source_list {source; target} -> - Procdesc.NodeMap.update target - (function Some source_list -> Some (source :: source_list) | None -> Some [source]) - loop_head_to_source_list ) - in + let loop_head_to_source_nodes_map = get_loop_head_to_source_nodes cfg in Procdesc.NodeMap.fold (fun loop_head source_list (Control.({exit_map; loop_head_to_guard_nodes}), loop_head_to_loop_nodes) -> diff --git a/infer/src/checkers/registerCheckers.ml b/infer/src/checkers/registerCheckers.ml index 36e81d853..f51797a43 100644 --- a/infer/src/checkers/registerCheckers.ml +++ b/infer/src/checkers/registerCheckers.ml @@ -100,6 +100,11 @@ let all_checkers = ; active= Config.cost ; callbacks= [(Procedure Cost.checker, Language.Clang); (Procedure Cost.checker, Language.Java)] } + ; { name= "loop hoisting" + ; active= Config.loop_hoisting + ; callbacks= + [(Procedure Hoisting.checker, Language.Clang); (Procedure Hoisting.checker, Language.Java)] + } ; { name= "Starvation analysis" ; active= Config.starvation ; callbacks= diff --git a/infer/src/dune.common.in b/infer/src/dune.common.in index 4ffd89a25..95e52c233 100644 --- a/infer/src/dune.common.in +++ b/infer/src/dune.common.in @@ -65,6 +65,7 @@ let common_libraries = ; "elina" ; "extlib" ; "mtime.clock.os" + ; "ocamlgraph" ; "oUnit" ; "parmap" ; "sqlite3" diff --git a/infer/tests/codetoanalyze/java/hoisting/Hoist.java b/infer/tests/codetoanalyze/java/hoisting/Hoist.java new file mode 100644 index 000000000..a7f0202ac --- /dev/null +++ b/infer/tests/codetoanalyze/java/hoisting/Hoist.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +class Hoist { + + int foo(int x, int y) { + return x + y; + } + + int bar(int x) { + return 3 * x; + } + + // will only report one even though both calls are invariant, due to + // location clashing (no column information) + void clash_function_calls_hoist(int size) { + int x = 10; + int y = 5; + for (int i = 0; i < size; i++) { + foo(x, foo(y, x + y)); + } + } + + // will report both + void two_function_call_hoist(int size) { + int x = 10; + int y = 5; + for (int i = 0; i < size; i++) { + foo(x, bar(y)); + } + } + + + // it is ok to move fun call to a temp. var + void reassigned_temp_hoist(int size) { + int x = 10; + int y = 5; + int d = 0; + for (int i = 0; i < size; i++) { + d = foo(x, y); + d = 30; + } + } + + // it is ok to just hoist function call into a temp var. + void used_in_loop_body_before_def_temp_hoist(int size, int[] M) { + int x = 10; + int y = 5; + int d = 20; + for (int i = 0; i < size; i++) { + M[i] = d; + d = foo(x, y); + } + } + + void loop_guard_hoist(int size, int[] M) { + int x = 10; + int d = 0; + int y = 5; + for (int i = 0; i < foo(d, size); i++) {} + } + + void legit_hoist(int size, int[] M) { + int x = 10; + int y = 5; + int d = 0; + for (int i = 0; i < size; i++) { + d = foo(x, x + y); + M[i] = d; + } + } + + // x is not invariant, hence d won't be invariant as well + void dep_not_invariant_dont_hoist(int size, int[] M) { + int x = 10; + int y = 5; + int d = 20; + for (int i = 0; i < size; i++) { + if (i > 3) { + x = 3; + } + d = foo(x, y); // shouldn't be hoisted + } + } + + // foo(x) is not guaranteed to execute + void not_guaranteed_to_execute_dont_hoist(int size, int x, int y) { + int i = 0; + + while (i < size) { + if (i > 3) { + foo(x, y); + } + i++; + } + } + + void x_not_invariant_dont_hoist(int size, int x, int y) { + int i = 0; + + while (i < size) { + x = foo(x, y) + y; + i++; + } + } + + void array_store_hoist(int size, int[] M) { + int x = 10; + int y = 5; + int d = 0; + for (int i = 0; i < size; i++) { + M[i] = foo(x, x + y); + } + } + + + // y = ... can be taken out of the inner loop + void nested_loop_hoist(int size, int x, int y) { + int i = 0; + + while (i < size) { + for (int j = 0; i < j; j++) { + y = foo(i, x); + } + i++; + } + } + + // j is not invariant, y can't be taken out + void nested_loop_dont_hoist(int size, int x, int y) { + int i = 0; + + while (i < size) { + for (int j = 0; j < i; j++) { + y = foo(j, x); + } + i++; + } + } +} diff --git a/infer/tests/codetoanalyze/java/hoisting/Makefile b/infer/tests/codetoanalyze/java/hoisting/Makefile new file mode 100644 index 000000000..2c2bb0556 --- /dev/null +++ b/infer/tests/codetoanalyze/java/hoisting/Makefile @@ -0,0 +1,13 @@ +# Copyright (c) 2018-present, Facebook, Inc. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +TESTS_DIR = ../../.. + +ANALYZER = checkers +INFER_OPTIONS = --loop-hoisting-only --debug-exceptions +INFERPRINT_OPTIONS = --issues-tests +SOURCES = $(wildcard *.java) + +include $(TESTS_DIR)/javac.make diff --git a/infer/tests/codetoanalyze/java/hoisting/issues.exp b/infer/tests/codetoanalyze/java/hoisting/issues.exp new file mode 100644 index 000000000..f252a3194 --- /dev/null +++ b/infer/tests/codetoanalyze/java/hoisting/issues.exp @@ -0,0 +1,9 @@ +codetoanalyze/java/hoisting/Hoist.java, Hoist.array_store_hoist(int,int[]):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 115] +codetoanalyze/java/hoisting/Hoist.java, Hoist.clash_function_calls_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 23] +codetoanalyze/java/hoisting/Hoist.java, Hoist.legit_hoist(int,int[]):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 71] +codetoanalyze/java/hoisting/Hoist.java, Hoist.loop_guard_hoist(int,int[]):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 63] +codetoanalyze/java/hoisting/Hoist.java, Hoist.nested_loop_hoist(int,int,int):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 126] +codetoanalyze/java/hoisting/Hoist.java, Hoist.reassigned_temp_hoist(int):void, 5, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 43] +codetoanalyze/java/hoisting/Hoist.java, Hoist.two_function_call_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 32] +codetoanalyze/java/hoisting/Hoist.java, Hoist.two_function_call_hoist(int):void, 4, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.bar(int) at line 32] +codetoanalyze/java/hoisting/Hoist.java, Hoist.used_in_loop_body_before_def_temp_hoist(int,int[]):void, 6, INVARIANT_CALL, no_bucket, ERROR, [Loop-invariant call to int Hoist.foo(int,int) at line 55] diff --git a/opam b/opam index 20d45a624..59dda8b99 100644 --- a/opam +++ b/opam @@ -43,6 +43,7 @@ depends: [ "javalib" {>="2.3.5"} "mtime" "ocamlfind" {build} + "ocamlgraph" "ounit" {="2.0.5"} "parmap" {>="1.0-rc8"} "ppx_deriving" {>="4.1"} diff --git a/opam.lock b/opam.lock index e06990984..fe309bd7a 100644 --- a/opam.lock +++ b/opam.lock @@ -44,6 +44,7 @@ ocaml-compiler-libs = v0.11.0 ocaml-migrate-parsetree = 1.0.11 ocamlbuild = 0.12.0 ocamlfind = 1.8.0 +ocamlgraph = 1.8.8 octavius = 1.2.0 ounit = 2.0.5 parmap = 1.0-rc10 diff --git a/scripts/toplevel_init b/scripts/toplevel_init index 9443358b9..4fb86cc80 100644 --- a/scripts/toplevel_init +++ b/scripts/toplevel_init @@ -9,6 +9,7 @@ #require "core.top";; #require "ctypes";; #require "ctypes.stubs";; +#require "ocamlgraph";; #require "ppx_compare";; #require "sawja";; #require "sqlite3";;