https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/196124
>From 7cce97d9c4bb79636116e81d4a74416996b29974 Mon Sep 17 00:00:00 2001 From: Aviral Goel <[email protected]> Date: Wed, 6 May 2026 10:14:25 -0700 Subject: [PATCH] [clang][ssaf] Add `clang-ssaf-analyzer` --- .../Tool/Utils.cpp | 4 + .../ssaf-analyzer/Inputs/lu-badext.txt | 1 + .../ssaf-analyzer/Inputs/lu-corrupt.json | 1 + .../Scalable/ssaf-analyzer/Inputs/lu-noext | 1 + .../ssaf-analyzer/Inputs/lu-tags-only.json | 44 ++++++ .../ssaf-analyzer/Inputs/lu-wrong-type.json | 1 + .../Scalable/ssaf-analyzer/Inputs/lu.json | 126 ++++++++++++++++ .../Scalable/ssaf-analyzer/Outputs/all.json | 90 +++++++++++ .../Scalable/ssaf-analyzer/Outputs/both.json | 81 ++++++++++ .../Scalable/ssaf-analyzer/Outputs/pairs.json | 70 +++++++++ .../Scalable/ssaf-analyzer/Outputs/tags.json | 56 +++++++ .../Scalable/ssaf-analyzer/analyzer.test | 141 ++++++++++++++++++ .../Scalable/ssaf-analyzer/cli-errors.test | 53 +++++++ .../Analysis/Scalable/ssaf-analyzer/help.test | 23 +++ .../Scalable/ssaf-analyzer/lit.local.cfg | 4 + .../validation-errors-permissions.test | 28 ++++ .../ssaf-analyzer/validation-errors.test | 45 ++++++ .../Scalable/ssaf-analyzer/version.test | 5 + clang/test/CMakeLists.txt | 1 + clang/tools/CMakeLists.txt | 1 + .../tools/clang-ssaf-analyzer/CMakeLists.txt | 25 ++++ .../clang-ssaf-analyzer/SSAFAnalyzer.cpp | 134 +++++++++++++++++ 22 files changed, 935 insertions(+) create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-badext.txt create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-corrupt.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-noext create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-wrong-type.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/all.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/both.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/pairs.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/tags.json create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/cli-errors.test create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/help.test create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/lit.local.cfg create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors-permissions.test create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors.test create mode 100644 clang/test/Analysis/Scalable/ssaf-analyzer/version.test create mode 100644 clang/tools/clang-ssaf-analyzer/CMakeLists.txt create mode 100644 clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp diff --git a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp index 6a78620b757e6..f14f34eb3bbb0 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp +++ b/clang/lib/ScalableStaticAnalysisFramework/Tool/Utils.cpp @@ -136,6 +136,10 @@ llvm::StringRef clang::ssaf::getToolName() { return ToolName; } void clang::ssaf::loadPlugins(llvm::ArrayRef<std::string> Paths) { for (const std::string &PluginPath : Paths) { + if (!fs::exists(PluginPath)) { + fail(ErrorMessages::FailedToLoadPlugin, PluginPath, + ErrorMessages::PathDoesNotExist); + } std::string ErrMsg; if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(PluginPath.c_str(), &ErrMsg)) { diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-badext.txt b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-badext.txt new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-badext.txt @@ -0,0 +1 @@ +{} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-corrupt.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-corrupt.json new file mode 100644 index 0000000000000..b22e81a41ba54 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-corrupt.json @@ -0,0 +1 @@ +{ this is not valid json } diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-noext b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-noext new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-noext @@ -0,0 +1 @@ +{} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json new file mode 100644 index 0000000000000..c562a25f3c55d --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-tags-only.json @@ -0,0 +1,44 @@ +{ + "data": [ + { + "summary_name": "TagsEntitySummary", + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "tags": ["important", "public"] + } + } + ] + } + ], + "id_table": [ + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + } + ], + "linkage_table": [ + { + "id": 0, + "linkage": { + "type": "External" + } + } + ], + "lu_namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-wrong-type.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-wrong-type.json new file mode 100644 index 0000000000000..b5d8bb58d9bc3 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu-wrong-type.json @@ -0,0 +1 @@ +[1, 2, 3] diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json new file mode 100644 index 0000000000000..9c30fefc55dd5 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Inputs/lu.json @@ -0,0 +1,126 @@ +{ + "data": [ + { + "summary_name": "TagsEntitySummary", + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "tags": ["important", "public"] + } + }, + { + "entity_id": 1, + "entity_summary": { + "tags": ["internal", "deprecated", "important"] + } + }, + { + "entity_id": 2, + "entity_summary": { + "tags": ["public"] + } + } + ] + }, + { + "summary_name": "PairsEntitySummary", + "summary_data": [ + { + "entity_id": 0, + "entity_summary": { + "pairs": [ + {"first": {"@": 1}, "second": {"@": 2}} + ] + } + }, + { + "entity_id": 1, + "entity_summary": { + "pairs": [ + {"first": {"@": 0}, "second": {"@": 2}}, + {"first": {"@": 2}, "second": {"@": 0}} + ] + } + }, + { + "entity_id": 2, + "entity_summary": { + "pairs": [ + {"first": {"@": 0}, "second": {"@": 1}}, + {"first": {"@": 1}, "second": {"@": 0}}, + {"first": {"@": 0}, "second": {"@": 0}} + ] + } + } + ] + } + ], + "id_table": [ + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + }, + { + "id": 1, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + }, + { + "id": 2, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@baz#" + } + } + ], + "linkage_table": [ + { + "id": 0, + "linkage": { + "type": "External" + } + }, + { + "id": 1, + "linkage": { + "type": "External" + } + }, + { + "id": 2, + "linkage": { + "type": "External" + } + } + ], + "lu_namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/all.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/all.json new file mode 100644 index 0000000000000..d408b64d05af3 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/all.json @@ -0,0 +1,90 @@ +{ + "id_table": [ + { + "id": 1, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + }, + { + "id": 2, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@baz#" + } + }, + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + } + ], + "results": [ + { + "analysis_name": "PairsAnalysisResult", + "result": { + "pair_counts": [ + { + "count": 1, + "entity_id": { + "@": 0 + } + }, + { + "count": 2, + "entity_id": { + "@": 1 + } + }, + { + "count": 3, + "entity_id": { + "@": 2 + } + } + ] + } + }, + { + "analysis_name": "TagsAnalysisResult", + "result": { + "tags": [ + "deprecated", + "important", + "internal", + "public" + ] + } + }, + { + "analysis_name": "TagsPairsAnalysisResult", + "result": { + "entity_count": 3, + "max_pairs_per_entity": 3, + "total_pair_count": 6, + "unique_tag_count": 4 + } + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/both.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/both.json new file mode 100644 index 0000000000000..e5f7170814295 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/both.json @@ -0,0 +1,81 @@ +{ + "id_table": [ + { + "id": 1, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + }, + { + "id": 2, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@baz#" + } + }, + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + } + ], + "results": [ + { + "analysis_name": "PairsAnalysisResult", + "result": { + "pair_counts": [ + { + "count": 1, + "entity_id": { + "@": 0 + } + }, + { + "count": 2, + "entity_id": { + "@": 1 + } + }, + { + "count": 3, + "entity_id": { + "@": 2 + } + } + ] + } + }, + { + "analysis_name": "TagsAnalysisResult", + "result": { + "tags": [ + "deprecated", + "important", + "internal", + "public" + ] + } + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/pairs.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/pairs.json new file mode 100644 index 0000000000000..3f52b0acfd900 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/pairs.json @@ -0,0 +1,70 @@ +{ + "id_table": [ + { + "id": 1, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + }, + { + "id": 2, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@baz#" + } + }, + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + } + ], + "results": [ + { + "analysis_name": "PairsAnalysisResult", + "result": { + "pair_counts": [ + { + "count": 1, + "entity_id": { + "@": 0 + } + }, + { + "count": 2, + "entity_id": { + "@": 1 + } + }, + { + "count": 3, + "entity_id": { + "@": 2 + } + } + ] + } + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/tags.json b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/tags.json new file mode 100644 index 0000000000000..aa41194746ea9 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/Outputs/tags.json @@ -0,0 +1,56 @@ +{ + "id_table": [ + { + "id": 1, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + }, + { + "id": 2, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@baz#" + } + }, + { + "id": 0, + "name": { + "namespace": [ + { + "kind": "LinkUnit", + "name": "test.exe" + } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + } + ], + "results": [ + { + "analysis_name": "TagsAnalysisResult", + "result": { + "tags": [ + "deprecated", + "important", + "internal", + "public" + ] + } + } + ] +} diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test b/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test new file mode 100644 index 0000000000000..0abdcef15a449 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/analyzer.test @@ -0,0 +1,141 @@ +// Tests for clang-ssaf-analyzer that require plugin support. + +// REQUIRES: plugins + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// ============================================================================ +// Error: unknown analysis name +// ============================================================================ + +// RUN: not %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/unknown.json -a NoSuchAnalysis 2>&1 \ +// RUN: | FileCheck %s --check-prefix=UNKNOWN +// UNKNOWN: no analysis registered for 'AnalysisName(NoSuchAnalysis)' + +// ============================================================================ +// Error: valid analysis name but LUSummary lacks entity data for it +// ============================================================================ + +// RUN: not %clang-ssaf-analyzer-with-plugin %S/Inputs/lu-tags-only.json \ +// RUN: -o %t/missing-data.json -a PairsAnalysisResult 2>&1 \ +// RUN: | FileCheck %s --check-prefix=MISSING-DATA +// MISSING-DATA: no data for analysis 'AnalysisName(PairsAnalysisResult)' in LUSummary + +// ============================================================================ +// Success: run TagsAnalysisResult only (single analysis) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/tags.json -a TagsAnalysisResult +// RUN: diff %S/Outputs/tags.json %t/tags.json + +// ============================================================================ +// Success: run PairsAnalysisResult only (single analysis) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/pairs.json -a PairsAnalysisResult +// RUN: diff %S/Outputs/pairs.json %t/pairs.json + +// ============================================================================ +// Success: run two summary analyses +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/both.json -a TagsAnalysisResult -a PairsAnalysisResult +// RUN: diff %S/Outputs/both.json %t/both.json + +// ============================================================================ +// Success: reversed order produces the same result (order-independent) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/both-rev.json -a PairsAnalysisResult -a TagsAnalysisResult +// RUN: diff %S/Outputs/both.json %t/both-rev.json + +// ============================================================================ +// Success: duplicate analysis name is deduplicated +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/tags-dup.json -a TagsAnalysisResult -a TagsAnalysisResult +// RUN: diff %S/Outputs/tags.json %t/tags-dup.json + +// ============================================================================ +// Success: derived analysis pulls dependencies automatically +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/all.json -a TagsPairsAnalysisResult +// RUN: diff %S/Outputs/all.json %t/all.json + +// ============================================================================ +// Success: derived + one explicit dep (redundant dep is deduplicated) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/all-partial-dup.json -a TagsPairsAnalysisResult -a TagsAnalysisResult +// RUN: diff %S/Outputs/all.json %t/all-partial-dup.json + +// ============================================================================ +// Success: derived + both explicit deps (all redundant, deduplicated) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/all-full-dup.json \ +// RUN: -a TagsPairsAnalysisResult -a TagsAnalysisResult -a PairsAnalysisResult +// RUN: diff %S/Outputs/all.json %t/all-full-dup.json + +// ============================================================================ +// Success: run all example-plugin analyses explicitly +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/all-default.json \ +// RUN: -a TagsAnalysisResult -a PairsAnalysisResult -a TagsPairsAnalysisResult +// RUN: diff %S/Outputs/all.json %t/all-default.json + +// ============================================================================ +// Success: --analysis alias for -a +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/analysis-alias.json --analysis TagsAnalysisResult +// RUN: diff %S/Outputs/tags.json %t/analysis-alias.json + +// ============================================================================ +// Success: -l alias for --load +// ============================================================================ + +// RUN: clang-ssaf-analyzer -l %llvmshlibdir/SSAFExamplePlugin%pluginext \ +// RUN: %S/Inputs/lu.json -o %t/load-alias.json -a PairsAnalysisResult +// RUN: diff %S/Outputs/pairs.json %t/load-alias.json + +// ============================================================================ +// Error: mix of valid and invalid analysis names (partial failure) +// ============================================================================ + +// RUN: not %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/mixed.json -a TagsAnalysisResult -a NoSuchAnalysis -a PairsAnalysisResult 2>&1 \ +// RUN: | FileCheck %s --check-prefix=MIXED +// MIXED: no analysis registered for 'AnalysisName(NoSuchAnalysis)' + +// ============================================================================ +// Success: -o specified twice (last value wins) +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/ignored.json -o %t/last-wins.json -a TagsAnalysisResult +// RUN: diff %S/Outputs/tags.json %t/last-wins.json + +// ============================================================================ +// Round-trip: analyze then read back with ssaf-format +// ============================================================================ + +// RUN: %clang-ssaf-analyzer-with-plugin %S/Inputs/lu.json \ +// RUN: -o %t/roundtrip.json -a TagsPairsAnalysisResult +// RUN: clang-ssaf-format --type wpa --load %llvmshlibdir/SSAFExamplePlugin%pluginext \ +// RUN: %t/roundtrip.json -o %t/roundtrip-copy.json +// RUN: diff %S/Outputs/all.json %t/roundtrip-copy.json diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/cli-errors.test b/clang/test/Analysis/Scalable/ssaf-analyzer/cli-errors.test new file mode 100644 index 0000000000000..0367ba8b8c484 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/cli-errors.test @@ -0,0 +1,53 @@ +// Tests for clang-ssaf-analyzer CLI errors that do not require plugins. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// ============================================================================ +// CLI error: no arguments +// ============================================================================ + +// RUN: not clang-ssaf-analyzer 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-ARGS +// NO-ARGS: clang-ssaf-analyzer{{(\.exe)?}}: Not enough positional command line arguments specified! + +// ============================================================================ +// CLI error: missing -o +// ============================================================================ + +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -a Foo 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-OUTPUT +// NO-OUTPUT: clang-ssaf-analyzer{{(\.exe)?}}: for the -o option: must be specified at least once! + +// ============================================================================ +// Error: nonexistent plugin path +// ============================================================================ + +// RUN: not clang-ssaf-analyzer --load /nonexistent/path/plugin.so \ +// RUN: %S/Inputs/lu.json -o %t/plugin-err.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=BAD-PLUGIN +// BAD-PLUGIN: clang-ssaf-analyzer: error: failed to load plugin '/nonexistent/path/plugin.so': Path does not exist + +// ============================================================================ +// Error: empty analysis name +// ============================================================================ + +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o %t/empty-name.json -a "" 2>&1 \ +// RUN: | FileCheck %s --check-prefix=EMPTY-NAME +// EMPTY-NAME: clang-ssaf-analyzer: error: analysis name must not be empty + +// ============================================================================ +// Error: corrupt input JSON (parse error) +// ============================================================================ + +// RUN: not clang-ssaf-analyzer %S/Inputs/lu-corrupt.json -o %t/corrupt.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=CORRUPT +// CORRUPT: clang-ssaf-analyzer: error: reading LUSummary from file '{{.*}}lu-corrupt.json' + +// ============================================================================ +// Error: valid JSON but wrong top-level type (array instead of object) +// ============================================================================ + +// RUN: not clang-ssaf-analyzer %S/Inputs/lu-wrong-type.json -o %t/wrong-type.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=WRONG-TYPE +// WRONG-TYPE: clang-ssaf-analyzer: error: reading LUSummary from file '{{.*}}lu-wrong-type.json' diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/help.test b/clang/test/Analysis/Scalable/ssaf-analyzer/help.test new file mode 100644 index 0000000000000..e4b5eca320b08 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/help.test @@ -0,0 +1,23 @@ +// Test clang-ssaf-analyzer help option + +// RUN: clang-ssaf-analyzer --help-list-hidden \ +// RUN: | FileCheck %s --match-full-lines + +// CHECK: OVERVIEW: SSAF Analyzer +// CHECK-EMPTY: +// CHECK-NEXT: USAGE: clang-ssaf-analyzer{{(\.exe)?}} [options] <input file> +// CHECK-EMPTY: +// CHECK-NEXT: OPTIONS: +// CHECK-NEXT: -a <name> - Analysis name to run +// CHECK-NEXT: --analysis - Alias for -a +// 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: -l - Alias for --load +// CHECK-NEXT: --load=<path> - Load a plugin shared library +// 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-analyzer/lit.local.cfg b/clang/test/Analysis/Scalable/ssaf-analyzer/lit.local.cfg new file mode 100644 index 0000000000000..59a179abf4d3b --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/lit.local.cfg @@ -0,0 +1,4 @@ +config.substitutions.insert(0, + ("%clang-ssaf-analyzer-with-plugin", + "clang-ssaf-analyzer --load {}/SSAFExamplePlugin{}".format( + config.llvm_shlib_dir, config.llvm_plugin_ext))) diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors-permissions.test b/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors-permissions.test new file mode 100644 index 0000000000000..bcd4f036ac550 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors-permissions.test @@ -0,0 +1,28 @@ +// Tests for clang-ssaf-analyzer input validation requiring file permission support. +// UNSUPPORTED: system-windows +// REQUIRES: non-root-user + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// Output parent directory exists but is not writable. +// RUN: mkdir -p %t/output-dir +// RUN: chmod -w %t/output-dir +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o %t/output-dir/output.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-WRITE-PERM +// RUN: chmod +w %t/output-dir +// NO-WRITE-PERM: clang-ssaf-analyzer: error: failed to validate path '{{.*}}output.json': Parent directory is not writable + +// Input is a broken symlink. +// RUN: ln -sf %t/lu-nonexistent %t/lu-dangling.json +// RUN: not clang-ssaf-analyzer %t/lu-dangling.json -o %t/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=BROKEN-SYMLINK +// BROKEN-SYMLINK: clang-ssaf-analyzer: error: failed to validate path '{{.*}}lu-dangling.json': Path does not exist + +// Input file exists but is not readable. +// RUN: cp %S/Inputs/lu.json %t/lu-unreadable.json +// RUN: chmod -r %t/lu-unreadable.json +// RUN: not clang-ssaf-analyzer %t/lu-unreadable.json -o %t/out2.json 2>&1 \ +// RUN: | FileCheck %s --check-prefix=NO-READ-PERM +// RUN: chmod +r %t/lu-unreadable.json +// NO-READ-PERM: clang-ssaf-analyzer: error: reading LUSummary from file '{{.*}}lu-unreadable.json' diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors.test b/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors.test new file mode 100644 index 0000000000000..692991d157c52 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/validation-errors.test @@ -0,0 +1,45 @@ +// Tests for clang-ssaf-analyzer input validation. + +// RUN: rm -rf %t +// RUN: mkdir -p %t + +// No extension on output. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o lu 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-EXT-OUTPUT +// NO-EXT-OUTPUT: clang-ssaf-analyzer: error: failed to validate path 'lu': Extension not supplied + +// No extension on input. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu-noext -o %t/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-EXT-INPUT +// NO-EXT-INPUT: clang-ssaf-analyzer: error: failed to validate path '{{.*}}lu-noext': Extension not supplied + +// Invalid extension on output. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o out.txt 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-EXT-OUTPUT +// BAD-EXT-OUTPUT: clang-ssaf-analyzer: error: failed to validate path 'out.txt': No format registered for extension 'txt' + +// Invalid extension on input. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu-badext.txt -o %t/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=BAD-EXT-INPUT +// BAD-EXT-INPUT: clang-ssaf-analyzer: error: failed to validate path '{{.*}}lu-badext.txt': No format registered for extension 'txt' + +// Output directory does not exist. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o %S/Outputs/NonExistentDirectory/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=OUTPUT-PARENT-DIR-MISSING +// OUTPUT-PARENT-DIR-MISSING: clang-ssaf-analyzer: error: failed to validate path '{{.*}}out.json': Parent directory does not exist + +// Input is a directory, not a file. +// RUN: not clang-ssaf-analyzer %S/Inputs -o %t/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=INPUT-IS-DIR +// INPUT-IS-DIR: clang-ssaf-analyzer: error: failed to validate path '{{.*}}Inputs': Path is not a file + +// Input file does not exist. +// RUN: not clang-ssaf-analyzer %S/Inputs/lu-nonexistent.json -o %t/out.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=NO-INPUT-FILE +// NO-INPUT-FILE: clang-ssaf-analyzer: error: failed to validate path '{{.*}}lu-nonexistent.json': Path does not exist + +// Output file already exists. +// RUN: touch %t/already-exists.json +// RUN: not clang-ssaf-analyzer %S/Inputs/lu.json -o %t/already-exists.json 2>&1 \ +// RUN: | FileCheck %s --match-full-lines --check-prefix=OUTPUT-EXISTS +// OUTPUT-EXISTS: clang-ssaf-analyzer: error: failed to validate path '{{.*}}already-exists.json': File already exists diff --git a/clang/test/Analysis/Scalable/ssaf-analyzer/version.test b/clang/test/Analysis/Scalable/ssaf-analyzer/version.test new file mode 100644 index 0000000000000..a859934f60606 --- /dev/null +++ b/clang/test/Analysis/Scalable/ssaf-analyzer/version.test @@ -0,0 +1,5 @@ +// Test clang-ssaf-analyzer version + +// RUN: clang-ssaf-analyzer --version | FileCheck %s + +// CHECK: clang-ssaf-analyzer 0.1 diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt index cb1f34d2e49bc..dbd1fafae7f3e 100644 --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -104,6 +104,7 @@ list(APPEND CLANG_TEST_DEPS clang-linker-wrapper clang-nvlink-wrapper clang-offload-bundler + clang-ssaf-analyzer clang-ssaf-format clang-ssaf-linker clang-sycl-linker diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt index 891043ec31f77..a16089be041bf 100644 --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -15,6 +15,7 @@ add_clang_subdirectory(clang-linker-wrapper) add_clang_subdirectory(clang-nvlink-wrapper) add_clang_subdirectory(clang-offload-bundler) 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-sycl-linker) diff --git a/clang/tools/clang-ssaf-analyzer/CMakeLists.txt b/clang/tools/clang-ssaf-analyzer/CMakeLists.txt new file mode 100644 index 0000000000000..67732867181b6 --- /dev/null +++ b/clang/tools/clang-ssaf-analyzer/CMakeLists.txt @@ -0,0 +1,25 @@ +set(LLVM_LINK_COMPONENTS + Option + Support + ) + +if(CLANG_PLUGIN_SUPPORT) + set(support_plugins SUPPORT_PLUGINS) +endif() + +add_clang_tool(clang-ssaf-analyzer + SSAFAnalyzer.cpp + ${support_plugins} + ) + +clang_target_link_libraries(clang-ssaf-analyzer + PRIVATE + clangBasic + clangScalableStaticAnalysisFrameworkAnalyses + clangScalableStaticAnalysisFrameworkCore + clangScalableStaticAnalysisFrameworkTool + ) + +if(CLANG_PLUGIN_SUPPORT) + export_executable_symbols_for_plugins(clang-ssaf-analyzer) +endif() diff --git a/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp b/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp new file mode 100644 index 0000000000000..14f037beb60c1 --- /dev/null +++ b/clang/tools/clang-ssaf-analyzer/SSAFAnalyzer.cpp @@ -0,0 +1,134 @@ +//===- SSAFAnalyzer.cpp - SSAF Analyzer 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 SSAF analyzer tool that runs whole-program +// analyses over an LUSummary and writes the resulting WPASuite to an +// output file. +// +//===----------------------------------------------------------------------===// + +#include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/LUSummary.h" +#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisDriver.h" +#include "clang/ScalableStaticAnalysisFramework/Core/WholeProgramAnalysis/AnalysisName.h" +#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep +#include "clang/ScalableStaticAnalysisFramework/Tool/Utils.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/InitLLVM.h" +#include <memory> +#include <string> + +using namespace llvm; +using namespace clang::ssaf; + +namespace { + +//===----------------------------------------------------------------------===// +// Command-Line Options +//===----------------------------------------------------------------------===// + +cl::OptionCategory SsafAnalyzerCategory("clang-ssaf-analyzer options"); + +cl::opt<std::string> InputPath(cl::Positional, cl::desc("<input file>"), + cl::Required, cl::cat(SsafAnalyzerCategory)); + +cl::opt<std::string> OutputPath("o", cl::desc("Output file path"), + cl::value_desc("path"), cl::Required, + cl::cat(SsafAnalyzerCategory)); + +cl::list<std::string> AnalysisNames("a", cl::desc("Analysis name to run"), + cl::value_desc("name"), + cl::cat(SsafAnalyzerCategory)); + +cl::alias AnalysisNamesAlias("analysis", cl::aliasopt(AnalysisNames), + cl::desc("Alias for -a")); + +cl::list<std::string> LoadPlugins("load", + cl::desc("Load a plugin shared library"), + cl::value_desc("path"), + cl::cat(SsafAnalyzerCategory)); + +cl::alias LoadPluginsAlias("l", cl::aliasopt(LoadPlugins), + cl::desc("Alias for --load")); + +//===----------------------------------------------------------------------===// +// Input Validation +//===----------------------------------------------------------------------===// + +struct AnalyzerInput { + FormatFile InputFile; + FormatFile OutputFile; + llvm::SmallVector<AnalysisName> Names; +}; + +AnalyzerInput validate() { + AnalyzerInput AI; + + // Validate the input path. + AI.InputFile = FormatFile::fromInputPath(InputPath); + + // Validate the output path. + AI.OutputFile = FormatFile::fromOutputPath(OutputPath); + + // Build and validate analysis names. + for (const auto &Name : AnalysisNames) { + if (Name.empty()) { + fail("analysis name must not be empty"); + } + AI.Names.push_back(AnalysisName(Name)); + } + + return AI; +} + +//===----------------------------------------------------------------------===// +// Analysis Pipeline +//===----------------------------------------------------------------------===// + +void analyze(const AnalyzerInput &AI) { + // Read the LUSummary. + auto ExpectedLU = AI.InputFile.Format->readLUSummary(AI.InputFile.Path); + if (!ExpectedLU) { + fail(ExpectedLU.takeError()); + } + + // Run analyses. If specific names were given, run only those; + // otherwise run all registered analyses. + AnalysisDriver Driver(std::make_unique<LUSummary>(std::move(*ExpectedLU))); + auto ExpectedSuite = + AI.Names.empty() ? std::move(Driver).run() : Driver.run(AI.Names); + if (!ExpectedSuite) { + fail(ExpectedSuite.takeError()); + } + + // Write the WPASuite. + if (auto Err = AI.OutputFile.Format->writeWPASuite(*ExpectedSuite, + AI.OutputFile.Path)) { + fail(std::move(Err)); + } +} + +} // namespace + +//===----------------------------------------------------------------------===// +// Driver +//===----------------------------------------------------------------------===// + +int main(int argc, const char **argv) { + llvm::StringRef ToolHeading = "SSAF Analyzer"; + + InitLLVM X(argc, argv); + initTool(argc, argv, "0.1", SsafAnalyzerCategory, ToolHeading); + + loadPlugins(LoadPlugins); + + AnalyzerInput AI = validate(); + analyze(AI); + + return 0; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
