https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/184421
From a47b080c567a91e277e7eda42df88970fd51a282 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Tue, 24 Feb 2026 17:30:17 +0000 Subject: [PATCH 1/2] [clang][ssaf] Add --ssaf-extract-summaries= and --ssaf-tu-summary-file= options Along the way, it also fixes the static registration of the builtin (upstream) formats and extractors. --- .../TUSummaryExtractorFrontendAction.h | 33 ++ .../Scalable/SSAFBuiltinForceLinker.h | 28 ++ .../clang/Analysis/Scalable/SSAFForceLinker.h | 25 ++ .../SerializationFormatRegistry.h | 14 +- .../Scalable/TUSummary/ExtractorRegistry.h | 9 + .../clang/Basic/DiagnosticFrontendKinds.td | 18 + .../include/clang/Frontend/FrontendOptions.h | 7 + clang/include/clang/Options/Options.td | 20 + clang/lib/Analysis/Scalable/CMakeLists.txt | 1 + .../TUSummaryExtractorFrontendAction.cpp | 181 +++++++++ .../JSONFormat/JSONFormatImpl.cpp | 6 +- clang/lib/Driver/ToolChains/Clang.cpp | 3 + clang/lib/FrontendTool/CMakeLists.txt | 1 + .../ExecuteCompilerInvocation.cpp | 6 + .../Analysis/SSAF/command-line-interface.cpp | 22 ++ clang/test/Analysis/SSAF/help.cpp | 7 + .../Analysis/Scalable/CMakeLists.txt | 1 + .../TUSummaryExtractorFrontendActionTest.cpp | 362 ++++++++++++++++++ .../Scalable/Registries/FancyAnalysisData.cpp | 2 + .../Registries/MockSerializationFormat.cpp | 2 + .../Registries/MockSummaryExtractor1.cpp | 6 +- .../Registries/MockSummaryExtractor2.cpp | 6 +- .../SerializationFormatRegistryTest.cpp | 10 +- .../SummaryExtractorRegistryTest.cpp | 1 + .../Scalable/SSAFBuiltinTestForceLinker.h | 51 +++ .../Analysis/Scalable/SSAFTestForceLinker.h | 23 ++ .../Analysis/Scalable/TestFixture.cpp | 1 + 27 files changed, 836 insertions(+), 10 deletions(-) create mode 100644 clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h create mode 100644 clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h create mode 100644 clang/include/clang/Analysis/Scalable/SSAFForceLinker.h create mode 100644 clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp create mode 100644 clang/test/Analysis/SSAF/command-line-interface.cpp create mode 100644 clang/test/Analysis/SSAF/help.cpp create mode 100644 clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp create mode 100644 clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h create mode 100644 clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h diff --git a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h new file mode 100644 index 0000000000000..a91955d2a2139 --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h @@ -0,0 +1,33 @@ +//===- TUSummaryExtractorFrontendAction.h -----------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H + +#include "clang/Frontend/FrontendAction.h" +#include <memory> + +namespace clang::ssaf { + +/// Wraps the existing \c FrontendAction and injects the extractor +/// \c ASTConsumers into the pipeline after the ASTConsumers of the wrapped +/// action. +class TUSummaryExtractorFrontendAction final : public WrapperFrontendAction { +public: + explicit TUSummaryExtractorFrontendAction( + std::unique_ptr<FrontendAction> WrappedAction); + ~TUSummaryExtractorFrontendAction(); + +protected: + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H diff --git a/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h b/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h new file mode 100644 index 0000000000000..f13f131e89bc6 --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h @@ -0,0 +1,28 @@ +//===- SSAFBuiltinForceLinker.h ---------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file pulls in all built-in SSAF extractor and format registrations +/// by referencing their anchor symbols, preventing the static linker from +/// discarding the containing object files. +/// +/// Include this header (with IWYU pragma: keep) in any translation unit that +/// must guarantee these registrations are active — typically the entry point +/// of a binary that uses clangAnalysisScalable. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H + +// This anchor is used to force the linker to link the JSONFormat registration. +extern volatile int SSAFJSONFormatAnchorSource; +[[maybe_unused]] static int SSAFJSONFormatAnchorDestination = + SSAFJSONFormatAnchorSource; + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_SSAFBUILTINFORCELINKER_H diff --git a/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h b/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h new file mode 100644 index 0000000000000..a59c5f144a6f5 --- /dev/null +++ b/clang/include/clang/Analysis/Scalable/SSAFForceLinker.h @@ -0,0 +1,25 @@ +//===- SSAFForceLinker.h ----------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file pulls in all built-in SSAF extractor and format registrations +/// by referencing their anchor symbols, preventing the static linker from +/// discarding the containing object files. +/// +/// Include this header (with IWYU pragma: keep) in any translation unit that +/// must guarantee these registrations are active — typically the entry point +/// of a binary that uses clangAnalysisScalable. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H +#define LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H + +#include "SSAFBuiltinForceLinker.h" // IWYU pragma: keep + +#endif // LLVM_CLANG_ANALYSIS_SCALABLE_SSAFFORCELINKER_H diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h index ef060dd27c522..7edd7b5b561d9 100644 --- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h +++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h @@ -24,10 +24,11 @@ // // Insert this code to the cpp file: // -// LLVM_INSTANTIATE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>) -// +// // NOLINTNEXTLINE(misc-use-internal-linkage) +// volatile int SSAFMyFormatAnchorSource = 0; // static SerializationFormatRegistry::Add<MyFormat> // RegisterFormat("MyFormat", "My awesome serialization format"); +// LLVM_INSTANTIATE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>) // // Then implement the formatter for the specific analysis and register the // format info for it: @@ -49,6 +50,15 @@ // "The MyFormat format info implementation for MyAnalysis" // ); // +// Finally, insert a use of the new anchor symbol into the force-linker header: +// clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h: +// +// This anchor is used to force the linker to link the MyFormat registration. +// +// extern volatile int SSAFMyFormatAnchorSource; +// [[maybe_unused]] static int SSAFMyFormatAnchorDestination = +// SSAFMyFormatAnchorSource; +// //===----------------------------------------------------------------------===// #ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_REGISTRY_H diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h b/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h index 29f5925ed6af6..d9147871a5101 100644 --- a/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h +++ b/clang/include/clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h @@ -9,9 +9,18 @@ // Registry for TUSummaryExtractors, and some helper functions. // To register some custom extractor, insert this code: // +// // NOLINTNEXTLINE(misc-use-internal-linkage) +// volatile int SSAFMyExtractorAnchorSource = 0; // static TUSummaryExtractorRegistry::Add<MyExtractor> // X("MyExtractor", "My awesome extractor"); // +// Finally, insert a use of the new anchor symbol into the force-linker header: +// clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h: +// +// extern volatile int SSAFMyExtractorAnchorSource; +// [[maybe_unused]] static int SSAFMyExtractorAnchorDestination = +// SSAFMyExtractorAnchorSource; +// //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_EXTRACTORREGISTRY_H diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 5c62bb70ebd0f..a01c07d39bcd7 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -382,6 +382,24 @@ def warn_profile_data_misexpect : Warning< def err_extract_api_ignores_file_not_found : Error<"file '%0' specified by '--extract-api-ignores=' not found">, DefaultFatal; +def err__ssaf_extract_tu_summary_file_unknown_output_format : + Error<"unknown output summary file format '%0' " + "specified by '--ssaf-tu-summary-file=%1'">, DefaultFatal; + +def err__ssaf_extract_tu_summary_file_unknown_format : + Error<"failed to parse the value of '--ssaf-tu-summary-file=%0' " + "the value must follow the '<path>.<format>' pattern">, DefaultFatal; + +def err__ssaf_must_enable_summary_extractors : + Error<"must enable some summary extractors using the " + "'--ssaf-extract-summaries=' option">, DefaultFatal; + +def err__ssaf_extract_summary_unknown_extractor_name : + Error<"no summary extractor%s0 %plural{1:was|:were}0 registered with name: %1">, DefaultFatal; + +def err__ssaf_write_tu_summary_failed : + Error<"failed to write TU summary to '%0': %1">, DefaultFatal; + def warn_missing_symbol_graph_dir : Warning< "missing symbol graph output directory, defaulting to working directory">, InGroup<ExtractAPIMisuse>; diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index 9e05181ac916c..0d8eb6a1b7379 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -543,6 +543,13 @@ class FrontendOptions { /// minimization hints. std::string DumpMinimizationHintsPath; + /// List of SSAF extractors to enable. + std::vector<std::string> SSAFExtractSummaries; + + /// The TU summary output file with the file extension representing the file + /// format. + std::string SSAFTUSummaryFile; + public: FrontendOptions() : DisableFree(false), RelocatablePCH(false), ShowHelp(false), diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 1021d95e4005b..2d593a909def7 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -274,6 +274,10 @@ def StaticAnalyzer_Group : OptionGroup<"<Static analyzer group>">, DocName<"Static analyzer options">, DocBrief<[{ Flags controlling the behavior of the Clang Static Analyzer.}]>; +def SSAF_Group : OptionGroup<"<ssaf options>">, + DocName<"SSAF options">, DocBrief<[{ +Flags controlling the behavior of the Scalable Static Analysis Framework (SSAF).}]>; + // gfortran options that we recognize in the driver and pass along when // invoking GCC to compile Fortran code. def gfortran_Group : OptionGroup<"<gfortran group>">, @@ -941,6 +945,22 @@ def W_Joined : Joined<["-"], "W">, Group<W_Group>, def Xanalyzer : Separate<["-"], "Xanalyzer">, HelpText<"Pass <arg> to the static analyzer">, MetaVarName<"<arg>">, Group<StaticAnalyzer_Group>; +def _ssaf_extract_summaries : + CommaJoined<["--"], "ssaf-extract-summaries=">, + MetaVarName<"<summary-names>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText<"Comma-separated list of summary names to extract">, + MarshallingInfoStringVector<FrontendOpts<"SSAFExtractSummaries">>; +def _ssaf_tu_summary_file : + Joined<["--"], "ssaf-tu-summary-file=">, + MetaVarName<"<path>.<format>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText< + "The output file for the extracted summaries. " + "The extension selects which file format to use.">, + MarshallingInfoString<FrontendOpts<"SSAFTUSummaryFile">>; def Xarch__ : JoinedAndSeparate<["-"], "Xarch_">, Flags<[NoXarchOption]>, diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt index 81550df4565cb..032e0b9dc75fd 100644 --- a/clang/lib/Analysis/Scalable/CMakeLists.txt +++ b/clang/lib/Analysis/Scalable/CMakeLists.txt @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS add_clang_library(clangAnalysisScalable ASTEntityMapping.cpp EntityLinker/EntityLinker.cpp + Frontend/TUSummaryExtractorFrontendAction.cpp Model/BuildNamespace.cpp Model/EntityId.cpp Model/EntityIdTable.cpp diff --git a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp new file mode 100644 index 0000000000000..c8ed937c62fa0 --- /dev/null +++ b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp @@ -0,0 +1,181 @@ +//===- TUSummaryExtractorFrontendAction.cpp -------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" +#include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummaryExtractor.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Path.h" +#include <memory> +#include <string> +#include <vector> + +using namespace clang; +using namespace ssaf; + +static std::optional<std::pair<llvm::StringRef, llvm::StringRef>> +parseOutputFileFormatAndPathOrReportError(DiagnosticsEngine &Diags, + StringRef SSAFTUSummaryFile) { + + StringRef Ext = llvm::sys::path::extension(SSAFTUSummaryFile); + StringRef FilePath = SSAFTUSummaryFile.drop_back(Ext.size()); + + if (!Ext.consume_front(".") || FilePath.empty()) { + Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_format) + << SSAFTUSummaryFile; + return std::nullopt; + } + + if (!isFormatRegistered(Ext)) { + Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_output_format) + << Ext << SSAFTUSummaryFile; + return std::nullopt; + } + + return std::pair{Ext, FilePath}; +} + +/// Return \c true if reported unrecognized extractors. +static bool +reportUnrecognizedExtractorNames(DiagnosticsEngine &Diags, + ArrayRef<std::string> SSAFExtractSummaries) { + if (SSAFExtractSummaries.empty()) { + Diags.Report(diag::err__ssaf_must_enable_summary_extractors); + return true; + } + + std::vector<StringRef> UnrecognizedExtractorNames; + for (StringRef Name : SSAFExtractSummaries) + if (!isTUSummaryExtractorRegistered(Name)) + UnrecognizedExtractorNames.push_back(Name); + + if (!UnrecognizedExtractorNames.empty()) { + Diags.Report(diag::err__ssaf_extract_summary_unknown_extractor_name) + << UnrecognizedExtractorNames.size() + << llvm::join(UnrecognizedExtractorNames, ", "); + return true; + } + + return false; +} + +static std::vector<std::unique_ptr<ASTConsumer>> +makeTUSummaryExtractors(TUSummaryBuilder &Builder, + ArrayRef<std::string> SSAFExtractSummaries) { + std::vector<std::unique_ptr<ASTConsumer>> Extractors; + Extractors.reserve(SSAFExtractSummaries.size()); + for (StringRef Name : SSAFExtractSummaries) { + assert(isTUSummaryExtractorRegistered(Name)); + Extractors.push_back(makeTUSummaryExtractor(Name, Builder)); + } + return Extractors; +} + +namespace { + +/// Drives all extractor \c ASTConsumers and serializes the completed +/// \c TUSummary. +/// +/// Derives from \c MultiplexConsumer so every \c ASTConsumer virtual method is +/// automatically forwarded to each extractor. +class TUSummaryRunner final : public MultiplexConsumer { +public: + static std::unique_ptr<TUSummaryRunner> create(CompilerInstance &CI, + StringRef InFile); + +private: + TUSummaryRunner(StringRef InFile, std::unique_ptr<SerializationFormat> Format, + const FrontendOptions &Opts); + + void HandleTranslationUnit(ASTContext &Ctx) override; + + TUSummary Summary; + TUSummaryBuilder Builder = TUSummaryBuilder(Summary); + std::unique_ptr<SerializationFormat> Format; + const FrontendOptions &Opts; +}; +} // namespace + +std::unique_ptr<TUSummaryRunner> TUSummaryRunner::create(CompilerInstance &CI, + StringRef InFile) { + const FrontendOptions &Opts = CI.getFrontendOpts(); + DiagnosticsEngine &Diags = CI.getDiagnostics(); + + auto MaybePair = + parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSummaryFile); + if (!MaybePair.has_value()) + return nullptr; + auto [FormatName, OutputPath] = MaybePair.value(); + + if (reportUnrecognizedExtractorNames(Diags, Opts.SSAFExtractSummaries)) + return nullptr; + + return std::unique_ptr<TUSummaryRunner>{ + new TUSummaryRunner{InFile, makeFormat(FormatName), Opts}}; +} + +TUSummaryRunner::TUSummaryRunner(StringRef InFile, + std::unique_ptr<SerializationFormat> Format, + const FrontendOptions &Opts) + : MultiplexConsumer(std::vector<std::unique_ptr<ASTConsumer>>{}), + Summary(BuildNamespace(BuildNamespaceKind::CompilationUnit, InFile)), + Format(std::move(Format)), Opts(Opts) { + assert(this->Format); + + // Now the Summary and the builders are constructed, we can also construct the + // extractors. + auto Extractors = makeTUSummaryExtractors(Builder, Opts.SSAFExtractSummaries); + assert(!Extractors.empty()); + + // We must initialize the Consumers here because our extractors need a + // Builder that holds a reference to the TUSummary, which would be only + // initialized after the MultiplexConsumer ctor. This is the only way we can + // avoid the use of the TUSummary before it starts its lifetime. + MultiplexConsumer::Consumers = std::move(Extractors); +} + +void TUSummaryRunner::HandleTranslationUnit(ASTContext &Ctx) { + // First, invoke the Summary Extractors. + MultiplexConsumer::HandleTranslationUnit(Ctx); + + // Then serialize the result. + if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSummaryFile)) { + Ctx.getDiagnostics().Report(diag::err__ssaf_write_tu_summary_failed) + << Opts.SSAFTUSummaryFile << llvm::toString(std::move(Err)); + } +} + +TUSummaryExtractorFrontendAction::~TUSummaryExtractorFrontendAction() = default; + +TUSummaryExtractorFrontendAction::TUSummaryExtractorFrontendAction( + std::unique_ptr<FrontendAction> WrappedAction) + : WrapperFrontendAction(std::move(WrappedAction)) {} + +std::unique_ptr<ASTConsumer> +TUSummaryExtractorFrontendAction::CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) { + auto WrappedConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); + if (!WrappedConsumer) + return nullptr; + + if (auto Runner = TUSummaryRunner::create(CI, InFile)) { + std::vector<std::unique_ptr<ASTConsumer>> Consumers; + Consumers.reserve(2); + Consumers.push_back(std::move(WrappedConsumer)); + Consumers.push_back(std::move(Runner)); + return std::make_unique<MultiplexConsumer>(std::move(Consumers)); + } + return WrappedConsumer; +} diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp index 0cf35fdae6927..d62a26b921b74 100644 --- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp +++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp @@ -8,9 +8,13 @@ #include "JSONFormatImpl.h" -#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" #include "llvm/Support/Registry.h" +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFJSONFormatAnchorSource = 0; +static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat> + RegisterFormat("json", "JSON serialization format"); LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>) namespace clang::ssaf { diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index e860136aae5b3..226734297278c 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7694,6 +7694,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.AddLastArg(CmdArgs, options::OPT_fmax_tokens_EQ); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_extract_summaries); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_tu_summary_file); + // Handle serialized diagnostics. if (Arg *A = Args.getLastArg(options::OPT__serialize_diags)) { CmdArgs.push_back("-serialize-diagnostic-file"); diff --git a/clang/lib/FrontendTool/CMakeLists.txt b/clang/lib/FrontendTool/CMakeLists.txt index 66213f76eb968..15d44b0dfb653 100644 --- a/clang/lib/FrontendTool/CMakeLists.txt +++ b/clang/lib/FrontendTool/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS ) set(link_libs + clangAnalysisScalable clangBasic clangCodeGen clangDriver diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index 05f646b43e3c4..5f3b7f101375e 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -11,6 +11,8 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/Analysis/Scalable/SSAFForceLinker.h" // IWYU pragma: keep #include "clang/Basic/DiagnosticFrontend.h" #include "clang/CodeGen/CodeGenAction.h" #include "clang/Config/config.h" @@ -207,6 +209,10 @@ CreateFrontendAction(CompilerInstance &CI) { Act = std::make_unique<ASTMergeAction>(std::move(Act), FEOpts.ASTMergeFiles); + if (!FEOpts.SSAFTUSummaryFile.empty()) { + Act = std::make_unique<ssaf::TUSummaryExtractorFrontendAction>( + std::move(Act)); + } return Act; } diff --git a/clang/test/Analysis/SSAF/command-line-interface.cpp b/clang/test/Analysis/SSAF/command-line-interface.cpp new file mode 100644 index 0000000000000..7deb2ab17bc70 --- /dev/null +++ b/clang/test/Analysis/SSAF/command-line-interface.cpp @@ -0,0 +1,22 @@ +// DEFINE: %{filecheck} = FileCheck %s --match-full-lines --check-prefix + +// The flags should behave the same way on the clang driver and also on CC1. + +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=foobar 2>&1 | %{filecheck}=NOT-MATCHING-THE-PATTERN +// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=foobar 2>&1 | %{filecheck}=NOT-MATCHING-THE-PATTERN +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.unknownfmt 2>&1 | %{filecheck}=UNKNOWN-FILE-FORMAT +// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.unknownfmt 2>&1 | %{filecheck}=UNKNOWN-FILE-FORMAT +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 2>&1 | %{filecheck}=NO-EXTRACTORS-ENABLED +// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json 2>&1 | %{filecheck}=NO-EXTRACTORS-ENABLED +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json --ssaf-extract-summaries=extractor1 2>&1 | %{filecheck}=NO-EXTRACTOR-WITH-NAME +// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json --ssaf-extract-summaries=extractor1 2>&1 | %{filecheck}=NO-EXTRACTOR-WITH-NAME +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json --ssaf-extract-summaries=extractor1,extractor2 2>&1 | %{filecheck}=NO-EXTRACTORS-WITH-NAME +// RUN: not %clang_cc1 -fsyntax-only %s --ssaf-tu-summary-file=%t.ssaf.json --ssaf-extract-summaries=extractor1,extractor2 2>&1 | %{filecheck}=NO-EXTRACTORS-WITH-NAME + +void empty() {} + +// NOT-MATCHING-THE-PATTERN: fatal error: failed to parse the value of '--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' pattern +// UNKNOWN-FILE-FORMAT: fatal error: unknown output summary file format 'unknownfmt' specified by '--ssaf-tu-summary-file={{.+}}.ssaf.unknownfmt' +// NO-EXTRACTORS-ENABLED: fatal error: must enable some summary extractors using the '--ssaf-extract-summaries=' option +// NO-EXTRACTOR-WITH-NAME: fatal error: no summary extractor was registered with name: extractor1 +// NO-EXTRACTORS-WITH-NAME: fatal error: no summary extractors were registered with name: extractor1, extractor2 diff --git a/clang/test/Analysis/SSAF/help.cpp b/clang/test/Analysis/SSAF/help.cpp new file mode 100644 index 0000000000000..3755b96e3ce4f --- /dev/null +++ b/clang/test/Analysis/SSAF/help.cpp @@ -0,0 +1,7 @@ +// RUN: %clang --help 2>&1 | FileCheck %s +// RUN: %clang_cc1 --help 2>&1 | FileCheck %s + +// CHECK: --ssaf-extract-summaries=<summary-names> +// CHECK-NEXT: Comma-separated list of summary names to extract +// CHECK-NEXT: --ssaf-tu-summary-file=<path>.<format> +// CHECK-NEXT: The output file for the extracted summaries. The extension selects which file format to use. diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt index f021263312d29..fc9e23ee7526b 100644 --- a/clang/unittests/Analysis/Scalable/CMakeLists.txt +++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt @@ -8,6 +8,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests EntityLinkerTest.cpp EntityNameTest.cpp ErrorBuilderTest.cpp + Frontend/TUSummaryExtractorFrontendActionTest.cpp ModelStringConversionsTest.cpp Registries/FancyAnalysisData.cpp Registries/MockSerializationFormat.cpp diff --git a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp new file mode 100644 index 0000000000000..987ee89500cf3 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp @@ -0,0 +1,362 @@ +//===- TUSummaryExtractorFrontendActionTest.cpp ---------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" +#include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h" +#include "clang/Analysis/Scalable/TUSummary/TUSummaryExtractor.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include <memory> +#include <string> +#include <vector> + +using namespace clang; +using namespace ssaf; +using ::testing::Contains; +using ::testing::UnorderedElementsAre; + +static auto errorsMsgsOf(const TextDiagnosticBuffer &Diags) { + auto Errors = llvm::make_range(Diags.err_begin(), Diags.err_end()); + return llvm::make_second_range(Errors); +} +namespace { + +/// A no-op TUSummaryExtractor suitable for use with a real TUSummaryBuilder. +class NoOpExtractor : public TUSummaryExtractor { +public: + using TUSummaryExtractor::TUSummaryExtractor; + void HandleTranslationUnit(ASTContext &Ctx) override {} +}; +} // namespace + +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFNoOpExtractorAnchorSource = 0; +static TUSummaryExtractorRegistry::Add<NoOpExtractor> + RegisterNoOp("NoOpExtractor", "No-op extractor for frontend action tests"); + +namespace { +class FailingSerializationFormat final : public SerializationFormat { +public: + static llvm::Error failing(llvm::StringRef Component) { + return llvm::createStringError( + "error from always failing serialization format: " + Component); + } + + llvm::Expected<TUSummary> readTUSummary(llvm::StringRef Path) override { + return failing("readTUSummary"); + } + + llvm::Error writeTUSummary(const TUSummary &Summary, + llvm::StringRef Path) override { + return failing("writeTUSummary"); + } + + llvm::Expected<TUSummaryEncoding> + readTUSummaryEncoding(llvm::StringRef Path) override { + return failing("readTUSummaryEncoding"); + } + + llvm::Error writeTUSummaryEncoding(const TUSummaryEncoding &SummaryEncoding, + llvm::StringRef Path) override { + return failing("writeTUSummaryEncoding"); + } + + llvm::Expected<LUSummary> readLUSummary(llvm::StringRef Path) override { + return failing("readLUSummary"); + } + + llvm::Error writeLUSummary(const LUSummary &Summary, + llvm::StringRef Path) override { + return failing("writeLUSummary"); + } + + llvm::Expected<LUSummaryEncoding> + readLUSummaryEncoding(llvm::StringRef Path) override { + return failing("readLUSummaryEncoding"); + } + + llvm::Error writeLUSummaryEncoding(const LUSummaryEncoding &SummaryEncoding, + llvm::StringRef Path) override { + return failing("writeLUSummaryEncoding"); + } +}; +} // namespace + +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFFailingSerializationFormatAnchorSource = 0; +static SerializationFormatRegistry::Add<FailingSerializationFormat> + RegisterFormat( + "FailingSerializationFormat", + "A serialization format that fails on every possible operation."); + +using EventLog = std::vector<std::string>; + +namespace { + +/// An ASTConsumer that logs callback invocations into a shared log. +class RecordingASTConsumer : public ASTConsumer { +public: + RecordingASTConsumer(EventLog &Log, std::string Tag) + : Log(Log), Tag(std::move(Tag)) {} + + void Initialize(ASTContext &Ctx) override { + Log.push_back(Tag + "::Initialize"); + } + bool HandleTopLevelDecl(DeclGroupRef D) override { + Log.push_back(Tag + "::HandleTopLevelDecl"); + return true; + } + void HandleTranslationUnit(ASTContext &Ctx) override { + Log.push_back(Tag + "::HandleTranslationUnit"); + } + +private: + EventLog &Log; + std::string Tag; +}; + +/// A FrontendAction that returns a RecordingASTConsumer with the tag "Wrapped". +class RecordingAction : public ASTFrontendAction { +public: + EventLog &getLog() { return Log; } + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &, + StringRef) override { + return std::make_unique<RecordingASTConsumer>(Log, /*Tag=*/"Wrapped"); + } + +private: + EventLog Log; +}; + +class FailingAction : public ASTFrontendAction { +public: + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &, + StringRef) override { + return nullptr; + } +}; + +/// Creates a CompilerInstance configured with an in-memory "test.cc" file +/// containing "int x = 42;". +static std::unique_ptr<CompilerInstance> +makeCompiler(TextDiagnosticBuffer &DiagBuf) { + auto Invocation = std::make_shared<CompilerInvocation>(); + Invocation->getPreprocessorOpts().addRemappedFile( + "test.cc", llvm::MemoryBuffer::getMemBuffer("int x = 42;").release()); + Invocation->getFrontendOpts().Inputs.push_back( + FrontendInputFile("test.cc", Language::CXX)); + Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly; + Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu"; + auto Compiler = std::make_unique<CompilerInstance>(std::move(Invocation)); + Compiler->setVirtualFileSystem(llvm::vfs::getRealFileSystem()); + Compiler->createDiagnostics(&DiagBuf, /*ShouldOwnClient=*/false); + return Compiler; +} + +struct TUSummaryExtractorFrontendActionTest : testing::Test { + using PathString = llvm::SmallString<128>; + PathString TestDir; + TextDiagnosticBuffer DiagBuf; + std::unique_ptr<CompilerInstance> Compiler = makeCompiler(DiagBuf); + + void SetUp() override { + std::error_code EC = llvm::sys::fs::createUniqueDirectory( + "ssaf-frontend-action-test", TestDir); + ASSERT_FALSE(EC) << "Failed to create temp directory: " << EC.message(); + } + + void TearDown() override { llvm::sys::fs::remove_directories(TestDir); } + + std::string makePath(llvm::StringRef FileOrDirectoryName) const { + PathString FullPath = TestDir; + llvm::sys::path::append(FullPath, FileOrDirectoryName); + return FullPath.str().str(); + } +}; + +TEST_F(TUSummaryExtractorFrontendActionTest, + WrappedActionFailsToCreateConsumer) { + // Configure valid SSAF options so the failure is purely from the wrapped + // action, not from runner creation. + std::string Output = makePath("output.MockSerializationFormat"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"}; + + TUSummaryExtractorFrontendAction ExtractorAction( + std::make_unique<FailingAction>()); + Compiler->ExecuteAction(ExtractorAction); + + // If the wrapped action fails, the ExtractorAction should not output. + EXPECT_FALSE(llvm::sys::fs::exists(Output)); +} + +TEST_F(TUSummaryExtractorFrontendActionTest, + RunnerFailsWithInvalidFormat_WrappedConsumerStillRuns) { + // Use an unregistered format extension so TUSummaryRunner::create fails. + std::string Output = makePath("output.xyz"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"}; + + auto Wrapped = std::make_unique<RecordingAction>(); + const EventLog &Log = Wrapped->getLog(); + TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped)); + + // The runner fails, so ExecuteAction should return false due to the fatal + // diagnostic. + EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction)); + + // The wrapped consumer should still have run. + EXPECT_THAT(Log, Contains("Wrapped::Initialize")); + EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit")); + + // Exactly one error about the unknown format. + EXPECT_THAT(errorsMsgsOf(DiagBuf), + UnorderedElementsAre( + "unknown output summary file format 'xyz' specified by " + "'--ssaf-tu-summary-file=" + + Output + "'")); + + // No output should have been created due to the failure. + EXPECT_FALSE(llvm::sys::fs::exists(Output)); +} + +TEST_F(TUSummaryExtractorFrontendActionTest, + RunnerFailsWithUnknownExtractor_WrappedConsumerStillRuns) { + std::string Output = makePath("output.MockSerializationFormat"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NonExistentExtractor"}; + + auto Wrapped = std::make_unique<RecordingAction>(); + const EventLog &Log = Wrapped->getLog(); + TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped)); + EXPECT_FALSE(Compiler->ExecuteAction(ExtractorAction)); + + // The wrapped consumer should still have run. + EXPECT_THAT(Log, Contains("Wrapped::Initialize")); + EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit")); + + // Exactly one error about the unknown extractor. + EXPECT_THAT(errorsMsgsOf(DiagBuf), + UnorderedElementsAre("no summary extractor was registered with " + "name: NonExistentExtractor")); + + // No output should have been created due to the failure. + EXPECT_FALSE(llvm::sys::fs::exists(Output)); +} + +TEST_F(TUSummaryExtractorFrontendActionTest, + RunnerSucceeds_ASTConsumerCallbacksPropagate) { + std::string Output = makePath("output.MockSerializationFormat"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"}; + + auto Wrapped = std::make_unique<RecordingAction>(); + const EventLog &Log = Wrapped->getLog(); + TUSummaryExtractorFrontendAction ExtractorAction(std::move(Wrapped)); + EXPECT_TRUE(Compiler->ExecuteAction(ExtractorAction)); + + // All wrapped ASTConsumer callbacks should have fired, not just + // HandleTranslationUnit. + EXPECT_THAT(Log, Contains("Wrapped::Initialize")); + EXPECT_THAT(Log, Contains("Wrapped::HandleTopLevelDecl")); + EXPECT_THAT(Log, Contains("Wrapped::HandleTranslationUnit")); + EXPECT_EQ(DiagBuf.getNumErrors(), 0U); + + // The runner should have written output. + EXPECT_TRUE(llvm::sys::fs::exists(Output)); +} + +// Use a custom action that checks whether the output path exists during +// HandleTranslationUnit — it should not, because the wrapped consumer runs +// before the runner. +struct OrderCheckingAction : public ASTFrontendAction { + EventLog Log; + std::string OutputPath; + + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + struct Consumer : public ASTConsumer { + Consumer(EventLog &Log, std::string OutputPath) + : Log(Log), OutputPath(std::move(OutputPath)) {} + void Initialize(ASTContext &) override { + Log.push_back("Wrapped::Initialize"); + } + bool HandleTopLevelDecl(DeclGroupRef) override { + Log.push_back("Wrapped::HandleTopLevelDecl"); + return true; + } + void HandleTranslationUnit(ASTContext &) override { + bool Exists = llvm::sys::fs::exists(OutputPath); + Log.push_back(std::string("OutputExistsDuringWrappedHTU=") + + (Exists ? "true" : "false")); + Log.push_back("Wrapped::HandleTranslationUnit"); + } + + EventLog &Log; + std::string OutputPath; + }; + return std::make_unique<Consumer>(Log, OutputPath); + } +}; +TEST_F(TUSummaryExtractorFrontendActionTest, + RunnerSucceeds_WrappedRunsBeforeRunner) { + std::string Output = makePath("output.MockSerializationFormat"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"}; + + auto Wrapped = std::make_unique<OrderCheckingAction>(); + Wrapped->OutputPath = Output; + const EventLog &Log = Wrapped->Log; + TUSummaryExtractorFrontendAction Action(std::move(Wrapped)); + + EXPECT_TRUE(Compiler->ExecuteAction(Action)); + EXPECT_EQ(DiagBuf.getNumErrors(), 0U); + + // The output should NOT have existed when the wrapped consumer's + // HandleTranslationUnit ran (wrapped is at index 0, runner at index 1). + EXPECT_THAT(Log, Contains("OutputExistsDuringWrappedHTU=false")); + + // After ExecuteAction, the output should exist. + EXPECT_TRUE(llvm::sys::fs::exists(Output)); +} + +TEST_F(TUSummaryExtractorFrontendActionTest, RunnerFailsToWrite) { + std::string Output = makePath("output.FailingSerializationFormat"); + Compiler->getFrontendOpts().SSAFTUSummaryFile = Output; + Compiler->getFrontendOpts().SSAFExtractSummaries = {"NoOpExtractor"}; + + TUSummaryExtractorFrontendAction Action(std::make_unique<RecordingAction>()); + + // This should fail because the summary writing fails and emits an error + // diagnostic. + EXPECT_FALSE(Compiler->ExecuteAction(Action)); + EXPECT_THAT( + errorsMsgsOf(DiagBuf), + UnorderedElementsAre( + "failed to write TU summary to '" + Output + + "': error from always failing serialization format: writeTUSummary")); + + // No output should have been created due to the failure. + EXPECT_FALSE(llvm::sys::fs::exists(Output)); +} + +} // namespace diff --git a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp index 7faaaade6b6f0..20c6856fc761d 100644 --- a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp @@ -54,6 +54,8 @@ struct FancyAnalysisFormatInfo final : FormatInfo { }; } // namespace +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFFancyAnalysisDataAnchorSource = 0; static llvm::Registry<FormatInfo>::Add<FancyAnalysisFormatInfo> RegisterFormatInfo("FancyAnalysisData", "Format info for FancyAnalysisData for the " diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp index 13c8e103768a1..84af685c7ff80 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp @@ -154,6 +154,8 @@ llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary, return llvm::Error::success(); } +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSerializationFormatAnchorSource = 0; static SerializationFormatRegistry::Add<MockSerializationFormat> RegisterFormat("MockSerializationFormat", "A serialization format for testing"); diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp index ec31e2e16bc0a..785c2dd438a15 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp @@ -38,7 +38,9 @@ class MockSummaryExtractor1 : public TUSummaryExtractor { } }; +} // namespace + +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSummaryExtractor1AnchorSource = 0; static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1> RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1"); - -} // namespace diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp index 90127a160bfa9..2439cccdc39ba 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp @@ -38,7 +38,9 @@ class MockSummaryExtractor2 : public TUSummaryExtractor { } }; +} // namespace + +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSummaryExtractor2AnchorSource = 0; static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2> RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2"); - -} // namespace diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp index 8b6d1a5ae15cf..f8b104ce35840 100644 --- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp @@ -8,6 +8,7 @@ #include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" #include "clang/Analysis/Scalable/TUSummary/TUSummary.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/FileSystem.h" @@ -15,6 +16,7 @@ #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Support/Error.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" #include <memory> @@ -53,9 +55,11 @@ TEST(SerializationFormatRegistryTest, isFormatRegistered) { } TEST(SerializationFormatRegistryTest, EnumeratingRegistryEntries) { - auto Formats = SerializationFormatRegistry::entries(); - ASSERT_EQ(std::distance(Formats.begin(), Formats.end()), 1U); - EXPECT_EQ(Formats.begin()->getName(), "MockSerializationFormat"); + auto NamesOf = [](const auto &Entry) { return Entry.getName(); }; + auto Names = llvm::map_range(SerializationFormatRegistry::entries(), NamesOf); + using testing::UnorderedElementsAre; + EXPECT_THAT(Names, UnorderedElementsAre("MockSerializationFormat", "json", + "FailingSerializationFormat")); } TEST(SerializationFormatRegistryTest, Roundtrip) { diff --git a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp index a17a4f145b038..ab51f2c3fc530 100644 --- a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp @@ -41,6 +41,7 @@ TEST(SummaryExtractorRegistryTest, EnumeratingRegistryEntries) { EXPECT_EQ(ActualNames, (std::set<llvm::StringRef>{ "MockSummaryExtractor1", "MockSummaryExtractor2", + "NoOpExtractor", })); } diff --git a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h new file mode 100644 index 0000000000000..81766a1196761 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h @@ -0,0 +1,51 @@ +//===- SSAFBuiltinTestForceLinker.h -----------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file pulls in all test-only SSAF mock extractor and format +/// registrations by referencing their anchor symbols. +/// +/// Include this header (with IWYU pragma: keep) in a translation unit that +/// is compiled into the SSAF unittest binary. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H +#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H + +// Force the linker to link NoOpExtractor registration. +extern volatile int SSAFNoOpExtractorAnchorSource; +[[maybe_unused]] static int SSAFNoOpExtractorAnchorDestination = + SSAFNoOpExtractorAnchorSource; + +// Force the linker to link MockSummaryExtractor1 registration. +extern volatile int SSAFMockSummaryExtractor1AnchorSource; +[[maybe_unused]] static int SSAFMockSummaryExtractor1AnchorDestination = + SSAFMockSummaryExtractor1AnchorSource; + +// Force the linker to link MockSummaryExtractor2 registration. +extern volatile int SSAFMockSummaryExtractor2AnchorSource; +[[maybe_unused]] static int SSAFMockSummaryExtractor2AnchorDestination = + SSAFMockSummaryExtractor2AnchorSource; + +// Force the linker to link FailingSerializationFormat registration. +extern volatile int SSAFFailingSerializationFormatAnchorSource; +[[maybe_unused]] static int SSAFFailingSerializationFormatAnchorDestination = + SSAFFailingSerializationFormatAnchorSource; + +// Force the linker to link MockSerializationFormat registration. +extern volatile int SSAFMockSerializationFormatAnchorSource; +[[maybe_unused]] static int SSAFMockSerializationFormatAnchorDestination = + SSAFMockSerializationFormatAnchorSource; + +// Force the linker to link FancyAnalysisData format info registration. +extern volatile int SSAFFancyAnalysisDataAnchorSource; +[[maybe_unused]] static int SSAFFancyAnalysisDataAnchorDestination = + SSAFFancyAnalysisDataAnchorSource; + +#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFBUILTINTESTFORCELINKER_H diff --git a/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h b/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h new file mode 100644 index 0000000000000..144fe1af13f95 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/SSAFTestForceLinker.h @@ -0,0 +1,23 @@ +//===- SSAFTestForceLinker.h ------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file pulls in all test-only SSAF mock extractor and format +/// registrations by referencing their anchor symbols. +/// +/// Include this header (with IWYU pragma: keep) in a translation unit that +/// is compiled into the SSAF unittest binary. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H +#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H + +#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep + +#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_SSAFTESTFORCELINKER_H diff --git a/clang/unittests/Analysis/Scalable/TestFixture.cpp b/clang/unittests/Analysis/Scalable/TestFixture.cpp index ef021fccd3683..d05f218ef0d93 100644 --- a/clang/unittests/Analysis/Scalable/TestFixture.cpp +++ b/clang/unittests/Analysis/Scalable/TestFixture.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "TestFixture.h" +#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep #include "clang/Analysis/Scalable/Model/BuildNamespace.h" #include "clang/Analysis/Scalable/Model/EntityId.h" #include "clang/Analysis/Scalable/Model/EntityLinkage.h" From bcfeac9ce81e72869249e0c8b5d54c8db14f1e70 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Thu, 5 Mar 2026 12:19:11 +0000 Subject: [PATCH 2/2] Make SSAF errors downgradable --- .../clang/Basic/DiagnosticFrontendKinds.td | 35 +++++++++++-------- clang/include/clang/Basic/DiagnosticGroups.td | 3 ++ .../TUSummaryExtractorFrontendAction.cpp | 10 +++--- .../Analysis/SSAF/command-line-interface.cpp | 10 +++--- .../Analysis/SSAF/downgradable-errors.cpp | 15 ++++++++ 5 files changed, 48 insertions(+), 25 deletions(-) create mode 100644 clang/test/Analysis/SSAF/downgradable-errors.cpp diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index a01c07d39bcd7..b104e19d23d34 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -379,26 +379,31 @@ def warn_profile_data_misexpect : Warning< BackendInfo, InGroup<MisExpect>; } // end of instrumentation issue category -def err_extract_api_ignores_file_not_found : - Error<"file '%0' specified by '--extract-api-ignores=' not found">, DefaultFatal; +def warn__ssaf_extract_tu_summary_file_unknown_output_format : + Warning<"unknown output summary file format '%0' " + "specified by '--ssaf-tu-summary-file=%1'">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; -def err__ssaf_extract_tu_summary_file_unknown_output_format : - Error<"unknown output summary file format '%0' " - "specified by '--ssaf-tu-summary-file=%1'">, DefaultFatal; +def warn__ssaf_extract_tu_summary_file_unknown_format : + Warning<"failed to parse the value of '--ssaf-tu-summary-file=%0' " + "the value must follow the '<path>.<format>' pattern">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; -def err__ssaf_extract_tu_summary_file_unknown_format : - Error<"failed to parse the value of '--ssaf-tu-summary-file=%0' " - "the value must follow the '<path>.<format>' pattern">, DefaultFatal; +def warn__ssaf_must_enable_summary_extractors : + Warning<"must enable some summary extractors using the " + "'--ssaf-extract-summaries=' option">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; -def err__ssaf_must_enable_summary_extractors : - Error<"must enable some summary extractors using the " - "'--ssaf-extract-summaries=' option">, DefaultFatal; +def warn__ssaf_extract_summary_unknown_extractor_name : + Warning<"no summary extractor%s0 %plural{1:was|:were}0 registered with name: %1">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; -def err__ssaf_extract_summary_unknown_extractor_name : - Error<"no summary extractor%s0 %plural{1:was|:were}0 registered with name: %1">, DefaultFatal; +def warn__ssaf_write_tu_summary_failed : + Warning<"failed to write TU summary to '%0': %1">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; -def err__ssaf_write_tu_summary_failed : - Error<"failed to write TU summary to '%0': %1">, DefaultFatal; +def err_extract_api_ignores_file_not_found : + Error<"file '%0' specified by '--extract-api-ignores=' not found">, DefaultFatal; def warn_missing_symbol_graph_dir : Warning< "missing symbol graph output directory, defaulting to working directory">, diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 5a1b990bd0943..337a84263f256 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1891,6 +1891,9 @@ def BitIntExtension : DiagGroup<"bit-int-extension">; // Warnings about misuse of ExtractAPI options. def ExtractAPIMisuse : DiagGroup<"extractapi-misuse">; +// Warnings related to the "Scalable Static Analysis Framework" - SSAF. +def ScalableStaticAnalysisFramework : DiagGroup<"scalable-static-analysis-framework">; + // Warnings about using the non-standard extension having an explicit specialization // with a storage class specifier. def ExplicitSpecializationStorageClass : DiagGroup<"explicit-specialization-storage-class">; diff --git a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp index c8ed937c62fa0..ac0b8950c5987 100644 --- a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp +++ b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp @@ -33,13 +33,13 @@ parseOutputFileFormatAndPathOrReportError(DiagnosticsEngine &Diags, StringRef FilePath = SSAFTUSummaryFile.drop_back(Ext.size()); if (!Ext.consume_front(".") || FilePath.empty()) { - Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_format) + Diags.Report(diag::warn__ssaf_extract_tu_summary_file_unknown_format) << SSAFTUSummaryFile; return std::nullopt; } if (!isFormatRegistered(Ext)) { - Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_output_format) + Diags.Report(diag::warn__ssaf_extract_tu_summary_file_unknown_output_format) << Ext << SSAFTUSummaryFile; return std::nullopt; } @@ -52,7 +52,7 @@ static bool reportUnrecognizedExtractorNames(DiagnosticsEngine &Diags, ArrayRef<std::string> SSAFExtractSummaries) { if (SSAFExtractSummaries.empty()) { - Diags.Report(diag::err__ssaf_must_enable_summary_extractors); + Diags.Report(diag::warn__ssaf_must_enable_summary_extractors); return true; } @@ -62,7 +62,7 @@ reportUnrecognizedExtractorNames(DiagnosticsEngine &Diags, UnrecognizedExtractorNames.push_back(Name); if (!UnrecognizedExtractorNames.empty()) { - Diags.Report(diag::err__ssaf_extract_summary_unknown_extractor_name) + Diags.Report(diag::warn__ssaf_extract_summary_unknown_extractor_name) << UnrecognizedExtractorNames.size() << llvm::join(UnrecognizedExtractorNames, ", "); return true; @@ -152,7 +152,7 @@ void TUSummaryRunner::HandleTranslationUnit(ASTContext &Ctx) { // Then serialize the result. if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSummaryFile)) { - Ctx.getDiagnostics().Report(diag::err__ssaf_write_tu_summary_failed) + Ctx.getDiagnostics().Report(diag::warn__ssaf_write_tu_summary_failed) << Opts.SSAFTUSummaryFile << llvm::toString(std::move(Err)); } } diff --git a/clang/test/Analysis/SSAF/command-line-interface.cpp b/clang/test/Analysis/SSAF/command-line-interface.cpp index 7deb2ab17bc70..a632f487f2bb7 100644 --- a/clang/test/Analysis/SSAF/command-line-interface.cpp +++ b/clang/test/Analysis/SSAF/command-line-interface.cpp @@ -15,8 +15,8 @@ void empty() {} -// NOT-MATCHING-THE-PATTERN: fatal error: failed to parse the value of '--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' pattern -// UNKNOWN-FILE-FORMAT: fatal error: unknown output summary file format 'unknownfmt' specified by '--ssaf-tu-summary-file={{.+}}.ssaf.unknownfmt' -// NO-EXTRACTORS-ENABLED: fatal error: must enable some summary extractors using the '--ssaf-extract-summaries=' option -// NO-EXTRACTOR-WITH-NAME: fatal error: no summary extractor was registered with name: extractor1 -// NO-EXTRACTORS-WITH-NAME: fatal error: no summary extractors were registered with name: extractor1, extractor2 +// NOT-MATCHING-THE-PATTERN: error: failed to parse the value of '--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' pattern [-Wscalable-static-analysis-framework] +// UNKNOWN-FILE-FORMAT: error: unknown output summary file format 'unknownfmt' specified by '--ssaf-tu-summary-file={{.+}}.ssaf.unknownfmt' [-Wscalable-static-analysis-framework] +// NO-EXTRACTORS-ENABLED: error: must enable some summary extractors using the '--ssaf-extract-summaries=' option [-Wscalable-static-analysis-framework] +// NO-EXTRACTOR-WITH-NAME: error: no summary extractor was registered with name: extractor1 [-Wscalable-static-analysis-framework] +// NO-EXTRACTORS-WITH-NAME: error: no summary extractors were registered with name: extractor1, extractor2 [-Wscalable-static-analysis-framework] diff --git a/clang/test/Analysis/SSAF/downgradable-errors.cpp b/clang/test/Analysis/SSAF/downgradable-errors.cpp new file mode 100644 index 0000000000000..494e3e71092ac --- /dev/null +++ b/clang/test/Analysis/SSAF/downgradable-errors.cpp @@ -0,0 +1,15 @@ +// DEFINE: %{filecheck} = FileCheck %s --match-full-lines --check-prefix + +// RUN: not %clang -fsyntax-only %s --ssaf-tu-summary-file=foobar 2>&1 | %{filecheck}=DEFAULT-ERROR +// RUN: %clang -fsyntax-only %s --ssaf-tu-summary-file=foobar -Wno-error=scalable-static-analysis-framework 2>&1 | %{filecheck}=DEMOTED-TO-WARNING +// RUN: %clang -fsyntax-only %s --ssaf-tu-summary-file=foobar -Wno-scalable-static-analysis-framework 2>&1 | count 0 + +// This test demonstrates that the "scalable-static-analysis-framework" diagnostics can be downgraded or completely silenced with the right flags. + +void empty() {} + +// DEFAULT-ERROR: error: failed to parse the value of '--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' pattern [-Wscalable-static-analysis-framework] +// DEFAULT-ERROR: 1 error generated. + +// DEMOTED-TO-WARNING: warning: failed to parse the value of '--ssaf-tu-summary-file=foobar' the value must follow the '<path>.<format>' pattern [-Wscalable-static-analysis-framework] +// DEMOTED-TO-WARNING: 1 warning generated. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
