Author: Jan Korous Date: 2026-10-13T19:59:50-07:00 New Revision: 07195e29cdbacd063bbb3e1abfb36d68ea6b5dcd
URL: https://github.com/llvm/llvm-project/commit/07195e29cdbacd063bbb3e1abfb36d68ea6b5dcd DIFF: https://github.com/llvm/llvm-project/commit/07195e29cdbacd063bbb3e1abfb36d68ea6b5dcd.diff LOG: [clang][ssaf] Add clang-ssaf-merge-transformation-report (#231664) A source transformation writes a SARIF report for each translation unit. Add a tool that merges the reports of the translation units of a link unit into one, the way clang-ssaf-src-edit-merge merges their edits. Each report numbers its rules and artifacts on its own, so the tool renumbers every result against the merged run before it drops the ones that are identical, such as a finding in a header that several translation units include. clang lists a run's artifacts in URI order but numbers them in the order it first refers to them, so an artifact is known by its own index. The inputs must come from the same transformation, and a merged report can be merged again. --------- Co-authored-by: Kazu Hirata <[email protected]> Added: clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-artifact-index.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-rule-index.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/empty.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/other-transformation.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-a.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-b.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-c.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-clang-order.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/two-runs.sarif clang/test/Analysis/Scalable/ssaf-merge-transformation-report/clang-artifact-order.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/cli-errors.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/empty.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/help.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/merge.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/nested.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/order-invariance.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/plugin-reports.test clang/test/Analysis/Scalable/ssaf-merge-transformation-report/version.test clang/tools/clang-ssaf-merge-transformation-report/CMakeLists.txt clang/tools/clang-ssaf-merge-transformation-report/MergeTransformationReport.cpp Modified: clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.md clang/test/CMakeLists.txt clang/tools/CMakeLists.txt Removed: ################################################################################ diff --git a/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.md b/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.md index af7fc7f56aa8c..a0b52b3f7df38 100644 --- a/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.md +++ b/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.md @@ -65,3 +65,19 @@ $ clang-apply-replacements --remove-change-desc-files <dir-with-yaml> The transformation report can be consumed by any SARIF viewer. + +## Merging transformation reports + +A transformation report covers one translation unit. +`clang-ssaf-merge-transformation-report` merges the reports of the +translation units of a link unit into one: + +```console +$ clang-ssaf-merge-transformation-report foo.sarif bar.sarif -o prg.sarif +``` + +The inputs must come from the same transformation. A finding that several +inputs share, such as one in a header that both translation units include, +is reported once, and the result does not depend on the order of the +inputs. A merged report can be merged again. + diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-artifact-index.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-artifact-index.sarif new file mode 100644 index 0000000000000..893cd17a4a1b5 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-artifact-index.sarif @@ -0,0 +1,71 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 120, + "location": { + "index": 0, + "uri": "file:///src/a.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 5, + "uri": "file:///src/a.cpp" + }, + "region": { + "endColumn": 5, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "declarator spelled through a macro is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-rule-index.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-rule-index.sarif new file mode 100644 index 0000000000000..093a89d467923 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/bad-rule-index.sarif @@ -0,0 +1,71 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 120, + "location": { + "index": 0, + "uri": "file:///src/a.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "file:///src/a.cpp" + }, + "region": { + "endColumn": 5, + "endLine": 1, + "startColumn": 1, + "startLine": 1 + } + } + } + ], + "message": { + "text": "declarator spelled through a macro is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 3 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/empty.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/empty.sarif new file mode 100644 index 0000000000000..944201f0be593 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/empty.sarif @@ -0,0 +1,21 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [], + "columnKind": "unicodeCodePoints", + "results": [], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/other-transformation.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/other-transformation.sarif new file mode 100644 index 0000000000000..2a950fd626de5 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/other-transformation.sarif @@ -0,0 +1,21 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [], + "columnKind": "unicodeCodePoints", + "results": [], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (fix-noexcept)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-a.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-a.sarif new file mode 100644 index 0000000000000..24a766b14c605 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-a.sarif @@ -0,0 +1,106 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 120, + "location": { + "index": 0, + "uri": "file:///src/a.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + }, + { + "length": 60, + "location": { + "index": 1, + "uri": "file:///src/shared.h" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "file:///src/shared.h" + }, + "region": { + "endColumn": 12, + "endLine": 3, + "startColumn": 8, + "startLine": 3 + } + } + } + ], + "message": { + "text": "multi-level pointer indirection is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "file:///src/a.cpp" + }, + "region": { + "endColumn": 7, + "endLine": 7, + "startColumn": 3, + "startLine": 7 + } + } + } + ], + "message": { + "text": "declarator spelled through a macro is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-b.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-b.sarif new file mode 100644 index 0000000000000..40d17d8736758 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-b.sarif @@ -0,0 +1,118 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 60, + "location": { + "index": 0, + "uri": "file:///src/shared.h" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + }, + { + "length": 90, + "location": { + "index": 1, + "uri": "file:///src/b.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "file:///src/shared.h" + }, + "region": { + "endColumn": 12, + "endLine": 3, + "startColumn": 8, + "startLine": 3 + } + } + } + ], + "message": { + "text": "multi-level pointer indirection is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 1 + }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "file:///src/b.cpp" + }, + "region": { + "endColumn": 5, + "endLine": 5, + "startColumn": 1, + "startLine": 5 + } + } + } + ], + "message": { + "text": "other finding" + }, + "ruleId": "cpp-bounded-buffers-other", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-other", + "name": "" + }, + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-c.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-c.sarif new file mode 100644 index 0000000000000..51086f43164e9 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-c.sarif @@ -0,0 +1,71 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 40, + "location": { + "index": 0, + "uri": "file:///src/c.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0, + "uri": "file:///src/c.cpp" + }, + "region": { + "endColumn": 9, + "endLine": 2, + "startColumn": 5, + "startLine": 2 + } + } + } + ], + "message": { + "text": "multi-level pointer indirection is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-clang-order.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-clang-order.sarif new file mode 100644 index 0000000000000..8124ebbd875cc --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/tu-clang-order.sarif @@ -0,0 +1,124 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [ + { + "length": 120, + "location": { + "index": 1, + "uri": "file:///src/a.cpp" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + }, + { + "length": 60, + "location": { + "index": 0, + "uri": "file:///src/shared.h" + }, + "mimeType": "text/plain", + "roles": [ + "resultFile" + ] + } + ], + "columnKind": "unicodeCodePoints", + "results": [ + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "endColumn": 12, + "endLine": 3, + "startColumn": 8, + "startLine": 3 + } + } + } + ], + "message": { + "text": "multi-level pointer indirection is not yet rewritten" + }, + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + }, + { + "level": "note", + "locations": [ + { + "physicalLocation": { + "artifactLocation": { + "index": 1, + "uri": "file:///src/a.cpp" + }, + "region": { + "endColumn": 7, + "endLine": 7, + "startColumn": 3, + "startLine": 7 + } + } + } + ], + "message": { + "text": "declarator spelled through a macro is not yet rewritten" + }, + "relatedLocations": [ + { + "id": 1, + "message": { + "text": "the macro" + }, + "physicalLocation": { + "artifactLocation": { + "index": 0 + }, + "region": { + "endColumn": 5, + "endLine": 2, + "startColumn": 1, + "startLine": 2 + } + } + } + ], + "ruleId": "cpp-bounded-buffers-skipped", + "ruleIndex": 0 + } + ], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [ + { + "defaultConfiguration": { + "enabled": true, + "level": "warning", + "rank": -1 + }, + "fullDescription": { + "text": "" + }, + "id": "cpp-bounded-buffers-skipped", + "name": "" + } + ], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/two-runs.sarif b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/two-runs.sarif new file mode 100644 index 0000000000000..6fc4646bfe1b7 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/Inputs/two-runs.sarif @@ -0,0 +1,36 @@ +{ + "$schema": "https://docs.oasis-open.org/sarif/sarif/v2.1.0/cos02/schemas/sarif-schema-2.1.0.json", + "runs": [ + { + "artifacts": [], + "columnKind": "unicodeCodePoints", + "results": [], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [], + "version": "24.0.0git" + } + } + }, + { + "artifacts": [], + "columnKind": "unicodeCodePoints", + "results": [], + "tool": { + "driver": { + "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", + "informationUri": "https://clang.llvm.org/docs/UsersManual.html", + "language": "en-US", + "name": "clang-ssaf", + "rules": [], + "version": "24.0.0git" + } + } + } + ], + "version": "2.1.0" +} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/clang-artifact-order.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/clang-artifact-order.test new file mode 100644 index 0000000000000..67de8eb13660c --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/clang-artifact-order.test @@ -0,0 +1,23 @@ +// clang lists a run's artifacts in URI order but numbers them in the order +// they were first referred to, and its results refer to them by that number. +// A location that names its artifact only by number, in a result or in a +// related location, is placed by the artifact's own index. + +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-clang-order.sarif \ +// RUN: %S/Inputs/tu-b.sarif -o %t/merged.sarif +// RUN: FileCheck %s --input-file=%t/merged.sarif +// RUN: grep 'multi-level pointer indirection' %t/merged.sarif | count 1 + +// CHECK: "results": [ +// CHECK: "index": 0, +// CHECK-NEXT: "uri": "file:///src/a.cpp" +// CHECK: "text": "declarator spelled through a macro is not yet rewritten" +// CHECK: "relatedLocations": [ +// CHECK: "index": 2, +// CHECK-NEXT: "uri": "file:///src/shared.h" +// CHECK: "index": 1, +// CHECK-NEXT: "uri": "file:///src/b.cpp" +// CHECK: "index": 2, +// CHECK-NEXT: "uri": "file:///src/shared.h" +// CHECK: "text": "multi-level pointer indirection is not yet rewritten" diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/cli-errors.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/cli-errors.test new file mode 100644 index 0000000000000..93a26189d8206 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/cli-errors.test @@ -0,0 +1,87 @@ +// Tests for clang-ssaf-merge-transformation-report errors. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// ============================================================================ +// CLI error: no arguments +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-ARGS +// NO-ARGS: clang-ssaf-merge-transformation-report{{(\.exe)?}}: Not enough positional command line arguments specified! + +// ============================================================================ +// CLI error: missing -o +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-OUTPUT +// NO-OUTPUT: clang-ssaf-merge-transformation-report{{(\.exe)?}}: for the -o option: must be specified at least once! + +// ============================================================================ +// Error: nonexistent input +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report %t/missing.sarif \ +// RUN: -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=MISSING -DPATH=%t/missing.sarif +// MISSING: clang-ssaf-merge-transformation-report: error: failed to read '[[PATH]]': {{.*}} + +// ============================================================================ +// Error: input is not JSON +// ============================================================================ + +// RUN: echo 'not json' > %t/not-json.sarif +// RUN: not clang-ssaf-merge-transformation-report %t/not-json.sarif \ +// RUN: -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NOT-JSON -DPATH=%t/not-json.sarif +// NOT-JSON: clang-ssaf-merge-transformation-report: error: failed to parse '[[PATH]]': {{.*}} + +// ============================================================================ +// Error: input does not have exactly one run +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report %S/Inputs/two-runs.sarif \ +// RUN: -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=TWO-RUNS +// TWO-RUNS: clang-ssaf-merge-transformation-report: error: '{{.*}}two-runs.sarif' is not a SARIF log with exactly one run + +// ============================================================================ +// Error: inputs from diff erent transformations +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/other-transformation.sarif -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=OTHER +// OTHER: clang-ssaf-merge-transformation-report: error: '{{.*}}other-transformation.sarif' reports on 'clang ScalableStaticAnalysisFramework source transformation (fix-noexcept)', not on 'clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)' + +// ============================================================================ +// Error: a result refers to a rule its run does not define +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report \ +// RUN: %S/Inputs/bad-rule-index.sarif -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=BAD-RULE +// BAD-RULE: clang-ssaf-merge-transformation-report: error: '{{.*}}bad-rule-index.sarif': a result refers to rule 3, which its run does not define + +// ============================================================================ +// Error: a location refers to an artifact its run does not define +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report \ +// RUN: %S/Inputs/bad-artifact-index.sarif -o %t/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=BAD-ARTIFACT +// BAD-ARTIFACT: clang-ssaf-merge-transformation-report: error: '{{.*}}bad-artifact-index.sarif': a location refers to artifact 5, which its run does not define + +// ============================================================================ +// Error: the output cannot be written +// ============================================================================ + +// RUN: not clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: -o %t/no-such-dir/out.sarif 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CANNOT-WRITE -DPATH=%t/no-such-dir/out.sarif +// CANNOT-WRITE: clang-ssaf-merge-transformation-report: error: failed to write '[[PATH]]': {{.*}} + +// No error leaves an output behind. +// RUN: not ls %t/out.sarif diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/empty.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/empty.test new file mode 100644 index 0000000000000..f2a2fc048ee14 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/empty.test @@ -0,0 +1,16 @@ +// Reports without results merge into a report without results, and add +// nothing to a report that has some. + +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/empty.sarif \ +// RUN: %S/Inputs/empty.sarif -o %t/empty.sarif +// RUN: FileCheck %s --input-file=%t/empty.sarif +// CHECK: "artifacts": [], +// CHECK: "results": [], +// CHECK: "rules": [], + +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: -o %t/a.sarif +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/empty.sarif \ +// RUN: %S/Inputs/tu-a.sarif -o %t/empty-a.sarif +// RUN: diff %t/a.sarif %t/empty-a.sarif diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/help.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/help.test new file mode 100644 index 0000000000000..8014d652b2eb3 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/help.test @@ -0,0 +1,19 @@ +// Test clang-ssaf-merge-transformation-report help option + +// RUN: clang-ssaf-merge-transformation-report --help-list-hidden \ +// RUN: | FileCheck %s --match-full-lines + +// CHECK: OVERVIEW: SSAF Transformation Report Merger +// CHECK-EMPTY: +// CHECK-NEXT: USAGE: clang-ssaf-merge-transformation-report{{(\.exe)?}} [options] <input.sarif>... +// CHECK-EMPTY: +// CHECK-NEXT: OPTIONS: +// CHECK-NEXT: -h - Alias for --help +// CHECK-NEXT: --help - Display available options (--help-hidden for more) +// CHECK-NEXT: --help-hidden - Display all available options +// CHECK-NEXT: --help-list - Display list of available options (--help-list-hidden for more) +// CHECK-NEXT: --help-list-hidden - Display list of all available options +// CHECK-NEXT: -o <path> - Output file path +// CHECK-NEXT: --print-all-options - Print all option values after command line parsing +// CHECK-NEXT: --print-options - Print non-default options after command line parsing +// CHECK-NEXT: --version - Display the version of this program diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/merge.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/merge.test new file mode 100644 index 0000000000000..12b6832189a15 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/merge.test @@ -0,0 +1,51 @@ +// Two reports that both carry a finding in a shared header, under diff erent +// rule and artifact indices, merge into one run. + +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/tu-b.sarif -o %t/merged.sarif +// RUN: FileCheck %s --input-file=%t/merged.sarif + +// '-' writes the report to stdout. +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/tu-b.sarif -o - > %t/stdout.sarif +// RUN: diff %t/merged.sarif %t/stdout.sarif + +// The artifacts of both runs, once each and numbered in URI order. +// CHECK: "artifacts": [ +// CHECK: "index": 0, +// CHECK-NEXT: "uri": "file:///src/a.cpp" +// CHECK: "index": 1, +// CHECK-NEXT: "uri": "file:///src/b.cpp" +// CHECK: "index": 2, +// CHECK-NEXT: "uri": "file:///src/shared.h" + +// The results in location order, each pointing into the merged rules and +// artifacts. The header's finding is kept once. +// CHECK: "results": [ +// CHECK: "index": 0, +// CHECK-NEXT: "uri": "file:///src/a.cpp" +// CHECK: "ruleId": "cpp-bounded-buffers-skipped", +// CHECK-NEXT: "ruleIndex": 1 +// CHECK: "index": 1, +// CHECK-NEXT: "uri": "file:///src/b.cpp" +// CHECK: "ruleId": "cpp-bounded-buffers-other", +// CHECK-NEXT: "ruleIndex": 0 +// CHECK: "index": 2, +// CHECK-NEXT: "uri": "file:///src/shared.h" +// CHECK: "text": "multi-level pointer indirection is not yet rewritten" +// CHECK: "ruleId": "cpp-bounded-buffers-skipped", +// CHECK-NEXT: "ruleIndex": 1 +// CHECK-NOT: "text": "multi-level pointer indirection is not yet rewritten" + +// The rules of both runs, once each and in id order, under the driver of the +// first input. +// CHECK: "tool": { +// CHECK: "fullName": "clang ScalableStaticAnalysisFramework source transformation (cpp-bounded-buffers)", +// CHECK: "name": "clang-ssaf", +// CHECK: "rules": [ +// CHECK: "id": "cpp-bounded-buffers-other", +// CHECK: "id": "cpp-bounded-buffers-skipped", +// CHECK-NOT: "id": +// CHECK: "version": "24.0.0git" +// CHECK: "version": "2.1.0" diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/nested.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/nested.test new file mode 100644 index 0000000000000..47db227e2f1e7 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/nested.test @@ -0,0 +1,16 @@ +// A merged report merges again like the reports it was made from, so that an +// archive's report can feed the report of the program it is linked into. + +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/tu-b.sarif -o %t/ab.sarif +// RUN: clang-ssaf-merge-transformation-report %t/ab.sarif \ +// RUN: %S/Inputs/tu-c.sarif -o %t/ab-c.sarif +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/tu-b.sarif %S/Inputs/tu-c.sarif -o %t/abc.sarif +// RUN: diff %t/ab-c.sarif %t/abc.sarif + +// Merging a report with itself adds nothing. +// RUN: clang-ssaf-merge-transformation-report %t/ab.sarif %t/ab.sarif \ +// RUN: -o %t/ab-ab.sarif +// RUN: diff %t/ab.sarif %t/ab-ab.sarif diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/order-invariance.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/order-invariance.test new file mode 100644 index 0000000000000..07986cd628158 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/order-invariance.test @@ -0,0 +1,8 @@ +// The merged report does not depend on the order of the inputs. + +// RUN: rm -rf %t && mkdir -p %t +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-a.sarif \ +// RUN: %S/Inputs/tu-b.sarif %S/Inputs/tu-c.sarif -o %t/abc.sarif +// RUN: clang-ssaf-merge-transformation-report %S/Inputs/tu-c.sarif \ +// RUN: %S/Inputs/tu-b.sarif %S/Inputs/tu-a.sarif -o %t/cba.sarif +// RUN: diff %t/abc.sarif %t/cba.sarif diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/plugin-reports.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/plugin-reports.test new file mode 100644 index 0000000000000..fd62ce4223cc3 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/plugin-reports.test @@ -0,0 +1,47 @@ +// Reports written by clang for two translation units merge into one, and a +// report given twice contributes its findings once. + +// REQUIRES: plugins + +// RUN: rm -rf %t && split-file %s %t +// DEFINE: %{transform} = %clang_cc1 \ +// DEFINE: -load %llvmshlibdir/SSAFTestTransformationPlugin%pluginext \ +// DEFINE: --ssaf-source-transformation=test-transformation \ +// DEFINE: --ssaf-global-scope-analysis-result=%S/../source-edit-generation/Inputs/two-function-suite.json \ +// DEFINE: --ssaf-link-unit-id=lu -emit-obj +// RUN: %{transform} --ssaf-compilation-unit-id=a --ssaf-src-edit-file=%t/a.yaml \ +// RUN: --ssaf-transformation-report-file=%t/a.sarif -o %t/a.o %t/a.cpp +// RUN: %{transform} --ssaf-compilation-unit-id=b --ssaf-src-edit-file=%t/b.yaml \ +// RUN: --ssaf-transformation-report-file=%t/b.sarif -o %t/b.o %t/b.cpp + +// RUN: clang-ssaf-merge-transformation-report %t/a.sarif %t/b.sarif \ +// RUN: %t/a.sarif -o %t/merged.sarif +// RUN: FileCheck %s --input-file=%t/merged.sarif +// RUN: grep '"text": "visited alpha"' %t/merged.sarif | count 1 + +// CHECK: "artifacts": [ +// CHECK: "index": 0, +// CHECK-NEXT: "uri": "file://{{.*}}a.cpp" +// CHECK: "index": 1, +// CHECK-NEXT: "uri": "file://{{.*}}b.cpp" +// CHECK: "results": [ +// CHECK: "index": 0, +// CHECK-NEXT: "uri": "file://{{.*}}a.cpp" +// CHECK: "text": "visited alpha" +// CHECK: "ruleId": "test-touches-function", +// CHECK-NEXT: "ruleIndex": 0 +// CHECK: "index": 1, +// CHECK-NEXT: "uri": "file://{{.*}}b.cpp" +// CHECK: "text": "visited beta" +// CHECK: "ruleId": "test-touches-function", +// CHECK-NEXT: "ruleIndex": 0 +// CHECK: "name": "clang-ssaf", +// CHECK: "rules": [ +// CHECK: "id": "test-touches-function", +// CHECK-NOT: "id": + +//--- a.cpp +void alpha() {} + +//--- b.cpp +void beta() {} diff --git a/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/version.test b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/version.test new file mode 100644 index 0000000000000..94b7e457d5881 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-merge-transformation-report/version.test @@ -0,0 +1,5 @@ +// Test clang-ssaf-merge-transformation-report version + +// RUN: clang-ssaf-merge-transformation-report --version | FileCheck %s + +// CHECK: clang-ssaf-merge-transformation-report 0.1 diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt index 72a4f2760df84..793bed1fbc0a3 100644 --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -107,6 +107,7 @@ list(APPEND CLANG_TEST_DEPS clang-ssaf-analyzer clang-ssaf-format clang-ssaf-linker + clang-ssaf-merge-transformation-report clang-ssaf-src-edit-merge clang-sycl-linker diagtool diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt index 182489653b805..c38efad58db16 100644 --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_subdirectory(clang-scan-deps) add_clang_subdirectory(clang-ssaf-analyzer) add_clang_subdirectory(clang-ssaf-format) add_clang_subdirectory(clang-ssaf-linker) +add_clang_subdirectory(clang-ssaf-merge-transformation-report) if(NOT WIN32) add_clang_subdirectory(clang-ssaf-orchestrator-make) endif() diff --git a/clang/tools/clang-ssaf-merge-transformation-report/CMakeLists.txt b/clang/tools/clang-ssaf-merge-transformation-report/CMakeLists.txt new file mode 100644 index 0000000000000..d243b07be0874 --- /dev/null +++ b/clang/tools/clang-ssaf-merge-transformation-report/CMakeLists.txt @@ -0,0 +1,12 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_tool(clang-ssaf-merge-transformation-report + MergeTransformationReport.cpp + ) + +clang_target_link_libraries(clang-ssaf-merge-transformation-report + PRIVATE + clangScalableStaticAnalysisTool + ) diff --git a/clang/tools/clang-ssaf-merge-transformation-report/MergeTransformationReport.cpp b/clang/tools/clang-ssaf-merge-transformation-report/MergeTransformationReport.cpp new file mode 100644 index 0000000000000..3ca4b22e36ff9 --- /dev/null +++ b/clang/tools/clang-ssaf-merge-transformation-report/MergeTransformationReport.cpp @@ -0,0 +1,386 @@ +//===- MergeTransformationReport.cpp - SSAF Report Merge Tool -------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the tool that merges SARIF transformation reports, as +// written by clang's --ssaf-transformation-report-file or by this tool, into +// one report. Every input run numbers its rules and artifacts on its own, so +// results are renumbered against the merged run, and identical results, such +// as a finding in a header that several translation units include, are kept +// once. An artifact is numbered by its own location index where it has one, +// since clang lists artifacts in URI order but numbers them in the order they +// were first referred to. +// +//===----------------------------------------------------------------------===// + +#include "clang/ScalableStaticAnalysis/Tool/Utils.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/raw_ostream.h" +#include <cstdint> +#include <map> +#include <optional> +#include <set> +#include <string> +#include <tuple> +#include <vector> + +using namespace llvm; +using namespace clang::ssaf; + +namespace { + +//===----------------------------------------------------------------------===// +// Command-Line Options +//===----------------------------------------------------------------------===// + +cl::OptionCategory MergeTransformationReportCategory( + "clang-ssaf-merge-transformation-report options"); + +cl::list<std::string> InputPaths(cl::Positional, cl::desc("<input.sarif>..."), + cl::OneOrMore, + cl::cat(MergeTransformationReportCategory)); + +cl::opt<std::string> OutputPath("o", cl::desc("Output file path"), + cl::value_desc("path"), cl::Required, + cl::cat(MergeTransformationReportCategory)); + +//===----------------------------------------------------------------------===// +// Error Messages +//===----------------------------------------------------------------------===// + +namespace ErrorMessages { + +constexpr const char *CannotRead = "failed to read '{0}': {1}"; + +constexpr const char *CannotParse = "failed to parse '{0}': {1}"; + +constexpr const char *NotOneRun = + "'{0}' is not a SARIF log with exactly one run"; + +constexpr const char *OtherTransformation = + "'{0}' reports on '{1}', not on '{2}'"; + +constexpr const char *UnknownRule = + "'{0}': a result refers to rule {1}, which its run does not define"; + +constexpr const char *UnknownArtifact = + "'{0}': a location refers to artifact {1}, which its run does not define"; + +constexpr const char *CannotWrite = "failed to write '{0}': {1}"; + +} // namespace ErrorMessages + +//===----------------------------------------------------------------------===// +// Reading and Writing +//===----------------------------------------------------------------------===// + +json::Value readLog(StringRef Path) { + ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = MemoryBuffer::getFile(Path); + if (!Buffer) { + fail(ErrorMessages::CannotRead, Path, Buffer.getError().message()); + } + Expected<json::Value> Log = json::parse((*Buffer)->getBuffer()); + if (!Log) { + fail(ErrorMessages::CannotParse, Path, toString(Log.takeError())); + } + return std::move(*Log); +} + +json::Object &getRun(json::Value &Log, StringRef Path) { + json::Object *Root = Log.getAsObject(); + json::Array *Runs = Root ? Root->getArray("runs") : nullptr; + if (!Runs || Runs->size() != 1 || !Runs->front().getAsObject()) { + fail(ErrorMessages::NotOneRun, Path); + } + return *Runs->front().getAsObject(); +} + +void writeLog(const json::Value &Log, StringRef Path) { + std::error_code EC; + ToolOutputFile Out(Path, EC, sys::fs::OF_Text); + if (EC) { + fail(ErrorMessages::CannotWrite, Path, EC.message()); + } + Out.os() << formatv("{0:2}", Log) << "\n"; + Out.os().flush(); + if (Out.os().has_error()) { + fail(ErrorMessages::CannotWrite, Path, Out.os().error().message()); + } + Out.keep(); +} + +StringRef getString(const json::Object *Object, StringRef Key) { + if (!Object) { + return ""; + } + return Object->getString(Key).value_or(""); +} + +json::Object *getArtifactLocation(json::Value &Location) { + json::Object *Object = Location.getAsObject(); + json::Object *Physical = + Object ? Object->getObject("physicalLocation") : nullptr; + return Physical ? Physical->getObject("artifactLocation") : nullptr; +} + +/// Returns a copy of \p Object without the members named in \p Excluded. +json::Object copyExcept(const json::Object &Object, + ArrayRef<StringRef> Excluded) { + json::Object Copy; + for (const auto &[Key, Value] : Object) { + if (!is_contained(Excluded, StringRef(Key))) { + Copy[Key] = Value; + } + } + return Copy; +} + +//===----------------------------------------------------------------------===// +// Merging +//===----------------------------------------------------------------------===// + +/// A result whose rule and artifacts are named by id and URI rather than by +/// index, so that results from diff erent runs compare equal. +struct Finding { + std::string Uri; + int64_t Line; + int64_t Column; + std::string RuleId; + std::string Message; + std::string Serialized; + json::Object Result; + + bool operator<(const Finding &Other) const { + return std::tie(Uri, Line, Column, RuleId, Message, Serialized) < + std::tie(Other.Uri, Other.Line, Other.Column, Other.RuleId, + Other.Message, Other.Serialized); + } +}; + +class ReportMerger { +public: + void add(json::Value Log, StringRef Path); + json::Value takeMerged(); + +private: + void addFinding(json::Object Result); + + bool HasInput = false; + json::Object Root; + json::Object Run; + json::Object Tool; + json::Object Driver; + std::map<std::string, json::Value> Rules; + std::map<std::string, json::Value> Artifacts; + std::set<std::string> Seen; + std::vector<Finding> Findings; +}; + +void ReportMerger::add(json::Value Log, StringRef Path) { + json::Object &InputRun = getRun(Log, Path); + const json::Object *InputTool = InputRun.getObject("tool"); + const json::Object *InputDriver = + InputTool ? InputTool->getObject("driver") : nullptr; + + if (!HasInput) { + HasInput = true; + Root = copyExcept(*Log.getAsObject(), {"runs"}); + Run = copyExcept(InputRun, {"artifacts", "results", "tool"}); + if (InputTool) { + Tool = copyExcept(*InputTool, {"driver"}); + } + if (InputDriver) { + Driver = copyExcept(*InputDriver, {"rules"}); + } + } else if (getString(InputDriver, "name") != getString(&Driver, "name") || + getString(InputDriver, "fullName") != + getString(&Driver, "fullName")) { + fail(ErrorMessages::OtherTransformation, Path, + getString(InputDriver, "fullName"), getString(&Driver, "fullName")); + } + + std::vector<std::string> RuleIds; + if (const json::Array *InputRules = + InputDriver ? InputDriver->getArray("rules") : nullptr) { + for (const json::Value &Rule : *InputRules) { + std::string Id = getString(Rule.getAsObject(), "id").str(); + Rules.try_emplace(Id, Rule); + RuleIds.push_back(std::move(Id)); + } + } + + std::map<int64_t, std::string> Uris; + if (const json::Array *InputArtifacts = InputRun.getArray("artifacts")) { + for (auto [Position, Artifact] : enumerate(*InputArtifacts)) { + const json::Object *Object = Artifact.getAsObject(); + const json::Object *Location = + Object ? Object->getObject("location") : nullptr; + std::string Uri = getString(Location, "uri").str(); + int64_t Index = static_cast<int64_t>(Position); + if (Location) { + Index = Location->getInteger("index").value_or(Index); + } + Artifacts.try_emplace(Uri, Artifact); + Uris[Index] = std::move(Uri); + } + } + + json::Array *Results = InputRun.getArray("results"); + if (!Results) { + return; + } + for (json::Value &Value : *Results) { + json::Object *Result = Value.getAsObject(); + if (!Result) { + continue; + } + if (std::optional<int64_t> Index = Result->getInteger("ruleIndex")) { + if (*Index < 0 || *Index >= static_cast<int64_t>(RuleIds.size())) { + fail(ErrorMessages::UnknownRule, Path, *Index); + } + Result->try_emplace("ruleId", RuleIds[*Index]); + Result->erase("ruleIndex"); + } + for (StringRef Key : {"locations", "relatedLocations"}) { + json::Array *Locations = Result->getArray(Key); + if (!Locations) { + continue; + } + for (json::Value &Location : *Locations) { + json::Object *Artifact = getArtifactLocation(Location); + if (!Artifact) { + continue; + } + if (std::optional<int64_t> Index = Artifact->getInteger("index")) { + auto Uri = Uris.find(*Index); + if (Uri == Uris.end()) { + fail(ErrorMessages::UnknownArtifact, Path, *Index); + } + Artifact->try_emplace("uri", Uri->second); + Artifact->erase("index"); + } + } + } + addFinding(std::move(*Result)); + } +} + +void ReportMerger::addFinding(json::Object Result) { + json::Value Value(std::move(Result)); + Finding F; + F.Serialized = formatv("{0}", Value); + if (!Seen.insert(F.Serialized).second) { + return; + } + json::Object &Object = *Value.getAsObject(); + F.Line = 0; + F.Column = 0; + if (json::Array *Locations = Object.getArray("locations"); + Locations && !Locations->empty()) { + F.Uri = getString(getArtifactLocation(Locations->front()), "uri").str(); + const json::Object *Location = Locations->front().getAsObject(); + const json::Object *Physical = + Location ? Location->getObject("physicalLocation") : nullptr; + if (const json::Object *Region = + Physical ? Physical->getObject("region") : nullptr) { + F.Line = Region->getInteger("startLine").value_or(0); + F.Column = Region->getInteger("startColumn").value_or(0); + } + } + F.RuleId = getString(&Object, "ruleId").str(); + F.Message = getString(Object.getObject("message"), "text").str(); + F.Result = std::move(Object); + Findings.push_back(std::move(F)); +} + +json::Value ReportMerger::takeMerged() { + std::map<std::string, int64_t> RuleIndex; + json::Array MergedRules; + for (auto &[Id, Rule] : Rules) { + RuleIndex[Id] = MergedRules.size(); + MergedRules.push_back(std::move(Rule)); + } + + std::map<std::string, int64_t> ArtifactIndex; + json::Array MergedArtifacts; + for (auto &[Uri, Artifact] : Artifacts) { + int64_t Index = MergedArtifacts.size(); + ArtifactIndex[Uri] = Index; + if (json::Object *Object = Artifact.getAsObject()) { + if (json::Object *Location = Object->getObject("location")) { + (*Location)["index"] = Index; + } + } + MergedArtifacts.push_back(std::move(Artifact)); + } + + llvm::sort(Findings); + json::Array MergedResults; + for (Finding &F : Findings) { + auto Rule = RuleIndex.find(F.RuleId); + if (Rule != RuleIndex.end()) { + F.Result["ruleIndex"] = Rule->second; + } + for (StringRef Key : {"locations", "relatedLocations"}) { + json::Array *Locations = F.Result.getArray(Key); + if (!Locations) { + continue; + } + for (json::Value &Location : *Locations) { + json::Object *Artifact = getArtifactLocation(Location); + if (!Artifact) { + continue; + } + auto Index = ArtifactIndex.find(getString(Artifact, "uri").str()); + if (Index != ArtifactIndex.end()) { + (*Artifact)["index"] = Index->second; + } + } + } + MergedResults.push_back(std::move(F.Result)); + } + + Driver["rules"] = std::move(MergedRules); + Tool["driver"] = std::move(Driver); + Run["tool"] = std::move(Tool); + Run["artifacts"] = std::move(MergedArtifacts); + Run["results"] = std::move(MergedResults); + json::Array Runs; + Runs.push_back(std::move(Run)); + Root["runs"] = std::move(Runs); + return std::move(Root); +} + +} // namespace + +//===----------------------------------------------------------------------===// +// Driver +//===----------------------------------------------------------------------===// + +int main(int argc, const char **argv) { + llvm::StringRef ToolHeading = "SSAF Transformation Report Merger"; + + InitLLVM X(argc, argv); + initTool(argc, argv, "0.1", MergeTransformationReportCategory, ToolHeading); + + ReportMerger Merger; + for (const std::string &Path : InputPaths) { + Merger.add(readLog(Path), Path); + } + writeLog(Merger.takeMerged(), OutputPath); + + return 0; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
