Add support for modeling @Expensive methods using .inferconfig

Summary:public
Use the configuration file .inferconfig to model the library method that are considered expensive

Reviewed By: cristianoc

Differential Revision: D3045288

fb-gh-sync-id: e58d85c
shipit-source-id: e58d85c
master
jrm 9 years ago committed by Facebook Github Bot 3
parent f4a0f2781a
commit cc4fcd6837

@ -268,6 +268,8 @@ class AnalyzerWrapper(object):
# to be reported
infer_options += ['-allow_specs_cleanup']
infer_options += ['-inferconfig_home', os.getcwd()]
if self.args.analyzer == config.ANALYZER_ERADICATE:
infer_options += ['-eradicate']
elif self.args.analyzer == config.ANALYZER_CHECKERS:

@ -46,6 +46,13 @@ let proc_stats_filename = "proc_stats.json"
let global_tenv_filename = "global.tenv"
(** Name of the infer configuration file *)
let inferconfig_file = ".inferconfig"
let inferconfig_home : string option ref = ref None
let suppress_warnings_annotations : string option ref = ref None
(** List of paths to the directories containing specs for library functions. *)
let specs_library = ref []

@ -7,12 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*)
(** Name of the infer configuration file *)
let inferconfig_file = ".inferconfig"
let inferconfig_home = ref None
let suppress_warnings_annotations = ref None
module L = Logging
(** Look up a key in a json file containing a list of strings *)
let lookup_string_list key json =
@ -60,58 +55,8 @@ let is_matching patterns =
with Not_found -> false)
patterns
module FileContainsStringMatcher = struct
type matcher = DB.source_file -> bool
let default_matcher : matcher = fun _ -> false
let file_contains regexp file_in =
let rec loop () =
try
(Str.search_forward regexp (input_line file_in) 0) >= 0
with
| Not_found -> loop ()
| End_of_file -> false in
loop ()
let create_matcher s_patterns =
if s_patterns = [] then
default_matcher
else
let source_map = ref DB.SourceFileMap.empty in
let regexp =
Str.regexp (join_strings "\\|" s_patterns) in
fun source_file ->
try
DB.SourceFileMap.find source_file !source_map
with Not_found ->
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
end
module type MATCHABLE_JSON = sig
val json_key : string
end
module type Matcher = sig
type matcher = DB.source_file -> Procname.t -> bool
val load_matcher : string -> matcher
end
module FileOrProcMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = DB.source_file -> Procname.t -> bool
let default_matcher : matcher =
fun _ _ -> false
type method_pattern = {
type method_pattern = {
class_name : string;
method_name : string option;
parameters : (string list) option
@ -129,27 +74,30 @@ struct
| Method_pattern of Config.language * method_pattern
| Source_contains of Config.language * string
let language_of_string = function
| "Java" -> Config.Java
| l -> failwith ("Inferconfig JSON key " ^ M.json_key ^ " not supported for language " ^ l)
let detect_language assoc =
let rec loop = function
| [] ->
failwith
("No language found for " ^ M.json_key ^ " in " ^ inferconfig_file)
| (key, `String s) :: _ when key = "language" ->
language_of_string s
let language_of_string json_key = function
| "Java" -> Config.Java
| l -> failwith ("Inferconfig JSON key " ^ json_key ^ " not supported for language " ^ l)
let detect_language json_key assoc =
let rec loop = function
| [] ->
failwith
("No language found for " ^ json_key ^ " in " ^ Config.inferconfig_file)
| (key, `String s) :: _ when key = "language" ->
language_of_string json_key s
| _:: tl -> loop tl in
loop assoc
let detect_pattern assoc =
let language = detect_language assoc in
let is_method_pattern key = IList.exists (string_equal key) ["class"; "method"]
and is_source_contains key = IList.exists (string_equal key) ["source_contains"] in
let rec loop = function
| [] ->
failwith ("Unknown pattern for " ^ M.json_key ^ " in " ^ inferconfig_file)
(* Detect the kind of pattern, method pattern or pattern based on the content of the source file.
Detecting the kind of patterns in a first step makes it easier to parse the parts of the
pattern in a second step *)
let detect_pattern json_key assoc =
let language = detect_language json_key assoc in
let is_method_pattern key = IList.exists (string_equal key) ["class"; "method"]
and is_source_contains key = IList.exists (string_equal key) ["source_contains"] in
let rec loop = function
| [] ->
failwith ("Unknown pattern for " ^ json_key ^ " in " ^ Config.inferconfig_file)
| (key, _) :: _ when is_method_pattern key ->
Method_pattern (language, default_method_pattern)
| (key, _) :: _ when is_source_contains key ->
@ -157,7 +105,8 @@ struct
| _:: tl -> loop tl in
loop assoc
let create_pattern (assoc : (string * Yojson.Basic.json) list) =
(* Translate a JSON entry into a matching pattern *)
let create_pattern json_key (assoc : (string * Yojson.Basic.json) list) =
let collect_params l =
let collect accu = function
| `String s -> s:: accu
@ -176,22 +125,96 @@ struct
IList.fold_left loop default_method_pattern assoc
and create_string_contains assoc =
let loop sc = function
| (key, `String pattern) when key = "source_contains" -> pattern
| (key, _) when key = "language" -> sc
| _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in
IList.fold_left loop default_source_contains assoc in
match detect_pattern assoc with
| (key, `String pattern) when key = "source_contains" -> pattern
| (key, _) when key = "language" -> sc
| _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in
IList.fold_left loop default_source_contains assoc in
match detect_pattern json_key assoc with
| Method_pattern (language, _) ->
Method_pattern (language, create_method_pattern assoc)
| Source_contains (language, _) ->
Source_contains (language, create_string_contains assoc)
let rec translate accu (json : Yojson.Basic.json) : pattern list =
match json with
| `Assoc l -> (create_pattern l):: accu
| `List l -> IList.fold_left translate accu l
(* Translate all the JSON entries into matching patterns *)
let rec translate json_key accu (json : Yojson.Basic.json) : pattern list =
match json with
| `Assoc l -> (create_pattern json_key l):: accu
| `List l -> IList.fold_left (translate json_key) accu l
| _ -> assert false
(* Creates a list of matching patterns for the given inferconfig file *)
let load_patterns json_key inferconfig =
let found =
Yojson.Basic.Util.filter_member
json_key
[Yojson.Basic.from_file inferconfig] in
IList.fold_left (translate json_key) [] found
(* Check if a proc name is matching the name given as string *)
let match_method language proc_name method_name =
not (SymExec.function_is_builtin proc_name) &&
match language with
| Config.Java ->
Procname.java_get_method proc_name = method_name
| Config.C_CPP ->
Procname.c_get_method proc_name = method_name
(* Module to create matcher based on strings present in the source file *)
module FileContainsStringMatcher = struct
type matcher = DB.source_file -> bool
let default_matcher : matcher = fun _ -> false
let file_contains regexp file_in =
let rec loop () =
try
(Str.search_forward regexp (input_line file_in) 0) >= 0
with
| Not_found -> loop ()
| End_of_file -> false in
loop ()
let create_matcher s_patterns =
if s_patterns = [] then
default_matcher
else
let source_map = ref DB.SourceFileMap.empty in
let regexp =
Str.regexp (join_strings "\\|" s_patterns) in
fun source_file ->
try
DB.SourceFileMap.find source_file !source_map
with Not_found ->
try
let file_in = open_in (DB.source_file_to_string source_file) in
let pattern_found = file_contains regexp file_in in
close_in file_in;
source_map := DB.SourceFileMap.add source_file pattern_found !source_map;
pattern_found
with Sys_error _ -> false
end
module type MATCHABLE_JSON = sig
val json_key : string
end
module type Matcher = sig
type matcher = DB.source_file -> Procname.t -> bool
val load_matcher : string -> matcher
end
(* Module to create matcher based on source file names or class names and method names *)
module FileOrProcMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = DB.source_file -> Procname.t -> bool
let default_matcher : matcher =
fun _ _ -> false
let create_method_matcher m_patterns =
if m_patterns = [] then
default_matcher
@ -234,20 +257,12 @@ struct
let load_matcher inferconfig =
if Sys.file_exists inferconfig then
try
let patterns =
let found =
Yojson.Basic.Util.filter_member
M.json_key
[Yojson.Basic.from_file inferconfig] in
IList.fold_left translate [] found in
create_file_matcher patterns
with Sys_error _ ->
default_matcher
else default_matcher
create_file_matcher (load_patterns M.json_key inferconfig)
else
default_matcher
(*
let pp_pattern fmt pattern =
let _pp_pattern fmt pattern =
let pp_string fmt s =
Format.fprintf fmt "%s" s in
let pp_option pp_value fmt = function
@ -272,9 +287,30 @@ struct
| Source_contains (language, sc) ->
Format.fprintf fmt "Source contains (%s) {\n%a}\n"
(Config.string_of_language language) pp_source_contains sc
*)
end (* of module FileOrProcMatcher *)
(* Module to create patterns that will match all overriding methods in the pattern *)
module OverridesMatcher = functor (M : MATCHABLE_JSON) ->
struct
type matcher = (string -> bool) -> Procname.t -> bool
let default_matcher _ _ = false
let load_matcher inferconfig =
if Sys.file_exists inferconfig then
fun is_subtype proc_name ->
let is_matching = function
| Method_pattern (language, mp) ->
is_subtype mp.class_name
&& Option.map_default (match_method language proc_name) false mp.method_name
| _ -> failwith "Expecting method pattern" in
IList.exists is_matching (load_patterns M.json_key inferconfig)
else
default_matcher
end
module NeverReturnNull = FileOrProcMatcher(struct
let json_key = "never_returning_null"
@ -288,10 +324,15 @@ module SkipTranslationMatcher = FileOrProcMatcher(struct
let json_key = "skip_translation"
end)
module ModeledExpensiveMatcher = OverridesMatcher(struct
let json_key = "modeled_expensive"
end)
let inferconfig () = match !inferconfig_home with
| Some dir -> Filename.concat dir inferconfig_file
| None -> inferconfig_file
let inferconfig () =
match !Config.inferconfig_home with
| Some dir -> Filename.concat dir Config.inferconfig_file
| None -> Config.inferconfig_file
let load_filters analyzer =
let inferconfig_file = inferconfig () in
@ -364,7 +405,7 @@ let test () =
let matching_s =
join_strings ", "
(IList.map string_of_analyzer matching) in
Logging.stderr "%s -> {%s}@."
L.stderr "%s -> {%s}@."
(DB.source_file_to_rel_path source_file)
matching_s)
(Sys.getcwd ())

@ -7,10 +7,6 @@
* of patent rights can be found in the PATENTS file in the same directory.
*)
val inferconfig_home : string option ref
val suppress_warnings_annotations : string option ref
(** get the path to the .inferconfig file *)
val inferconfig : unit -> string
@ -47,5 +43,10 @@ module SkipTranslationMatcher : Matcher
module SuppressWarningsMatcher : Matcher
module ModeledExpensiveMatcher : sig
type matcher = (string -> bool) -> Procname.t -> bool
val load_matcher : string -> matcher
end
(** Load the config file and list the files to report on *)
val test: unit -> unit

@ -176,7 +176,7 @@ let arg_desc =
"setup the analyzer for the path filtering"
;
"-inferconfig_home",
Arg.String (fun s -> Inferconfig.inferconfig_home := Some s),
Arg.String (fun s -> Config.inferconfig_home := Some s),
Some "dir",
"Path to the .inferconfig file"
;

@ -667,35 +667,47 @@ let base_arg_desc =
Arg.String (fun s -> Config.results_dir := s),
Some "dir",
"set the project results directory (default dir=" ^ Config.default_results_dir ^ ")";
"-coverage",
Arg.Unit (fun () -> Config.worklist_mode:= 2),
None,
"analysis mode to maximize coverage (can take longer)";
"-lib",
Arg.String (fun s -> Config.specs_library := filename_to_absolute s :: !Config.specs_library),
Some "dir",
"add dir to the list of directories to be searched for spec files";
"-specs-dir-list-file",
Arg.String (fun s -> Config.specs_library := (read_specs_dir_list_file s) @ !Config.specs_library),
Some "file",
"add the newline-separated directories listed in <file> to the list of directories to \
be searched for spec files";
"-models",
Arg.String (fun s -> Config.add_models (filename_to_absolute s)),
Some "zip file",
"add a zip file containing the models";
"-ziplib",
Arg.String (fun s -> Config.add_zip_library (filename_to_absolute s)),
Some "zip file",
"add a zip file containing library spec files";
"-project_root",
Arg.String (fun s -> Config.project_root := Some (filename_to_absolute s)),
Some "dir",
"root directory of the project";
"-infer_cache",
Arg.String (fun s -> Config.JarCache.infer_cache := Some (filename_to_absolute s)),
Some "dir",
"Select a directory to contain the infer cache";
"-inferconfig_home",
Arg.String (fun s -> Config.inferconfig_home := Some s),
Some "dir",
"Path to the .inferconfig file";
]
let reserved_arg_desc =

@ -29,6 +29,17 @@ let expensive_overrides_unexpensive =
"CHECKERS_EXPENSIVE_OVERRIDES_UNANNOTATED"
let is_modeled_expensive =
let matcher =
lazy (let config_file = Inferconfig.inferconfig () in
Inferconfig.ModeledExpensiveMatcher.load_matcher config_file) in
fun tenv proc_name ->
not (SymExec.function_is_builtin proc_name) &&
let classname =
Typename.Java.from_string (Procname.java_get_class proc_name) in
(Lazy.force matcher) (AndroidFramework.is_subclass tenv classname) proc_name
let check_attributes check attributes =
let annotated_signature = Annotations.get_annotated_signature attributes in
let ret_annotation, _ = annotated_signature.Annotations.ret in
@ -75,24 +86,6 @@ let method_overrides_no_allocation tenv pname =
method_overrides method_is_no_allcation tenv pname
let is_modeled_expensive tenv pname =
if SymExec.function_is_builtin pname then false
else if Procname.java_get_method pname <> "findViewById" then false
else
let package =
match Procname.java_get_package pname with
| None -> ""
| Some p -> p in
let classname =
Mangled.from_package_class package (Procname.java_get_simple_class pname) in
match Sil.tenv_lookup tenv (Typename.TN_csu (Csu.Class Csu.Java, classname)) with
| None -> false
| Some struct_typ ->
let typ = Sil.Tstruct struct_typ in
AndroidFramework.is_view typ tenv
|| AndroidFramework.is_activity typ tenv
let method_is_expensive tenv pname =
is_modeled_expensive tenv pname
|| check_method Annotations.ia_is_expensive pname
@ -239,9 +232,9 @@ let report_allocation_stack pname loc trace stack_str constructor_pname call_loc
Reporting.log_error pname ~loc: (Some loc) ~ltr: (Some final_trace) exn
let report_call_stack end_of_stack lookup_next_calls report tenv pname pdesc loc calls =
let report_call_stack end_of_stack lookup_next_calls report pname pdesc loc calls =
let rec loop visited_pnames (trace, stack_str) (callee_pname, callee_loc) =
if end_of_stack tenv callee_pname then
if end_of_stack callee_pname then
report pname loc trace stack_str callee_pname callee_loc
else
let next_calls = lookup_next_calls callee_pname in
@ -261,14 +254,14 @@ let report_call_stack end_of_stack lookup_next_calls report tenv pname pdesc loc
let report_expensive_calls tenv pname pdesc loc calls =
report_call_stack
method_is_expensive lookup_expensive_calls report_expensive_call_stack
tenv pname pdesc loc calls
(method_is_expensive tenv) lookup_expensive_calls
report_expensive_call_stack pname pdesc loc calls
let report_allocations tenv pname pdesc loc calls =
let report_allocations pname pdesc loc calls =
report_call_stack
(fun _ p -> Procname.is_constructor p) lookup_allocations report_allocation_stack
tenv pname pdesc loc calls
Procname.is_constructor lookup_allocations
report_allocation_stack pname pdesc loc calls
let check_one_procedure tenv pname pdesc =
@ -311,7 +304,7 @@ let check_one_procedure tenv pname pdesc =
if performance_critical then
report_expensive_calls tenv pname pdesc loc call_summary.Specs.expensive_calls;
if no_allocation then
report_allocations tenv pname pdesc loc call_summary.Specs.allocations
report_allocations pname pdesc loc call_summary.Specs.allocations
let callback_performance_checker

@ -52,6 +52,8 @@ val is_android_lib_class : Typename.t -> bool
(** Path to the android.jar file containing real code, not just the method stubs as in the SDK *)
val non_stub_android_jar : unit -> string
val is_subclass : Sil.tenv -> Typename.t -> string -> bool
(** [is_exception tenv class_name] checks if class_name is of type java.lang.Exception *)
val is_exception : Sil.tenv -> Typename.t -> bool

@ -11,32 +11,20 @@
open Javalib_pack
let suppress_warnings_lookup = ref None
let load_suppress_warnings_lookup () =
let default_matcher = fun _ -> false in
let matcher =
match !Inferconfig.suppress_warnings_annotations with
| Some f ->
(try
let m = Inferconfig.SuppressWarningsMatcher.load_matcher f in
(m DB.source_file_empty)
with Yojson.Json_error _ ->
default_matcher)
| None -> failwith "Local config expected!" in
suppress_warnings_lookup := Some matcher
let is_suppress_warnings_annotated proc_name =
let is_suppress_warnings_annotated =
let matcher =
let () =
match !suppress_warnings_lookup with
| None ->
load_suppress_warnings_lookup ()
| Some _ -> () in
Option.get !suppress_warnings_lookup in
matcher proc_name
lazy
(let default_matcher = fun _ -> false in
match !Config.suppress_warnings_annotations with
| Some f ->
(try
let m = Inferconfig.SuppressWarningsMatcher.load_matcher f in
(m DB.source_file_empty)
with Yojson.Json_error _ ->
default_matcher)
| None -> failwith "Local config expected!") in
fun proc_name ->
(Lazy.force matcher) proc_name
let suppress_warnings =

@ -63,7 +63,7 @@ let arg_desc =
"Set the path to the javac verbose output"
;
"-suppress_warnings_out",
Arg.String (fun s -> Inferconfig.suppress_warnings_annotations := Some s),
Arg.String (fun s -> Config.suppress_warnings_annotations := Some s),
Some "Path",
"Path to list of collected @SuppressWarnings annotations"
;

@ -18,5 +18,17 @@
"language": "Java",
"source_contains": "_SHOULD_BE_SKIPPED_"
}
],
"modeled_expensive": [
{
"language": "Java",
"class": "android.app.Activity",
"method": "findViewById"
},
{
"language": "Java",
"class": "android.view.View",
"method": "findViewById"
}
]
}

@ -16,6 +16,8 @@ java_library(
)
out = 'out'
inferconfig_file = '$(location //infer/tests/codetoanalyze/java:inferconfig)'
copy_inferconfig = ' '.join(['cp', inferconfig_file, '$SRCDIR'])
clean_cmd = ' '.join(['rm', '-rf', out])
classpath = ':'.join([('$(classpath ' + path + ')') for path in dependencies])
infer_cmd = ' '.join([
@ -30,7 +32,7 @@ infer_cmd = ' '.join([
'$SRCS',
])
copy_cmd = ' '.join(['cp', out + '/report.csv', '$OUT'])
command = ' && '.join([clean_cmd, infer_cmd, copy_cmd])
command = ' && '.join([clean_cmd, copy_inferconfig, infer_cmd, copy_cmd])
genrule(
name = 'analyze',

Loading…
Cancel
Save