[clang][ssaf] Add clang-ssaf-analyzer (#196124)
This patch introduces `clang-ssaf-analyzer`, a new SSAF tool that runs whole-program analyses over an `LUSummary` and writes the resulting `WPASuite` to an output file.
This commit is contained in:
1 parent
625cf4026f
commit
c5bc0a2cdb
22 files changed
+935
No files matched your search
@@ -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)) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{ this is not valid json }
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[1, 2, 3]
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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)))
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
// Test clang-ssaf-analyzer version
|
||||
|
||||
// RUN: clang-ssaf-analyzer --version | FileCheck %s
|
||||
|
||||
// CHECK: clang-ssaf-analyzer 0.1
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user