Add differential and filtering functionalities to Infer, to compare two analyses

Summary:
Given two analysis results, it's now possible to compare them with the following command:
  infer --diff --report-current reportA.json --report-previous reportB.json --file-renamings file_renamings.json
this command will then generate 3 files in `infer-out/differential/{introduced, fixed, preexisting}.json`, whose meaning is the following:
- `introduced.json` has all issues in `current` that are not in `previous`
- `fixed.json` has all issues in `previous` that are not in `current`
- `preexisting.json` has all issues that are in both `current` and `previous`

The json files generated can then be used to categorise results coming from incremental analyses of a codebase.

Reviewed By: jvillard

Differential Revision: D4482517

fbshipit-source-id: 1f7df3e
master
Martino Luca 8 years ago committed by Facebook Github Bot
parent bc852ec0d1
commit f8a65e698c

4
.gitignore vendored

@ -33,6 +33,10 @@ duplicates.txt
/infer/tests/build_systems/clang_compilation_db_relpath/compile_commands.json /infer/tests/build_systems/clang_compilation_db_relpath/compile_commands.json
/infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/app_built /infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/app_built
/infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/build/ /infer/tests/build_systems/codetoanalyze/xcodebuild/simple_app/build/
infer/tests/build_systems/differential_*/**/*.class
infer/tests/build_systems/differential_*/**/Diff*.java
infer/tests/build_systems/differential_*/infer-current
infer/tests/build_systems/differential_*/infer-previous
# generated by oUnit # generated by oUnit
/oUnit-all.cache /oUnit-all.cache

@ -22,6 +22,10 @@ BUILD_SYSTEMS_TESTS += \
clang_translation \ clang_translation \
clang_unknown_ext \ clang_unknown_ext \
delete_results_dir \ delete_results_dir \
differential_resolve_infer_eradicate_conflict \
differential_skip_anonymous_class_renamings \
differential_skip_duplicated_types_on_filenames \
differential_skip_duplicated_types_on_filenames_with_renamings \
fail_on_issue \ fail_on_issue \
j1 \ j1 \
linters \ linters \

@ -545,7 +545,7 @@ let to_simplified_string withclass::withclass=false p =>
/** Convert a proc name to a filename */ /** Convert a proc name to a filename */
let to_filename proc_name => let to_filename proc_name =>
Escape.escape_filename @@ Utils.string_append_crc_cutoff @@ to_unique_id proc_name; Escape.escape_filename @@ SourceFile.append_crc_cutoff @@ to_unique_id proc_name;
/** Pretty print a proc name */ /** Pretty print a proc name */

