https://github.com/steakhal updated https://github.com/llvm/llvm-project/pull/184421
From aeda1b84c24530942c19b0ec283bbe0468486339 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 | 1 + .../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 | 8 + 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 | 21 ++ .../Scalable/Registries/FancyAnalysisData.cpp | 5 + .../Registries/MockSerializationFormat.cpp | 4 + .../Registries/MockSummaryExtractor1.cpp | 5 + .../Registries/MockSummaryExtractor2.cpp | 5 + .../SerializationFormatRegistryTest.cpp | 9 +- .../Scalable/SSAFBuiltinTestForceLinker.h | 41 ++++ .../Analysis/Scalable/SSAFTestForceLinker.h | 23 +++ .../Analysis/Scalable/TestFixture.cpp | 1 + 25 files changed, 473 insertions(+), 3 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..8a8c50b0a5695 --- /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 : 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..54c449fc0c11d 100644 --- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h +++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h @@ -28,6 +28,7 @@ // // static SerializationFormatRegistry::Add<MyFormat> // RegisterFormat("MyFormat", "My awesome serialization format"); +// TODO: Mention anchors and force linker .h // // Then implement the formatter for the specific analysis and register the // format info for it: 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..486d0c38a1412 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..6c3eba9daa77e 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 what file format to be used.">, + 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..8ea4ac3dfad49 --- /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 the 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..dc70667834ea7 100644 --- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp +++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp @@ -8,10 +8,18 @@ #include "JSONFormatImpl.h" +#include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" #include "clang/Analysis/Scalable/TUSummary/TUSummary.h" #include "llvm/Support/Registry.h" LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>) +static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat> + RegisterFormat("json", "JSON serialization format"); + +// This anchor is used to force the linker to link in the generated object file +// and thus register JSONFormat in the SerializationFormatRegistry. +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFJSONFormatAnchorSource = 0; 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..ff7cbc6cf5289 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..936dd3b1fae94 --- /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 what file format to be used. 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..5d64b9403706d --- /dev/null +++ b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp @@ -0,0 +1,21 @@ +//===- 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 "gtest/gtest.h" + +using namespace clang; +using namespace ssaf; + +namespace { + +TEST(TUSummaryExtractorFrontendActionTest, SomeTest) { + // TODO: Add tests. +} + +} // namespace diff --git a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp index 7faaaade6b6f0..6feb939760923 100644 --- a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp @@ -58,3 +58,8 @@ static llvm::Registry<FormatInfo>::Add<FancyAnalysisFormatInfo> RegisterFormatInfo("FancyAnalysisData", "Format info for FancyAnalysisData for the " "MockSerializationFormat format"); + +// This anchor is used to force the linker to link in the generated object file +// and thus register FancyAnalysisFormatInfo. +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFFancyAnalysisDataAnchorSource = 0; diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp index 13c8e103768a1..0bcdba5640e3c 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp @@ -157,6 +157,10 @@ llvm::Error MockSerializationFormat::writeTUSummary(const TUSummary &Summary, static SerializationFormatRegistry::Add<MockSerializationFormat> RegisterFormat("MockSerializationFormat", "A serialization format for testing"); +// This anchor is used to force the linker to link in the generated object file +// and thus register JSONFormat in the SerializationFormatRegistry. +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSerializationFormatAnchorSource = 0; llvm::Expected<TUSummaryEncoding> MockSerializationFormat::readTUSummaryEncoding(llvm::StringRef Path) { diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp index ec31e2e16bc0a..2153f49c20dfa 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp @@ -42,3 +42,8 @@ static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1> RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1"); } // namespace + +// This anchor is used to force the linker to link in the generated object file +// and thus register MockSummaryExtractor1. +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSummaryExtractor1AnchorSource = 0; diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp index 90127a160bfa9..61270c953ddd9 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp @@ -42,3 +42,8 @@ static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2> RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2"); } // namespace + +// This anchor is used to force the linker to link in the generated object file +// and thus register MockSummaryExtractor2. +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFMockSummaryExtractor2AnchorSource = 0; diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp index 8b6d1a5ae15cf..791a1fe0501e5 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,10 @@ 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")); } TEST(SerializationFormatRegistryTest, Roundtrip) { diff --git a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h new file mode 100644 index 0000000000000..5169eb504be48 --- /dev/null +++ b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h @@ -0,0 +1,41 @@ +//===- 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 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 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 56ec1255610b4c2c7aa676db5674890a690b0c86 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Wed, 4 Mar 2026 12:50:54 +0000 Subject: [PATCH 2/2] Final touches, fixes, and tests! --- .../developer-docs/Extensibility.rst | 146 ++++++++ .../TUSummaryExtractorFrontendAction.h | 2 +- .../SerializationFormatRegistry.h | 15 +- .../Scalable/TUSummary/ExtractorRegistry.h | 9 + .../include/clang/Frontend/FrontendOptions.h | 2 +- clang/include/clang/Options/Options.td | 2 +- .../TUSummaryExtractorFrontendAction.cpp | 16 +- .../JSONFormat/JSONFormatImpl.cpp | 10 +- .../ExecuteCompilerInvocation.cpp | 2 +- .../TUSummaryExtractorFrontendActionTest.cpp | 345 +++++++++++++++++- .../Scalable/Registries/FancyAnalysisData.cpp | 7 +- .../Registries/MockSerializationFormat.cpp | 6 +- .../Registries/MockSummaryExtractor1.cpp | 7 +- .../Registries/MockSummaryExtractor2.cpp | 7 +- .../SerializationFormatRegistryTest.cpp | 3 +- .../SummaryExtractorRegistryTest.cpp | 1 + .../Scalable/SSAFBuiltinTestForceLinker.h | 10 + 17 files changed, 546 insertions(+), 44 deletions(-) create mode 100644 clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst diff --git a/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst b/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst new file mode 100644 index 0000000000000..6c8ec7d73add4 --- /dev/null +++ b/clang/docs/ScalableStaticAnalysisFramework/developer-docs/Extensibility.rst @@ -0,0 +1,146 @@ +============= +Extensibility +============= + +.. WARNING:: The framework is rapidly evolving. + The documentation might be out-of-sync of the implementation. + The purpose of this documentation to give context for upcoming reviews. + +This document describes how to extend the framework with new formats and extractors in different contexts. +Downstream users can extend the framework - let it be in-tree or out-of-tree. + +There are two + +Static extensibility +******************** + +When building SSAF, new formats and extractors are registered via ``llvm::Registry`` translation unit local + + +Two flags control summary extraction: + +- ``--ssaf-extract-summaries=<name1>,<name2>,...``: Comma-separated list of summary extractor names to enable. +- ``--ssaf-tu-summary-file=<path>.<format>``: Output file for the extracted summaries. The file extension selects the serialization format (e.g. ``.json``). + +Both flags are forwarded from the driver to ``-cc1``. + +Example invocation: + +.. code-block:: bash + + clang --ssaf-extract-summaries=MyAwesomeAnalysis \ + --ssaf-tu-summary-file=/tmp/out.json \ + -fsyntax-only input.cpp + +Activation +========== + +When ``--ssaf-tu-summary-file=`` is non-empty, ``CreateFrontendAction()`` (in ``ExecuteCompilerInvocation.cpp``) +wraps the original ``FrontendAction`` inside a ``TUSummaryExtractorFrontendAction``. +This is a ``WrapperFrontendAction``, so the wrapped action (e.g. ``-fsyntax-only``) still runs normally. +It could have been also the codegen action (``-c``). + +Lifetime of a summary extraction +********************************* + +The ``TUSummaryExtractorFrontendAction`` just wraps the original frontend action and triggers the extraction. +If the ``TUSummaryExtractorFrontendAction`` fails to fulfill - in other words, create the extractors or fails to serialize and write the extracted summaries, then it issues a fatal error. + +Details of ``CreateASTConsumer()`` +================================== + +#. Create the wrapped ``FrontendAction`` consumers by calling ``CreateASTConsumer()`` on it. +#. Create a ``TUSummaryRunner`` (via ``TUSummaryRunner::create()``). This step validates the command-line arguments: + + #. Parse the output file path and format extension from ``--ssaf-tu-summary-file=``. Diagnose if the value doesn't follow the ``<path>.<format>`` pattern, or if the format is not registered. + #. For each requested extractor name (from ``--ssaf-extract-summaries=``), check if it is registered in the ``TUSummaryExtractorRegistry``, and diagnose if not. + #. Instantiate the ``SerializationFormat`` for the requested format. + +#. If validation fails, the ``TUSummaryRunner`` is not created, and only the wrapped consumer is returned (the compilation proceeds without summary extraction, but the error diagnostics are emitted). +#. Otherwise, both the wrapped consumer and the ``TUSummaryRunner`` are added to a ``MultiplexConsumer`` and returned. This ensures the wrapped action's consumer runs first. + +Details of ``TUSummaryRunner`` +============================== + +``TUSummaryRunner`` derives from ``MultiplexConsumer`` so every ``ASTConsumer`` callback is automatically forwarded to each extractor. + +Construction: + +#. Create the ``TUSummary`` and ``TUSummaryBuilder``. +#. Call ``makeTUSummaryExtractor()`` for each requested analysis name. + + #. Look up in the *summary registry* the relevant *Info* object and call the ``Factory`` function pointer to create the relevant ``ASTConsumer``. + #. A mutable ``TUSummaryBuilder`` reference is passed to the constructor, so the analysis can create ``EntityID`` objects and map them to ``TUSummaryData`` objects in their implementation. Their custom metadata needs to inherit from ``TUSummaryData`` to achieve this. + +``HandleTranslationUnit()``: + +#. First, invoke the summary extractors (via ``MultiplexConsumer::HandleTranslationUnit``). +#. Then call the virtual ``writeTUSummary()`` on the serialization format, leading to the desired format handler (such as JSON or binary or something custom - provided by a plugin). + + #. Create the directory structure for the enabled analyses. + #. Serialize ``entities``, ``entity_linkage``, etc. by calling the matching virtual functions, dispatching to the concrete implementation. + #. The same goes for each enabled analysis: serialize the ``EntityID`` to ``TUSummaryData`` mapping using the analysis-provided ``Serialize`` function pointer. + +Implementation details +********************** + +Global Registries +================= + +The framework uses `llvm::Registry\<\> <https://llvm.org/doxygen/classllvm_1_1Registry.html>`_ +as an extension point for adding new summary analyses or serialization formats. +Each entry in the *registry* holds a name, a description and a pointer to a constructor. + +**Pros**: + + - Decentralizes the registration. There is not a single place in the source code where we spell out all of the analyses/formats. + - Plays nicely with downstream extensibility, as downstream users can add their own analyses/formats without touching the source code of the framework; while still benefiting from the upstream-provided analyses/formats. + - Works with static and dynamic linking. In other words, plugins as shared objects compose naturally. + +**Cons**: + + - Registration slows down all ``clang`` users by a tiny amount, even if they don't invoke the summary extraction framework. + - As the registration is now decoupled, it's now a global program property; and potentially more difficult to reason about. + - Complicates testing. + +Force-linker headers +-------------------- + +Because ``llvm::Registry`` entries live in separate translation units, the static linker may discard the +object files containing them if nothing else references those files. +To prevent this, each registration file defines a ``volatile int`` anchor symbol (e.g. ``SSAFJSONFormatAnchorSource``), +and a corresponding *force-linker header* references it: + +- ``clang/include/clang/Analysis/Scalable/SSAFBuiltinForceLinker.h`` — for upstream (built-in) registrations. +- ``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` — umbrella header that includes the above (and is meant for downstream extension). + +Include the appropriate force-linker 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``. + +Downstream users should have their own ``ForceLinker`` header and include it from the +``clang/include/clang/Analysis/Scalable/SSAFForceLinker.h`` umbrella header to avoid merge-conflicts. + +Example for adding a custom summary extraction +----------------------------------------------- + +.. code-block:: c++ + + //--- MyAnalysis.cpp + class MyAnalysis : public TUSummaryExtractor { + using TUSummaryExtractor::TUSummaryExtractor; + // Implementation... + }; + + // NOLINTNEXTLINE(misc-use-internal-linkage) + volatile int SSAFMyAnalysisAnchorSource = 0; + static TUSummaryExtractorRegistry::Add<MyAnalysis> + RegisterExtractor("MyAwesomeAnalysis", "The analysis produces some awesome results"); + +Then add the anchor reference to the force-linker header (``SSAFBuiltinForceLinker.h``): + +.. code-block:: c++ + + extern volatile int SSAFMyAnalysisAnchorSource; + [[maybe_unused]] static int SSAFMyAnalysisAnchorDestination = + SSAFMyAnalysisAnchorSource; diff --git a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h index 8a8c50b0a5695..a91955d2a2139 100644 --- a/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h +++ b/clang/include/clang/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.h @@ -17,7 +17,7 @@ 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 : public WrapperFrontendAction { +class TUSummaryExtractorFrontendAction final : public WrapperFrontendAction { public: explicit TUSummaryExtractorFrontendAction( std::unique_ptr<FrontendAction> WrappedAction); diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h index 54c449fc0c11d..f0f05682bd396 100644 --- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h +++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h @@ -24,11 +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"); -// TODO: Mention anchors and force linker .h +// LLVM_INSTANTIATE_REGISTRY(llvm::Registry<MyFormat::FormatInfo>) // // Then implement the formatter for the specific analysis and register the // format info for it: @@ -50,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 JSONFormat 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/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index 486d0c38a1412..0d8eb6a1b7379 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -548,7 +548,7 @@ class FrontendOptions { /// The TU summary output file with the file extension representing the file /// format. - std::string SSAFTUSUmmaryFile; + std::string SSAFTUSummaryFile; public: FrontendOptions() diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 6c3eba9daa77e..270e29102ec63 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -960,7 +960,7 @@ def _ssaf_tu_summary_file : HelpText< "The output file for the extracted summaries. " "The extension selects what file format to be used.">, - MarshallingInfoString<FrontendOpts<"SSAFTUSUmmaryFile">>; + MarshallingInfoString<FrontendOpts<"SSAFTUSummaryFile">>; def Xarch__ : JoinedAndSeparate<["-"], "Xarch_">, Flags<[NoXarchOption]>, diff --git a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp index 8ea4ac3dfad49..34dd4ab709136 100644 --- a/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp +++ b/clang/lib/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendAction.cpp @@ -27,20 +27,20 @@ using namespace ssaf; static std::optional<std::pair<llvm::StringRef, llvm::StringRef>> parseOutputFileFormatAndPathOrReportError(DiagnosticsEngine &Diags, - StringRef SSAFTUSUmmaryFile) { + StringRef SSAFTUSummaryFile) { - StringRef Ext = llvm::sys::path::extension(SSAFTUSUmmaryFile); - StringRef FilePath = SSAFTUSUmmaryFile.drop_back(Ext.size()); + 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; + << SSAFTUSummaryFile; return std::nullopt; } if (!isFormatRegistered(Ext)) { Diags.Report(diag::err__ssaf_extract_tu_summary_file_unknown_output_format) - << Ext << SSAFTUSUmmaryFile; + << Ext << SSAFTUSummaryFile; return std::nullopt; } @@ -114,7 +114,7 @@ std::unique_ptr<TUSummaryRunner> TUSummaryRunner::create(CompilerInstance &CI, DiagnosticsEngine &Diags = CI.getDiagnostics(); auto MaybePair = - parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSUmmaryFile); + parseOutputFileFormatAndPathOrReportError(Diags, Opts.SSAFTUSummaryFile); if (!MaybePair.has_value()) return nullptr; auto [FormatName, OutputPath] = MaybePair.value(); @@ -151,9 +151,9 @@ void TUSummaryRunner::HandleTranslationUnit(ASTContext &Ctx) { MultiplexConsumer::HandleTranslationUnit(Ctx); // Then serialize the result. - if (auto Err = Format->writeTUSummary(Summary, Opts.SSAFTUSUmmaryFile)) { + 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)); + << Opts.SSAFTUSummaryFile << llvm::toString(std::move(Err)); } } diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp index dc70667834ea7..d62a26b921b74 100644 --- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp +++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat/JSONFormatImpl.cpp @@ -9,17 +9,13 @@ #include "JSONFormatImpl.h" #include "clang/Analysis/Scalable/Serialization/SerializationFormatRegistry.h" -#include "clang/Analysis/Scalable/TUSummary/TUSummary.h" #include "llvm/Support/Registry.h" -LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>) -static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat> - RegisterFormat("json", "JSON serialization format"); - -// This anchor is used to force the linker to link in the generated object file -// and thus register JSONFormat in the SerializationFormatRegistry. // 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/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index ff7cbc6cf5289..5f3b7f101375e 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -209,7 +209,7 @@ CreateFrontendAction(CompilerInstance &CI) { Act = std::make_unique<ASTMergeAction>(std::move(Act), FEOpts.ASTMergeFiles); - if (!FEOpts.SSAFTUSUmmaryFile.empty()) { + if (!FEOpts.SSAFTUSummaryFile.empty()) { Act = std::make_unique<ssaf::TUSummaryExtractorFrontendAction>( std::move(Act)); } diff --git a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp index 5d64b9403706d..573d0dbe15474 100644 --- a/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp +++ b/clang/unittests/Analysis/Scalable/Frontend/TUSummaryExtractorFrontendActionTest.cpp @@ -7,15 +7,356 @@ //===----------------------------------------------------------------------===// #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 { -TEST(TUSummaryExtractorFrontendActionTest, SomeTest) { - // TODO: Add tests. +/// 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)); + + // The wrapped all 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 6feb939760923..20c6856fc761d 100644 --- a/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/FancyAnalysisData.cpp @@ -54,12 +54,9 @@ 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 " "MockSerializationFormat format"); - -// This anchor is used to force the linker to link in the generated object file -// and thus register FancyAnalysisFormatInfo. -// NOLINTNEXTLINE(misc-use-internal-linkage) -volatile int SSAFFancyAnalysisDataAnchorSource = 0; diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp index 0bcdba5640e3c..84af685c7ff80 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSerializationFormat.cpp @@ -154,13 +154,11 @@ 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"); -// This anchor is used to force the linker to link in the generated object file -// and thus register JSONFormat in the SerializationFormatRegistry. -// NOLINTNEXTLINE(misc-use-internal-linkage) -volatile int SSAFMockSerializationFormatAnchorSource = 0; llvm::Expected<TUSummaryEncoding> MockSerializationFormat::readTUSummaryEncoding(llvm::StringRef Path) { diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp index 2153f49c20dfa..785c2dd438a15 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor1.cpp @@ -38,12 +38,9 @@ class MockSummaryExtractor1 : public TUSummaryExtractor { } }; -static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1> - RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1"); - } // namespace -// This anchor is used to force the linker to link in the generated object file -// and thus register MockSummaryExtractor1. // NOLINTNEXTLINE(misc-use-internal-linkage) volatile int SSAFMockSummaryExtractor1AnchorSource = 0; +static TUSummaryExtractorRegistry::Add<MockSummaryExtractor1> + RegisterExtractor("MockSummaryExtractor1", "Mock summary extractor 1"); diff --git a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp index 61270c953ddd9..2439cccdc39ba 100644 --- a/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/MockSummaryExtractor2.cpp @@ -38,12 +38,9 @@ class MockSummaryExtractor2 : public TUSummaryExtractor { } }; -static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2> - RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2"); - } // namespace -// This anchor is used to force the linker to link in the generated object file -// and thus register MockSummaryExtractor2. // NOLINTNEXTLINE(misc-use-internal-linkage) volatile int SSAFMockSummaryExtractor2AnchorSource = 0; +static TUSummaryExtractorRegistry::Add<MockSummaryExtractor2> + RegisterExtractor("MockSummaryExtractor2", "Mock summary extractor 2"); diff --git a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp index 791a1fe0501e5..f8b104ce35840 100644 --- a/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp +++ b/clang/unittests/Analysis/Scalable/Registries/SerializationFormatRegistryTest.cpp @@ -58,7 +58,8 @@ TEST(SerializationFormatRegistryTest, EnumeratingRegistryEntries) { 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")); + 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 index 5169eb504be48..81766a1196761 100644 --- a/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h +++ b/clang/unittests/Analysis/Scalable/SSAFBuiltinTestForceLinker.h @@ -18,6 +18,11 @@ #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 = @@ -28,6 +33,11 @@ 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 = _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
