diff --git a/Makefile b/Makefile index 80febe6b1..888af7a11 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ BUILD_SYSTEMS_TESTS += \ linters \ project_root_rel \ reactive \ + results_xml \ run_hidden_linters \ tracebugs \ utf8_in_procname \ diff --git a/infer/man/man1/infer-full.txt b/infer/man/man1/infer-full.txt index fdbceb1f9..a7543c1f5 100644 --- a/infer/man/man1/infer-full.txt +++ b/infer/man/man1/infer-full.txt @@ -829,6 +829,10 @@ OPTIONS DEPRECATED: Specify the file containing perf profiler data to read See also infer-analyze(1). + --pmd-xml + Activates: Output issues in (PMD) XML format in + infer-out/report.xml (Conversely: --no-pmd-xml) See also infer-run(1). + --print-active-checkers Activates: Print the active checkers before starting the analysis (Conversely: --no-print-active-checkers) See also infer-analyze(1). diff --git a/infer/man/man1/infer-run.txt b/infer/man/man1/infer-run.txt index c029fee46..477f14a7f 100644 --- a/infer/man/man1/infer-run.txt +++ b/infer/man/man1/infer-run.txt @@ -88,6 +88,10 @@ OPTIONS Show this manual with all internal options in the INTERNAL OPTIONS section + --pmd-xml + Activates: Output issues in (PMD) XML format in + infer-out/report.xml (Conversely: --no-pmd-xml) + --print-logs Activates: Also log messages to stdout and stderr (Conversely: --no-print-logs) diff --git a/infer/man/man1/infer.txt b/infer/man/man1/infer.txt index 0130eee37..8de1edfd1 100644 --- a/infer/man/man1/infer.txt +++ b/infer/man/man1/infer.txt @@ -829,6 +829,10 @@ OPTIONS DEPRECATED: Specify the file containing perf profiler data to read See also infer-analyze(1). + --pmd-xml + Activates: Output issues in (PMD) XML format in + infer-out/report.xml (Conversely: --no-pmd-xml) See also infer-run(1). + --print-active-checkers Activates: Print the active checkers before starting the analysis (Conversely: --no-print-active-checkers) See also infer-analyze(1). diff --git a/infer/src/base/Config.ml b/infer/src/base/Config.ml index 5b45e4c9d..e7e8fe3f8 100644 --- a/infer/src/base/Config.ml +++ b/infer/src/base/Config.ml @@ -1705,6 +1705,12 @@ and patterns_skip_translation = "Matcher or list of matchers for names of files that should not be analyzed at all." ) +and pmd_xml = + CLOpt.mk_bool ~long:"pmd-xml" + ~in_help:InferCommand.[(Run, manual_generic)] + "Output issues in (PMD) XML format in infer-out/report.xml" + + and print_active_checkers = CLOpt.mk_bool ~long:"print-active-checkers" ~in_help:InferCommand.[(Analyze, manual_generic)] @@ -2927,6 +2933,8 @@ and patterns_skip_implementation = match patterns_skip_implementation with k, r and patterns_skip_translation = match patterns_skip_translation with k, r -> (k, !r) +and pmd_xml = !pmd_xml + and print_active_checkers = !print_active_checkers and print_builtins = !print_builtins diff --git a/infer/src/base/Config.mli b/infer/src/base/Config.mli index e63e7e2e0..6e797440e 100644 --- a/infer/src/base/Config.mli +++ b/infer/src/base/Config.mli @@ -417,6 +417,8 @@ val only_cheap_debug : bool val only_footprint : bool +val pmd_xml : bool + val print_active_checkers : bool val print_builtins : bool diff --git a/infer/src/base/ResultsDirEntryName.ml b/infer/src/base/ResultsDirEntryName.ml index abcffec62..9984aa007 100644 --- a/infer/src/base/ResultsDirEntryName.ml +++ b/infer/src/base/ResultsDirEntryName.ml @@ -29,6 +29,7 @@ type id = | ReportHtml | ReportJson | ReportText + | ReportXML | RetainCycles | RunState | Specs @@ -147,6 +148,11 @@ let of_id = function ; kind= File ; before_incremental_analysis= Delete ; before_caching_capture= Delete } + | ReportXML -> + { rel_path= "report.xml" + ; kind= File + ; before_incremental_analysis= Delete + ; before_caching_capture= Delete } | RetainCycles -> { rel_path= "retain_cycle_dotty" ; kind= Directory diff --git a/infer/src/base/ResultsDirEntryName.mli b/infer/src/base/ResultsDirEntryName.mli index b1cfa04ea..087b4de65 100644 --- a/infer/src/base/ResultsDirEntryName.mli +++ b/infer/src/base/ResultsDirEntryName.mli @@ -30,6 +30,7 @@ type id = | ReportHtml (** directory of the HTML report *) | ReportJson (** the main product of the analysis: [report.json] *) | ReportText (** a human-readable textual version of [report.json] *) + | ReportXML (** a PMD-style XML version of [report.json] *) | RetainCycles (** directory of retain cycles dotty files *) | RunState (** internal data about the last infer run *) | Specs (** directory containing summaries as .specs files *) diff --git a/infer/src/integration/Driver.ml b/infer/src/integration/Driver.ml index ae52fb1a7..e46504b83 100644 --- a/infer/src/integration/Driver.ml +++ b/infer/src/integration/Driver.ml @@ -198,7 +198,9 @@ let report ?(suppress_console = false) () = TextReport.create_from_json ~quiet:(Config.quiet || suppress_console) ~console_limit:Config.report_console_limit ~report_txt:(ResultsDir.get_path ReportText) - ~report_json:issues_json ) ; + ~report_json:issues_json ; + if Config.pmd_xml then + XMLReport.write ~xml_path:(ResultsDir.get_path ReportXML) ~json_path:issues_json ) ; if Config.(test_determinator && process_clang_ast) then TestDeterminator.merge_test_determinator_results () ; () diff --git a/infer/src/integration/Help.ml b/infer/src/integration/Help.ml index 631980a22..bf86ac7aa 100644 --- a/infer/src/integration/Help.ml +++ b/infer/src/integration/Help.ml @@ -19,6 +19,10 @@ let basename_checker_prefix = "checker-" let basename_of_checker {Checker.id} = basename_checker_prefix ^ id +let url_fragment_of_issue_type unique_id = + Printf.sprintf "%s#%s" all_issues_basename (String.lowercase unique_id) + + let get_checker_web_documentation (checker : Checker.config) = match checker.kind with | UserFacing {title; markdown_body} -> @@ -268,7 +272,7 @@ let pp_checker_issue_types f checker = Checker.equal issue_checker checker ) in let pp_issue f {IssueType.unique_id} = - F.fprintf f "- [%s](%s.md#%s)@\n" unique_id all_issues_basename (String.lowercase unique_id) + F.fprintf f "- [%s](%s)@\n" unique_id (url_fragment_of_issue_type unique_id) in List.iter checker_issues ~f:(pp_issue f) diff --git a/infer/src/integration/Help.mli b/infer/src/integration/Help.mli index d6b75e771..4748b267c 100644 --- a/infer/src/integration/Help.mli +++ b/infer/src/integration/Help.mli @@ -21,3 +21,7 @@ val show_issue_types : IssueType.t list -> unit val write_website : website_root:string -> unit (** generate files for the fbinfer.com website *) + +val url_fragment_of_issue_type : string -> string +(** given an issue type unique ID, return the URL fragment relative to the website documentation, + e.g. [url_fragment_of_issue_type "NULL_DEREFERENCE"] is ["all-issue-types#null_dereference"] *) diff --git a/infer/src/integration/XMLReport.ml b/infer/src/integration/XMLReport.ml new file mode 100644 index 000000000..0fdf4ebb8 --- /dev/null +++ b/infer/src/integration/XMLReport.ml @@ -0,0 +1,53 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open! IStd +module F = Format + +let pp_current_date_and_time f = + let {Unix.tm_year; tm_mon; tm_mday; tm_hour; tm_min; tm_sec} = Unix.time () |> Unix.localtime in + F.fprintf f "%d-%02d-%dT%d:%d:%d.000000" (1900 + tm_year) tm_mon tm_mday tm_hour tm_min tm_sec + + +let pp_xml_issue f (issue : Jsonbug_t.jsonbug) = + let java_class_name, java_package, method_name = + let java_result = + let open IOption.Let_syntax in + if String.is_suffix ~suffix:".java" issue.file then + let* package_class_method, _formals_types = String.lsplit2 ~on:'(' issue.procedure in + let* package_class, method_ = String.rsplit2 ~on:'.' package_class_method in + let+ package, class_ = String.rsplit2 ~on:'.' package_class in + (package, class_, method_) + else None + in + match java_result with None -> ("", "", issue.procedure) | Some result -> result + in + F.fprintf f + {| + %s + |} + issue.file (max issue.column 0) issue.line (max issue.column 0) (issue.line + 1) java_class_name + method_name java_package issue.bug_type + (Help.url_fragment_of_issue_type issue.bug_type) + issue.qualifier + + +let is_user_visible (issue : Jsonbug_t.jsonbug) = + Option.is_none issue.censored_reason && not (String.equal issue.severity "INFO") + + +let pp_xml_issue_filter f issue = if is_user_visible issue then pp_xml_issue f issue + +let write ~xml_path ~json_path = + let report = Atdgen_runtime.Util.Json.from_file Jsonbug_j.read_report json_path in + Utils.with_file_out xml_path ~f:(fun out_channel -> + let f = F.formatter_of_out_channel out_channel in + F.fprintf f {|@[%cpmd version="5.4.1" date="%t"> + %a +@.|} '<' pp_current_date_and_time + (Pp.seq ~sep:"\n" pp_xml_issue_filter) + report ) diff --git a/infer/src/integration/XMLReport.mli b/infer/src/integration/XMLReport.mli new file mode 100644 index 000000000..e7bd55bad --- /dev/null +++ b/infer/src/integration/XMLReport.mli @@ -0,0 +1,11 @@ +(* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open! IStd + +val write : xml_path:string -> json_path:string -> unit +(** read the JSON report at [json_path] and translates it to a PMD-style XML report in [xml_path] *) diff --git a/infer/tests/build_systems/results_xml/Makefile b/infer/tests/build_systems/results_xml/Makefile new file mode 100644 index 000000000..f97143bec --- /dev/null +++ b/infer/tests/build_systems/results_xml/Makefile @@ -0,0 +1,20 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +TESTS_DIR = ../.. + +CODETOANALYZE_DIR = ../codetoanalyze + +CLANG_OPTIONS = -c +INFER_OPTIONS = --project-root $(CODETOANALYZE_DIR) --pmd-xml +INFERPRINT_OPTIONS = --issues-tests +SOURCES = $(CODETOANALYZE_DIR)/hello.c + +include $(TESTS_DIR)/clang.make + +issues.exp.test: infer-out/report.json +# massage the first line of the XML to delete non-reproducible information (the current date) + $(QUIET)sed -e 's/date="[^"]*"/date=""/' infer-out/report.xml > $@ + diff --git a/infer/tests/build_systems/results_xml/issues.exp b/infer/tests/build_systems/results_xml/issues.exp new file mode 100644 index 000000000..044ace4ac --- /dev/null +++ b/infer/tests/build_systems/results_xml/issues.exp @@ -0,0 +1,5 @@ + + + pointer `s` last assigned on line 11 could be null and is dereferenced at line 12, column 3. + +