diff --git a/infer/lib/linter_rules/linters.al b/infer/lib/linter_rules/linters.al index 5d12d2a33..4216daed9 100644 --- a/infer/lib/linter_rules/linters.al +++ b/infer/lib/linter_rules/linters.al @@ -229,4 +229,5 @@ DEFINE-CHECKER POINTER_TO_INTEGRAL_IMPLICIT_CAST = { WHEN has_cast_kind("PointerToIntegral") HOLDS-IN-NODE ImplicitCastExpr; SET message = "Implicit conversion from %child_type% to %type% in usage of %name%"; + SET doc_url = "https://clang.llvm.org/docs/DiagnosticsReference.html#wint-conversion"; }; diff --git a/infer/src/IR/Errlog.ml b/infer/src/IR/Errlog.ml index c1ab49098..b21436ad3 100644 --- a/infer/src/IR/Errlog.ml +++ b/infer/src/IR/Errlog.ml @@ -82,7 +82,8 @@ type err_data = { loc_trace : loc_trace; err_class : Exceptions.err_class; visibility : Exceptions.visibility; - linters_def_file : string option + linters_def_file : string option; + doc_url : string option; } let compare_err_data err_data1 err_data2 = @@ -216,7 +217,7 @@ let update errlog_old errlog_new = (fun err_key l -> ignore (add_issue errlog_old err_key l)) errlog_new -let log_issue err_kind err_log loc (node_id, node_key) session ltr ?linters_def_file exn = +let log_issue err_kind err_log loc (node_id, node_key) session ltr ?linters_def_file ?doc_url exn = let err_name, err_desc, ml_loc_opt, visibility, severity, force_kind, eclass = Exceptions.recognize_exception exn in let err_kind = match force_kind with @@ -247,6 +248,7 @@ let log_issue err_kind err_log loc (node_id, node_key) session ltr ?linters_def_ err_class = eclass; visibility; linters_def_file; + doc_url; } in let err_key = { err_kind; diff --git a/infer/src/IR/Errlog.mli b/infer/src/IR/Errlog.mli index b9245bbe4..d500b2e17 100644 --- a/infer/src/IR/Errlog.mli +++ b/infer/src/IR/Errlog.mli @@ -59,7 +59,8 @@ type err_data = private { loc_trace : loc_trace; err_class : Exceptions.err_class; visibility : Exceptions.visibility; - linters_def_file : string option + linters_def_file : string option; + doc_url : string option; (* url to documentation of the issue type *) } (** Type of the error log *) @@ -95,7 +96,7 @@ val update : t -> t -> unit val log_issue : Exceptions.err_kind -> t -> Location.t -> (int * int) -> int -> loc_trace -> - ?linters_def_file:string -> exn -> unit + ?linters_def_file:string -> ?doc_url:string -> exn -> unit (** {2 Functions for manipulating per-file error tables} *) diff --git a/infer/src/backend/InferPrint.re b/infer/src/backend/InferPrint.re index 3e37387e3..95a009e2d 100644 --- a/infer/src/backend/InferPrint.re +++ b/infer/src/backend/InferPrint.re @@ -503,6 +503,7 @@ module IssuesJson = { infer_source_loc: json_ml_loc, bug_type_hum: Localise.to_human_readable_string key.err_name, linters_def_file: err_data.linters_def_file, + doc_url: err_data.doc_url, traceview_id: None }; if (not !is_first_item) { diff --git a/infer/src/backend/jsonbug.atd b/infer/src/backend/jsonbug.atd index e6a859590..fa8d43abb 100644 --- a/infer/src/backend/jsonbug.atd +++ b/infer/src/backend/jsonbug.atd @@ -23,6 +23,7 @@ type jsonbug = { bug_class : string; kind : string; bug_type : string; + ?doc_url : string option; qualifier : string; severity : string; visibility : string; diff --git a/infer/src/backend/reporting.ml b/infer/src/backend/reporting.ml index d9da22be3..3fe1ed591 100644 --- a/infer/src/backend/reporting.ml +++ b/infer/src/backend/reporting.ml @@ -17,12 +17,14 @@ type log_t = ?session: int -> ?ltr: Errlog.loc_trace -> ?linters_def_file:string -> + ?doc_url:string -> exn -> unit type log_issue_from_errlog = Errlog.t -> log_t -let log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file exn = +let log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file + ?doc_url exn = let loc = match loc with | None -> State.get_loc () | Some loc -> loc in @@ -40,10 +42,11 @@ let log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_ | _ -> let err_name, _, _, _, _, _, _ = Exceptions.recognize_exception exn in (Localise.to_issue_id err_name) in if (Inferconfig.is_checker_enabled err_name) then - Errlog.log_issue err_kind err_log loc node_id session ltr ?linters_def_file exn + Errlog.log_issue err_kind err_log loc node_id session ltr ?linters_def_file ?doc_url exn -let log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file exn = +let log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file + ?doc_url exn = let is_generated_method = Typ.Procname.java_is_generated (Specs.get_proc_name summary) in let should_suppress_lint = @@ -55,7 +58,8 @@ let log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters () (* Skip the reporting *) else let err_log = summary.Specs.attributes.ProcAttributes.err_log in - log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file exn + log_issue_from_errlog err_kind err_log ?loc ?node_id ?session ?ltr ?linters_def_file + ?doc_url exn let log_issue_deprecated ?(store_summary=false) @@ -66,10 +70,12 @@ let log_issue_deprecated ?session ?ltr ?linters_def_file + ?doc_url exn = match Specs.get_summary proc_name with | Some summary -> - log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file exn; + log_issue_from_summary err_kind summary ?loc ?node_id ?session ?ltr ?linters_def_file + ?doc_url exn; if store_summary then (* TODO (#16348004): This is currently needed as ThreadSafety works as a cluster checker *) Specs.store_summary summary diff --git a/infer/src/backend/reporting.mli b/infer/src/backend/reporting.mli index 864527642..f4dcd21d5 100644 --- a/infer/src/backend/reporting.mli +++ b/infer/src/backend/reporting.mli @@ -17,6 +17,7 @@ type log_t = ?session: int -> ?ltr: Errlog.loc_trace -> ?linters_def_file:string -> + ?doc_url:string -> exn -> unit diff --git a/infer/src/backend/state.ml b/infer/src/backend/state.ml index 2706f0ff1..884ea3ed5 100644 --- a/infer/src/backend/state.ml +++ b/infer/src/backend/state.ml @@ -314,6 +314,7 @@ type log_issue = ?session: int -> ?ltr: Errlog.loc_trace -> ?linters_def_file:string -> + ?doc_url:string -> exn -> unit diff --git a/infer/src/backend/state.mli b/infer/src/backend/state.mli index 5356ce467..dcfcbd8ee 100644 --- a/infer/src/backend/state.mli +++ b/infer/src/backend/state.mli @@ -92,6 +92,7 @@ type log_issue = ?session: int -> ?ltr: Errlog.loc_trace -> ?linters_def_file:string -> + ?doc_url:string -> exn -> unit diff --git a/infer/src/clang/ALVar.ml b/infer/src/clang/ALVar.ml index 51c4e1c61..d010d61fc 100644 --- a/infer/src/clang/ALVar.ml +++ b/infer/src/clang/ALVar.ml @@ -16,6 +16,7 @@ type keyword = | Suggestion | Severity | Mode + | Doc_url type formula_id = Formula_id of string[@@deriving compare] @@ -48,6 +49,7 @@ let keyword_to_string k = | Suggestion -> "suggestion" | Severity -> "severity" | Mode -> "mode" + | Doc_url -> "doc_url" let is_report_when_keyword k = match k with @@ -74,6 +76,11 @@ let is_mode_keyword k = | Mode -> true | _ -> false +let is_doc_url_keyword k = + match k with + | Doc_url -> 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 446b467db..a40fceb0a 100644 --- a/infer/src/clang/ALVar.mli +++ b/infer/src/clang/ALVar.mli @@ -14,6 +14,7 @@ type keyword = | Suggestion | Severity | Mode + | Doc_url type formula_id = Formula_id of string @@ -43,6 +44,8 @@ val is_severity_keyword : keyword -> bool val is_mode_keyword : keyword -> bool +val is_doc_url_keyword : keyword -> bool + val str_match_regex : string -> string -> bool val compare_str_with_alexp : string -> alexp -> bool diff --git a/infer/src/clang/ComponentKit.ml b/infer/src/clang/ComponentKit.ml index ba0d20522..d70183f20 100644 --- a/infer/src/clang/ComponentKit.ml +++ b/infer/src/clang/ComponentKit.ml @@ -132,6 +132,7 @@ let mutable_local_vars_advice context an = "Local variable " ^ MF.monospaced_to_string named_decl_info.ni_name ^ " should be const to avoid reassignment"; suggestion = Some "Add a const (after the asterisk for pointer types)."; + doc_url = None; loc = CFrontend_checkers.location_from_dinfo context decl_info } else None @@ -162,6 +163,7 @@ let component_factory_function_advice context an = "Prefer subclassing CKCompositeComponent to static helper functions \ that return a CKComponent subclass." ); + doc_url = None; loc = CFrontend_checkers.location_from_dinfo context decl_info } else None @@ -205,6 +207,7 @@ let component_with_unconventional_superclass_advice context an = suggestion = Some ( "Instead, create a new subclass of CKCompositeComponent." ); + doc_url = None; loc = CFrontend_checkers.location_from_decl context if_decl } else @@ -259,6 +262,7 @@ let component_with_multiple_factory_methods_advice context an = suggestion = Some "Instead, always expose all parameters in a single \ designated initializer and document which are optional."; + doc_url = None; loc = CFrontend_checkers.location_from_decl context meth_decl }) (List.drop factory_methods 1) | _ -> assert false in @@ -311,6 +315,7 @@ let rec _component_initializer_with_side_effects_advice description = "No Side-effects"; suggestion = Some "Your +new method should not modify any \ global variables or global state."; + doc_url = None; loc = CFrontend_checkers.location_from_stmt context call_stmt } | _ -> @@ -344,6 +349,7 @@ let component_file_line_count_info (context: CLintersContext.context) dec = mode = CIssue.Off; description = "Line count analytics"; suggestion = None; + doc_url = None; loc = { Location.line = i; Location.col = 0; @@ -390,6 +396,7 @@ let component_file_cyclomatic_complexity_info (context: CLintersContext.context) mode = CIssue.Off; description = "Cyclomatic Complexity Incremental Marker"; suggestion = None; + doc_url = None; loc = loc } | _ -> None diff --git a/infer/src/clang/cFrontend_errors.ml b/infer/src/clang/cFrontend_errors.ml index 974348f45..e7e9564f6 100644 --- a/infer/src/clang/cFrontend_errors.ml +++ b/infer/src/clang/cFrontend_errors.ml @@ -169,6 +169,7 @@ let create_parsed_linters linters_def_file checkers : linter list = suggestion = None; loc = Location.dummy; severity = Exceptions.Kwarning; + doc_url = None; mode = CIssue.On; } in let issue_desc, condition, whitelist_paths, blacklist_paths = @@ -184,6 +185,8 @@ let create_parsed_linters linters_def_file checkers : linter list = {issue with severity = string_to_err_kind sev}, cond, wl_paths, bl_paths | CDesc (av, m) when ALVar.is_mode_keyword av -> {issue with mode = string_to_issue_mode m }, cond, wl_paths, bl_paths + | CDesc (av, doc) when ALVar.is_doc_url_keyword av -> + {issue with doc_url = Some doc }, cond, wl_paths, bl_paths | CPath (`WhitelistPath, paths) -> issue, cond, paths, bl_paths | CPath (`BlacklistPath, paths) -> @@ -360,7 +363,7 @@ let log_frontend_issue translation_unit_context method_decl_opt key issue_desc l |> QualifiedCppName.to_qual_string in let key = Hashtbl.hash (key ^ method_name) in Reporting.log_issue_from_errlog err_kind errlog exn ~loc ~ltr:trace - ~node_id:(0, key) ?linters_def_file + ~node_id:(0, key) ?linters_def_file ?doc_url:issue_desc.CIssue.doc_url let get_current_method context (an : Ctl_parser_types.ast_node) = match an with diff --git a/infer/src/clang/cIssue.ml b/infer/src/clang/cIssue.ml index bfb00189e..15b4ac654 100644 --- a/infer/src/clang/cIssue.ml +++ b/infer/src/clang/cIssue.ml @@ -17,6 +17,7 @@ type issue_desc = { mode : mode; description : string; (* Description in the error message *) suggestion : string option; (* an optional suggestion or correction *) + doc_url : string option; loc : Location.t; (* location in the code *) } diff --git a/infer/src/clang/cIssue.mli b/infer/src/clang/cIssue.mli index a0e0dcffb..96d8ea23b 100644 --- a/infer/src/clang/cIssue.mli +++ b/infer/src/clang/cIssue.mli @@ -17,6 +17,7 @@ type issue_desc = { mode : mode; description : string; (* Description in the error message *) suggestion : string option; (* an optional suggestion or correction *) + doc_url : string option; loc : Location.t; (* location in the code *) } diff --git a/infer/src/clang/ctl_parser.mly b/infer/src/clang/ctl_parser.mly index 7e196828f..0ad0fd763 100644 --- a/infer/src/clang/ctl_parser.mly +++ b/infer/src/clang/ctl_parser.mly @@ -178,9 +178,10 @@ clause: | "suggestion" -> ALVar.Suggestion | "severity" -> ALVar.Severity | "mode" -> ALVar.Mode + | "doc_url" -> ALVar.Doc_url | _ -> failwithf "string '%s' cannot be set in a SET clause. \ Use either of: \ - 'message', 'mode', 'severity' or 'suggestion'" $2 in + 'doc_url', 'message', 'mode', 'severity' or 'suggestion'" $2 in CTL.CDesc (alvar, $4) } | let_clause { $1 } ; diff --git a/infer/src/unit/DifferentialTestsUtils.ml b/infer/src/unit/DifferentialTestsUtils.ml index 00bd24b4f..e5cc01789 100644 --- a/infer/src/unit/DifferentialTestsUtils.ml +++ b/infer/src/unit/DifferentialTestsUtils.ml @@ -28,7 +28,8 @@ let create_fake_jsonbug ?(hash=1) ?(dotty=None) ?(infer_source_loc=None) - ?(linters_def_file=Some "file/at/certain/path.al") () : Jsonbug_t.jsonbug = + ?(linters_def_file=Some "file/at/certain/path.al") + ?doc_url () : Jsonbug_t.jsonbug = { bug_class; kind; @@ -50,6 +51,7 @@ let create_fake_jsonbug infer_source_loc; bug_type_hum = kind; linters_def_file; + doc_url; traceview_id = None; } 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 4a9bf6cde..4e48574cc 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 @@ -279,4 +279,5 @@ DEFINE-CHECKER WHITE_BLACKLIST_PATH_EXAMPLE = { SET message = "Found main method"; SET whitelist_path = { all_files }; SET blacklist_path = { filtered_files }; + SET doc_url = "www.example.com"; };