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.
+
+