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
33using namespace clang;
34using namespace ssaf;
35
36namespace {
37
38/// Concrete `SourceEditEmitter` that buffers replacements until flushed.
39class AccumulatorSourceEditEmitter final : public SourceEditEmitter {
40public:
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.
49class AccumulatorReportEmitter final : public TransformationReportEmitter {
50public:
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.
63class SourceTransformationRunner final : public MultiplexConsumer {
64public:
65 static std::unique_ptr<SourceTransformationRunner>
66 create(CompilerInstance &CI, StringRef InFile);
67
68private:
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.
85static 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`.
95enum 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`.
105enum 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.
113static 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
154std::unique_ptr<SourceTransformationRunner>
155SourceTransformationRunner::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
193SourceTransformationRunner::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
207void 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
233SourceTransformationFrontendAction::~SourceTransformationFrontendAction() =
234 default;
235
236SourceTransformationFrontendAction::SourceTransformationFrontendAction(
237 std::unique_ptr<FrontendAction> WrappedAction)
238 : WrapperFrontendAction(std::move(WrappedAction)) {}
239
240std::unique_ptr<ASTConsumer>
241SourceTransformationFrontendAction::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