[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:
Aviral Goel authored and GitHub committed 2026-05-06 14:00:52 -07:00
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
+1
View File
@@ -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
+1
View File
@@ -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;
}