https://github.com/jkorous-apple created https://github.com/llvm/llvm-project/pull/208590
Adds the four `--ssaf-*` driver flags (`--ssaf-source-transformation=`, `--ssaf-global-scope-analysis-result=`, `--ssaf-src-edit-file=`, `--ssaf-transformation-report-file=`) under `SSAF_Group`, marshalled into `FrontendOptions`. The compilation-unit identifier flag introduced earlier is reused. The driver forwards all four flags to `cc1`. Adds twelve `warn_ssaf_*` diagnostics under `-Wscalable-static-analysis-framework` (`DefaultError`) covering the orphan-flag matrix, unknown transformation names, unknown output formats, WPA-suite read failures, and edit/report write failures. Adds `clang::ssaf::SourceTransformationFrontendAction` — a `WrapperFrontendAction` that, when any source-edit flag is set, validates the CLI as a group, loads the WPASuite from the configured path, instantiates the named transformation, and serializes the accumulated edits and findings through the configured formats. The action is wrapped in `ExecuteCompilerInvocation` after the existing TU-summary wrap so both pipelines stack as independent `ASTConsumer`s on the same translation unit; they exchange no data. Assisted-By: Claude Opus 4.7 >From cfbf22e4351a66d877fcc01c74f92fe7146778d3 Mon Sep 17 00:00:00 2001 From: Jan Korous <[email protected]> Date: Thu, 9 Jul 2026 14:22:01 -0700 Subject: [PATCH] [clang][ssaf] Wire up the source-edit-generation pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the four `--ssaf-*` driver flags (`--ssaf-source-transformation=`, `--ssaf-global-scope-analysis-result=`, `--ssaf-src-edit-file=`, `--ssaf-transformation-report-file=`) under `SSAF_Group`, marshalled into `FrontendOptions`. The compilation-unit identifier flag introduced earlier is reused. The driver forwards all four flags to `cc1`. Adds twelve `warn_ssaf_*` diagnostics under `-Wscalable-static-analysis-framework` (`DefaultError`) covering the orphan-flag matrix, unknown transformation names, unknown output formats, WPA-suite read failures, and edit/report write failures. Adds `clang::ssaf::SourceTransformationFrontendAction` — a `WrapperFrontendAction` that, when any source-edit flag is set, validates the CLI as a group, loads the WPASuite from the configured path, instantiates the named transformation, and serializes the accumulated edits and findings through the configured formats. The action is wrapped in `ExecuteCompilerInvocation` after the existing TU-summary wrap so both pipelines stack as independent `ASTConsumer`s on the same translation unit; they exchange no data. Lit tests under `clang/test/Analysis/Scalable/source-edit-generation/` cover the orphan-flag matrix, unknown-name and unknown-format paths, the `-Wno-error=` / `-Wno-` levers, write failures, the end-to-end happy path through a test-only `SSAFTestTransformationPlugin`, and the coexistence of stage-1 and stage-2 in a single invocation. The plugin itself lives under `clang/test/` so no testing artifact ships in production code; it builds gated on `CLANG_PLUGIN_SUPPORT AND LLVM_ENABLE_PLUGINS AND NOT WIN32` and the plugin-using lit tests use `REQUIRES: plugins`. Assisted-By: Claude Opus 4.7 --- .../user-docs/SourceEditGeneration.rst | 63 +++++ .../clang/Basic/DiagnosticFrontendKinds.td | 46 ++++ clang/include/clang/Basic/DiagnosticIDs.h | 2 +- clang/include/clang/Frontend/SSAFOptions.h | 21 ++ clang/include/clang/Options/Options.td | 38 +++ .../SourceTransformationFrontendAction.h | 34 +++ clang/lib/Driver/ToolChains/Clang.cpp | 4 + .../ExecuteCompilerInvocation.cpp | 7 + .../Frontend/CMakeLists.txt | 3 + .../SourceTransformationFrontendAction.cpp | 234 ++++++++++++++++++ clang/test/Analysis/Scalable/help.cpp | 8 + .../Inputs/empty-suite.json | 5 + .../Inputs/two-function-suite.json | 26 ++ .../Plugins/CMakeLists.txt | 3 + .../TestTransformationPlugin/CMakeLists.txt | 16 ++ .../TestTransformation.cpp | 101 ++++++++ .../Plugins/lit.local.cfg | 2 + .../source-edit-generation/cli-errors.cpp | 51 ++++ .../source-edit-generation/coexistence.cpp | 33 +++ .../downgradable-errors.cpp | 35 +++ .../source-edit-generation/happy-path.cpp | 37 +++ .../source-edit-generation/write-failure.cpp | 41 +++ clang/test/CMakeLists.txt | 2 + 23 files changed, 811 insertions(+), 1 deletion(-) create mode 100644 clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.rst create mode 100644 clang/include/clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h create mode 100644 clang/lib/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Inputs/empty-suite.json create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Inputs/two-function-suite.json create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Plugins/CMakeLists.txt create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/CMakeLists.txt create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/TestTransformation.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/Plugins/lit.local.cfg create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/cli-errors.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/coexistence.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/downgradable-errors.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/happy-path.cpp create mode 100644 clang/test/Analysis/Scalable/source-edit-generation/write-failure.cpp diff --git a/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.rst b/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.rst new file mode 100644 index 0000000000000..25839c6f32c7f --- /dev/null +++ b/clang/docs/ScalableStaticAnalysis/user-docs/SourceEditGeneration.rst @@ -0,0 +1,63 @@ +============================== +Source Edit Generation +============================== + +Source edit generation relies on a ``WPASuite`` result produced by an +earlier whole-program analysis. It runs alongside the normal compile +and emits two per-translation-unit artifacts: + +- a *source-edit file* (``--ssaf-src-edit-file=``) containing + ``clang::tooling::Replacement`` records ready for + ``clang-apply-replacements``, +- a *transformation-report file* (``--ssaf-transformation-report-file=``) + containing diagnostic-style findings. + +Driver flags +============ + +Four flags control the pipeline; they are all both ``--ssaf-…`` driver +flags and ``cc1`` flags. The compilation-unit identifier is shared +with the summary extraction step. A given compilation unit needs to +receive the same identifier for both summary extraction and source +edit generation. + +.. list-table:: + :header-rows: 1 + + * - Flag + - Purpose + * - ``--ssaf-source-transformation=<name>`` + - Name of the transformation to run. + * - ``--ssaf-global-scope-analysis-result=<path>.<format>`` + - WPASuite input. The extension selects the serialization format. + * - ``--ssaf-src-edit-file=<path>`` + - Source-edit output. Always written as a + ``clang-apply-replacements``-compatible YAML document; the + file extension is not interpreted. + * - ``--ssaf-transformation-report-file=<path>`` + - Transformation-report output. Always written as a SARIF JSON + document; the file extension is not interpreted. + * - ``--ssaf-compilation-unit-id=<id>`` + - Stable identifier for this translation unit (also required by + the summary extraction). + +When ``--ssaf-source-transformation=`` is non-empty the framework wraps +the active ``FrontendAction`` in a ``SourceTransformationFrontendAction``; +otherwise the compile is byte-for-byte unchanged. + +Examples +======== + +Apply the source edits with ``clang-apply-replacements``: + +.. code-block:: console + + $ clang -c foo.cpp \ + --ssaf-source-transformation=my-transformation \ + --ssaf-global-scope-analysis-result=wpa.json \ + --ssaf-src-edit-file=foo.yaml \ + --ssaf-transformation-report-file=foo.sarif \ + --ssaf-compilation-unit-id=cu-foo + $ clang-apply-replacements --remove-change-desc-files <dir-with-yaml> + +The transformation report can be consumed by any SARIF viewer. diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td index cef2fc32a1642..4677af8400e26 100644 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -435,6 +435,52 @@ def warn_ssaf_tu_summary_requires_compilation_unit_id : "'--ssaf-compilation-unit-id=' to be set">, InGroup<ScalableStaticAnalysis>, DefaultError; +def warn_ssaf_source_transformation_unknown_name : + Warning<"no source transformation registered with name: %0">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_source_transformation_requires_wpa_file : + Warning<"option '--ssaf-source-transformation=' requires " + "'--ssaf-global-scope-analysis-result=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_source_transformation_requires_edit_file : + Warning<"option '--ssaf-source-transformation=' requires " + "'--ssaf-src-edit-file=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_source_transformation_requires_report_file : + Warning<"option '--ssaf-source-transformation=' requires " + "'--ssaf-transformation-report-file=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_source_transformation_requires_compilation_unit_id : + Warning<"option '--ssaf-source-transformation=' requires " + "'--ssaf-compilation-unit-id=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_read_wpa_suite_failed : + Warning<"failed to read whole-program analysis result from '%0': %1">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_src_edit_file_requires_transformation : + Warning<"option '--ssaf-src-edit-file=' requires " + "'--ssaf-source-transformation=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_write_src_edit_failed : + Warning<"failed to write source edits to '%0': %1">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_transformation_report_file_requires_transformation : + Warning<"option '--ssaf-transformation-report-file=' requires " + "'--ssaf-source-transformation=' to be set">, + InGroup<ScalableStaticAnalysis>, DefaultError; + +def warn_ssaf_write_transformation_report_failed : + Warning<"failed to write transformation report to '%0': %1">, + InGroup<ScalableStaticAnalysis>, 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/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h index 63b5e6a28aac0..2d7e32579b608 100644 --- a/clang/include/clang/Basic/DiagnosticIDs.h +++ b/clang/include/clang/Basic/DiagnosticIDs.h @@ -36,7 +36,7 @@ enum class Group; enum { DIAG_SIZE_COMMON = 300, DIAG_SIZE_DRIVER = 400, - DIAG_SIZE_FRONTEND = 200, + DIAG_SIZE_FRONTEND = 250, DIAG_SIZE_SERIALIZATION = 120, DIAG_SIZE_LEX = 500, DIAG_SIZE_PARSE = 800, diff --git a/clang/include/clang/Frontend/SSAFOptions.h b/clang/include/clang/Frontend/SSAFOptions.h index 738262cc4a713..c042a3fff6019 100644 --- a/clang/include/clang/Frontend/SSAFOptions.h +++ b/clang/include/clang/Frontend/SSAFOptions.h @@ -31,6 +31,27 @@ class SSAFOptions { /// Controlled by: --ssaf-compilation-unit-id std::string CompilationUnitId; + /// Name of the SSAF source transformation to run. Exactly one transformation + /// per invocation; non-empty implies the source-transformation pipeline is + /// active. + /// Controlled by: --ssaf-source-transformation + std::string SourceTransformation; + + /// Path of the WPASuite input consumed by the source transformation. The + /// extension selects which serialization format reads it. + /// Controlled by: --ssaf-global-scope-analysis-result + std::string GlobalScopeAnalysisResult; + + /// Path of the source-edit output file produced by the source + /// transformation. + /// Controlled by: --ssaf-src-edit-file + std::string SrcEditFile; + + /// Path of the transformation-report output file produced by the source + /// transformation. + /// Controlled by: --ssaf-transformation-report-file + std::string TransformationReportFile; + /// Show the list of available SSAF summary extractors and exit. /// Controlled by: --ssaf-list-extractors LLVM_PREFERRED_TYPE(bool) diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td index 677aaf00b5083..52711492d3cb1 100644 --- a/clang/include/clang/Options/Options.td +++ b/clang/include/clang/Options/Options.td @@ -982,6 +982,44 @@ def _ssaf_compilation_unit_id : "produced SSAF TU summary. Required when '--ssaf-tu-summary-file=' is " "set.">, MarshallingInfoString<SSAFOpts<"CompilationUnitId">>; +def _ssaf_source_transformation : + Joined<["--"], "ssaf-source-transformation=">, + MetaVarName<"<name>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText< + "Name of the SSAF source transformation to run. Exactly one transformation " + "per invocation.">, + MarshallingInfoString<SSAFOpts<"SourceTransformation">>; +def _ssaf_global_scope_analysis_result : + Joined<["--"], "ssaf-global-scope-analysis-result=">, + MetaVarName<"<path>.<format>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText< + "Path to the WPASuite file containing the whole-program analysis result " + "consumed by the source transformation. The extension selects which file " + "format to use.">, + MarshallingInfoString<SSAFOpts<"GlobalScopeAnalysisResult">>; +def _ssaf_src_edit_file : + Joined<["--"], "ssaf-src-edit-file=">, + MetaVarName<"<path>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText< + "Output file for the source edits produced by the source transformation. " + "The output is a YAML document compatible with " + "'clang-apply-replacements'.">, + MarshallingInfoString<SSAFOpts<"SrcEditFile">>; +def _ssaf_transformation_report_file : + Joined<["--"], "ssaf-transformation-report-file=">, + MetaVarName<"<path>">, + Group<SSAF_Group>, + Visibility<[ClangOption, CC1Option]>, + HelpText< + "Output file for the transformation report produced by the source " + "transformation. The output is a SARIF 2.1.0 JSON document.">, + MarshallingInfoString<SSAFOpts<"TransformationReportFile">>; def Xarch__ : JoinedAndSeparate<["-"], "Xarch_">, Flags<[NoXarchOption]>, diff --git a/clang/include/clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h b/clang/include/clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h new file mode 100644 index 0000000000000..1d554ca8343b8 --- /dev/null +++ b/clang/include/clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h @@ -0,0 +1,34 @@ +//===- SourceTransformationFrontendAction.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_SCALABLESTATICANALYSIS_FRONTEND_SOURCETRANSFORMATIONFRONTENDACTION_H +#define LLVM_CLANG_SCALABLESTATICANALYSIS_FRONTEND_SOURCETRANSFORMATIONFRONTENDACTION_H + +#include "clang/Frontend/FrontendAction.h" +#include <memory> + +namespace clang::ssaf { + +/// Wraps the existing \c FrontendAction and runs the source-transformation +/// pipeline alongside it. The transformation consumes a \c WPASuite read from +/// \c SSAFOptions::GlobalScopeAnalysisResult and emits source edits and a +/// transformation report to the configured output files. +class SourceTransformationFrontendAction final : public WrapperFrontendAction { +public: + explicit SourceTransformationFrontendAction( + std::unique_ptr<FrontendAction> WrappedAction); + ~SourceTransformationFrontendAction(); + +protected: + std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override; +}; + +} // namespace clang::ssaf + +#endif // LLVM_CLANG_SCALABLESTATICANALYSIS_FRONTEND_SOURCETRANSFORMATIONFRONTENDACTION_H diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index a30b01a675b99..32c8103e3556a 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -8073,6 +8073,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, Args.AddLastArg(CmdArgs, options::OPT__ssaf_extract_summaries); Args.AddLastArg(CmdArgs, options::OPT__ssaf_tu_summary_file); Args.AddLastArg(CmdArgs, options::OPT__ssaf_compilation_unit_id); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_source_transformation); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_global_scope_analysis_result); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_src_edit_file); + Args.AddLastArg(CmdArgs, options::OPT__ssaf_transformation_report_file); // Handle serialized diagnostics. if (Arg *A = Args.getLastArg(options::OPT__serialize_diags)) { diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index a206770c8490f..6c3a5d096c99b 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -24,6 +24,7 @@ #include "clang/FrontendTool/Utils.h" #include "clang/Options/Options.h" #include "clang/Rewrite/Frontend/FrontendActions.h" +#include "clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h" #include "clang/ScalableStaticAnalysis/Frontend/TUSummaryExtractorFrontendAction.h" #include "clang/ScalableStaticAnalysis/SSAFForceLinker.h" // IWYU pragma: keep #include "clang/StaticAnalyzer/Frontend/AnalyzerHelpFlags.h" @@ -214,6 +215,12 @@ CreateFrontendAction(CompilerInstance &CI) { Act = std::make_unique<ssaf::TUSummaryExtractorFrontendAction>( std::move(Act)); } + if (!CI.getSSAFOpts().SourceTransformation.empty() || + !CI.getSSAFOpts().SrcEditFile.empty() || + !CI.getSSAFOpts().TransformationReportFile.empty()) { + Act = std::make_unique<ssaf::SourceTransformationFrontendAction>( + std::move(Act)); + } return Act; } diff --git a/clang/lib/ScalableStaticAnalysis/Frontend/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/Frontend/CMakeLists.txt index 74e1d0cbe1ed0..58a4806754b45 100644 --- a/clang/lib/ScalableStaticAnalysis/Frontend/CMakeLists.txt +++ b/clang/lib/ScalableStaticAnalysis/Frontend/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS ) add_clang_library(clangScalableStaticAnalysisFrontend + SourceTransformationFrontendAction.cpp TUSummaryExtractorFrontendAction.cpp LINK_LIBS @@ -12,5 +13,7 @@ add_clang_library(clangScalableStaticAnalysisFrontend clangFrontend clangScalableStaticAnalysisAnalyses clangScalableStaticAnalysisCore + clangScalableStaticAnalysisSourceTransformation clangSema + clangToolingCore ) diff --git a/clang/lib/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.cpp b/clang/lib/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.cpp new file mode 100644 index 0000000000000..1fdc82009f8cc --- /dev/null +++ b/clang/lib/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.cpp @@ -0,0 +1,234 @@ +//===- SourceTransformationFrontendAction.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/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/MultiplexConsumer.h" +#include "clang/Frontend/SSAFOptions.h" +#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h" +#include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormatRegistry.h" +#include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/WPASuite.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/SARIFTransformationReportFormat.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/SourceEditEmitter.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/Transformation.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationRegistry.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationReportEmitter.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/IOSandbox.h" +#include "llvm/Support/Path.h" +#include <memory> +#include <string> +#include <utility> +#include <vector> + +using namespace clang; +using namespace ssaf; + +namespace { + +/// Concrete `SourceEditEmitter` that buffers replacements until flushed. +class AccumulatorSourceEditEmitter final : public SourceEditEmitter { +public: + void addReplacement(clang::tooling::Replacement R) override { + Replacements.push_back(std::move(R)); + } + + std::vector<clang::tooling::Replacement> Replacements; +}; + +/// Concrete `TransformationReportEmitter` that buffers results until flushed. +class AccumulatorReportEmitter final : public TransformationReportEmitter { +public: + void addResult(StringRef RuleId, clang::SarifResultLevel Level, + clang::CharSourceRange Range, StringRef Message) override { + Results.push_back({RuleId.str(), Level, Range, Message.str()}); + } + + std::vector<ReportResult> Results; +}; + +/// Per-TU runner: owns the loaded `WPASuite`, the accumulator emitters, and +/// the user-supplied `Transformation`. Inherits from `MultiplexConsumer` so +/// the transformation's `ASTConsumer` virtuals are forwarded for free; +/// serializes both outputs after the AST walk completes. +class SourceTransformationRunner final : public MultiplexConsumer { +public: + static std::unique_ptr<SourceTransformationRunner> + create(CompilerInstance &CI, StringRef InFile); + +private: + SourceTransformationRunner(WPASuite Suite, const SSAFOptions &Opts, + StringRef InFile); + + void HandleTranslationUnit(ASTContext &Ctx) override; + + WPASuite Suite; + AccumulatorSourceEditEmitter Edits; + AccumulatorReportEmitter Report; + const SSAFOptions &Opts; + std::string InFile; +}; + +} // namespace + +/// Returns the bare extension of \p Path (no leading dot), or `std::nullopt` if +/// \p Path is empty or has no recognizable extension. +static std::optional<StringRef> bareExtension(StringRef Path) { + StringRef Ext = llvm::sys::path::extension(Path); + if (!Ext.consume_front(".")) + return std::nullopt; + return Ext; +} + +/// Returns `true` if any orphan-flag warning was reported. Every missing +/// companion flag fires its own diagnostic in a single pass so the user +/// sees the full list of CLI mistakes at once. +static bool reportOrphanFlagMisuse(DiagnosticsEngine &Diags, + const SSAFOptions &Opts) { + bool Reported = false; + + if (!Opts.SourceTransformation.empty()) { + if (Opts.GlobalScopeAnalysisResult.empty()) { + Diags.Report(diag::warn_ssaf_source_transformation_requires_wpa_file); + Reported = true; + } + if (Opts.SrcEditFile.empty()) { + Diags.Report(diag::warn_ssaf_source_transformation_requires_edit_file); + Reported = true; + } + if (Opts.TransformationReportFile.empty()) { + Diags.Report(diag::warn_ssaf_source_transformation_requires_report_file); + Reported = true; + } + if (Opts.CompilationUnitId.empty()) { + Diags.Report( + diag::warn_ssaf_source_transformation_requires_compilation_unit_id); + Reported = true; + } + } else { + if (!Opts.SrcEditFile.empty()) { + Diags.Report(diag::warn_ssaf_src_edit_file_requires_transformation); + Reported = true; + } + if (!Opts.TransformationReportFile.empty()) { + Diags.Report( + diag::warn_ssaf_transformation_report_file_requires_transformation); + Reported = true; + } + } + + return Reported; +} + +std::unique_ptr<SourceTransformationRunner> +SourceTransformationRunner::create(CompilerInstance &CI, StringRef InFile) { + const SSAFOptions &Opts = CI.getSSAFOpts(); + DiagnosticsEngine &Diags = CI.getDiagnostics(); + + if (reportOrphanFlagMisuse(Diags, Opts)) + return nullptr; + if (Opts.SourceTransformation.empty()) + return nullptr; + + if (!isTransformationRegistered(Opts.SourceTransformation)) { + Diags.Report(diag::warn_ssaf_source_transformation_unknown_name) + << Opts.SourceTransformation; + return nullptr; + } + + std::optional<StringRef> WPAExt = + bareExtension(Opts.GlobalScopeAnalysisResult); + std::unique_ptr<SerializationFormat> WPAFormat = + WPAExt && isFormatRegistered(*WPAExt) ? makeFormat(*WPAExt) : nullptr; + if (!WPAFormat) { + Diags.Report(diag::warn_ssaf_read_wpa_suite_failed) + << Opts.GlobalScopeAnalysisResult << "unknown serialization format"; + return nullptr; + } + llvm::sys::sandbox::ScopedSetting Guard = llvm::sys::sandbox::scopedDisable(); + llvm::Expected<WPASuite> SuiteOrErr = + WPAFormat->readWPASuite(Opts.GlobalScopeAnalysisResult); + if (!SuiteOrErr) { + Diags.Report(diag::warn_ssaf_read_wpa_suite_failed) + << Opts.GlobalScopeAnalysisResult + << llvm::toString(SuiteOrErr.takeError()); + return nullptr; + } + + return std::unique_ptr<SourceTransformationRunner>{ + new SourceTransformationRunner(std::move(*SuiteOrErr), Opts, InFile)}; +} + +SourceTransformationRunner::SourceTransformationRunner(WPASuite Suite, + const SSAFOptions &Opts, + StringRef InFile) + : MultiplexConsumer(std::vector<std::unique_ptr<ASTConsumer>>{}), + Suite(std::move(Suite)), Opts(Opts), InFile(InFile) { + // The transformation must be constructed after Suite/Edits/Report start + // their lifetimes — those references are captured in its base ctor. + std::vector<std::unique_ptr<ASTConsumer>> Consumers; + Consumers.push_back(makeTransformation(Opts.SourceTransformation, this->Suite, + Edits, Report)); + assert(Consumers.front()); + MultiplexConsumer::Consumers = std::move(Consumers); +} + +void SourceTransformationRunner::HandleTranslationUnit(ASTContext &Ctx) { + // First, run the transformation. + MultiplexConsumer::HandleTranslationUnit(Ctx); + + llvm::sys::sandbox::ScopedSetting Guard = llvm::sys::sandbox::scopedDisable(); + + // Then serialize the source edits. + clang::tooling::TranslationUnitReplacements EditDoc; + EditDoc.MainSourceFile = InFile; + EditDoc.Replacements = std::move(Edits.Replacements); + if (auto Err = writeYAMLSourceEdits(EditDoc, Opts.SrcEditFile)) { + Ctx.getDiagnostics().Report(diag::warn_ssaf_write_src_edit_failed) + << Opts.SrcEditFile << llvm::toString(std::move(Err)); + } + + // And the transformation report. + ReportDocument ReportDoc{Opts.SourceTransformation, Ctx.getSourceManager(), + std::move(Report.Results)}; + if (auto Err = writeSARIFTransformationReport( + ReportDoc, Opts.TransformationReportFile)) { + Ctx.getDiagnostics().Report( + diag::warn_ssaf_write_transformation_report_failed) + << Opts.TransformationReportFile << llvm::toString(std::move(Err)); + } +} + +SourceTransformationFrontendAction::~SourceTransformationFrontendAction() = + default; + +SourceTransformationFrontendAction::SourceTransformationFrontendAction( + std::unique_ptr<FrontendAction> WrappedAction) + : WrapperFrontendAction(std::move(WrappedAction)) {} + +std::unique_ptr<ASTConsumer> +SourceTransformationFrontendAction::CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) { + auto WrappedConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); + if (!WrappedConsumer) + return nullptr; + + if (auto Runner = SourceTransformationRunner::create(CI, InFile)) { + CI.getCodeGenOpts().ClearASTBeforeBackend = false; + 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/Scalable/help.cpp b/clang/test/Analysis/Scalable/help.cpp index 15d6d109360d8..0c82e21323fdb 100644 --- a/clang/test/Analysis/Scalable/help.cpp +++ b/clang/test/Analysis/Scalable/help.cpp @@ -7,8 +7,16 @@ // HELP-NEXT: Stable identifier used as the CompilationUnit namespace name of every produced SSAF TU summary. Required when '--ssaf-tu-summary-file=' is set. // HELP-NEXT: --ssaf-extract-summaries=<summary-names> // HELP-NEXT: Comma-separated list of summary names to extract +// HELP-NEXT: --ssaf-global-scope-analysis-result=<path>.<format> +// HELP-NEXT: Path to the WPASuite file containing the whole-program analysis result consumed by the source transformation. The extension selects which file format to use. // HELP-NEXT: --ssaf-list-extractors Display the list of available SSAF summary extractors // HELP-NEXT: --ssaf-list-formats Display the list of available SSAF serialization formats +// HELP-NEXT: --ssaf-source-transformation=<name> +// HELP-NEXT: Name of the SSAF source transformation to run. Exactly one transformation per invocation. +// HELP-NEXT: --ssaf-src-edit-file=<path> +// HELP-NEXT: Output file for the source edits produced by the source transformation. The output is a YAML document compatible with 'clang-apply-replacements'. +// HELP-NEXT: --ssaf-transformation-report-file=<path> +// HELP-NEXT: Output file for the transformation report produced by the source transformation. The output is a SARIF 2.1.0 JSON document. // HELP-NEXT: --ssaf-tu-summary-file=<path>.<format> // HELP-NEXT: The output file for the extracted summaries. The extension selects which file format to use. diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Inputs/empty-suite.json b/clang/test/Analysis/Scalable/source-edit-generation/Inputs/empty-suite.json new file mode 100644 index 0000000000000..c9a2c4421b807 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Inputs/empty-suite.json @@ -0,0 +1,5 @@ +{ + "id_table": [], + "results": [], + "type": "WPASuite" +} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Inputs/two-function-suite.json b/clang/test/Analysis/Scalable/source-edit-generation/Inputs/two-function-suite.json new file mode 100644 index 0000000000000..bb63780cce01d --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Inputs/two-function-suite.json @@ -0,0 +1,26 @@ +{ + "id_table": [ + { + "id": 0, + "name": { + "namespace": [ + { "kind": "LinkUnit", "name": "test.exe" } + ], + "suffix": "", + "usr": "c:@F@foo#" + } + }, + { + "id": 1, + "name": { + "namespace": [ + { "kind": "LinkUnit", "name": "test.exe" } + ], + "suffix": "", + "usr": "c:@F@bar#" + } + } + ], + "results": [], + "type": "WPASuite" +} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Plugins/CMakeLists.txt b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/CMakeLists.txt new file mode 100644 index 0000000000000..a91194e9ca8f9 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/CMakeLists.txt @@ -0,0 +1,3 @@ +if(CLANG_PLUGIN_SUPPORT AND LLVM_ENABLE_PLUGINS AND NOT WIN32) + add_subdirectory(TestTransformationPlugin) +endif() diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/CMakeLists.txt b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/CMakeLists.txt new file mode 100644 index 0000000000000..7f170be16d2cc --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/CMakeLists.txt @@ -0,0 +1,16 @@ +# Do NOT set LLVM_LINK_COMPONENTS or clang_target_link_libraries here. +# +# Static link would produce a second copy of LLVM and Clang libraries in the +# plugin's address space alongside the copies already loaded by clang itself, +# resulting in two distinct llvm::Registry instances and broken registration. +# Let the dynamic loader resolve every symbol from clang at load time. + +add_llvm_library(SSAFTestTransformationPlugin MODULE BUILDTREE_ONLY + TestTransformation.cpp + PLUGIN_TOOL clang + ) + +target_include_directories(SSAFTestTransformationPlugin PRIVATE + $<TARGET_PROPERTY:clangScalableStaticAnalysisCore,INTERFACE_INCLUDE_DIRECTORIES> + $<TARGET_PROPERTY:clangScalableStaticAnalysisSourceTransformation,INTERFACE_INCLUDE_DIRECTORIES> + ) diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/TestTransformation.cpp b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/TestTransformation.cpp new file mode 100644 index 0000000000000..d3edc34882cef --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/TestTransformationPlugin/TestTransformation.cpp @@ -0,0 +1,101 @@ +//===- TestTransformation.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 +// +//===----------------------------------------------------------------------===// +// +// A transformation used only by lit tests for the source-edit-generation +// pipeline. It walks every function in the main source file, emits a +// zero-length `/*T*/` comment at the function body's start, and adds one +// `test-touches-function` note per visited function. Its level is always +// `Note` — the goal is to exercise the framework's plumbing, not to +// produce meaningful findings. The level rises to `Warning` when the +// input WPASuite's id table is non-empty, giving lit tests a knob to +// confirm the suite is read at all without depending on namespace +// matching. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/Basic/Sarif.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" +#include "clang/ScalableStaticAnalysis/Core/Model/EntityIdTable.h" +#include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/WPASuite.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/Transformation.h" +#include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationRegistry.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/SmallString.h" + +using namespace clang; +using namespace clang::ssaf; + +namespace { + +class TestTransformation final : public Transformation { +public: + using Transformation::Transformation; + + void HandleTranslationUnit(ASTContext &Ctx) override { + bool SuiteIsNonEmpty = Suite.getIdTable().count() > 0; + Visitor V{*this, Ctx, SuiteIsNonEmpty}; + V.TraverseDecl(Ctx.getTranslationUnitDecl()); + } + +private: + class Visitor : public RecursiveASTVisitor<Visitor> { + public: + Visitor(TestTransformation &T, ASTContext &Ctx, bool SuiteIsNonEmpty) + : T(T), Ctx(Ctx), Level(SuiteIsNonEmpty + ? clang::SarifResultLevel::Warning + : clang::SarifResultLevel::Note) {} + + bool VisitFunctionDecl(FunctionDecl *FD) { + if (!FD->hasBody()) + return true; + SourceManager &SM = Ctx.getSourceManager(); + if (!SM.isInMainFile(FD->getLocation())) + return true; + + Stmt *Body = FD->getBody(); + SourceLocation BodyStart = Body->getBeginLoc(); + if (BodyStart.isInvalid()) + return true; + + llvm::SmallString<64> FilePath(SM.getFilename(BodyStart)); + unsigned Offset = SM.getFileOffset(BodyStart); + T.Edits.addReplacement( + clang::tooling::Replacement(FilePath, Offset, /*Length=*/0, "/*T*/")); + + CharSourceRange Range = Lexer::getAsCharRange( + CharSourceRange::getTokenRange(FD->getNameInfo().getSourceRange()), + SM, Ctx.getLangOpts()); + std::string Message = "visited " + FD->getNameAsString(); + T.Report.addResult("test-touches-function", Level, Range, Message); + return true; + } + + private: + TestTransformation &T; + ASTContext &Ctx; + clang::SarifResultLevel Level; + }; +}; + +} // namespace + +namespace clang::ssaf { +// NOLINTNEXTLINE(misc-use-internal-linkage) +volatile int SSAFTestTransformationAnchorSource = 0; +} // namespace clang::ssaf + +static TransformationRegistry::Add<TestTransformation> + RegisterTestTransformation("test-transformation", + "Test transformation for the SSAF " + "source-edit-generation lit suite"); + diff --git a/clang/test/Analysis/Scalable/source-edit-generation/Plugins/lit.local.cfg b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/lit.local.cfg new file mode 100644 index 0000000000000..bc7dd40525f86 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/Plugins/lit.local.cfg @@ -0,0 +1,2 @@ +config.suffixes = [] +config.unsupported = True diff --git a/clang/test/Analysis/Scalable/source-edit-generation/cli-errors.cpp b/clang/test/Analysis/Scalable/source-edit-generation/cli-errors.cpp new file mode 100644 index 0000000000000..a0adf61c8c6fb --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/cli-errors.cpp @@ -0,0 +1,51 @@ +// CLI errors for the source-edit-generation pipeline. Every misuse of the +// four `--ssaf-{source-transformation,global-scope-analysis-result, +// src-edit-file,transformation-report-file}=` flags emits a default-error +// diagnostic under `-Wscalable-static-analysis-framework`. The runner +// produces no edit/report files and the rest of the compile pipeline is +// untouched. + +// DEFINE: %{filecheck} = FileCheck %s --match-full-lines --check-prefix +// DEFINE: %{base} = --ssaf-source-transformation=does-not-exist \ +// DEFINE: --ssaf-global-scope-analysis-result=%S/Inputs/empty-suite.json \ +// DEFINE: --ssaf-src-edit-file=%t/edits.yaml \ +// DEFINE: --ssaf-transformation-report-file=%t/report.sarif \ +// DEFINE: --ssaf-compilation-unit-id=cu + +// ============================================================================= +// 1. Unknown transformation name. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: not %clang -c %s -o %t/test.o %{base} 2>&1 | %{filecheck}=UNKNOWN-NAME +// RUN: not %clang_cc1 %s %{base} 2>&1 | %{filecheck}=UNKNOWN-NAME +// UNKNOWN-NAME: error: no source transformation registered with name: does-not-exist [-Wscalable-static-analysis-framework] +// RUN: not test -e %t/edits.yaml +// RUN: not test -e %t/report.sarif + +// ============================================================================= +// 2. Orphan companion flags: --ssaf-source-transformation= alone. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: not %clang -c %s -o %t/test.o --ssaf-source-transformation=does-not-exist 2>&1 | %{filecheck}=ORPHAN-COMPANIONS +// ORPHAN-COMPANIONS-DAG: error: option '--ssaf-source-transformation=' requires '--ssaf-global-scope-analysis-result=' to be set [-Wscalable-static-analysis-framework] +// ORPHAN-COMPANIONS-DAG: error: option '--ssaf-source-transformation=' requires '--ssaf-src-edit-file=' to be set [-Wscalable-static-analysis-framework] +// ORPHAN-COMPANIONS-DAG: error: option '--ssaf-source-transformation=' requires '--ssaf-transformation-report-file=' to be set [-Wscalable-static-analysis-framework] +// ORPHAN-COMPANIONS-DAG: error: option '--ssaf-source-transformation=' requires '--ssaf-compilation-unit-id=' to be set [-Wscalable-static-analysis-framework] + +// ============================================================================= +// 3. Reverse orphans: edit/report file set without transformation flag. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: not %clang -c %s -o %t/test.o --ssaf-src-edit-file=%t/e.yaml 2>&1 | %{filecheck}=ORPHAN-EDIT +// ORPHAN-EDIT: error: option '--ssaf-src-edit-file=' requires '--ssaf-source-transformation=' to be set [-Wscalable-static-analysis-framework] +// RUN: not test -e %t/e.yaml + +// RUN: rm -rf %t && mkdir -p %t +// RUN: not %clang -c %s -o %t/test.o --ssaf-transformation-report-file=%t/r.sarif 2>&1 | %{filecheck}=ORPHAN-REPORT +// ORPHAN-REPORT: error: option '--ssaf-transformation-report-file=' requires '--ssaf-source-transformation=' to be set [-Wscalable-static-analysis-framework] +// RUN: not test -e %t/r.sarif + +void foo() {} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/coexistence.cpp b/clang/test/Analysis/Scalable/source-edit-generation/coexistence.cpp new file mode 100644 index 0000000000000..85268c1d0dc62 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/coexistence.cpp @@ -0,0 +1,33 @@ +// Stage-1 (TU-summary extraction) and stage-2 (source-edit generation) can +// both be active in a single clang invocation. Their flags do not interact +// at the data layer — the source transformation reads its WPASuite from +// disk, not from the in-flight extractor. The two pipelines stack as +// independent ASTConsumers; both produce their per-TU output files. + +// REQUIRES: plugins + +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -load %llvmshlibdir/SSAFTestTransformationPlugin%pluginext \ +// RUN: --ssaf-extract-summaries=CallGraph \ +// RUN: --ssaf-tu-summary-file=%t/tu.json \ +// RUN: --ssaf-source-transformation=test-transformation \ +// RUN: --ssaf-global-scope-analysis-result=%S/Inputs/empty-suite.json \ +// RUN: --ssaf-src-edit-file=%t/edits.yaml \ +// RUN: --ssaf-transformation-report-file=%t/report.sarif \ +// RUN: --ssaf-compilation-unit-id=cu \ +// RUN: -emit-obj -o %t/test.o %s + +// All four artifacts must be present. +// RUN: test -e %t/test.o +// RUN: test -e %t/tu.json +// RUN: test -e %t/edits.yaml +// RUN: test -e %t/report.sarif + +// And the source-transformation outputs are non-trivial (the plugin emits +// one replacement and one finding per function in the main file). +// RUN: FileCheck --check-prefix=EDITS --input-file=%t/edits.yaml %s +// EDITS: ReplacementText: '/*T*/' +// RUN: FileCheck --check-prefix=REPORT --input-file=%t/report.sarif %s +// REPORT: "ruleId": "test-touches-function" + +void foo() {} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/downgradable-errors.cpp b/clang/test/Analysis/Scalable/source-edit-generation/downgradable-errors.cpp new file mode 100644 index 0000000000000..23855a67f4572 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/downgradable-errors.cpp @@ -0,0 +1,35 @@ +// The source-edit-generation diagnostics are downgradable via +// `-Wno-error=scalable-static-analysis-framework` and silenceable via +// `-Wno-scalable-static-analysis-framework`. In both cases the +// compilation continues normally and produces its object file, but no +// edit or report file is written. + +// DEFINE: %{filecheck} = FileCheck %s --match-full-lines --check-prefix +// DEFINE: %{flags} = --ssaf-source-transformation=does-not-exist \ +// DEFINE: --ssaf-global-scope-analysis-result=%S/Inputs/empty-suite.json \ +// DEFINE: --ssaf-src-edit-file=%t/edits.yaml \ +// DEFINE: --ssaf-transformation-report-file=%t/report.sarif \ +// DEFINE: --ssaf-compilation-unit-id=cu + +// ============================================================================= +// 1. -Wno-error=scalable-static-analysis-framework downgrades to a warning. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang -c %s -o %t/test.o -Wno-error=scalable-static-analysis-framework %{flags} 2>&1 | %{filecheck}=WARNING +// WARNING: warning: no source transformation registered with name: does-not-exist [-Wscalable-static-analysis-framework] +// RUN: test -e %t/test.o +// RUN: not test -e %t/edits.yaml +// RUN: not test -e %t/report.sarif + +// ============================================================================= +// 2. -Wno-scalable-static-analysis-framework silences the diagnostic. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang -c %s -o %t/test.o -Wno-scalable-static-analysis-framework %{flags} 2>&1 | count 0 +// RUN: test -e %t/test.o +// RUN: not test -e %t/edits.yaml +// RUN: not test -e %t/report.sarif + +void foo() {} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/happy-path.cpp b/clang/test/Analysis/Scalable/source-edit-generation/happy-path.cpp new file mode 100644 index 0000000000000..d22c901217c31 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/happy-path.cpp @@ -0,0 +1,37 @@ +// End-to-end test of the source-edit-generation pipeline driven by the +// `test-transformation` plugin. Walks every function in the main source +// file, inserts a zero-length `/*T*/` comment at each function body's +// start, and emits one `test-touches-function` finding per function. +// The finding's level is `Warning` if the function's USR is in the input +// WPASuite, `Note` otherwise — verifying the WPASuite is actually read. + +// REQUIRES: plugins + +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -load %llvmshlibdir/SSAFTestTransformationPlugin%pluginext \ +// RUN: --ssaf-source-transformation=test-transformation \ +// RUN: --ssaf-global-scope-analysis-result=%S/Inputs/two-function-suite.json \ +// RUN: --ssaf-src-edit-file=%t/edits.yaml \ +// RUN: --ssaf-transformation-report-file=%t/report.sarif \ +// RUN: --ssaf-compilation-unit-id=cu \ +// RUN: -emit-obj -o %t/test.o %s + +// RUN: FileCheck --check-prefix=EDITS --input-file=%t/edits.yaml %s +// EDITS: MainSourceFile: {{.*}}happy-path.cpp +// EDITS: Replacements: +// EDITS-DAG: Offset: {{[0-9]+}} +// EDITS-DAG: ReplacementText: '/*T*/' + +// RUN: FileCheck --check-prefix=REPORT --input-file=%t/report.sarif %s +// REPORT-DAG: "name": "clang-ssaf" +// REPORT-DAG: "fullName": {{.*}}test-transformation +// REPORT-DAG: "ruleId": "test-touches-function" +// REPORT-DAG: "level": "warning" +// REPORT-DAG: "uri": "file://{{.*}}happy-path.cpp" + +// `foo` and `bar` are in two-function-suite.json's id_table, so their +// findings escalate from Note to Warning. `baz` is not — its finding +// stays at Note. +void foo() {} +void bar() {} +void baz() {} diff --git a/clang/test/Analysis/Scalable/source-edit-generation/write-failure.cpp b/clang/test/Analysis/Scalable/source-edit-generation/write-failure.cpp new file mode 100644 index 0000000000000..74ef0421045a8 --- /dev/null +++ b/clang/test/Analysis/Scalable/source-edit-generation/write-failure.cpp @@ -0,0 +1,41 @@ +// When the source-edit or transformation-report writer's `write` returns an +// `llvm::Error`, the framework reports a default-error diagnostic. With +// `-Wno-error=scalable-static-analysis-framework` the diagnostic downgrades +// to a warning and the rest of the compile pipeline finishes normally. + +// REQUIRES: plugins + +// RUN: rm -rf %t && mkdir -p %t + +// ============================================================================= +// 1. Source-edit write fails because the parent directory does not exist. +// ============================================================================= + +// RUN: %clang_cc1 -load %llvmshlibdir/SSAFTestTransformationPlugin%pluginext \ +// RUN: -Wno-error=scalable-static-analysis-framework \ +// RUN: --ssaf-source-transformation=test-transformation \ +// RUN: --ssaf-global-scope-analysis-result=%S/Inputs/empty-suite.json \ +// RUN: --ssaf-src-edit-file=%t/missing-dir/edits.yaml \ +// RUN: --ssaf-transformation-report-file=%t/report.sarif \ +// RUN: --ssaf-compilation-unit-id=cu \ +// RUN: -emit-obj -o %t/test.o %s 2>&1 | FileCheck --check-prefix=EDIT-FAIL %s +// EDIT-FAIL: warning: failed to write source edits to '{{.*}}/missing-dir/edits.yaml'{{.*}}[-Wscalable-static-analysis-framework] +// RUN: test -e %t/test.o + +// ============================================================================= +// 2. Transformation-report write fails. +// ============================================================================= + +// RUN: rm -rf %t && mkdir -p %t +// RUN: %clang_cc1 -load %llvmshlibdir/SSAFTestTransformationPlugin%pluginext \ +// RUN: -Wno-error=scalable-static-analysis-framework \ +// RUN: --ssaf-source-transformation=test-transformation \ +// RUN: --ssaf-global-scope-analysis-result=%S/Inputs/empty-suite.json \ +// RUN: --ssaf-src-edit-file=%t/edits.yaml \ +// RUN: --ssaf-transformation-report-file=%t/missing-dir/report.sarif \ +// RUN: --ssaf-compilation-unit-id=cu \ +// RUN: -emit-obj -o %t/test.o %s 2>&1 | FileCheck --check-prefix=REPORT-FAIL %s +// REPORT-FAIL: warning: failed to write transformation report to '{{.*}}/missing-dir/report.sarif'{{.*}}[-Wscalable-static-analysis-framework] +// RUN: test -e %t/test.o + +void foo() {} diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt index 15170bbc2d1ed..92883005dd88d 100644 --- a/clang/test/CMakeLists.txt +++ b/clang/test/CMakeLists.txt @@ -211,7 +211,9 @@ endif() if(CLANG_PLUGIN_SUPPORT AND LLVM_ENABLE_PLUGINS AND NOT WIN32) list(APPEND CLANG_TEST_DEPS SSAFExamplePlugin + SSAFTestTransformationPlugin ) + add_subdirectory(Analysis/Scalable/source-edit-generation/Plugins) endif() if (HAVE_CLANG_REPL_SUPPORT) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
