https://github.com/steakhal created https://github.com/llvm/llvm-project/pull/186463
This reverts commit 3548ec95178c00a2895a65b435945ce318396c8e and adapts the code to the new ScalableStaticAnalysisFramework/ directory layout. Re-adds: - TUSummaryExtractorFrontendAction and its integration into ExecuteCompilerInvocation - --ssaf-extract-summaries= and --ssaf-tu-summary-file= CLI options - SSAFForceLinker / SSAFBuiltinForceLinker headers and anchor symbols - Diagnostics under -Wscalable-static-analysis-framework - Lit tests for the CLI and unit tests for the frontend action - Changes the Formats to be lowercase - and match their spellings in the file paths. From c896ca9fb5235023ec3fc4a2e9b7d65b34ba4a08 Mon Sep 17 00:00:00 2001 From: Balazs Benics <[email protected]> Date: Thu, 12 Mar 2026 12:21:39 +0000 Subject: [PATCH] Reapply "[clang][ssaf] Add --ssaf-extract-summaries= and --ssaf-tu-summary-file= options" This reverts commit 3548ec95178c00a2895a65b435945ce318396c8e and adapts the code to the new ScalableStaticAnalysisFramework/ directory layout. Re-adds: - TUSummaryExtractorFrontendAction and its integration into ExecuteCompilerInvocation - --ssaf-extract-summaries= and --ssaf-tu-summary-file= CLI options - SSAFForceLinker / SSAFBuiltinForceLinker headers and anchor symbols - Diagnostics under -Wscalable-static-analysis-framework - Lit tests for the CLI and unit tests for the frontend action - Changes the Formats to be lowercase - and match their spellings in the file paths. --- .../clang/Basic/DiagnosticFrontendKinds.td | 23 ++ clang/include/clang/Basic/DiagnosticGroups.td | 3 + .../include/clang/Frontend/FrontendOptions.h | 7 + clang/include/clang/Options/Options.td | 20 + .../Core/Serialization/JSONFormat.h | 4 - .../SerializationFormatRegistry.h | 14 +- .../Core/TUSummary/ExtractorRegistry.h | 9 + .../TUSummaryExtractorFrontendAction.h | 33 ++ .../SSAFBuiltinForceLinker.h | 28 ++ .../SSAFForceLinker.h | 25 ++ clang/lib/Driver/ToolChains/Clang.cpp | 3 + clang/lib/FrontendTool/CMakeLists.txt | 2 + .../ExecuteCompilerInvocation.cpp | 6 + .../CMakeLists.txt | 1 + .../JSONFormat/JSONFormatImpl.cpp | 7 +- .../Frontend/CMakeLists.txt | 14 + .../TUSummaryExtractorFrontendAction.cpp | 181 +++++++++ .../Analysis/SSAF/command-line-interface.cpp | 22 ++ .../Analysis/SSAF/downgradable-errors.cpp | 15 + clang/test/Analysis/SSAF/help.cpp | 7 + .../Analysis/Scalable/ssaf-format/list.test | 2 +- clang/tools/ssaf-format/SSAFFormat.cpp | 10 +- clang/tools/ssaf-linker/SSAFLinker.cpp | 11 +- .../CMakeLists.txt | 3 + .../TUSummaryExtractorFrontendActionTest.cpp | 366 ++++++++++++++++++ .../Registries/FancyAnalysisData.cpp | 2 + .../Registries/MockSerializationFormat.cpp | 2 + .../Registries/MockSummaryExtractor1.cpp | 6 +- .../Registries/MockSummaryExtractor2.cpp | 6 +- .../SummaryExtractorRegistryTest.cpp | 1 + .../SSAFBuiltinTestForceLinker.h | 51 +++ .../SSAFTestForceLinker.h | 23 ++ .../TestFixture.cpp | 1 + .../secondary/clang/lib/FrontendTool/BUILD.gn | 2 + .../Frontend/BUILD.gn | 15 + .../ScalableStaticAnalysisFramework/BUILD.gn | 3 + 36 files changed, 898 insertions(+), 30 deletions(-) create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h create mode 100644 clang/include/clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Frontend/CMakeLists.txt create mode 100644 clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp create mode 100644 clang/test/Analysis/SSAF/command-line-interface.cpp create mode 100644 clang/test/Analysis/SSAF/downgradable-errors.cpp create mode 100644 clang/test/Analysis/SSAF/help.cpp create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/SSAFBuiltinTestForceLinker.h create mode 100644 clang/unittests/ScalableStaticAnalysisFramework/SSAFTestForceLinker.h create mode 100644 llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Frontend/BUILD.gn diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index 5c62bb70ebd0f..00db1e7ee5afa 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -379,6 +379,29 @@ def warn_profile_data_misexpect : Warning< BackendInfo, InGroup<MisExpect>; } // end of instrumentation issue category +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 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 warn_ssaf_must_enable_summary_extractors : + Warning<"must enable some summary extractors using the " + "'--ssaf-extract-summaries=' option">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; + +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 warn_ssaf_write_tu_summary_failed : + Warning<"failed to write TU summary to '%0': %1">, + InGroup<ScalableStaticAnalysisFramework>, DefaultError; + def err_extract_api_ignores_file_not_found : Error<"file '%0' specified by '--extract-api-ignores=' not found">, DefaultFatal; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 5d39f12d5c00f..e440c9d2fb982 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1907,6 +1907,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/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 8e17cd5ae15b5..692df65abb367 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/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h index 6c5303c928661..47b46cbe42698 100644 --- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h @@ -28,10 +28,6 @@ class EntityIdTable; class EntitySummary; class SummaryName; -/// Call this from main() to prevent the linker from dead-stripping the -/// JSONFormat library and its static registration objects. -void initializeJSONFormat(); - class JSONFormat final : public SerializationFormat { using Array = llvm::json::Array; using Object = llvm::json::Object; diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h index 9f83955b884e8..ea916dd397b79 100644 --- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/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/ScalableStaticAnalysisFramework/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 LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_SERIALIZATION_SERIALIZATIONFORMATREGISTRY_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h b/clang/include/clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h index a49d0e5faeeb1..9c3f7088c62e5 100644 --- a/clang/include/clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h +++ b/clang/include/clang/ScalableStaticAnalysisFramework/Core/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/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h: +// +// extern volatile int SSAFMyExtractorAnchorSource; +// [[maybe_unused]] static int SSAFMyExtractorAnchorDestination = +// SSAFMyExtractorAnchorSource; +// //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_CORE_TUSUMMARY_EXTRACTORREGISTRY_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h b/clang/include/clang/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h new file mode 100644 index 0000000000000..fe5d75149914e --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/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_SCALABLESTATICANALYSISFRAMEWORK_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_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_SCALABLESTATICANALYSISFRAMEWORK_FRONTEND_TUSUMMARYEXTRACTORFRONTENDACTION_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFBuiltinForceLinker.h new file mode 100644 index 0000000000000..5f201487ca1fe --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/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 clangScalableStaticAnalysisFrameworkCore. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_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_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H diff --git a/clang/include/clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h b/clang/include/clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h new file mode 100644 index 0000000000000..204a504c36435 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysisFramework/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 clangScalableStaticAnalysisFrameworkCore. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFFORCELINKER_H +#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFFORCELINKER_H + +#include "SSAFBuiltinForceLinker.h" // IWYU pragma: keep + +#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFFORCELINKER_H diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index c5b75bdb2511f..3b852528d92c4 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -7707,6 +7707,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..a451eb967e904 100644 --- a/clang/lib/FrontendTool/CMakeLists.txt +++ b/clang/lib/FrontendTool/CMakeLists.txt @@ -4,6 +4,8 @@ set(LLVM_LINK_COMPONENTS ) set(link_libs + clangScalableStaticAnalysisFrameworkCore + clangScalableStaticAnalysisFrameworkFrontend clangBasic clangCodeGen clangDriver diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index c8ad63bc989a4..e4622496758ac 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -23,6 +23,8 @@ #include "clang/FrontendTool/Utils.h" #include "clang/Options/Options.h" #include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep #include "clang/StaticAnalyzer/Frontend/AnalyzerHelpFlags.h" #include "clang/StaticAnalyzer/Frontend/FrontendActions.h" #include "llvm/Option/OptTable.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/lib/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt index 194a13a1af845..d3d75430233fe 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt +++ b/clang/lib/ScalableStaticAnalysisFramework/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(Core) +add_subdirectory(Frontend) diff --git a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp index 0f1b9ccf6258e..4072532d4972c 100644 --- a/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp +++ b/clang/lib/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat/JSONFormatImpl.cpp @@ -9,18 +9,17 @@ #include "JSONFormatImpl.h" #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h" -#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h" #include "llvm/Support/Registry.h" +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFJSONFormatAnchorSource = 0; LLVM_INSTANTIATE_REGISTRY(llvm::Registry<clang::ssaf::JSONFormat::FormatInfo>) static clang::ssaf::SerializationFormatRegistry::Add<clang::ssaf::JSONFormat> - RegisterJSONFormat("JSON", "JSON serialization format"); + RegisterJSONFormat("json", "JSON serialization format"); namespace clang::ssaf { -void initializeJSONFormat() {} - //---------------------------------------------------------------------------- // JSON Reader and Writer //---------------------------------------------------------------------------- diff --git a/clang/lib/ScalableStaticAnalysisFramework/Frontend/CMakeLists.txt b/clang/lib/ScalableStaticAnalysisFramework/Frontend/CMakeLists.txt new file mode 100644 index 0000000000000..b90d9c0ded1a9 --- /dev/null +++ b/clang/lib/ScalableStaticAnalysisFramework/Frontend/CMakeLists.txt @@ -0,0 +1,14 @@ +set(LLVM_LINK_COMPONENTS + Support + ) + +add_clang_library(clangScalableStaticAnalysisFrameworkFrontend + TUSummaryExtractorFrontendAction.cpp + + LINK_LIBS + clangAST + clangBasic + clangFrontend + clangScalableStaticAnalysisFrameworkCore + clangSema + ) diff --git a/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp b/clang/lib/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.cpp new file mode 100644 index 0000000000000..9a75b20fa548b --- /dev/null +++ b/clang/lib/ScalableStaticAnalysisFramework/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/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.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::warn_ssaf_extract_tu_summary_file_unknown_format) + << SSAFTUSummaryFile; + return std::nullopt; + } + + if (!isFormatRegistered(Ext)) { + Diags.Report(diag::warn_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::warn_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::warn_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::warn_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/test/Analysis/SSAF/command-line-interface.cpp b/clang/test/Analysis/SSAF/command-line-interface.cpp new file mode 100644 index 0000000000000..a632f487f2bb7 --- /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: 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. 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/test/Analysis/Scalable/ssaf-format/list.test b/clang/test/Analysis/Scalable/ssaf-format/list.test index 47a678766aed1..4d389d78543ef 100644 --- a/clang/test/Analysis/Scalable/ssaf-format/list.test +++ b/clang/test/Analysis/Scalable/ssaf-format/list.test @@ -5,5 +5,5 @@ // CHECK: Registered serialization formats: // CHECK-EMPTY: -// CHECK-NEXT: 1. JSON JSON serialization format +// CHECK-NEXT: 1. json JSON serialization format // CHECK-NEXT: Analyses: (none) diff --git a/clang/tools/ssaf-format/SSAFFormat.cpp b/clang/tools/ssaf-format/SSAFFormat.cpp index 9b5312e0e085b..497d7437a08ae 100644 --- a/clang/tools/ssaf-format/SSAFFormat.cpp +++ b/clang/tools/ssaf-format/SSAFFormat.cpp @@ -15,6 +15,7 @@ #include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/TUSummaryEncoding.h" #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h" #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CommandLine.h" @@ -152,14 +153,11 @@ SerializationFormat *getFormatForExtension(llvm::StringRef Extension) { return It->second.get(); } - // SerializationFormats are uppercase while file extensions are lowercase. - std::string CapitalizedExtension = Extension.upper(); - - if (!isFormatRegistered(CapitalizedExtension)) { + if (!isFormatRegistered(Extension)) { return nullptr; } - auto Format = makeFormat(CapitalizedExtension); + auto Format = makeFormat(Extension); SerializationFormat *Result = Format.get(); assert(Result); @@ -470,8 +468,6 @@ int main(int argc, const char **argv) { loadPlugins(); - initializeJSONFormat(); - if (ListFormats) { listFormats(); } else { diff --git a/clang/tools/ssaf-linker/SSAFLinker.cpp b/clang/tools/ssaf-linker/SSAFLinker.cpp index e0b1cfdc02160..904cecb5d10fc 100644 --- a/clang/tools/ssaf-linker/SSAFLinker.cpp +++ b/clang/tools/ssaf-linker/SSAFLinker.cpp @@ -14,9 +14,9 @@ #include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/EntityLinker.h" #include "clang/ScalableStaticAnalysisFramework/Core/EntityLinker/TUSummaryEncoding.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h" -#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/JSONFormat.h" #include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h" #include "clang/ScalableStaticAnalysisFramework/Core/Support/ErrorBuilder.h" +#include "clang/ScalableStaticAnalysisFramework/SSAFForceLinker.h" // IWYU pragma: keep #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/CommandLine.h" @@ -133,14 +133,11 @@ SerializationFormat *getFormatForExtension(llvm::StringRef Extension) { return It->second.get(); } - // SerializationFormats are uppercase while file extensions are lowercase. - std::string CapitalizedExtension = Extension.upper(); - - if (!isFormatRegistered(CapitalizedExtension)) { + if (!isFormatRegistered(Extension)) { return nullptr; } - auto Format = makeFormat(CapitalizedExtension); + auto Format = makeFormat(Extension); SerializationFormat *Result = Format.get(); assert(Result); @@ -304,8 +301,6 @@ int main(int argc, const char **argv) { // Parse command-line arguments and exit with an error if they are invalid. cl::ParseCommandLineOptions(argc, argv, "SSAF Linker\n"); - initializeJSONFormat(); - llvm::TimerGroup LinkerTimers(ToolName, "SSAF Linker"); LinkerInput LI; diff --git a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt b/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt index f7ab9c24f3723..d2d3536c198cb 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/CMakeLists.txt +++ b/clang/unittests/ScalableStaticAnalysisFramework/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 @@ -26,8 +27,10 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests CLANG_LIBS clangAST clangASTMatchers + clangBasic clangFrontend clangScalableStaticAnalysisFrameworkCore + clangScalableStaticAnalysisFrameworkFrontend clangSerialization clangTooling diff --git a/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp new file mode 100644 index 0000000000000..d684366ed53ce --- /dev/null +++ b/clang/unittests/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendActionTest.cpp @@ -0,0 +1,366 @@ +//===- 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/ScalableStaticAnalysisFramework/Frontend/TUSummaryExtractorFrontendAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendOptions.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" +#include "clang/Lex/PreprocessorOptions.h" +#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormat.h" +#include "clang/ScalableStaticAnalysisFramework/Core/Serialization/SerializationFormatRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h" +#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryExtractor.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"); + } + + void forEachRegisteredAnalysis( + llvm::function_ref<void(llvm::StringRef Name, llvm::StringRef Desc)> + Callback) const override {} +}; +} // 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/ScalableStaticAnalysisFramework/Registries/FancyAnalysisData.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/FancyAnalysisData.cpp index 084835190f7bd..313c53518dfe8 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/FancyAnalysisData.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/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/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp index e7a3e90e0bb31..535b5fced0da6 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSerializationFormat.cpp @@ -160,6 +160,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/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor1.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor1.cpp index 7f4e9a91febbb..1bce78c8b1030 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor1.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/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/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor2.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor2.cpp index 640228e2b7e2c..242f427f5e346 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/MockSummaryExtractor2.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/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/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp b/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp index 5211319063b60..2018beebd53da 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/Registries/SummaryExtractorRegistryTest.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/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/ScalableStaticAnalysisFramework/SSAFBuiltinTestForceLinker.h b/clang/unittests/ScalableStaticAnalysisFramework/SSAFBuiltinTestForceLinker.h new file mode 100644 index 0000000000000..05d96af80cb27 --- /dev/null +++ b/clang/unittests/ScalableStaticAnalysisFramework/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_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINTESTFORCELINKER_H +#define LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSISFRAMEWORK_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_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINTESTFORCELINKER_H diff --git a/clang/unittests/ScalableStaticAnalysisFramework/SSAFTestForceLinker.h b/clang/unittests/ScalableStaticAnalysisFramework/SSAFTestForceLinker.h new file mode 100644 index 0000000000000..dd2077569a4eb --- /dev/null +++ b/clang/unittests/ScalableStaticAnalysisFramework/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_SCALABLESTATICANALYSISFRAMEWORK_SSAFTESTFORCELINKER_H +#define LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSISFRAMEWORK_SSAFTESTFORCELINKER_H + +#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep + +#endif // LLVM_CLANG_UNITTESTS_SCALABLESTATICANALYSISFRAMEWORK_SSAFTESTFORCELINKER_H diff --git a/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.cpp b/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.cpp index c1c41997abcf2..772eaf069a350 100644 --- a/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.cpp +++ b/clang/unittests/ScalableStaticAnalysisFramework/TestFixture.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "TestFixture.h" +#include "SSAFBuiltinTestForceLinker.h" // IWYU pragma: keep #include "clang/ScalableStaticAnalysisFramework/Core/Model/BuildNamespace.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h" #include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityLinkage.h" diff --git a/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn index 707eabf1af70b..60157daa66d40 100644 --- a/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/FrontendTool/BUILD.gn @@ -12,6 +12,8 @@ static_library("FrontendTool") { "//clang/lib/Frontend", "//clang/lib/Frontend/Rewrite", "//clang/lib/Options", + "//clang/lib/ScalableStaticAnalysisFramework/Core", + "//clang/lib/ScalableStaticAnalysisFramework/Frontend", "//llvm/lib/Option", "//llvm/lib/Support", ] diff --git a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Frontend/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Frontend/BUILD.gn new file mode 100644 index 0000000000000..96da539ae24da --- /dev/null +++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysisFramework/Frontend/BUILD.gn @@ -0,0 +1,15 @@ +static_library("Frontend") { + output_name = "clangScalableStaticAnalysisFrameworkFrontend" + configs += [ "//llvm/utils/gn/build:clang_code" ] + deps = [ + "//clang/lib/AST", + "//clang/lib/Basic", + "//clang/lib/Frontend", + "//clang/lib/ScalableStaticAnalysisFramework/Core", + "//clang/lib/Sema", + "//llvm/lib/Support", + ] + sources = [ + "TUSummaryExtractorFrontendAction.cpp", + ] +} diff --git a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn index 8e19ddf03dd77..9c5b5b18fe94b 100644 --- a/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/unittests/ScalableStaticAnalysisFramework/BUILD.gn @@ -5,8 +5,10 @@ unittest("ClangScalableAnalysisTests") { deps = [ "//clang/lib/AST", "//clang/lib/ASTMatchers", + "//clang/lib/Basic", "//clang/lib/Frontend", "//clang/lib/ScalableStaticAnalysisFramework/Core", + "//clang/lib/ScalableStaticAnalysisFramework/Frontend", "//clang/lib/Serialization", "//clang/lib/Tooling", "//llvm/lib/Testing/Support", @@ -22,6 +24,7 @@ unittest("ClangScalableAnalysisTests") { "EntityLinkerTest.cpp", "EntityNameTest.cpp", "ErrorBuilderTest.cpp", + "Frontend/TUSummaryExtractorFrontendActionTest.cpp", "ModelStringConversionsTest.cpp", "Registries/FancyAnalysisData.cpp", "Registries/MockSerializationFormat.cpp", _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
