diff --git a/infer/src/clang/ALVar.ml b/infer/src/clang/ALVar.ml index f42339286..e7743c042 100644 --- a/infer/src/clang/ALVar.ml +++ b/infer/src/clang/ALVar.ml @@ -14,6 +14,7 @@ type keyword = | Suggestion | Severity | Mode + | Path type formula_id = Formula_id of string[@@deriving compare] @@ -46,6 +47,7 @@ let keyword_to_string k = | Suggestion -> "suggestion" | Severity -> "severity" | Mode -> "mode" + | Path -> "path" let is_report_when_keyword k = match k with @@ -72,6 +74,11 @@ let is_mode_keyword k = | Mode -> true | _ -> false +let is_path_keyword k = + match k with + | Path -> true + | _ -> false + (* true if and only if a substring of container matches the regular expression defined by contained *) diff --git a/infer/src/clang/ALVar.mli b/infer/src/clang/ALVar.mli index cc08c07a6..75f08cad7 100644 --- a/infer/src/clang/ALVar.mli +++ b/infer/src/clang/ALVar.mli @@ -14,6 +14,7 @@ type keyword = | Suggestion | Severity | Mode + | Path type formula_id = Formula_id of string @@ -43,6 +44,10 @@ val is_severity_keyword : keyword -> bool val is_mode_keyword : keyword -> bool +val is_path_keyword : keyword -> bool + +val str_match_regex : string -> string -> bool + val compare_str_with_alexp : string -> alexp -> bool module FormulaIdMap : Caml.Map.S with type key = formula_id diff --git a/infer/src/clang/cFrontend_checkers_main.ml b/infer/src/clang/cFrontend_checkers_main.ml index affab2e4a..cb57bf2d7 100644 --- a/infer/src/clang/cFrontend_checkers_main.ml +++ b/infer/src/clang/cFrontend_checkers_main.ml @@ -256,7 +256,8 @@ let do_frontend_checks (trans_unit_ctx: CFrontend_config.translation_unit_contex CTL.create_ctl_evaluation_tracker trans_unit_ctx.source_file; try let parsed_linters = parse_ctl_files Config.linters_def_file in - let filtered_parsed_linters = CFrontend_errors.filter_parsed_linters parsed_linters in + let filtered_parsed_linters = + CFrontend_errors.filter_parsed_linters parsed_linters trans_unit_ctx.source_file in CFrontend_errors.parsed_linters := filtered_parsed_linters; let source_file = trans_unit_ctx.CFrontend_config.source_file in Logging.out "Start linting file %a with rules: @\n%s@\n" diff --git a/infer/src/clang/cFrontend_errors.ml b/infer/src/clang/cFrontend_errors.ml index 43b32e5c8..2f83cb565 100644 --- a/infer/src/clang/cFrontend_errors.ml +++ b/infer/src/clang/cFrontend_errors.ml @@ -15,10 +15,11 @@ type linter = { condition : CTL.t; issue_desc : CIssue.issue_desc; def_file : string option; + path : string option; } (* If in linter developer mode and if current linter was passed, filter it out *) -let filter_parsed_linters parsed_linters = +let filter_parsed_linters_developer parsed_linters = if List.length parsed_linters > 1 && Config.linters_developer_mode then match Config.linter with | None -> @@ -31,6 +32,17 @@ let filter_parsed_linters parsed_linters = ) parsed_linters else parsed_linters +let filter_parsed_linters_by_path parsed_linters source_file = + let filter_parsed_linter_by_path linter = + match linter.path with + | Some path -> ALVar.str_match_regex (SourceFile.to_rel_path source_file) path + | None -> true in + List.filter ~f:filter_parsed_linter_by_path parsed_linters + +let filter_parsed_linters parsed_linters source_file = + let linters = filter_parsed_linters_developer parsed_linters in + filter_parsed_linters_by_path linters source_file + let linters_to_string linters = let linter_to_string linters = List.map ~f:(fun (rule : linter) -> rule.issue_desc.name) linters in @@ -126,39 +138,47 @@ let string_to_issue_mode m = (Logging.out "\n[ERROR] Mode %s does not exist. Please specify ON/OFF\n" s; assert false) +let string_to_path path = Some path + (** Convert a parsed checker in list of linters *) let create_parsed_linters linters_def_file checkers : linter list = let open CIssue in let open CTL in Logging.out "\n Converting checkers in (condition, issue) pairs\n"; - let do_one_checker c = + let do_one_checker checker : linter = let dummy_issue = { - name = c.name; + name = checker.name; description = ""; suggestion = None; loc = Location.dummy; severity = Exceptions.Kwarning; mode = CIssue.On; } in - let issue_desc, condition = List.fold ~f:(fun (issue', cond') d -> - match d with + let issue_desc, condition, path = + let process_linter_definitions (issue, cond, path) description = + match description with | CSet (av, phi) when ALVar.is_report_when_keyword av -> - issue', phi - | CDesc (av, msg) when ALVar.is_message_keyword av -> - {issue' with description = msg}, cond' - | CDesc (av, sugg) when ALVar.is_suggestion_keyword av -> - {issue' with suggestion = Some sugg}, cond' + issue, phi, path + | CDesc (av, msg) when ALVar.is_message_keyword av -> + {issue with description = msg}, cond, path + | CDesc (av, sugg) when ALVar.is_suggestion_keyword av -> + {issue with suggestion = Some sugg}, cond, path | CDesc (av, sev) when ALVar.is_severity_keyword av -> - {issue' with severity = string_to_err_kind sev}, cond' + {issue with severity = string_to_err_kind sev}, cond, path | CDesc (av, m) when ALVar.is_mode_keyword av -> - {issue' with mode = string_to_issue_mode m }, cond' - | _ -> issue', cond') ~init:(dummy_issue, CTL.False) c.definitions in - if Config.debug_mode then ( - Logging.out "\nMaking condition and issue desc for checker '%s'\n" - c.name; - Logging.out "\nCondition =\n %a\n" CTL.Debug.pp_formula condition; - Logging.out "\nIssue_desc = %a\n" CIssue.pp_issue issue_desc); - {condition; issue_desc; def_file = Some linters_def_file} in + {issue with mode = string_to_issue_mode m }, cond, path + | CDesc (av, path') when ALVar.is_path_keyword av -> + issue, cond, string_to_path path' + | _ -> issue, cond, path in + List.fold + ~f:process_linter_definitions + ~init:(dummy_issue, CTL.False, None) + checker.definitions in + Logging.out "\nMaking condition and issue desc for checker '%s'\n" + checker.name; + Logging.out "\nCondition =\n %a\n" CTL.Debug.pp_formula condition; + Logging.out "\nIssue_desc = %a\n" CIssue.pp_issue issue_desc; + {condition; issue_desc; def_file = Some linters_def_file; path;} in List.map ~f:do_one_checker checkers let rec apply_substitution f sub = diff --git a/infer/src/clang/cFrontend_errors.mli b/infer/src/clang/cFrontend_errors.mli index f16ee4198..64f1ea1b1 100644 --- a/infer/src/clang/cFrontend_errors.mli +++ b/infer/src/clang/cFrontend_errors.mli @@ -13,9 +13,10 @@ type linter = { condition : CTL.t; issue_desc : CIssue.issue_desc; def_file : string option; + path : string option; } -val filter_parsed_linters : linter list -> linter list +val filter_parsed_linters : linter list -> SourceFile.t -> linter list val linters_to_string : linter list -> string diff --git a/infer/src/clang/ctl_parser.mly b/infer/src/clang/ctl_parser.mly index f5db1f1bd..250bfe261 100644 --- a/infer/src/clang/ctl_parser.mly +++ b/infer/src/clang/ctl_parser.mly @@ -148,8 +148,10 @@ clause: | "suggestion" -> ALVar.Suggestion | "severity" -> ALVar.Severity | "mode" -> ALVar.Mode - | _ -> failwith ("[ERROR] string '%s' cannot be set in a SET clause. " ^ - "Use either of: 'message', 'suggestion', 'severity', or 'mode'\n") in + | "path" -> ALVar.Path + | _ -> IStd.failwithf "[ERROR] string '%s' cannot be set in a SET clause. \ + Use either of: \ + 'message', 'mode', 'severity', 'suggestion' or 'path'" $2 in CTL.CDesc (alvar, $4) } | let_clause { $1 } ; diff --git a/infer/src/clang/ctl_parser_types.ml b/infer/src/clang/ctl_parser_types.ml index 1695bb5e0..97bf5b12c 100644 --- a/infer/src/clang/ctl_parser_types.ml +++ b/infer/src/clang/ctl_parser_types.ml @@ -38,11 +38,6 @@ let ast_node_name an = | _ -> "" let infer_prefix = "__infer_ctl_" -let report_when_const = "report_when" -let message_const = "message" -let suggestion_const = "suggestion" -let severity_const = "severity" -let mode_const = "mode" exception ALParsingException of string diff --git a/infer/src/clang/ctl_parser_types.mli b/infer/src/clang/ctl_parser_types.mli index 0ef7cee86..114608a61 100644 --- a/infer/src/clang/ctl_parser_types.mli +++ b/infer/src/clang/ctl_parser_types.mli @@ -21,11 +21,6 @@ val ast_node_type : ast_node -> string exception ALParsingException of string val infer_prefix : string -val report_when_const : string -val message_const : string -val suggestion_const : string -val severity_const : string -val mode_const : string (** Data structures for type parser. Correspondence with clang types inferred from diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m b/infer/tests/codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m new file mode 100644 index 000000000..6ce423667 --- /dev/null +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m @@ -0,0 +1,9 @@ +/* + * 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. + */ +int main() { return 0; } diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp index c0198778e..a5b17167e 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/issues.exp @@ -1,3 +1,8 @@ +codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m, main, 9, ALL_PATH_NO_FILTER_EXAMPLE, [] +codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m, main, 9, FILTER_BY_ALL_PATH_EXAMPLE, [] +codetoanalyze/objc/linters-for-test-only/filter_by_path/include_file.m, main, 9, FILTER_BY_PATH_EXAMPLE, [] +codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 9, ALL_PATH_NO_FILTER_EXAMPLE, [] +codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 9, FILTER_BY_ALL_PATH_EXAMPLE, [] codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 10, TEST_VAR_TYPE_CHECK, [] codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 11, TEST_IMPLICIT_CAST_CHECK, [] codetoanalyze/objc/linters-for-test-only/implicit_cast.c, main, 11, TEST_VAR_TYPE_CHECK, [] diff --git a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al index 5a523cf30..26b5e7866 100644 --- a/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al +++ b/infer/tests/codetoanalyze/objc/linters-for-test-only/linters_example.al @@ -215,3 +215,26 @@ DEFINE-CHECKER TEST_NAMESPACE_NAME = { SET message = "Found a namespace with name...."; }; + +DEFINE-CHECKER FILTER_BY_PATH_EXAMPLE = { + SET report_when = + WHEN declaration_has_name("main") + HOLDS-IN-NODE FunctionDecl; + SET message = "Found main method"; + SET path = "codetoanalyze/objc/linters-for-test-only/filter_by_path/.*"; +}; + +DEFINE-CHECKER ALL_PATH_NO_FILTER_EXAMPLE = { + SET report_when = + WHEN declaration_has_name("main") + HOLDS-IN-NODE FunctionDecl; + SET message = "Found main method"; +}; + +DEFINE-CHECKER FILTER_BY_ALL_PATH_EXAMPLE = { + SET report_when = + WHEN declaration_has_name("main") + HOLDS-IN-NODE FunctionDecl; + SET message = "Found main method"; + SET path = ".*"; +};