|
|
|
@ -9,6 +9,8 @@
|
|
|
|
|
|
|
|
|
|
open! IStd
|
|
|
|
|
|
|
|
|
|
module CLOpt = CommandLineOption
|
|
|
|
|
module F = Format
|
|
|
|
|
module L = Logging
|
|
|
|
|
|
|
|
|
|
type path_filter = SourceFile.t -> bool
|
|
|
|
@ -93,6 +95,16 @@ module FileContainsStringMatcher = struct
|
|
|
|
|
with Sys_error _ -> false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
type method_pattern = {
|
|
|
|
|
class_name : string;
|
|
|
|
|
method_name : string option;
|
|
|
|
|
parameters : (string list) option;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type pattern =
|
|
|
|
|
| Method_pattern of Config.language * method_pattern
|
|
|
|
|
| Source_contains of Config.language * string
|
|
|
|
|
|
|
|
|
|
(* Module to create matcher based on source file names or class names and method names *)
|
|
|
|
|
module FileOrProcMatcher = struct
|
|
|
|
|
|
|
|
|
@ -110,9 +122,9 @@ module FileOrProcMatcher = struct
|
|
|
|
|
(fun map pattern ->
|
|
|
|
|
let previous =
|
|
|
|
|
try
|
|
|
|
|
String.Map.find_exn map pattern.Config.class_name
|
|
|
|
|
String.Map.find_exn map pattern.class_name
|
|
|
|
|
with Not_found -> [] in
|
|
|
|
|
String.Map.add ~key:pattern.Config.class_name ~data:(pattern:: previous) map)
|
|
|
|
|
String.Map.add ~key:pattern.class_name ~data:(pattern :: previous) map)
|
|
|
|
|
String.Map.empty
|
|
|
|
|
m_patterns in
|
|
|
|
|
let do_java pname_java =
|
|
|
|
@ -122,7 +134,7 @@ module FileOrProcMatcher = struct
|
|
|
|
|
let class_patterns = String.Map.find_exn pattern_map class_name in
|
|
|
|
|
IList.exists
|
|
|
|
|
(fun p ->
|
|
|
|
|
match p.Config.method_name with
|
|
|
|
|
match p.method_name with
|
|
|
|
|
| None -> true
|
|
|
|
|
| Some m -> String.equal m method_name)
|
|
|
|
|
class_patterns
|
|
|
|
@ -138,8 +150,8 @@ module FileOrProcMatcher = struct
|
|
|
|
|
let create_file_matcher patterns =
|
|
|
|
|
let s_patterns, m_patterns =
|
|
|
|
|
let collect (s_patterns, m_patterns) = function
|
|
|
|
|
| Config.Source_contains (_, s) -> (s:: s_patterns, m_patterns)
|
|
|
|
|
| Config.Method_pattern (_, mp) -> (s_patterns, mp :: m_patterns) in
|
|
|
|
|
| Source_contains (_, s) -> (s:: s_patterns, m_patterns)
|
|
|
|
|
| Method_pattern (_, mp) -> (s_patterns, mp :: m_patterns) in
|
|
|
|
|
IList.fold_left collect ([], []) patterns in
|
|
|
|
|
let s_matcher =
|
|
|
|
|
let matcher = FileContainsStringMatcher.create_matcher s_patterns in
|
|
|
|
@ -163,16 +175,16 @@ module FileOrProcMatcher = struct
|
|
|
|
|
Format.fprintf fmt "[%a]"
|
|
|
|
|
(Pp.semicolon_seq_oneline Pp.text pp_string) l in
|
|
|
|
|
Format.fprintf fmt "%a%a%a"
|
|
|
|
|
(pp_key_value pp_string) ("class", Some mp.Config.class_name)
|
|
|
|
|
(pp_key_value pp_string) ("method", mp.Config.method_name)
|
|
|
|
|
(pp_key_value pp_params) ("parameters", mp.Config.parameters)
|
|
|
|
|
(pp_key_value pp_string) ("class", Some mp.class_name)
|
|
|
|
|
(pp_key_value pp_string) ("method", mp.method_name)
|
|
|
|
|
(pp_key_value pp_params) ("parameters", mp.parameters)
|
|
|
|
|
and pp_source_contains fmt sc =
|
|
|
|
|
Format.fprintf fmt " pattern: %s\n" sc in
|
|
|
|
|
match pattern with
|
|
|
|
|
| Config.Method_pattern (language, mp) ->
|
|
|
|
|
| Method_pattern (language, mp) ->
|
|
|
|
|
Format.fprintf fmt "Method pattern (%s) {\n%a}\n"
|
|
|
|
|
(Config.string_of_language language) pp_method_pattern mp
|
|
|
|
|
| Config.Source_contains (language, sc) ->
|
|
|
|
|
| Source_contains (language, sc) ->
|
|
|
|
|
Format.fprintf fmt "Source contains (%s) {\n%a}\n"
|
|
|
|
|
(Config.string_of_language language) pp_source_contains sc
|
|
|
|
|
|
|
|
|
@ -184,19 +196,146 @@ module OverridesMatcher = struct
|
|
|
|
|
let load_matcher patterns =
|
|
|
|
|
fun is_subtype proc_name ->
|
|
|
|
|
let is_matching = function
|
|
|
|
|
| Config.Method_pattern (language, mp) ->
|
|
|
|
|
is_subtype mp.Config.class_name
|
|
|
|
|
&& (Option.value_map ~f:(match_method language proc_name) ~default:false
|
|
|
|
|
mp.Config.method_name)
|
|
|
|
|
| Method_pattern (language, mp) ->
|
|
|
|
|
is_subtype mp.class_name
|
|
|
|
|
&& (Option.value_map ~f:(match_method language proc_name) ~default:false mp.method_name)
|
|
|
|
|
| _ -> failwith "Expecting method pattern" in
|
|
|
|
|
IList.exists is_matching patterns
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
let never_return_null_matcher = FileOrProcMatcher.load_matcher Config.patterns_never_returning_null
|
|
|
|
|
let skip_translation_matcher = FileOrProcMatcher.load_matcher Config.patterns_skip_translation
|
|
|
|
|
let suppress_warnings_matcher = FileOrProcMatcher.load_matcher Config.patterns_suppress_warnings
|
|
|
|
|
let modeled_expensive_matcher = OverridesMatcher.load_matcher Config.patterns_modeled_expensive
|
|
|
|
|
let patterns_of_json_with_key (json_key, json) =
|
|
|
|
|
let default_method_pattern = {
|
|
|
|
|
class_name = "";
|
|
|
|
|
method_name = None;
|
|
|
|
|
parameters = None
|
|
|
|
|
} in
|
|
|
|
|
|
|
|
|
|
let default_source_contains = "" in
|
|
|
|
|
|
|
|
|
|
let language_of_string = function
|
|
|
|
|
| "Java" ->
|
|
|
|
|
Ok Config.Java
|
|
|
|
|
| l ->
|
|
|
|
|
Error ("Inferconfig JSON key " ^ json_key ^ " not supported for language " ^ l) in
|
|
|
|
|
|
|
|
|
|
let rec detect_language = function
|
|
|
|
|
| [] ->
|
|
|
|
|
Error ("No language found for " ^ json_key ^ " in " ^ Config.inferconfig_file)
|
|
|
|
|
| ("language", `String s) :: _ ->
|
|
|
|
|
language_of_string s
|
|
|
|
|
| _:: tl ->
|
|
|
|
|
detect_language tl in
|
|
|
|
|
|
|
|
|
|
(* 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 assoc =
|
|
|
|
|
match detect_language assoc with
|
|
|
|
|
| Ok language ->
|
|
|
|
|
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
|
|
|
|
|
| [] ->
|
|
|
|
|
Error ("Unknown pattern for " ^ json_key ^ " in " ^ Config.inferconfig_file)
|
|
|
|
|
| (key, _) :: _ when is_method_pattern key ->
|
|
|
|
|
Ok (Method_pattern (language, default_method_pattern))
|
|
|
|
|
| (key, _) :: _ when is_source_contains key ->
|
|
|
|
|
Ok (Source_contains (language, default_source_contains))
|
|
|
|
|
| _:: tl -> loop tl in
|
|
|
|
|
loop assoc
|
|
|
|
|
| Error _ as error ->
|
|
|
|
|
error in
|
|
|
|
|
|
|
|
|
|
(* Translate a JSON entry into a matching pattern *)
|
|
|
|
|
let create_pattern (assoc : (string * Yojson.Basic.json) list) =
|
|
|
|
|
let collect_params l =
|
|
|
|
|
let collect accu = function
|
|
|
|
|
| `String s -> s:: accu
|
|
|
|
|
| _ -> failwith ("Unrecognised parameters in " ^ Yojson.Basic.to_string (`Assoc assoc)) in
|
|
|
|
|
IList.rev (IList.fold_left collect [] l) in
|
|
|
|
|
let create_method_pattern assoc =
|
|
|
|
|
let loop mp = function
|
|
|
|
|
| (key, `String s) when key = "class" ->
|
|
|
|
|
{ mp with class_name = s }
|
|
|
|
|
| (key, `String s) when key = "method" ->
|
|
|
|
|
{ mp with method_name = Some s }
|
|
|
|
|
| (key, `List l) when key = "parameters" ->
|
|
|
|
|
{ mp with parameters = Some (collect_params l) }
|
|
|
|
|
| (key, _) when key = "language" -> mp
|
|
|
|
|
| _ -> failwith ("Fails to parse " ^ Yojson.Basic.to_string (`Assoc assoc)) in
|
|
|
|
|
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
|
|
|
|
|
| Ok (Method_pattern (language, _)) ->
|
|
|
|
|
Ok (Method_pattern (language, create_method_pattern assoc))
|
|
|
|
|
| Ok (Source_contains (language, _)) ->
|
|
|
|
|
Ok (Source_contains (language, create_string_contains assoc))
|
|
|
|
|
| Error _ as error ->
|
|
|
|
|
error in
|
|
|
|
|
|
|
|
|
|
let warn_user msg =
|
|
|
|
|
F.eprintf "WARNING: in file %s: error parsing option %s@\n%s"
|
|
|
|
|
Config.inferconfig_file json_key msg in
|
|
|
|
|
|
|
|
|
|
(* Translate all the JSON entries into matching patterns *)
|
|
|
|
|
let rec translate accu = function
|
|
|
|
|
| `Assoc l -> (
|
|
|
|
|
match create_pattern l with
|
|
|
|
|
| Ok pattern ->
|
|
|
|
|
pattern :: accu
|
|
|
|
|
| Error msg ->
|
|
|
|
|
warn_user msg;
|
|
|
|
|
accu)
|
|
|
|
|
| `List l ->
|
|
|
|
|
IList.fold_left translate accu l
|
|
|
|
|
| json ->
|
|
|
|
|
warn_user (Printf.sprintf "expected list or assoc json type, but got value %s"
|
|
|
|
|
(Yojson.Basic.to_string json));
|
|
|
|
|
accu in
|
|
|
|
|
|
|
|
|
|
translate [] json
|
|
|
|
|
|
|
|
|
|
let modeled_expensive_matcher =
|
|
|
|
|
OverridesMatcher.load_matcher (patterns_of_json_with_key Config.patterns_modeled_expensive)
|
|
|
|
|
|
|
|
|
|
let never_return_null_matcher =
|
|
|
|
|
FileOrProcMatcher.load_matcher (patterns_of_json_with_key Config.patterns_never_returning_null)
|
|
|
|
|
|
|
|
|
|
let skip_translation_matcher =
|
|
|
|
|
FileOrProcMatcher.load_matcher (patterns_of_json_with_key Config.patterns_skip_translation)
|
|
|
|
|
|
|
|
|
|
let suppress_warnings_matcher =
|
|
|
|
|
let error msg =
|
|
|
|
|
F.eprintf
|
|
|
|
|
"There was an issue reading the option %s.@\n\
|
|
|
|
|
If you did not call %s directly, this is likely a bug in Infer.@\n\
|
|
|
|
|
%s@."
|
|
|
|
|
Config.suppress_warnings_annotations_long
|
|
|
|
|
(Filename.basename Sys.executable_name)
|
|
|
|
|
msg ;
|
|
|
|
|
[] in
|
|
|
|
|
let patterns =
|
|
|
|
|
match Config.suppress_warnings_out with
|
|
|
|
|
| Some path -> (
|
|
|
|
|
match Utils.read_optional_json_file path with
|
|
|
|
|
| Ok json -> (
|
|
|
|
|
let json_key = "suppress_warnings" in
|
|
|
|
|
match Yojson.Basic.Util.member json_key json with
|
|
|
|
|
| `Null -> []
|
|
|
|
|
| json -> patterns_of_json_with_key (json_key, json))
|
|
|
|
|
| Error msg -> error ("Could not read or parse the supplied " ^ path ^ ":\n" ^ msg)
|
|
|
|
|
)
|
|
|
|
|
| None when Config.current_exe <> CLOpt.Java -> []
|
|
|
|
|
| None when Option.is_some Config.generated_classes -> []
|
|
|
|
|
| None ->
|
|
|
|
|
error ("The option " ^ Config.suppress_warnings_annotations_long ^ " was not provided") in
|
|
|
|
|
FileOrProcMatcher.load_matcher patterns
|
|
|
|
|
|
|
|
|
|
let load_filters analyzer =
|
|
|
|
|
{
|
|
|
|
|