@ -0,0 +1,44 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
type t = {
introduced: Jsonbug_t.report;
fixed: Jsonbug_t.report;
preexisting: Jsonbug_t.report;
}
(** Set operations should keep duplicated issues with identical hashes *)
let of_reports ~(current_report: Jsonbug_t.report) ~(previous_report: Jsonbug_t.report) : t =
let to_map report =
List.fold_left
~f:(fun map issue -> Map.add_multi map ~key:issue.Jsonbug_t.hash ~data:issue)
~init:Int.Map.empty
report in
let fold_aux ~key:_ ~data (left, both, right) =
match data with
| `Left left' -> (List.rev_append left' left, both, right)
| `Both (both', _) -> (left, List.rev_append both' both, right)
| `Right right' -> (left, both, List.rev_append right' right) in
let introduced, preexisting, fixed =
Map.fold2 (to_map current_report) (to_map previous_report) ~f:fold_aux ~init:([],[],[]) in
{introduced; fixed; preexisting}
let to_files {introduced; fixed; preexisting} destdir =
Out_channel.write_all
(destdir ^/ "introduced.json")
~data:(Jsonbug_j.string_of_report introduced);
Out_channel.write_all
(destdir ^/ "fixed.json")
~data:(Jsonbug_j.string_of_report fixed);
Out_channel.write_all
(destdir ^/ "preexisting.json")
~data:(Jsonbug_j.string_of_report preexisting)

@ -0,0 +1,18 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
type t = {
introduced : Jsonbug_t.report;
fixed : Jsonbug_t.report;
preexisting : Jsonbug_t.report;
}
val of_reports : current_report:Jsonbug_t.report -> previous_report:Jsonbug_t.report -> t
val to_files : t -> string -> unit

@ -0,0 +1,219 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
module FileRenamings = struct
type renaming = {
current: string;
previous: string;
} [@@deriving compare]
type t = renaming list [@@deriving compare]
let equal = [%compare.equal : t]
let empty = []
let from_renamings rl : t = rl
(* A json renaming assoc list looks like:
[{"current": "aaa.java", "previous": "BBB.java"}, ...] *)
let from_json input : t =
let j = Yojson.Basic.from_string input in
let renaming_of_assoc assoc : renaming =
match assoc with
| `Assoc [("current", `String current); ("previous", `String previous)] -> {current; previous}
| _ -> failwithf "Expected JSON object of the following form: '%s', but instead got: '%s'"
"{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}"
(Yojson.Basic.to_string assoc) in
match j with
| `List json_renamings -> List.map ~f:renaming_of_assoc json_renamings
| _ -> failwithf "Expected JSON list but got '%s'" input
let from_json_file file : t = from_json (In_channel.read_all file)
let find_previous (t: t) current =
let r = List.find ~f:(fun r -> String.equal current r.current) t in
Option.map ~f:(fun r -> r.previous) r
let pp fmt t =
let pp_tuple fmt {current; previous} =
Format.fprintf fmt "{\"current\": \"%s\", \"previous\": \"%s\"}" current previous in
Format.fprintf fmt "[%a]" (Pp.comma_seq pp_tuple) t
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct
let from_renamings = from_renamings
let equal = equal
let pp = pp
end
end
(* Remove duplicates between two lists whenever pred is true for such element *)
let relative_complements ~cmp ?(pred=(fun _ -> true)) l1 l2 =
let rec aux last_dup ((out_l1, out_l2) as out) in_l1 in_l2 =
let is_last_seen_dup v = match last_dup with
| Some ld -> Int.equal (cmp ld v) 0
| None -> false in
match in_l1, in_l2 with
| i::is, f::fs when Int.equal (cmp i f) 0 -> (* i = f *)
if pred i then aux (Some i) (out_l1, out_l2) is fs
else aux None (i::out_l1, f::out_l2) is fs
| i::is, f::_ when cmp i f < 0 -> (* i < f *)
let out_l1' = if is_last_seen_dup i then out_l1 else i::out_l1 in
aux last_dup (out_l1', out_l2) is in_l2
| _::_, f::fs -> (* i > f *)
let out_l2' = if is_last_seen_dup f then out_l2 else f::out_l2 in
aux last_dup (out_l1, out_l2') in_l1 fs
| i::is, [] when is_last_seen_dup i -> aux last_dup out is in_l2
| [], f::fs when is_last_seen_dup f -> aux last_dup out in_l1 fs
| _, _ -> List.rev_append in_l1 out_l1, List.rev_append in_l2 out_l2 in
let l1_sorted = List.sort ~cmp l1 in
let l2_sorted = List.sort ~cmp l2 in
aux None ([], []) l1_sorted l2_sorted
type issue_file_with_renaming = Jsonbug_t.jsonbug * (string option)
let skip_duplicated_types_on_filenames
renamings
(diff: Differential.t) : Differential.t =
let compare_issue_file_with_renaming (issue1, previous_file1) (issue2, previous_file2) =
let f1, f2 =
Option.value previous_file1 ~default:issue1.Jsonbug_t.file,
Option.value previous_file2 ~default:issue2.Jsonbug_t.file in
String.compare f1 f2 in
let cmp ((issue1, _) as issue_with_previous_file1) ((issue2, _) as issue_with_previous_file2) =
[%compare : string * issue_file_with_renaming]
(issue1.Jsonbug_t.bug_type, issue_with_previous_file1)
(issue2.Jsonbug_t.bug_type, issue_with_previous_file2) in
let introduced, fixed =
(* All comparisons will be made against filenames *before* renamings.
This way, all introduced and fixed issues can be sorted independently
over the same domain. *)
let introduced_normalized =
List.map diff.introduced
~f:(fun i -> i, FileRenamings.find_previous renamings i.Jsonbug_t.file) in
let fixed_normalized = List.map diff.fixed ~f:(fun f -> f, None) in
let introduced_normalized', fixed_normalized' =
relative_complements ~cmp introduced_normalized fixed_normalized in
List.map ~f:fst introduced_normalized', List.map ~f:fst fixed_normalized' in
{introduced; fixed; preexisting = diff.preexisting}
let java_anon_class_pattern = Str.regexp "\\$[0-9]+"
type procedure_id = string
let compare_procedure_id pid1 pid2 =
(* THIS COMPARISON FUNCTION IS INTENDED FOR JAVA ONLY *)
let normalize_procedure_id pid =
let anon_token = "$ANON" in
Str.global_replace java_anon_class_pattern anon_token pid in
let pid1_norm = normalize_procedure_id pid1 in
let pid2_norm = normalize_procedure_id pid2 in
(* NOTE: The CRC may swallow some extra chars if the anon class has more
* digits (e.g. ...$9.abcde():int.A1B2 and ...$10.abcde():in.C1FF), and this
* makes the 2 strings different.
* Cut the length to the min_length to match the 2 strings *)
let pid1_norm_trimmed, pid2_norm_trimmed =
let min_length = min (String.length pid1_norm) (String.length pid2_norm) in
String.sub pid1_norm ~pos:0 ~len:min_length,
String.sub pid2_norm ~pos:0 ~len:min_length in
String.compare pid1_norm_trimmed pid2_norm_trimmed
let value_of_qualifier_tag qts tag =
match List.find ~f:(fun elem -> String.equal elem.Jsonbug_t.tag tag) qts with
| Some qt -> Some qt.Jsonbug_t.value
| None -> None
type file_extension = string [@@deriving compare]
type weak_hash = string * string * string * int * (string option) [@@deriving compare]
let skip_anonymous_class_renamings (diff: Differential.t) : Differential.t =
(*
* THIS HEURISTIC IS INTENDED FOR JAVA ONLY.
* Two issues are similar (for the purpose of anonymous class renamings detection)
* when all of the following apply:
* 1) they are Java files
* 2) their weak hashes match
* 3) their anonymous procedure ids match
*)
let string_of_procedure_id issue = SourceFile.strip_crc issue.Jsonbug_t.procedure_id in
let extension fname = snd (Filename.split_extension fname) in
let cmp (i1:Jsonbug_t.jsonbug) (i2:Jsonbug_t.jsonbug) =
[%compare :
(file_extension option) * weak_hash * procedure_id]
(extension i1.file,
(i1.kind, i1.bug_type, i1.file, i1.key,
value_of_qualifier_tag i1.qualifier_tags "call_procedure"),
string_of_procedure_id i1)
(extension i2.file,
(i2.kind, i2.bug_type, i2.file, i2.key,
value_of_qualifier_tag i2.qualifier_tags "call_procedure"),
string_of_procedure_id i2) in
let pred (issue: Jsonbug_t.jsonbug) =
let is_java_file () =
match extension issue.file with
| Some ext -> String.equal (String.lowercase ext) "java"
| None -> false in
let has_anonymous_class_token () =
try
ignore (Str.search_forward java_anon_class_pattern issue.procedure_id 0);
true
with Not_found -> false in
is_java_file () && has_anonymous_class_token () in
let introduced, fixed = relative_complements ~cmp ~pred diff.introduced diff.fixed in
{introduced; fixed; preexisting = diff.preexisting}
(* Filter out null dereferences reported by infer if file has eradicate
enabled, to avoid double reporting. *)
let resolve_infer_eradicate_conflict
(analyzer: Config.analyzer)
(filters_of_analyzer: Config.analyzer -> Inferconfig.filters)
(diff: Differential.t) : Differential.t =
let should_discard_issue (issue: Jsonbug_t.jsonbug) =
let file_is_whitelisted () =
let source_file = SourceFile.UNSAFE.from_string issue.file in
let filters = filters_of_analyzer Config.Eradicate in
filters.path_filter source_file in
Config.equal_analyzer analyzer Config.Infer &&
String.equal issue.bug_type (Localise.to_string Localise.null_dereference) &&
file_is_whitelisted () in
let filter issues = List.filter ~f:(Fn.non should_discard_issue) issues in
{
introduced = filter diff.introduced;
fixed = filter diff.fixed;
preexisting = filter diff.preexisting;
}
let do_filter
(diff: Differential.t)
(renamings: FileRenamings.t)
~(skip_duplicated_types: bool): Differential.t =
if Config.filtering then (
diff
|> (if Config.equal_analyzer Config.analyzer Config.Infer then
skip_anonymous_class_renamings
else Fn.id)
|> (if skip_duplicated_types then
skip_duplicated_types_on_filenames renamings
else Fn.id)
|> (if Config.resolve_infer_eradicate_conflict then
resolve_infer_eradicate_conflict Config.analyzer Inferconfig.create_filters
else Fn.id))
else diff
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY = struct
let relative_complements = relative_complements
let skip_duplicated_types_on_filenames = skip_duplicated_types_on_filenames
let java_anon_class_pattern = java_anon_class_pattern
let value_of_qualifier_tag = value_of_qualifier_tag
let skip_anonymous_class_renamings = skip_anonymous_class_renamings
let resolve_infer_eradicate_conflict = resolve_infer_eradicate_conflict
end

@ -0,0 +1,42 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
module FileRenamings :
sig
type renaming = {
current: string;
previous: string;
}
type t
val empty : t
val from_json : string -> t
val from_json_file : string -> t
val find_previous : t -> string -> string option
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig
val from_renamings : renaming list -> t
val equal : t -> t -> bool
val pp : Format.formatter -> t -> unit
end
end
val do_filter : Differential.t -> FileRenamings.t -> skip_duplicated_types:bool -> Differential.t
module VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY : sig
val relative_complements :
cmp:('a -> 'a -> int) -> ?pred:('a -> bool) -> 'a list -> 'a list -> 'a list * 'a list
val skip_duplicated_types_on_filenames : FileRenamings.t -> Differential.t -> Differential.t
val java_anon_class_pattern : Str.regexp
val value_of_qualifier_tag : Jsonbug_t.tag_value_record list -> string -> string option
val skip_anonymous_class_renamings : Differential.t -> Differential.t
val resolve_infer_eradicate_conflict :
Config.analyzer ->
(Config.analyzer -> Inferconfig.filters) ->
Differential.t -> Differential.t
end

@ -548,6 +548,8 @@ let pp_custom_of_report fmt report fields => {
| `Issue_field_hash => Format.fprintf fmt "%s%d" (comma_separator index) issue.hash | `Issue_field_hash => Format.fprintf fmt "%s%d" (comma_separator index) issue.hash
| `Issue_field_line_offset => | `Issue_field_line_offset =>
Format.fprintf fmt "%s%d" (comma_separator index) (issue.line - issue.procedure_start_line) Format.fprintf fmt "%s%d" (comma_separator index) (issue.line - issue.procedure_start_line)
| `Issue_field_procedure_id_without_crc =>
Format.fprintf fmt "%s%s" (comma_separator index) (SourceFile.strip_crc issue.procedure_id)
}; };
List.iteri f::pp_field fields; List.iteri f::pp_field fields;
Format.fprintf fmt "@." Format.fprintf fmt "@."

@ -434,7 +434,7 @@ let get_driver_mode () =
| None -> | None ->
driver_mode_of_build_cmd (List.rev Config.rest) driver_mode_of_build_cmd (List.rev Config.rest)
let () = let infer_mode () =
let driver_mode = get_driver_mode () in let driver_mode = get_driver_mode () in
if not (equal_driver_mode driver_mode Analyze || if not (equal_driver_mode driver_mode Analyze ||
Config.(buck || continue_capture || maven || reactive_mode)) then Config.(buck || continue_capture || maven || reactive_mode)) then
@ -466,3 +466,28 @@ let () =
); );
if Config.buck_cache_mode then if Config.buck_cache_mode then
clean_results_dir () clean_results_dir ()
let differential_mode () =
let arg_or_fail arg_name arg_opt =
match arg_opt with
| Some arg -> arg
| None -> failwithf "Expected '%s' argument not found" arg_name in
let load_report filename : Jsonbug_t.report =
Jsonbug_j.report_of_string (In_channel.read_all filename) in
let current_report = load_report (arg_or_fail "report-current" Config.report_current) in
let previous_report = load_report (arg_or_fail "report-previous" Config.report_previous) in
let file_renamings = match Config.file_renamings with
| Some f -> DifferentialFilters.FileRenamings.from_json_file f
| None -> DifferentialFilters.FileRenamings.empty in
let diff = DifferentialFilters.do_filter
(Differential.of_reports ~current_report ~previous_report)
file_renamings
~skip_duplicated_types:Config.skip_duplicated_types in
let out_path = Config.results_dir ^/ "differential" in
Unix.mkdir_p out_path;
Differential.to_files diff out_path
let () =
match Config.final_parse_action with
| Differential -> differential_mode ()
| _ -> infer_mode ()

