| 1 | //===- SourceTransformationFrontendAction.cpp -----------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "clang/ScalableStaticAnalysis/Frontend/SourceTransformationFrontendAction.h" |
| 10 | #include "clang/AST/ASTConsumer.h" |
| 11 | #include "clang/Basic/DiagnosticFrontend.h" |
| 12 | #include "clang/Frontend/CompilerInstance.h" |
| 13 | #include "clang/Frontend/MultiplexConsumer.h" |
| 14 | #include "clang/Frontend/SSAFOptions.h" |
| 15 | #include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormat.h" |
| 16 | #include "clang/ScalableStaticAnalysis/Core/Serialization/SerializationFormatRegistry.h" |
| 17 | #include "clang/ScalableStaticAnalysis/Core/WholeProgramAnalysis/WPASuite.h" |
| 18 | #include "clang/ScalableStaticAnalysis/SourceTransformation/SARIFTransformationReportFormat.h" |
| 19 | #include "clang/ScalableStaticAnalysis/SourceTransformation/SourceEditEmitter.h" |
| 20 | #include "clang/ScalableStaticAnalysis/SourceTransformation/Transformation.h" |
| 21 | #include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationRegistry.h" |
| 22 | #include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationReportEmitter.h" |
| 23 | #include "clang/ScalableStaticAnalysis/SourceTransformation/YAMLSourceEditFormat.h" |
| 24 | #include "clang/Tooling/Core/Replacement.h" |
| 25 | #include "llvm/ADT/StringRef.h" |
| 26 | #include "llvm/Support/IOSandbox.h" |
| 27 | #include "llvm/Support/Path.h" |
| 28 | #include <memory> |
| 29 | #include <string> |
| 30 | #include <utility> |
| 31 | #include <vector> |
| 32 | |
| 33 | using namespace clang; |
| 34 | using namespace ssaf; |
| 35 | |
| 36 | namespace { |
| 37 | |
| 38 | /// Concrete `SourceEditEmitter` that buffers replacements until flushed. |
| 39 | class AccumulatorSourceEditEmitter final : public SourceEditEmitter { |
| 40 | public: |
| 41 | void addReplacement(clang::tooling::Replacement R) override { |
| 42 | Replacements.push_back(x: std::move(R)); |
| 43 | } |
| 44 | |
| 45 | std::vector<clang::tooling::Replacement> Replacements; |
| 46 | }; |
| 47 | |
| 48 | /// Concrete `TransformationReportEmitter` that buffers results until flushed. |
| 49 | class AccumulatorReportEmitter final : public TransformationReportEmitter { |
| 50 | public: |
| 51 | void addResult(StringRef RuleId, clang::SarifResultLevel Level, |
| 52 | clang::CharSourceRange Range, StringRef Message) override { |
| 53 | Results.push_back(x: {.RuleId: RuleId.str(), .Level: Level, .Range: Range, .Message: Message.str()}); |
| 54 | } |
| 55 | |
| 56 | std::vector<ReportResult> Results; |
| 57 | }; |
| 58 | |
| 59 | /// Per-TU runner: owns the loaded `WPASuite`, the accumulator emitters, and |
| 60 | /// the user-supplied `Transformation`. Inherits from `MultiplexConsumer` so |
| 61 | /// the transformation's `ASTConsumer` virtuals are forwarded for free; |
| 62 | /// serializes both outputs after the AST walk completes. |
| 63 | class SourceTransformationRunner final : public MultiplexConsumer { |
| 64 | public: |
| 65 | static std::unique_ptr<SourceTransformationRunner> |
| 66 | create(CompilerInstance &CI, StringRef InFile); |
| 67 | |
| 68 | private: |
| 69 | SourceTransformationRunner(WPASuite Suite, const SSAFOptions &Opts, |
| 70 | StringRef InFile); |
| 71 | |
| 72 | void HandleTranslationUnit(ASTContext &Ctx) override; |
| 73 | |
| 74 | WPASuite Suite; |
| 75 | AccumulatorSourceEditEmitter Edits; |
| 76 | AccumulatorReportEmitter Report; |
| 77 | const SSAFOptions &Opts; |
| 78 | std::string InFile; |
| 79 | }; |
| 80 | |
| 81 | } // namespace |
| 82 | |
| 83 | /// Returns the bare extension of \p Path (no leading dot), or `std::nullopt` if |
| 84 | /// \p Path is empty or has no recognizable extension. |
| 85 | static std::optional<StringRef> bareExtension(StringRef Path) { |
| 86 | StringRef Ext = llvm::sys::path::extension(path: Path); |
| 87 | if (!Ext.consume_front(Prefix: "." )) |
| 88 | return std::nullopt; |
| 89 | return Ext; |
| 90 | } |
| 91 | |
| 92 | /// Companion options required by `--ssaf-source-transformation=`. Values must |
| 93 | /// match the `%select` branch order in |
| 94 | /// `warn_ssaf_source_transformation_requires`. |
| 95 | enum SourceTransformationCompanion { |
| 96 | STCompanion_WPAFile, // --ssaf-global-scope-analysis-result= |
| 97 | STCompanion_EditFile, // --ssaf-src-edit-file= |
| 98 | STCompanion_ReportFile, // --ssaf-transformation-report-file= |
| 99 | STCompanion_CompilationUnitId, // --ssaf-compilation-unit-id= |
| 100 | }; |
| 101 | |
| 102 | /// Options that depend on `--ssaf-source-transformation=` being set. Values |
| 103 | /// must match the `%select` branch order in |
| 104 | /// `warn_ssaf_option_ignored_without_source_transformation`. |
| 105 | enum SourceTransformationDependent { |
| 106 | STDependent_EditFile, // --ssaf-src-edit-file= |
| 107 | STDependent_ReportFile, // --ssaf-transformation-report-file= |
| 108 | }; |
| 109 | |
| 110 | /// Returns `true` if any orphan-option warning was reported. Every missing |
| 111 | /// companion option fires its own diagnostic in a single pass so the user |
| 112 | /// sees the full list of CLI mistakes at once. |
| 113 | static bool reportOrphanOptionMisuse(DiagnosticsEngine &Diags, |
| 114 | const SSAFOptions &Opts) { |
| 115 | bool Reported = false; |
| 116 | |
| 117 | if (!Opts.SourceTransformation.empty()) { |
| 118 | if (Opts.GlobalScopeAnalysisResult.empty()) { |
| 119 | Diags.Report(DiagID: diag::warn_ssaf_source_transformation_requires) |
| 120 | << STCompanion_WPAFile; |
| 121 | Reported = true; |
| 122 | } |
| 123 | if (Opts.SrcEditFile.empty()) { |
| 124 | Diags.Report(DiagID: diag::warn_ssaf_source_transformation_requires) |
| 125 | << STCompanion_EditFile; |
| 126 | Reported = true; |
| 127 | } |
| 128 | if (Opts.TransformationReportFile.empty()) { |
| 129 | Diags.Report(DiagID: diag::warn_ssaf_source_transformation_requires) |
| 130 | << STCompanion_ReportFile; |
| 131 | Reported = true; |
| 132 | } |
| 133 | if (Opts.CompilationUnitId.empty()) { |
| 134 | Diags.Report(DiagID: diag::warn_ssaf_source_transformation_requires) |
| 135 | << STCompanion_CompilationUnitId; |
| 136 | Reported = true; |
| 137 | } |
| 138 | } else { |
| 139 | if (!Opts.SrcEditFile.empty()) { |
| 140 | Diags.Report(DiagID: diag::warn_ssaf_option_ignored_without_source_transformation) |
| 141 | << STDependent_EditFile; |
| 142 | Reported = true; |
| 143 | } |
| 144 | if (!Opts.TransformationReportFile.empty()) { |
| 145 | Diags.Report(DiagID: diag::warn_ssaf_option_ignored_without_source_transformation) |
| 146 | << STDependent_ReportFile; |
| 147 | Reported = true; |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | return Reported; |
| 152 | } |
| 153 | |
| 154 | std::unique_ptr<SourceTransformationRunner> |
| 155 | SourceTransformationRunner::create(CompilerInstance &CI, StringRef InFile) { |
| 156 | const SSAFOptions &Opts = CI.getSSAFOpts(); |
| 157 | DiagnosticsEngine &Diags = CI.getDiagnostics(); |
| 158 | |
| 159 | if (reportOrphanOptionMisuse(Diags, Opts)) |
| 160 | return nullptr; |
| 161 | if (Opts.SourceTransformation.empty()) |
| 162 | return nullptr; |
| 163 | |
| 164 | if (!isTransformationRegistered(Name: Opts.SourceTransformation)) { |
| 165 | Diags.Report(DiagID: diag::warn_ssaf_source_transformation_unknown_name) |
| 166 | << Opts.SourceTransformation; |
| 167 | return nullptr; |
| 168 | } |
| 169 | |
| 170 | std::optional<StringRef> WPAExt = |
| 171 | bareExtension(Path: Opts.GlobalScopeAnalysisResult); |
| 172 | std::unique_ptr<SerializationFormat> WPAFormat = |
| 173 | WPAExt && isFormatRegistered(FormatName: *WPAExt) ? makeFormat(FormatName: *WPAExt) : nullptr; |
| 174 | if (!WPAFormat) { |
| 175 | Diags.Report(DiagID: diag::warn_ssaf_read_wpa_suite_failed) |
| 176 | << Opts.GlobalScopeAnalysisResult << "unknown serialization format" ; |
| 177 | return nullptr; |
| 178 | } |
| 179 | llvm::sys::sandbox::ScopedSetting Guard = llvm::sys::sandbox::scopedDisable(); |
| 180 | llvm::Expected<WPASuite> SuiteOrErr = |
| 181 | WPAFormat->readWPASuite(Path: Opts.GlobalScopeAnalysisResult); |
| 182 | if (!SuiteOrErr) { |
| 183 | Diags.Report(DiagID: diag::warn_ssaf_read_wpa_suite_failed) |
| 184 | << Opts.GlobalScopeAnalysisResult |
| 185 | << llvm::toString(E: SuiteOrErr.takeError()); |
| 186 | return nullptr; |
| 187 | } |
| 188 | |
| 189 | return std::unique_ptr<SourceTransformationRunner>{ |
| 190 | new SourceTransformationRunner(std::move(*SuiteOrErr), Opts, InFile)}; |
| 191 | } |
| 192 | |
| 193 | SourceTransformationRunner::SourceTransformationRunner(WPASuite Suite, |
| 194 | const SSAFOptions &Opts, |
| 195 | StringRef InFile) |
| 196 | : MultiplexConsumer(std::vector<std::unique_ptr<ASTConsumer>>{}), |
| 197 | Suite(std::move(Suite)), Opts(Opts), InFile(InFile) { |
| 198 | // The transformation must be constructed after Suite/Edits/Report start |
| 199 | // their lifetimes — those references are captured in its base ctor. |
| 200 | std::vector<std::unique_ptr<ASTConsumer>> Consumers; |
| 201 | Consumers.push_back(x: makeTransformation(Name: Opts.SourceTransformation, Suite: this->Suite, |
| 202 | Edits, Report)); |
| 203 | assert(Consumers.front()); |
| 204 | MultiplexConsumer::Consumers = std::move(Consumers); |
| 205 | } |
| 206 | |
| 207 | void SourceTransformationRunner::HandleTranslationUnit(ASTContext &Ctx) { |
| 208 | // First, run the transformation. |
| 209 | MultiplexConsumer::HandleTranslationUnit(Ctx); |
| 210 | |
| 211 | llvm::sys::sandbox::ScopedSetting Guard = llvm::sys::sandbox::scopedDisable(); |
| 212 | |
| 213 | // Then serialize the source edits. |
| 214 | clang::tooling::TranslationUnitReplacements EditDoc; |
| 215 | EditDoc.MainSourceFile = InFile; |
| 216 | EditDoc.Replacements = std::move(Edits.Replacements); |
| 217 | if (auto Err = writeYAMLSourceEdits(Doc: EditDoc, Path: Opts.SrcEditFile)) { |
| 218 | Ctx.getDiagnostics().Report(DiagID: diag::warn_ssaf_write_src_edit_failed) |
| 219 | << Opts.SrcEditFile << llvm::toString(E: std::move(Err)); |
| 220 | } |
| 221 | |
| 222 | // And the transformation report. |
| 223 | ReportDocument ReportDoc{.TransformationName: Opts.SourceTransformation, .SM: Ctx.getSourceManager(), |
| 224 | .Results: std::move(Report.Results)}; |
| 225 | if (auto Err = writeSARIFTransformationReport( |
| 226 | Doc: ReportDoc, Path: Opts.TransformationReportFile)) { |
| 227 | Ctx.getDiagnostics().Report( |
| 228 | DiagID: diag::warn_ssaf_write_transformation_report_failed) |
| 229 | << Opts.TransformationReportFile << llvm::toString(E: std::move(Err)); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | SourceTransformationFrontendAction::~SourceTransformationFrontendAction() = |
| 234 | default; |
| 235 | |
| 236 | SourceTransformationFrontendAction::SourceTransformationFrontendAction( |
| 237 | std::unique_ptr<FrontendAction> WrappedAction) |
| 238 | : WrapperFrontendAction(std::move(WrappedAction)) {} |
| 239 | |
| 240 | std::unique_ptr<ASTConsumer> |
| 241 | SourceTransformationFrontendAction::CreateASTConsumer(CompilerInstance &CI, |
| 242 | StringRef InFile) { |
| 243 | auto WrappedConsumer = WrapperFrontendAction::CreateASTConsumer(CI, InFile); |
| 244 | if (!WrappedConsumer) |
| 245 | return nullptr; |
| 246 | |
| 247 | if (auto Runner = SourceTransformationRunner::create(CI, InFile)) { |
| 248 | CI.getCodeGenOpts().ClearASTBeforeBackend = false; |
| 249 | std::vector<std::unique_ptr<ASTConsumer>> Consumers; |
| 250 | Consumers.reserve(n: 2); |
| 251 | Consumers.push_back(x: std::move(WrappedConsumer)); |
| 252 | Consumers.push_back(x: std::move(Runner)); |
| 253 | return std::make_unique<MultiplexConsumer>(args: std::move(Consumers)); |
| 254 | } |
| 255 | return WrappedConsumer; |
| 256 | } |
| 257 | |