Add ability to skip translation with negation (source doesn't contain)

Summary:
This diff adds the ability to skip translation with  `... && neg ( pattern)` logic so that we can skip translation of some files if the source does not contain a pattern.

Note that `skip-translation` expects a list of patterns as disjunctions:
https://www.internalfb.com/intern/diffusion/INFER/browse/master/infer/src/IR/inferconfig.ml?commit=76ae5fa0d3376573f6d04814e47ff6b5a9dd9746&lines=74

 whereas we want the ability to have conjuctions inside.

## Context
Immutability analysis requires analyzing generated code which might have `Immutable` annotations. When analysing fbandroid, we skip all generated code:

```
"skip-translation": [
     {
      "source_contains": "generated",
      "language": "Java"
     }
   ],
```

However, rather than analyzing all generated code (which might be expensive across all targets) by removing the above, with this diff, we only analyze generated code that doesn't contain e.g. `Immutable`  and skip all other generated code as before:

```
"skip-translation": [
     {
      "source_contains": "generated",
      "source_not_contains": "Immutable",
      "language": "Java"
     }
   ],
```

Reviewed By: ngorogiannis

Differential Revision: D25328931

fbshipit-source-id: 3ae6ae92a
master
Ezgi Çiçek 4 years ago committed by Facebook GitHub Bot
parent 559dc82bd6
commit da950064d2

@ -50,45 +50,73 @@ let match_method language proc_name method_name =
&& String.equal (Procname.get_method proc_name) method_name && String.equal (Procname.get_method proc_name) method_name
type contains_pattern = {contains: string; not_contains: string option}
(* Module to create matcher based on strings present in the source file *) (* Module to create matcher based on strings present in the source file *)
module FileContainsStringMatcher = struct module FileContainsStringMatcher = struct
type matcher = SourceFile.t -> bool type matcher = SourceFile.t -> bool
let default_matcher : matcher = fun _ -> false let default_matcher : matcher = fun _ -> false
let file_contains regexp file_in = (* check if the file contains the regexp but not regexp_not *)
let rec loop () = let file_contains regexp regexp_not_opt source_file =
let rec loop regexp file_in =
try Str.search_forward regexp (In_channel.input_line_exn file_in) 0 >= 0 with try Str.search_forward regexp (In_channel.input_line_exn file_in) 0 >= 0 with
| Caml.Not_found -> | Caml.Not_found ->
loop () loop regexp file_in
| End_of_file -> | End_of_file ->
false false
in in
loop () let path = SourceFile.to_abs_path source_file in
let contains_regexp = Utils.with_file_in path ~f:(fun file_in -> loop regexp file_in) in
contains_regexp
let create_matcher s_patterns = && (* [loop] leaves the read position where it found the match,
hence we read the file twice to check if it doesn't contain
regexp_not *)
Option.value_map regexp_not_opt ~default:true ~f:(fun regexp_not ->
let file_in = In_channel.create path in
not (loop regexp_not file_in) )
let create_matcher (s_patterns : contains_pattern list) =
if List.is_empty s_patterns then default_matcher if List.is_empty s_patterns then default_matcher
else else
let source_map = ref SourceFile.Map.empty in let source_map = ref SourceFile.Map.empty in
let regexp = Str.regexp (String.concat ~sep:"\\|" s_patterns) in let not_contains_patterns =
fun source_file -> List.exists ~f:(fun {not_contains} -> Option.is_some not_contains) s_patterns
try SourceFile.Map.find source_file !source_map in
with Caml.Not_found -> ( let disjunctive_regexp =
try Str.regexp (String.concat ~sep:"\\|" (List.map ~f:(fun {contains} -> contains) s_patterns))
let file_in = In_channel.create (SourceFile.to_abs_path source_file) in in
let pattern_found = file_contains regexp file_in in let cond check_regexp =
In_channel.close file_in ; if not_contains_patterns then
source_map := SourceFile.Map.add source_file pattern_found !source_map ; List.exists
pattern_found ~f:(fun {contains; not_contains} ->
with Sys_error _ -> false ) check_regexp (Str.regexp contains) (Option.map not_contains ~f:Str.regexp) )
s_patterns
else check_regexp disjunctive_regexp None
in
function
| source_file ->
let check_regexp regexp regexp_not_opt =
match SourceFile.Map.find_opt source_file !source_map with
| Some result ->
result
| None -> (
try
let pattern_found = file_contains regexp regexp_not_opt source_file in
source_map := SourceFile.Map.add source_file pattern_found !source_map ;
pattern_found
with Sys_error _ -> false )
in
cond check_regexp
end end
type method_pattern = {class_name: string; method_name: string option} type method_pattern = {class_name: string; method_name: string option}
type pattern = type pattern =
| Method_pattern of Language.t * method_pattern | Method_pattern of Language.t * method_pattern
| Source_contains of Language.t * string | Source_pattern of Language.t * contains_pattern
(* Module to create matcher based on source file names or class names and method names *) (* Module to create matcher based on source file names or class names and method names *)
module FileOrProcMatcher = struct module FileOrProcMatcher = struct
@ -127,7 +155,7 @@ module FileOrProcMatcher = struct
let create_file_matcher patterns = let create_file_matcher patterns =
let s_patterns, m_patterns = let s_patterns, m_patterns =
let collect (s_patterns, m_patterns) = function let collect (s_patterns, m_patterns) = function
| Source_contains (_, s) -> | Source_pattern (_, s) ->
(s :: s_patterns, m_patterns) (s :: s_patterns, m_patterns)
| Method_pattern (_, mp) -> | Method_pattern (_, mp) ->
(s_patterns, mp :: m_patterns) (s_patterns, mp :: m_patterns)
@ -158,9 +186,12 @@ module FileOrProcMatcher = struct
| Method_pattern (language, mp) -> | Method_pattern (language, mp) ->
Format.fprintf fmt "Method pattern (%s) {@\n%a}@\n" (Language.to_string language) Format.fprintf fmt "Method pattern (%s) {@\n%a}@\n" (Language.to_string language)
pp_method_pattern mp pp_method_pattern mp
| Source_contains (language, sc) -> | Source_pattern (language, {contains= sc; not_contains= None}) ->
Format.fprintf fmt "Source contains (%s) {@\n%a}@\n" (Language.to_string language) Format.fprintf fmt "Source contains (%s) {@\n%a}@\n" (Language.to_string language)
pp_source_contains sc pp_source_contains sc
| Source_pattern (language, {contains= sc; not_contains= Some snc}) ->
Format.fprintf fmt "Source contains (%s) {@\n%a} and not contains {@\n%a} @\n"
(Language.to_string language) pp_source_contains sc pp_source_contains snc
end end
(* of module FileOrProcMatcher *) (* of module FileOrProcMatcher *)
@ -180,6 +211,7 @@ end
let patterns_of_json_with_key (json_key, json) = let patterns_of_json_with_key (json_key, json) =
let default_method_pattern = {class_name= ""; method_name= None} in let default_method_pattern = {class_name= ""; method_name= None} in
let default_source_contains = "" in let default_source_contains = "" in
let default_not_contains = {contains= default_source_contains; not_contains= None} in
let language_of_string s = let language_of_string s =
match Language.of_string s with match Language.of_string s with
| Some Language.Java -> | Some Language.Java ->
@ -209,7 +241,7 @@ let patterns_of_json_with_key (json_key, json) =
| (key, _) :: _ when is_method_pattern key -> | (key, _) :: _ when is_method_pattern key ->
Ok (Method_pattern (language, default_method_pattern)) Ok (Method_pattern (language, default_method_pattern))
| (key, _) :: _ when is_source_contains key -> | (key, _) :: _ when is_source_contains key ->
Ok (Source_contains (language, default_source_contains)) Ok (Source_pattern (language, default_not_contains))
| _ :: tl -> | _ :: tl ->
loop tl loop tl
in in
@ -232,21 +264,23 @@ let patterns_of_json_with_key (json_key, json) =
in in
List.fold ~f:loop ~init:default_method_pattern assoc List.fold ~f:loop ~init:default_method_pattern assoc
and create_string_contains assoc = and create_string_contains assoc =
let loop sc = function let loop cp = function
| key, `String pattern when String.equal key "source_contains" -> | key, `String pattern when String.equal key "source_contains" ->
pattern {cp with contains= pattern}
| key, `String pattern when String.equal key "source_not_contains" ->
{cp with not_contains= Some pattern}
| key, _ when String.equal key "language" -> | key, _ when String.equal key "language" ->
sc cp
| _ -> | _ ->
L.(die UserError) "Failed to parse %s" (Yojson.Basic.to_string (`Assoc assoc)) L.(die UserError) "Failed to parse here %s" (Yojson.Basic.to_string (`Assoc assoc))
in in
List.fold ~f:loop ~init:default_source_contains assoc List.fold ~f:loop ~init:default_not_contains assoc
in in
match detect_pattern assoc with match detect_pattern assoc with
| Ok (Method_pattern (language, _)) -> | Ok (Method_pattern (language, _)) ->
Ok (Method_pattern (language, create_method_pattern assoc)) Ok (Method_pattern (language, create_method_pattern assoc))
| Ok (Source_contains (language, _)) -> | Ok (Source_pattern (language, _)) ->
Ok (Source_contains (language, create_string_contains assoc)) Ok (Source_pattern (language, create_string_contains assoc))
| Error _ as error -> | Error _ as error ->
error error
in in
@ -304,7 +338,10 @@ let filters_from_inferconfig inferconfig : filters =
is_matching (List.map ~f:Str.regexp inferconfig.blacklist) is_matching (List.map ~f:Str.regexp inferconfig.blacklist)
in in
let blacklist_files_containing_filter : path_filter = let blacklist_files_containing_filter : path_filter =
FileContainsStringMatcher.create_matcher inferconfig.blacklist_files_containing FileContainsStringMatcher.create_matcher
(List.map
~f:(fun s -> {contains= s; not_contains= None})
inferconfig.blacklist_files_containing)
in in
function function
| source_file -> | source_file ->

@ -0,0 +1,8 @@
{ "skip-translation": [
{
"language": "Java",
"source_contains": "@generated",
"source_not_contains": "@Immutable"
}
]
}

@ -7,7 +7,7 @@
package codetoanalyze.java.immutability; package codetoanalyze.java.immutability;
import com.moblica.common.xmob.utils.Immutable; import com.moblica.common.xmob.utils.Immutable;
// @generated
class ArrayTest { class ArrayTest {
@Immutable final int[] testArray = new int[] {0, 1, 2, 4}; @Immutable final int[] testArray = new int[] {0, 1, 2, 4};
@Immutable static String[] suitArray = {"spades", "hearts", "diamonds", "clubs"}; @Immutable static String[] suitArray = {"spades", "hearts", "diamonds", "clubs"};

Loading…
Cancel
Save