@ -63,7 +63,8 @@ let equal_section = [%compare.equal : section ]
let all_sections = let all_sections =
[ Analysis; BufferOverrun; Checkers; Clang; Crashcontext; Driver; Java; Print; Quandary ] [ Analysis; BufferOverrun; Checkers; Clang; Crashcontext; Driver; Java; Print; Quandary ]
type 'a parse = Infer of 'a | Javac | NoParse [@@deriving compare] (* NOTE: All variants must be also added to `all_parse_tags` below *)
type 'a parse = Differential | Infer of 'a | Javac | NoParse [@@deriving compare]
type parse_mode = section list parse [@@deriving compare] type parse_mode = section list parse [@@deriving compare]
@ -74,14 +75,20 @@ let equal_parse_action = [%compare.equal : parse_action ]
type parse_tag = unit parse [@@deriving compare] type parse_tag = unit parse [@@deriving compare]
let equal_parse_tag = [%compare.equal : parse_tag ] let equal_parse_tag = [%compare.equal : parse_tag ]
let all_parse_tags = [ Infer (); Javac; NoParse ] let all_parse_tags = [ Differential; Infer (); Javac; NoParse ]
let to_parse_tag = function | Infer _ -> Infer () | Javac -> Javac | NoParse -> NoParse (* NOTE: All variants must be also added to `all_parse_tags` below *)
let to_parse_tag tag =
match tag with
| Differential -> Differential
| Infer _ -> Infer ()
| Javac -> Javac
| NoParse -> NoParse
let accept_unknown_args = function let accept_unknown_args = function
| Infer Print | Javac | NoParse -> true | Infer Print | Javac | NoParse -> true
| Infer (Analysis | BufferOverrun | Checkers | Clang | Crashcontext | Driver | Java | Quandary) -> | Infer (Analysis | BufferOverrun | Checkers | Clang | Crashcontext | Driver | Java | Quandary)
false | Differential -> false
type desc = { type desc = {
long: string; short: string; meta: string; doc: string; spec: spec; long: string; short: string; meta: string; doc: string; spec: spec;
@ -240,7 +247,7 @@ let add parse_mode desc =
let full_desc_list = List.Assoc.find_exn parse_tag_desc_lists tag in let full_desc_list = List.Assoc.find_exn parse_tag_desc_lists tag in
full_desc_list := desc :: !full_desc_list ; full_desc_list := desc :: !full_desc_list ;
match parse_mode with match parse_mode with
| Javac | NoParse -> () | Differential | Javac | NoParse -> ()
| Infer sections -> | Infer sections ->
List.iter infer_section_desc_lists ~f:(fun (section, desc_list) -> List.iter infer_section_desc_lists ~f:(fun (section, desc_list) ->
let desc = if List.mem ~equal:equal_section sections section then let desc = if List.mem ~equal:equal_section sections section then
@ -252,7 +259,7 @@ let add parse_mode desc =
let deprecate_desc parse_mode ~long ~short ~deprecated desc = let deprecate_desc parse_mode ~long ~short ~deprecated desc =
let warn () = match parse_mode with let warn () = match parse_mode with
| Javac | NoParse -> () | Javac | NoParse -> ()
| Infer _ -> | Differential | Infer _ ->
warnf "WARNING: '-%s' is deprecated. Use '--%s'%s instead.@." warnf "WARNING: '-%s' is deprecated. Use '--%s'%s instead.@."
deprecated long (if short = "" then "" else Printf.sprintf " or '-%s'" short) in deprecated long (if short = "" then "" else Printf.sprintf " or '-%s'" short) in
let warn_then_f f x = warn (); f x in let warn_then_f f x = warn (); f x in
@ -287,7 +294,7 @@ let mk ?(deprecated=[]) ?(parse_mode=Infer [])
(* add desc for short option only for parsing, without documentation *) (* add desc for short option only for parsing, without documentation *)
let parse_mode_no_sections = match parse_mode with let parse_mode_no_sections = match parse_mode with
| Infer _ -> Infer [] | Infer _ -> Infer []
| Javac | NoParse -> parse_mode in | Differential | Javac | NoParse -> parse_mode in
if short <> "" then if short <> "" then
add parse_mode_no_sections {desc with long = ""; meta = ""; doc = ""} ; add parse_mode_no_sections {desc with long = ""; meta = ""; doc = ""} ;
(* add desc for deprecated options only for parsing, without documentation *) (* add desc for deprecated options only for parsing, without documentation *)
@ -617,7 +624,7 @@ let set_curr_speclist_for_parse_action ~incomplete ~usage parse_action =
match parse_action with match parse_action with
| Infer section -> | Infer section ->
List.Assoc.find_exn ~equal:equal_section infer_section_desc_lists section List.Assoc.find_exn ~equal:equal_section infer_section_desc_lists section
| Javac | NoParse -> | Differential | Javac | NoParse ->
to_parse_tag parse_action to_parse_tag parse_action
|> List.Assoc.find_exn ~equal:equal_parse_tag parse_tag_desc_lists in |> List.Assoc.find_exn ~equal:equal_parse_tag parse_tag_desc_lists in
let (exe_speclist, widths) = normalize !exe_descs in let (exe_speclist, widths) = normalize !exe_descs in
@ -676,6 +683,16 @@ let mk_rest_actions ?(parse_mode=Infer []) doc ~usage decode_action =
add parse_mode {long = "--"; short = ""; meta = ""; doc; spec; decode_json = fun _ -> []} ; add parse_mode {long = "--"; short = ""; meta = ""; doc; spec; decode_json = fun _ -> []} ;
rest rest
let mk_switch_parse_action
parse_action ~usage ?(deprecated=[]) ~long ?short ?parse_mode ?(meta="") doc =
let switch () =
select_parse_action ~incomplete:false ~usage parse_action |> ignore in
ignore(
mk ~deprecated ~long ?short ~default:() ?parse_mode ~meta doc
~default_to_string:(fun () -> "")
~decode_json:(string_json_decoder ~long)
~mk_setter:(fun _ _ -> switch ())
~mk_spec:(fun _ -> Unit switch))
let decode_inferconfig_to_argv path = let decode_inferconfig_to_argv path =
let json = match Utils.read_optional_json_file path with let json = match Utils.read_optional_json_file path with

@ -18,7 +18,7 @@ type section =
val all_sections : section list val all_sections : section list
type 'a parse = Infer of 'a | Javac | NoParse type 'a parse = Differential | Infer of 'a | Javac | NoParse
type parse_mode = section list parse [@@deriving compare] type parse_mode = section list parse [@@deriving compare]
@ -140,6 +140,11 @@ val mk_rest_actions :
usage:string -> (string -> parse_action) usage:string -> (string -> parse_action)
-> string list ref -> string list ref
(** when the option is found on the command line, the current parse action is discarded and the
following arguments are parsed using [parse_action] *)
val mk_switch_parse_action : parse_action -> usage:string -> unit t
(** environment variable use to pass arguments from parent to child processes *) (** environment variable use to pass arguments from parent to child processes *)
val args_env_var : string val args_env_var : string

@ -92,6 +92,7 @@ let issues_fields_symbols = [
("key", `Issue_field_key); ("key", `Issue_field_key);
("hash", `Issue_field_hash); ("hash", `Issue_field_hash);
("line_offset", `Issue_field_line_offset); ("line_offset", `Issue_field_line_offset);
("procedure_id_without_crc", `Issue_field_procedure_id_without_crc);
] ]
type os_type = Unix | Win32 | Cygwin type os_type = Unix | Win32 | Cygwin
@ -432,6 +433,10 @@ let inferconfig_path =
let anon_args = CLOpt.mk_anon () let anon_args = CLOpt.mk_anon ()
and () =
CLOpt.mk_switch_parse_action CLOpt.Differential ~usage:"infer --diff [options]"
~long:"diff" "[experimental] compute differential report"
and abs_struct = and abs_struct =
CLOpt.mk_int ~deprecated:["absstruct"] ~long:"abs-struct" ~default:1 CLOpt.mk_int ~deprecated:["absstruct"] ~long:"abs-struct" ~default:1
~meta:"int" "Specify abstraction level for fields of structs:\n\ ~meta:"int" "Specify abstraction level for fields of structs:\n\
@ -893,6 +898,11 @@ and fcp_syntax_only =
CLOpt.mk_bool ~long:"fcp-syntax-only" CLOpt.mk_bool ~long:"fcp-syntax-only"
"Skip creation of object files" "Skip creation of object files"
and file_renamings =
CLOpt.mk_path_opt
~long:"file-renamings" ~parse_mode:CLOpt.Differential
"JSON with a list of file renamings to use while computing differential reports"
and filter_paths = and filter_paths =
CLOpt.mk_bool ~long:"filter-paths" ~default:true CLOpt.mk_bool ~long:"filter-paths" ~default:true
"Filters specified in .inferconfig" "Filters specified in .inferconfig"
@ -1139,6 +1149,10 @@ and report =
CLOpt.mk_path_opt ~deprecated:["report"] ~long:"report" CLOpt.mk_path_opt ~deprecated:["report"] ~long:"report"
~meta:"file" "Write a report of the analysis results to a file" ~meta:"file" "Write a report of the analysis results to a file"
and report_current =
CLOpt.mk_path_opt ~long:"report-current" ~parse_mode:CLOpt.Differential
"report of the latest revision"
and report_custom_error = and report_custom_error =
CLOpt.mk_bool ~long:"report-custom-error" CLOpt.mk_bool ~long:"report-custom-error"
"" ""
@ -1151,6 +1165,15 @@ and report_hook =
passed --issues-csv, --issues-json, --issues-txt, --issues-xml, --project-root, and \ passed --issues-csv, --issues-json, --issues-txt, --issues-xml, --project-root, and \
--results-dir." --results-dir."
and report_previous =
CLOpt.mk_path_opt ~long:"report-previous" ~parse_mode:CLOpt.Differential
"report of the base revision to use for comparison"
and resolve_infer_eradicate_conflict =
CLOpt.mk_bool ~long:"resolve-infer-eradicate-conflict"
~default:false ~parse_mode:CLOpt.Differential
"Filter out Null Dereferences reported by Infer if Eradicate is enabled"
and rest = and rest =
CLOpt.mk_rest_actions CLOpt.mk_rest_actions
~parse_mode:CLOpt.(Infer [Driver]) ~parse_mode:CLOpt.(Infer [Driver])
@ -1189,6 +1212,10 @@ and skip_analysis_in_path =
~meta:"path prefix OCaml regex" ~meta:"path prefix OCaml regex"
"Ignore files whose path matches the given prefix (can be specified multiple times)" "Ignore files whose path matches the given prefix (can be specified multiple times)"
and skip_duplicated_types =
CLOpt.mk_bool ~long:"skip-duplicated-types" ~default:true ~parse_mode:CLOpt.Differential
"Skip fixed-then-introduced duplicated types while computing differential reports"
and skip_translation_headers = and skip_translation_headers =
CLOpt.mk_string_list ~deprecated:["skip_translation_headers"] ~long:"skip-translation-headers" CLOpt.mk_string_list ~deprecated:["skip_translation_headers"] ~long:"skip-translation-headers"
~parse_mode:CLOpt.(Infer [Clang]) ~parse_mode:CLOpt.(Infer [Clang])
@ -1544,8 +1571,10 @@ and fail_on_bug = !fail_on_bug
and failures_allowed = !failures_allowed and failures_allowed = !failures_allowed
and fcp_apple_clang = !fcp_apple_clang and fcp_apple_clang = !fcp_apple_clang
and fcp_syntax_only = !fcp_syntax_only and fcp_syntax_only = !fcp_syntax_only
and file_renamings = !file_renamings
and filter_paths = !filter_paths and filter_paths = !filter_paths
and filtering = !filtering and filtering = !filtering
and final_parse_action = parse_action
and flavors = !flavors and flavors = !flavors
and from_json_report = !from_json_report and from_json_report = !from_json_report
and frontend_debug = !frontend_debug and frontend_debug = !frontend_debug
@ -1598,10 +1627,13 @@ and quiet = !quiet
and reactive_mode = !reactive and reactive_mode = !reactive
and reactive_capture = !reactive_capture and reactive_capture = !reactive_capture
and report = !report and report = !report
and report_current = !report_current
and report_custom_error = !report_custom_error and report_custom_error = !report_custom_error
and report_hook = !report_hook and report_hook = !report_hook
and report_previous = !report_previous
and report_runtime_exceptions = !tracing and report_runtime_exceptions = !tracing
and reports_include_ml_loc = !reports_include_ml_loc and reports_include_ml_loc = !reports_include_ml_loc
and resolve_infer_eradicate_conflict = !resolve_infer_eradicate_conflict
and results_dir = !results_dir and results_dir = !results_dir
and save_analysis_results = !save_results and save_analysis_results = !save_results
and seconds_per_iteration = !seconds_per_iteration and seconds_per_iteration = !seconds_per_iteration
@ -1609,6 +1641,7 @@ and show_buckets = !print_buckets
and show_progress_bar = !progress_bar and show_progress_bar = !progress_bar
and siof_safe_methods = !siof_safe_methods and siof_safe_methods = !siof_safe_methods
and skip_analysis_in_path = !skip_analysis_in_path and skip_analysis_in_path = !skip_analysis_in_path
and skip_duplicated_types = !skip_duplicated_types
and skip_translation_headers = !skip_translation_headers and skip_translation_headers = !skip_translation_headers
and sources = !sources and sources = !sources
and sourcepath = !sourcepath and sourcepath = !sourcepath

@ -56,7 +56,8 @@ val issues_fields_symbols :
| `Issue_field_bug_trace | `Issue_field_bug_trace
| `Issue_field_key | `Issue_field_key
| `Issue_field_hash | `Issue_field_hash
| `Issue_field_line_offset]) list | `Issue_field_line_offset
| `Issue_field_procedure_id_without_crc]) list
type os_type = Unix | Win32 | Cygwin type os_type = Unix | Win32 | Cygwin
@ -224,8 +225,10 @@ val fail_on_bug : bool
val failures_allowed : bool val failures_allowed : bool
val fcp_apple_clang : string option val fcp_apple_clang : string option
val fcp_syntax_only : bool val fcp_syntax_only : bool
val file_renamings : string option
val filter_paths : bool val filter_paths : bool
val filtering : bool val filtering : bool
val final_parse_action : CommandLineOption.parse_action
val flavors : bool val flavors : bool
val from_json_report : string option val from_json_report : string option
val frontend_debug : bool val frontend_debug : bool
@ -251,7 +254,8 @@ val issues_fields : [`Issue_field_bug_class
| `Issue_field_bug_trace | `Issue_field_bug_trace
| `Issue_field_key | `Issue_field_key
| `Issue_field_hash | `Issue_field_hash
| `Issue_field_line_offset] list | `Issue_field_line_offset
| `Issue_field_procedure_id_without_crc] list
val iterations : int val iterations : int
val java_jar_compiler : string option val java_jar_compiler : string option
val javac_classes_out : string val javac_classes_out : string
@ -291,9 +295,12 @@ val quiet : bool
val reactive_mode : bool val reactive_mode : bool
val reactive_capture : bool val reactive_capture : bool
val report : string option val report : string option
val report_current : string option
val report_hook : string option val report_hook : string option
val report_previous : string option
val report_runtime_exceptions : bool val report_runtime_exceptions : bool
val reports_include_ml_loc : bool val reports_include_ml_loc : bool
val resolve_infer_eradicate_conflict : bool
val results_dir : string val results_dir : string
val save_analysis_results : string option val save_analysis_results : string option
val seconds_per_iteration : float option val seconds_per_iteration : float option
@ -301,6 +308,7 @@ val show_buckets : bool
val show_progress_bar : bool val show_progress_bar : bool
val siof_safe_methods : string list val siof_safe_methods : string list
val skip_analysis_in_path : string list val skip_analysis_in_path : string list
val skip_duplicated_types : bool
val skip_translation_headers : string list val skip_translation_headers : string list
val spec_abs_level : int val spec_abs_level : int
val specs_library : string list val specs_library : string list

@ -27,7 +27,7 @@ let source_dir_to_string source_dir = source_dir
(** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *) (** get the path to an internal file with the given extention (.cfg, .cg, .tenv) *)
let source_dir_get_internal_file source_dir extension = let source_dir_get_internal_file source_dir extension =
let source_dir_name = let source_dir_name =
Utils.string_append_crc_cutoff (Filename.chop_extension (Filename.basename source_dir)) in SourceFile.append_crc_cutoff (Filename.chop_extension (Filename.basename source_dir)) in
let fname = source_dir_name ^ extension in let fname = source_dir_name ^ extension in
Filename.concat source_dir fname Filename.concat source_dir fname

@ -31,6 +31,7 @@ let dup_formatter fmt1 fmt2 =
(** Name of dir for logging the output in the specific executable *) (** Name of dir for logging the output in the specific executable *)
let log_dir_of_action (action : CLOpt.parse_action) = match action with let log_dir_of_action (action : CLOpt.parse_action) = match action with
| Infer (Analysis | BufferOverrun | Checkers | Crashcontext | Quandary) -> "analyze" | Infer (Analysis | BufferOverrun | Checkers | Crashcontext | Quandary) -> "analyze"
| Differential -> "differential"
| Infer Driver -> "driver" | Infer Driver -> "driver"
| Infer Clang | Infer Clang
| Infer Java | Infer Java

@ -77,6 +77,23 @@ let to_rel_path fname =
| RelativeProjectRoot path -> path | RelativeProjectRoot path -> path
| _ -> to_abs_path fname | _ -> to_abs_path fname
let cutoff_length = 100
let crc_token = '.'
let append_crc_cutoff ?(key="") name =
let name_up_to_cutoff =
if String.length name <= cutoff_length
then name
else String.sub name ~pos:0 ~len:cutoff_length in
let crc_str =
let name_for_crc = name ^ key in
Utils.string_crc_hex32 name_for_crc in
name_up_to_cutoff ^ Char.to_string crc_token ^ crc_str
let strip_crc str =
(* Strip 32 characters of digest, plus 1 character of crc_token *)
String.sub ~pos:0 ~len:(String.length str - 33) str
(** string encoding of a source file (including path) as a single filename *) (** string encoding of a source file (including path) as a single filename *)
let encoding source_file = let encoding source_file =
let prefix = match source_file with let prefix = match source_file with
@ -92,7 +109,7 @@ let encoding source_file =
| `Enc_crc -> | `Enc_crc ->
let base = Filename.basename source_file_s in let base = Filename.basename source_file_s in
let dir = prefix ^ Filename.dirname source_file_s in let dir = prefix ^ Filename.dirname source_file_s in
Utils.string_append_crc_cutoff ~key:dir base append_crc_cutoff ~key:dir base
let empty = Absolute "" let empty = Absolute ""
@ -153,3 +170,11 @@ let changed_files_set =
) )
~init:Set.empty ~init:Set.empty
) )
module UNSAFE = struct
let from_string str =
if Filename.is_relative str then
RelativeProjectRoot str
else
Absolute str
end

