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