[Loop-hoisting] Add a new checker for hoisting invariant function calls

Reviewed By: mbouaziz

Differential Revision: D9460455

fbshipit-source-id: 7488c1a69
master
Ezgi Çiçek 6 years ago committed by Facebook Github Bot
parent 66ef5f5c15
commit 49e582fa49

@ -88,7 +88,7 @@ BUILD_SYSTEMS_TESTS += \
DIRECT_TESTS += \ DIRECT_TESTS += \
java_checkers java_eradicate java_infer java_lab java_tracing java_quandary \ 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) ifneq ($(ANT),no)
BUILD_SYSTEMS_TESTS += ant BUILD_SYSTEMS_TESTS += ant
endif endif

@ -373,3 +373,30 @@ end = struct
end end
module NormalOneInstrPerNode = OneInstrPerNode (Normal) 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

@ -116,3 +116,29 @@ module OneInstrPerNode (Base : S with module Node = DefaultNode) : sig
end end
module NormalOneInstrPerNode : module type of OneInstrPerNode (Normal) 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

@ -665,6 +665,7 @@ and ( annotation_reachability
, linters , linters
, litho , litho
, liveness , liveness
, loop_hoisting
, ownership , ownership
, printf_args , printf_args
, quandary , quandary
@ -713,6 +714,7 @@ and ( annotation_reachability
and litho = mk_checker ~long:"litho" "Experimental checkers supporting the Litho framework" and litho = mk_checker ~long:"litho" "Experimental checkers supporting the Litho framework"
and liveness = and liveness =
mk_checker ~long:"liveness" ~default:true "the detection of dead stores and unused variables" 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 ownership = mk_checker ~long:"ownership" ~default:true "the detection of C++ lifetime bugs"
and printf_args = and printf_args =
mk_checker ~long:"printf-args" ~default:true mk_checker ~long:"printf-args" ~default:true
@ -779,6 +781,7 @@ and ( annotation_reachability
, linters , linters
, litho , litho
, liveness , liveness
, loop_hoisting
, ownership , ownership
, printf_args , printf_args
, quandary , quandary
@ -2695,6 +2698,8 @@ and log_events = !log_events
and log_file = !log_file and log_file = !log_file
and loop_hoisting = !loop_hoisting
and max_nesting = !max_nesting and max_nesting = !max_nesting
and method_decls_info = !method_decls_info and method_decls_info = !method_decls_info

@ -458,6 +458,8 @@ val log_events : bool
val log_file : string val log_file : string
val loop_hoisting : bool
val max_nesting : int option val max_nesting : int option
val method_decls_info : string option val method_decls_info : string option

@ -256,6 +256,8 @@ let interface_not_thread_safe = from_string "INTERFACE_NOT_THREAD_SAFE"
let internal_error = from_string "Internal_error" let internal_error = from_string "Internal_error"
let invariant_call = from_string "INVARIANT_CALL"
let javascript_injection = from_string "JAVASCRIPT_INJECTION" let javascript_injection = from_string "JAVASCRIPT_INJECTION"
let leak_after_array_abstraction = from_string "Leak_after_array_abstraction" let leak_after_array_abstraction = from_string "Leak_after_array_abstraction"

@ -173,6 +173,8 @@ val interface_not_thread_safe : t
val internal_error : t val internal_error : t
val invariant_call : t
val javascript_injection : t val javascript_injection : t
val leak_after_array_abstraction : t val leak_after_array_abstraction : t

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

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

@ -19,34 +19,40 @@ let is_defined_outside loop_nodes reaching_defs var =
|> Option.value ~default:true |> Option.value ~default:true
(* check if the def of var is unique and satisfies function f_exp *) let is_fun_call_invariant tenv ~is_exp_invariant ~is_inv_by_default callee_pname params =
let is_def_unique_and_satisfy tenv var (loop_nodes: LoopNodes.t) f_exp = 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 let equals_var id = Var.equal var (Var.of_id id) in
(* Use O(1) is_singleton check *) (* Use O(1) is_singleton check *)
(* tedious parameter wrangling to make IContainer's fold interface happy *) (* tedious parameter wrangling to make IContainer's fold interface happy *)
IContainer.is_singleton 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 loop_nodes
&& LoopNodes.for_all && LoopNodes.for_all
(fun node -> (fun node ->
Procdesc.Node.get_instrs node Procdesc.Node.get_instrs node
|> Instrs.exists ~f:(function |> 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 true
| Sil.Store (exp_lhs, _, exp_rhs, _) | 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 true
| Sil.Call ((id, _), Const (Cfun callee_pname), params, _, _) when equals_var id -> ( | Sil.Call ((id, _), Const (Cfun callee_pname), params, _, _) when equals_var id ->
match is_fun_call_invariant tenv ~is_exp_invariant ~is_inv_by_default callee_pname
(* Take into account invariance behavior of modeled params
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 )
| _ -> | _ ->
false ) ) false ) )
loop_nodes loop_nodes
@ -84,7 +90,7 @@ let get_vars_in_loop loop_nodes =
(* A variable is invariant if (* A variable is invariant if
- its reaching definition is outside of the loop - its reaching definition is outside of the loop
- o.w. its definition is constant or invariant itself *) - 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 = let process_var_once var inv_vars =
(* if a variable is marked invariant once, it can't be invalidated (* if a variable is marked invariant once, it can't be invalidated
(i.e. invariance is monotonic) *) (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) if LoopNodes.is_empty in_loop_defs then (InvariantVars.add var inv_vars, true)
else if else if
(* its definition is unique and invariant *) (* 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) (is_exp_invariant inv_vars loop_nodes reaching_defs)
then (InvariantVars.add var inv_vars, true) then (InvariantVars.add var inv_vars, true)
else (inv_vars, false) ) 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 -> (fun loop_head loop_nodes inv_map ->
let inv_vars_in_loop = let inv_vars_in_loop =
get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes get_inv_vars_in_loop tenv reaching_defs_invariant_map loop_head loop_nodes
~is_inv_by_default:Config.cost_invariant_by_default
in in
L.(debug Analysis Medium) L.(debug Analysis Medium)
"@\n>>> loop head: %a --> inv vars: %a @\n" Procdesc.Node.pp loop_head InvariantVars.pp "@\n>>> loop head: %a --> inv vars: %a @\n" Procdesc.Node.pp loop_head InvariantVars.pp

@ -108,6 +108,14 @@ let remove_prune_node_pairs exit_nodes guard_nodes =
|> Control.GuardNodes.union exit_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 (* 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) exit_map : exit_node -> loop_head set (i.e. target of the back edges)
loop_head_to_guard_map : loop_head -> guard_nodes and 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 (* Since there could be multiple back-edges per loop, collect all
source nodes per loop head *) source nodes per loop head *)
(* loop_head (target of back-edges) --> source nodes *) (* loop_head (target of back-edges) --> source nodes *)
let loop_head_to_source_nodes_map = let loop_head_to_source_nodes_map = get_loop_head_to_source_nodes cfg in
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
Procdesc.NodeMap.fold Procdesc.NodeMap.fold
(fun loop_head source_list (fun loop_head source_list
(Control.({exit_map; loop_head_to_guard_nodes}), loop_head_to_loop_nodes) -> (Control.({exit_map; loop_head_to_guard_nodes}), loop_head_to_loop_nodes) ->

@ -100,6 +100,11 @@ let all_checkers =
; active= Config.cost ; active= Config.cost
; callbacks= [(Procedure Cost.checker, Language.Clang); (Procedure Cost.checker, Language.Java)] ; 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" ; { name= "Starvation analysis"
; active= Config.starvation ; active= Config.starvation
; callbacks= ; callbacks=

@ -65,6 +65,7 @@ let common_libraries =
; "elina" ; "elina"
; "extlib" ; "extlib"
; "mtime.clock.os" ; "mtime.clock.os"
; "ocamlgraph"
; "oUnit" ; "oUnit"
; "parmap" ; "parmap"
; "sqlite3" ; "sqlite3"

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

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

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

@ -43,6 +43,7 @@ depends: [
"javalib" {>="2.3.5"} "javalib" {>="2.3.5"}
"mtime" "mtime"
"ocamlfind" {build} "ocamlfind" {build}
"ocamlgraph"
"ounit" {="2.0.5"} "ounit" {="2.0.5"}
"parmap" {>="1.0-rc8"} "parmap" {>="1.0-rc8"}
"ppx_deriving" {>="4.1"} "ppx_deriving" {>="4.1"}

@ -44,6 +44,7 @@ ocaml-compiler-libs = v0.11.0
ocaml-migrate-parsetree = 1.0.11 ocaml-migrate-parsetree = 1.0.11
ocamlbuild = 0.12.0 ocamlbuild = 0.12.0
ocamlfind = 1.8.0 ocamlfind = 1.8.0
ocamlgraph = 1.8.8
octavius = 1.2.0 octavius = 1.2.0
ounit = 2.0.5 ounit = 2.0.5
parmap = 1.0-rc10 parmap = 1.0-rc10

@ -9,6 +9,7 @@
#require "core.top";; #require "core.top";;
#require "ctypes";; #require "ctypes";;
#require "ctypes.stubs";; #require "ctypes.stubs";;
#require "ocamlgraph";;
#require "ppx_compare";; #require "ppx_compare";;
#require "sawja";; #require "sawja";;
#require "sqlite3";; #require "sqlite3";;

Loading…
Cancel
Save