@ -60,3 +60,18 @@ val of_header : t -> t option
NOTE: it may include extra source_files if --changed-files-index contains paths to NOTE: it may include extra source_files if --changed-files-index contains paths to
header files *) header files *)
val changed_files_set : Set.t option val changed_files_set : Set.t option
(** Append a crc to the string, using string_crc_hex32.
Cut the string if it exceeds the cutoff limit.
Use an optional key to compute the crc. *)
val append_crc_cutoff : ?key:string -> string -> string
(** Strip any crc attached to any string generated by string_append_crc_cutoff *)
val strip_crc : string -> string
module UNSAFE : sig
(** Create a SourceFile from any path. This is unchecked and should not be
used when the existence of source files is a requirement. Furthermore,
absolute paths won't be made relative to project root.*)
val from_string : string -> t
end

@ -185,19 +185,8 @@ let remove_directory_tree path =
| _ -> Unix.remove (Fts.FTSENT.name ent) | _ -> Unix.remove (Fts.FTSENT.name ent)
) )
let string_crc_hex32 s = Digest.to_hex (Digest.string s) let string_crc_hex32 s = Digest.to_hex (Digest.string s)
let string_append_crc_cutoff ?(cutoff=100) ?(key="") name =
let name_up_to_cutoff =
if String.length name <= cutoff
then name
else String.sub name ~pos:0 ~len:cutoff in
let crc_str =
let name_for_crc = name ^ key in
string_crc_hex32 name_for_crc in
name_up_to_cutoff ^ "." ^ crc_str
let read_optional_json_file path = let read_optional_json_file path =
if Sys.file_exists path = `Yes then if Sys.file_exists path = `Yes then
try try

@ -18,11 +18,6 @@ val initial_timeofday : float
(** Compute a 32-character hexadecimal crc using the Digest module *) (** Compute a 32-character hexadecimal crc using the Digest module *)
val string_crc_hex32 : string -> string val string_crc_hex32 : string -> string
(** Append a crc to the string, using string_crc_hex32.
Cut the string if it exceeds the cutoff limit.
Use an optional key to compute the crc. *)
val string_append_crc_cutoff : ?cutoff:int -> ?key:string -> string -> string
(** copy a source file, return the number of lines, or None in case of error *) (** copy a source file, return the number of lines, or None in case of error *)
val copy_file : string -> string -> int option val copy_file : string -> string -> int option

@ -0,0 +1,523 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
open OUnit2
open DifferentialTestsUtils
type 'a outcome = Return of 'a | Raise of exn
let test_file_renamings_from_json =
let create_test test_input expected_output _ =
let test_output input = DifferentialFilters.FileRenamings.from_json input in
let pp_diff fmt (expected, actual) =
let pp = DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.pp in
Format.fprintf fmt "Expected %a but got %a" pp expected pp actual in
match expected_output with
| Return exp ->
assert_equal
~pp_diff
~cmp:DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.equal
exp
(test_output test_input)
| Raise exc ->
assert_raises exc (fun () -> test_output test_input) in
[
(
"test_file_renamings_from_json_with_good_input",
"[" ^
"{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}," ^
"{\"current\": \"ccc.java\", \"previous\": \"DDD.java\"}," ^
"{\"current\": \"eee.java\", \"previous\": \"FFF.java\"}" ^
"]",
Return (
DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [
{DifferentialFilters.FileRenamings.current = "aaa.java"; previous = "BBB.java"};
{DifferentialFilters.FileRenamings.current = "ccc.java"; previous = "DDD.java"};
{DifferentialFilters.FileRenamings.current = "eee.java"; previous = "FFF.java"};
]
)
);
(
"test_file_renamings_from_json_with_good_empty_input",
"[]",
Return (
DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings []
)
);
(
"test_file_renamings_from_json_with_well_formed_but_unexpected_input",
"{}",
Raise (
Failure "Expected JSON list but got '{}'"
)
);
(
"test_file_renamings_from_json_with_well_formed_but_unexpected_value",
"[{\"current\": 1, \"previous\": \"BBB.java\"}]",
Raise (
Failure ("Expected JSON object of the following form: " ^
"'{\"current\": \"aaa.java\", \"previous\": \"BBB.java\"}', " ^
"but instead got: '{\"current\":1,\"previous\":\"BBB.java\"}'")
)
);
(
"test_file_renamings_from_json_with_malformed_input",
"A",
Raise (
Yojson.Json_error "Line 1, bytes 0-1:\nInvalid token 'A'"
)
);
]
|> List.map
~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output)
let test_file_renamings_find_previous =
let renamings =
DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [
{DifferentialFilters.FileRenamings.current = "aaa.java"; previous = "BBB.java"};
{DifferentialFilters.FileRenamings.current = "ccc.java"; previous = "DDD.java"};
{DifferentialFilters.FileRenamings.current = "eee.java"; previous = "FFF.java"};
] in
let cmp s1 s2 = [%compare.equal : string option] s1 s2 in
let find_previous = DifferentialFilters.FileRenamings.find_previous in
let pp_diff fmt (expected, actual) =
let pp_str_opt fmt str_opt =
let out = match str_opt with
| Some str -> "Some " ^ str
| None -> "None" in
Format.fprintf fmt "%s" out in
Format.fprintf fmt "Expected '%a' but got '%a'" pp_str_opt expected pp_str_opt actual in
let create_test input expected_previous _ =
assert_equal ~cmp ~pp_diff expected_previous (find_previous renamings input) in
[
(
"test_file_renamings_find_previous_with_existing_value",
"ccc.java",
Some "DDD.java"
);
(
"test_file_renamings_find_previous_with_existing_value",
"abc.java",
None
)
]
|> List.map
~f:(fun (name, test_input, expected_output) ->
name >:: create_test test_input expected_output)
let test_relative_complements =
let create_test pred (l1, l2) (expected_l1, expected_l2) _ =
let cmp = Int.compare in
let output_l1, output_l2 =
DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.relative_complements
~cmp ~pred l1 l2 in
let list_equal l1 l2 = List.equal ~equal:(fun v1 v2 -> Int.equal (cmp v1 v2) 0) l1 l2 in
assert_equal
~pp_diff:(pp_diff_of_int_list "First list") ~cmp:list_equal expected_l1 output_l1;
assert_equal
~pp_diff:(pp_diff_of_int_list "Second list") ~cmp:list_equal expected_l2 output_l2 in
[
(
"test_relative_complements_with_always_true_pred",
(fun _ -> true),
([0;1;2;3;4;5], [5;3;7;1;1;2]),
([4;0], [7])
);
(
"test_relative_complements_with_even_numbers_pred",
(fun i -> Int.equal (i mod 2) 0), (* skip when even, keep odd *)
([0;1;2;3;4;5], [5;3;7;1;1;2]),
([5;4;3;1;0], [7;5;3;1;1])
);
(
"test_relative_complements_with_even_numbers_pred_2",
(fun i -> Int.equal (i mod 2) 0), (* skip when even, keep odd *)
([0;1;2;3;5;5], [1;1;2;3;4;7]),
([5;5;3;1;0], [7;4;3;1;1])
);
(
"test_relative_complements_with_always_true_pred_and_disjoint_lists_of_different_length",
(fun _ -> true),
([0;3;2;3;5], [9;7;6;8;4;6;9]),
([5;3;3;2;0], [9;9;8;7;6;6;4])
);
(
"test_relative_complements_with_always_true_pred_and_lists_of_different_length",
(fun _ -> true),
([0;3;2;3], [9;7;3;8;0;6;9;4]),
([2], [9;9;8;7;6;4])
);
(
"test_relative_complements_with_odd_numbers_on_lists_of_different_length",
(fun i -> Int.equal (i mod 2) 1), (* skip when odd, keep even *)
([0;3;2;3], [9;7;3;8;0;6;9;4]),
([2;0], [9;9;8;7;6;4;0])
);
(
"test_relative_complements_with_singleton_lists1",
(fun _ -> true),
([0], [0;1;0;0]),
([], [1])
);
(
"test_relative_complements_with_singleton_lists2",
(fun _ -> true),
([0;1;0;0], [0]),
([1], [])
);
(
"test_relative_complements_with_singleton_lists3",
(fun _ -> true),
([0], [0]),
([], [])
);
(
"test_relative_complements_with_singleton_lists4",
(fun _ -> true),
([0], [1]),
([0], [1])
);
(
"test_relative_complements_with_empty_lists1",
(fun _ -> true),
([], [0;1;0;0]),
([], [1;0;0;0])
);
(
"test_relative_complements_with_empty_lists2",
(fun _ -> true),
([0;1;0;0], []),
([1;0;0;0], [])
);
(
"test_relative_complements_with_empty_lists3",
(fun _ -> true),
([], []),
([], [])
);
]
|> List.map
~f:(fun (name, pred, test_input, expected_output) ->
name >:: create_test pred test_input expected_output)
let test_skip_duplicated_types_on_filenames =
let current_report = [
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:22 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:11 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1'.java" ~hash:111 ();
create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_4.java" ~hash:4 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 ();
create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:55 ();
] in
let previous_report = [
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2'.java" ~hash:222 ();
create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_5.java" ~hash:5 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 ();
create_fake_jsonbug ~bug_type:"bug_type_2" ~file:"file_3.java" ~hash:3 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_2.java" ~hash:2 ();
] in
let renamings =
DifferentialFilters.FileRenamings.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.from_renamings [
{DifferentialFilters.FileRenamings.current = "file_2'.java"; previous = "file_2.java"};
{DifferentialFilters.FileRenamings.current = "file_1'.java"; previous = "file_1.java"};
] in
let diff = Differential.of_reports ~current_report ~previous_report in
let diff' =
DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.skip_duplicated_types_on_filenames
renamings diff in
let do_assert _ =
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of introduced")
[4] (sorted_hashes_of_issues diff'.introduced);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of fixed")
[3] (sorted_hashes_of_issues diff'.fixed);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of preexisting")
[222] (sorted_hashes_of_issues diff'.preexisting) in
"test_skip_duplicated_types_on_filenames" >:: do_assert
let test_value_of_qualifier_tag =
let qts =
[{Jsonbug_t.tag = "tag1"; value = "value1"}; {Jsonbug_t.tag = "tag2"; value = "value2"}] in
let pp_diff fmt (expected, actual) =
let to_str v = Option.value v ~default:"NONE" in
Format.fprintf fmt "Expected: %s Found: %s" (to_str expected) (to_str actual) in
let do_assert _ =
assert_equal
~cmp:(Option.equal String.equal)
~pp_diff
(Some "value2")
(DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag
qts "tag2");
assert_equal
~cmp:(Option.equal String.equal)
~pp_diff
None
(DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag
qts "tag3");
assert_equal
~cmp:(Option.equal String.equal)
~pp_diff
(Some "value1")
(DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.value_of_qualifier_tag
qts "tag1") in
"test_value_of_qualifier_tag" >:: do_assert
let test_skip_anonymous_class_renamings =
let qt1 = [{Jsonbug_t.tag = "call_procedure"; value = "aValue1"}] in
let qt2 = [{Jsonbug_t.tag = "call_procedure"; value = "aValue2"}] in
let create_test input_diff (exp_introduced, exp_fixed, exp_preexisting) _ =
let diff' =
DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.skip_anonymous_class_renamings
input_diff in
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of introduced")
exp_introduced (sorted_hashes_of_issues diff'.introduced);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of fixed")
exp_fixed (sorted_hashes_of_issues diff'.fixed);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of preexisting")
exp_preexisting (sorted_hashes_of_issues diff'.preexisting) in
(* [(test_name, diff, expected hashes); ...] *)
[
("test_skip_anonymous_class_renamings_with_long_procedure_ids",
Differential.of_reports
~current_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
("com.whatever.package00.abcd." ^
"ABasicExampleFragment$83.onMenuItemActionExpand(android.view.MenuItem):b." ^
"5ab5e18cae498c35d887ce88f3d5fa82")
~file:"a.java"
~key:1
~qualifier_tags:qt1
~hash:3 ();
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
("com.whatever.package00.abcd." ^
"ABasicExampleFragment$83$7.onMenuItemActionExpand(android.view.MenuItem)." ^
"522cc747174466169781c9d2fc980dbc")
~file:"a.java"
~key:1
~qualifier_tags:qt1
~hash:4 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"procid5.c854fd4a98113d9ab5b82deb3545de89"
~file:"b.java"
~key:5
~hash:5 ();
]
~previous_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
("com.whatever.package00.abcd." ^
"ABasicExampleFragment$9.onMenuItemActionExpand(android.view.MenuItem):bo." ^
"ba1776155fba2899542401da5bc779a5")
~file:"a.java"
~key:1
~qualifier_tags:qt1
~hash:1 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"procid2.92095aee3f1884c37e96feae031f4931"
~file:"b.java"
~key:2
~hash:2 ();
],
([4;5], [2], []));
("test_skip_anonymous_class_renamings_with_empty_qualifier_tags",
Differential.of_reports
~current_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f"
~file:"a.java"
~key:1
~hash:1 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f"
~file:"a.java"
~key:1
~hash:3 ();
]
~previous_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
"com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03"
~file:"a.java"
~key:1
~hash:2 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d"
~file:"a.java"
~key:1
~hash:4 ();
],
([1], [2], []));
("test_skip_anonymous_class_renamings_with_matching_non_anonymous_procedure_ids",
Differential.of_reports
~current_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247"
~file:"a.java"
~key:1
~hash:1 ();
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247"
~file:"a.java"
~key:1
~hash:3 ();
]
~previous_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247"
~file:"a.java"
~key:1
~hash:2 ();
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:"com.whatever.package.Class.foo():bool.919f37fd0993058a01f438210ba8a247"
~file:"a.java"
~key:1
~hash:4 ();
],
([1;3], [2;4], []));
("test_skip_anonymous_class_renamings_with_non_java_files",
Differential.of_reports
~current_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
"com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6"
~file:"a.java"
~key:1
~hash:1 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"com.whatever.package.Class$1.foo():bool.bf13089cf4c47ff8ff089a1a4767324f"
~file:"a.mm"
~key:1
~hash:3 ();
]
~previous_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
"com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03"
~file:"a.java"
~key:1
~hash:2 ();
create_fake_jsonbug
~bug_type:"bug_type_2"
~procedure_id:"com.whatever.package.Class$8.foo():bool.cffd4e941668063eb802183dbd3e856d"
~file:"a.mm"
~key:1
~hash:4 ();
],
([3], [4], []));
("test_skip_anonymous_class_renamings_with_different_call_procedure_qualifier_tags",
Differential.of_reports
~current_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
"com.whatever.package.Class$3$1.foo():bool.9ff39eb5c53c81da9f6a7ade324345b6"
~file:"a.java"
~key:1
~qualifier_tags:qt1
~hash:1 ();
]
~previous_report:[
create_fake_jsonbug
~bug_type:"bug_type_1"
~procedure_id:
"com.whatever.package.Class$21$1.foo():bool.db89561ad9dab28587c8c04833f09b03"
~file:"a.java"
~key:1
~qualifier_tags:qt2
~hash:2 ();
],
([1], [2], []));
] |> List.map
~f:(fun (name, diff, expected_output) ->
name >:: create_test diff expected_output)
let test_resolve_infer_eradicate_conflict =
let fake_filters_factory analyzer =
match analyzer with
| Config.Eradicate ->
{
Inferconfig.path_filter = (function _ -> true); (* all paths are whitelisted *)
error_filter = (function _ -> failwith "error_filter is not needed");
proc_filter = (function _ -> failwith "proc_filter is not needed");
}
| _ -> failwith "This mock only supports Eradicate" in
let create_test analyzer (exp_introduced, exp_fixed, exp_preexisting) _ =
let null_dereference = Localise.to_string Localise.null_dereference in
let current_report = [
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:1 ();
create_fake_jsonbug ~bug_type:null_dereference ~file:"file_2.java" ~hash:2 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 ();
] in
let previous_report = [
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_1.java" ~hash:11 ();
create_fake_jsonbug ~bug_type:null_dereference ~file:"file_3.java" ~hash:3 ();
create_fake_jsonbug ~bug_type:"bug_type_1" ~file:"file_4.java" ~hash:4 ();
] in
let diff = Differential.of_reports ~current_report ~previous_report in
let diff' =
DifferentialFilters.VISIBLE_FOR_TESTING_DO_NOT_USE_DIRECTLY.resolve_infer_eradicate_conflict
analyzer fake_filters_factory diff in
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of introduced")
exp_introduced (sorted_hashes_of_issues diff'.introduced);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of fixed")
exp_fixed (sorted_hashes_of_issues diff'.fixed);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of preexisting")
exp_preexisting (sorted_hashes_of_issues diff'.preexisting) in
(* [(test_name, analyzer, expected_hashes); ...] *)
[
("test_resolve_infer_eradicate_conflict_runs_with_infer_analyzer",
Config.Infer,
([1], [11], [4]));
("test_resolve_infer_eradicate_conflict_skips_with_checkers_analyzer",
Config.Checkers,
([1;2], [3;11], [4]));
("test_resolve_infer_eradicate_conflict_skips_with_linters_analyzer",
Config.Linters,
([1;2], [3;11], [4]));
] |> List.map
~f:(fun (name, analyzer, expected_output) ->
name >:: create_test analyzer expected_output)
let tests = "differential_filters_suite" >:::
test_file_renamings_from_json @
test_file_renamings_find_previous @
test_relative_complements @
test_skip_anonymous_class_renamings @
test_resolve_infer_eradicate_conflict @
[test_skip_duplicated_types_on_filenames; test_value_of_qualifier_tag]

@ -0,0 +1,61 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
open OUnit2
open DifferentialTestsUtils
let current_report = [
create_fake_jsonbug ~hash:3 ();
create_fake_jsonbug ~hash:1 ();
create_fake_jsonbug ~hash:2 ();
create_fake_jsonbug ~hash:2 ();
create_fake_jsonbug ~hash:2 ();
]
let previous_report = [
create_fake_jsonbug ~hash:1 ();
create_fake_jsonbug ~hash:4 ();
create_fake_jsonbug ~hash:1 ();
]
let diff = Differential.of_reports ~current_report ~previous_report
(* Sets operations should keep duplicated issues with identical hashes *)
let test_diff_keeps_duplicated_hashes =
let hashes_expected = 3 in
let hashes_found = List.fold
~init:0
~f:(fun acc i -> if Int.equal i.Jsonbug_t.hash 2 then acc + 1 else acc)
diff.introduced in
let pp_diff fmt (expected, actual) =
Format.fprintf fmt
"Expected %d issues with hash=2 among the introduced, but got %d instead"
expected
actual in
let do_assert _ = assert_equal ~pp_diff hashes_expected hashes_found in
"test_diff_keeps_duplicated_hashes" >:: do_assert
(* Sets operations to compute introduced, fixed and preexisting issues are correct *)
let test_set_operations =
let do_assert _ =
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of introduced")
[2;2;2;3] (sorted_hashes_of_issues diff.introduced);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of fixed")
[4] (sorted_hashes_of_issues diff.fixed);
assert_equal
~pp_diff:(pp_diff_of_int_list "Hashes of preexisting")
[1] (sorted_hashes_of_issues diff.preexisting) in
"test_set_operations" >:: do_assert
let tests = "differential_suite" >::: [test_diff_keeps_duplicated_hashes; test_set_operations]

@ -0,0 +1,62 @@
(*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*)
open! IStd
let create_fake_jsonbug
?(bug_class="bug_class")
?(kind="kind")
?(bug_type="bug_type")
?(qualifier="qualifier")
?(severity="severity")
?(visibility="visibility")
?(line=1)
?(column=1)
?(procedure="procedure")
?(procedure_id="procedure_id")
?(procedure_start_line=1)
?(file="file/at/a/certain/path.java")
?(bug_trace=[])
?(key=1234)
?(qualifier_tags=[])
?(hash=1)
?(dotty=None)
?(infer_source_loc=None) () : Jsonbug_t.jsonbug =
{
bug_class;
kind;
bug_type;
qualifier;
severity;
visibility;
line;
column;
procedure;
procedure_id;
procedure_start_line;
file;
bug_trace;
key;
qualifier_tags;
hash;
dotty;
infer_source_loc;
}
let pp_diff_of_int_list group_name fmt (expected, actual) =
Format.fprintf fmt
"[%s]: Expected: [%a] Found: [%a]"
group_name
(Pp.comma_seq Format.pp_print_int) expected
(Pp.comma_seq Format.pp_print_int) actual
(* Sort hashes to make things easier to compare *)
let sorted_hashes_of_issues issues =
let hash i = i.Jsonbug_t.hash in
List.sort ~cmp:Int.compare (List.map ~f:hash issues)

@ -20,6 +20,8 @@ let () =
AddressTakenTests.tests; AddressTakenTests.tests;
BoundedCallTreeTests.tests; BoundedCallTreeTests.tests;
CopyPropagationTests.tests; CopyPropagationTests.tests;
DifferentialTests.tests;
DifferentialFiltersTests.tests;
ProcCfgTests.tests; ProcCfgTests.tests;
LivenessTests.tests; LivenessTests.tests;
SchedulerTests.tests; SchedulerTests.tests;

@ -0,0 +1,5 @@
{
"eradicate-whitelist-path-regex": [
"src/com"
]
}

@ -0,0 +1,26 @@
# Copyright (c) 2017 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# E2E test involving the resolve_infer_eradicate_conflict filter
TESTS_DIR = ../..
DIFFERENTIAL_ARGS = --resolve-infer-eradicate-conflict
CLEAN_EXTRA = src/com/example/Diff*.java src/Diff*.java *.class com/
include ../../differential.make
$(CURRENT_REPORT):
cp src/com/example/DiffExample.java.current src/com/example/DiffExample.java
cp src/DiffExampleTwo.java.current src/DiffExampleTwo.java
$(call silent_on_success,\
$(INFER_BIN) \
-o $(CURRENT_DIR) -- javac src/com/example/DiffExample.java src/DiffExampleTwo.java)
$(PREVIOUS_REPORT):
cp src/com/example/DiffExample.java.previous src/com/example/DiffExample.java
$(call silent_on_success,\
$(INFER_BIN) -o $(PREVIOUS_DIR) -- javac src/com/example/DiffExample.java)

@ -0,0 +1,2 @@
NULL_DEREFERENCE, src/DiffExampleTwo.java, void DiffExampleTwo.doSomethingTwo(), 1, DiffExampleTwo.doSomethingTwo():void.d8149869686ac2ef26a75ac4829094a7, DiffExampleTwo.doSomethingTwo():void
RESOURCE_LEAK, src/com/example/DiffExample.java, void DiffExample.openResource(), 5, com.example.DiffExample.openResource():void.75390f44594cb95db15fe8db9d07c4be, com.example.DiffExample.openResource():void

@ -0,0 +1,19 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the resolve_infer_eradicate_conflict filter
class DiffExampleTwo {
private String genString() {
return null;
}
private void doSomethingTwo() {
int i = this.genString().length();
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.example;
import java.io.FileInputStream;
// This example tests the resolve_infer_eradicate_conflict filter
class DiffExample {
private String genString() {
return null;
}
private int triggerNpe() {
return this.genString().length();
}
private void openResource() {
try {
FileInputStream fis = new FileInputStream("AAA");
fis.read();
fis.close();
} catch (Exception exc) {
// do nothing
}
}
}

@ -0,0 +1,24 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.example;
import java.io.FileInputStream;
// This example tests the resolve_infer_eradicate_conflict filter
class DiffExample {
private String genString() {
return null;
}
private int triggerNpe() {
return this.genString().length();
}
}

@ -0,0 +1,21 @@
# Copyright (c) 2017 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# E2E test involving the skip_anonymous_class_renamings filter
TESTS_DIR = ../..
CLEAN_EXTRA = src/Diff*.java *.class
include ../../differential.make
$(CURRENT_REPORT):
cp src/DiffExample.java.current src/DiffExample.java
$(call silent_on_success, $(INFER_BIN) -o $(CURRENT_DIR) -- javac src/*.java)
$(PREVIOUS_REPORT):
cp src/DiffExample.java.previous src/DiffExample.java
$(call silent_on_success, $(INFER_BIN) -o $(PREVIOUS_DIR) -- javac src/*.java)

@ -0,0 +1 @@
NULL_DEREFERENCE, src/DiffExample.java, void DiffExample$3$1.doSomething(), 1, DiffExample$3$1.doSomething():void.64afb6aca478af18163141bbb8999018, DiffExample$3$1.doSomething():void

@ -0,0 +1,39 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the skip_anonymous_class_renamings filter
class DiffExample {
private int checkAnonymousClasses() {
SimpleInterfaceExample sie1 = new SimpleInterfaceExample() {
public String getString() { return "111"; }
public int aaa() { return this.getString().length(); }
};
SimpleInterfaceExample sie2 = new SimpleInterfaceExample() {
public String getString() { return "111"; }
public int aaa() { return this.getString().length(); }
};
new SimpleInterfaceExample() {
public String getString() { return null; }
public int aaa() {
new SimpleNestedInterface() {
public void doSomething() {
int a = getString().length();
}
};
return 1;
}
};
return new SimpleInterfaceExample() {
public String getString() { return null; }
public int aaa() { return this.getString().length(); }
}.aaa();
}
}

@ -0,0 +1,27 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the skip_anonymous_class_renamings filter
class DiffExample {
private int checkAnonymousClasses() {
SimpleInterfaceExample sie1 = new SimpleInterfaceExample() {
public String getString() { return "111"; }
public int aaa() { return this.getString().length(); }
};
SimpleInterfaceExample sie2 = new SimpleInterfaceExample() {
public String getString() { return "111"; }
public int aaa() { return this.getString().length(); }
};
return new SimpleInterfaceExample() {
public String getString() { return null; }
public int aaa() { return this.getString().length(); }
}.aaa();
}
}

@ -0,0 +1,13 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the skip_anonymous_class_renamings filter
public interface SimpleInterfaceExample {
public String getString();
}

@ -0,0 +1,13 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the skip_anonymous_class_renamings filter
public interface SimpleNestedInterface {
public void doSomething();
}

@ -0,0 +1,24 @@
# Copyright (c) 2017 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# E2E test involving the skip_duplicated_types_on_filenames filter
TESTS_DIR = ../..
CLEAN_EXTRA = src/Diff*.java src/Diff*.java *.class
include ../../differential.make
$(CURRENT_REPORT):
cp src/DiffExample.java.current src/DiffExample.java
cp src/DiffExampleTwo.java.current src/DiffExampleTwo.java
$(call silent_on_success,\
$(INFER_BIN) -o $(CURRENT_DIR) -- javac src/DiffExample.java src/DiffExampleTwo.java)
$(PREVIOUS_REPORT):
cp src/DiffExample.java.previous src/DiffExample.java
$(call silent_on_success,\
$(INFER_BIN) -o $(PREVIOUS_DIR) -- javac src/DiffExample.java)

@ -0,0 +1,2 @@
RESOURCE_LEAK, src/DiffExample.java, void DiffExample.openResource(), 5, DiffExample.openResource():void.c57b56c0042a220d7416e229c4e61b99, DiffExample.openResource():void
NULL_DEREFERENCE, src/DiffExampleTwo.java, void DiffExampleTwo.doSomethingTwo(), 1, DiffExampleTwo.doSomethingTwo():void.d8149869686ac2ef26a75ac4829094a7, DiffExampleTwo.doSomethingTwo():void

@ -0,0 +1,33 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import java.io.FileInputStream;
// This example tests the skip_duplicated_types_on_filenames filter
class DiffExample {
private String genString() {
return null;
}
private int triggerNpeRenamed() {
return this.genString().length();
}
private void openResource() {
try {
FileInputStream fis = new FileInputStream("AAA");
fis.read();
fis.close();
} catch (Exception exc) {
// do nothing
}
}
}

@ -0,0 +1,22 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import java.io.FileInputStream;
// This example tests the skip_duplicated_types_on_filenames filter
class DiffExample {
private String genString() {
return null;
}
// rename this method to change its bug_hash
private int triggerNPE() {
return this.genString().length();
}
}

@ -0,0 +1,19 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
// This example tests the skip_duplicated_types_on_filenames filter
class DiffExampleTwo {
private String genString() {
return null;
}
private void doSomethingTwo() {
int i = this.genString().length();
}
}

@ -0,0 +1,24 @@
# Copyright (c) 2017 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# E2E test involving the skip_duplicated_types_on_filenames filter
TESTS_DIR = ../..
DIFFERENTIAL_ARGS = --file-renamings file_renamings.json
CLEAN_EXTRA = src/Diff*.java *.class
include ../../differential.make
$(CURRENT_REPORT):
cp src/DiffExampleRenamed.java.current src/DiffExampleRenamed.java
$(call silent_on_success,\
$(INFER_BIN) -o $(CURRENT_DIR) -- javac src/DiffExampleRenamed.java)
$(PREVIOUS_REPORT):
cp src/DiffExample.java.previous src/DiffExample.java
$(call silent_on_success,\
$(INFER_BIN) -o $(PREVIOUS_DIR) -- javac src/DiffExample.java)

@ -0,0 +1,6 @@
[
{
"current": "src/DiffExampleRenamed.java",
"previous": "src/DiffExample.java"
}
]

@ -0,0 +1,33 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import java.io.FileInputStream;
// This example tests the skip_duplicated_types_on_filenames filter
class DiffExample {
private String genString() {
return null;
}
private int triggerNpe() {
return this.genString().length();
}
private void openResource() {
try {
FileInputStream fis = new FileInputStream("AAA");
fis.read();
fis.close();
} catch (Exception exc) {
// do nothing
}
}
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2017 - present Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
import java.io.FileInputStream;
// This example tests the skip_duplicated_types_on_filenames filter
class DiffExampleRenamed {
private String genString() {
return null;
}
private int triggerNpe() {
return this.genString().length();
}
private void openResource() {
try {
FileInputStream fis = new FileInputStream("AAA");
fis.read();
fis.close();
} catch (Exception exc) {
// do nothing
}
}
}

@ -0,0 +1,65 @@
# Copyright (c) 2017 - present Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
# Targets that must be defined: CURRENT_REPORT and PREVIOUS_REPORT
# Optional variables: DIFFERENTIAL_ARGS, CLEAN_EXTRA
ROOT_DIR = $(TESTS_DIR)/../..
include $(ROOT_DIR)/Makefile.config
INFER_OUT = infer-out
DIFFERENTIAL_REPORT = $(INFER_OUT)/differential/introduced.json
EXPECTED_TEST_OUTPUT = introduced.exp.test
INFERPRINT_ISSUES_FIELDS = \
"bug_type,file,procedure,line_offset,procedure_id,procedure_id_without_crc"
CURRENT_DIR = infer-current
PREVIOUS_DIR = infer-previous
CURRENT_REPORT = $(CURRENT_DIR)/report.json
PREVIOUS_REPORT = $(PREVIOUS_DIR)/report.json
default: analyze
# the following dependency is to guarantee that the computation of
# PREVIOUS_REPORT and CURRENT_REPORT will be serialized
$(PREVIOUS_REPORT): $(CURRENT_REPORT)
.PHONY: analyze
analyze: $(CURRENT_REPORT) $(PREVIOUS_REPORT)
$(DIFFERENTIAL_REPORT): $(CURRENT_REPORT) $(PREVIOUS_REPORT)
$(INFER_BIN) -o $(INFER_OUT) --project-root $(CURDIR) --diff \
--report-current $(CURRENT_REPORT) --report-previous $(PREVIOUS_REPORT) \
$(DIFFERENTIAL_ARGS)
$(EXPECTED_TEST_OUTPUT): $(DIFFERENTIAL_REPORT) $(INFERPRINT_BIN)
$(INFERPRINT_BIN) \
--issues-fields $(INFERPRINT_ISSUES_FIELDS) \
--from-json-report $(INFER_OUT)/differential/introduced.json \
--issues-tests introduced.exp.test
$(INFERPRINT_BIN) \
--issues-fields $(INFERPRINT_ISSUES_FIELDS) \
--from-json-report $(INFER_OUT)/differential/fixed.json \
--issues-tests fixed.exp.test
$(INFERPRINT_BIN) \
--issues-fields $(INFERPRINT_ISSUES_FIELDS) \
--from-json-report $(INFER_OUT)/differential/preexisting.json \
--issues-tests preexisting.exp.test
.PHONY: print
print: $(EXPECTED_TEST_OUTPUT)
.PHONY: test
test: print
diff -u introduced.exp introduced.exp.test
diff -u fixed.exp fixed.exp.test
diff -u preexisting.exp preexisting.exp.test
.PHONY: clean
clean:
$(REMOVE_DIR) *.exp.test $(INFER_OUT) $(CURRENT_DIR) $(PREVIOUS_DIR) \
$(CLEAN_EXTRA)
Loading…
Cancel
Save