1//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===//
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/Tooling/Transformer/Stencil.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/ASTTypeTraits.h"
12#include "clang/AST/Expr.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include "clang/Basic/SourceLocation.h"
15#include "clang/Lex/Lexer.h"
16#include "clang/Tooling/Transformer/SourceCode.h"
17#include "clang/Tooling/Transformer/SourceCodeBuilders.h"
18#include "llvm/ADT/SmallVector.h"
19#include "llvm/ADT/Twine.h"
20#include "llvm/Support/Errc.h"
21#include "llvm/Support/Error.h"
22#include <memory>
23#include <string>
24
25using namespace clang;
26using namespace transformer;
27
28using ast_matchers::BoundNodes;
29using ast_matchers::MatchFinder;
30using llvm::errc;
31using llvm::Error;
32using llvm::Expected;
33using llvm::StringError;
34
35static llvm::Expected<DynTypedNode> getNode(const BoundNodes &Nodes,
36 StringRef Id) {
37 auto &NodesMap = Nodes.getMap();
38 auto It = NodesMap.find(x: Id);
39 if (It == NodesMap.end())
40 return llvm::make_error<llvm::StringError>(Args: llvm::errc::invalid_argument,
41 Args: "Id not bound: " + Id);
42 return It->second;
43}
44
45static Error printNode(StringRef Id, const MatchFinder::MatchResult &Match,
46 std::string *Result) {
47 std::string Output;
48 llvm::raw_string_ostream Os(Output);
49 auto NodeOrErr = getNode(Match.Nodes, Id);
50 if (auto Err = NodeOrErr.takeError())
51 return Err;
52 const PrintingPolicy PP(Match.Context->getLangOpts());
53 if (const auto *ND = NodeOrErr->get<NamedDecl>()) {
54 // For NamedDecls, we can do a better job than printing the whole thing.
55 ND->getNameForDiagnostic(Os, PP, false);
56 } else {
57 NodeOrErr->print(Os, PP);
58 }
59 *Result += Output;
60 return Error::success();
61}
62
63namespace {
64// An arbitrary fragment of code within a stencil.
65class RawTextStencil : public StencilInterface {
66 std::string Text;
67
68public:
69 explicit RawTextStencil(std::string T) : Text(std::move(T)) {}
70
71 std::string toString() const override {
72 std::string Result;
73 llvm::raw_string_ostream OS(Result);
74 OS << "\"";
75 OS.write_escaped(Str: Text);
76 OS << "\"";
77 return Result;
78 }
79
80 Error eval(const MatchFinder::MatchResult &Match,
81 std::string *Result) const override {
82 Result->append(str: Text);
83 return Error::success();
84 }
85};
86
87// A debugging operation to dump the AST for a particular (bound) AST node.
88class DebugPrintNodeStencil : public StencilInterface {
89 std::string Id;
90
91public:
92 explicit DebugPrintNodeStencil(std::string S) : Id(std::move(S)) {}
93
94 std::string toString() const override {
95 return (llvm::Twine("dPrint(\"") + Id + "\")").str();
96 }
97
98 Error eval(const MatchFinder::MatchResult &Match,
99 std::string *Result) const override {
100 return printNode(Id, Match, Result);
101 }
102};
103
104// Operators that take a single node Id as an argument.
105enum class UnaryNodeOperator {
106 Parens,
107 Deref,
108 MaybeDeref,
109 AddressOf,
110 MaybeAddressOf,
111 Describe,
112};
113
114// Generic container for stencil operations with a (single) node-id argument.
115class UnaryOperationStencil : public StencilInterface {
116 UnaryNodeOperator Op;
117 std::string Id;
118
119public:
120 UnaryOperationStencil(UnaryNodeOperator Op, std::string Id)
121 : Op(Op), Id(std::move(Id)) {}
122
123 std::string toString() const override {
124 StringRef OpName;
125 switch (Op) {
126 case UnaryNodeOperator::Parens:
127 OpName = "expression";
128 break;
129 case UnaryNodeOperator::Deref:
130 OpName = "deref";
131 break;
132 case UnaryNodeOperator::MaybeDeref:
133 OpName = "maybeDeref";
134 break;
135 case UnaryNodeOperator::AddressOf:
136 OpName = "addressOf";
137 break;
138 case UnaryNodeOperator::MaybeAddressOf:
139 OpName = "maybeAddressOf";
140 break;
141 case UnaryNodeOperator::Describe:
142 OpName = "describe";
143 break;
144 }
145 return (OpName + "(\"" + Id + "\")").str();
146 }
147
148 Error eval(const MatchFinder::MatchResult &Match,
149 std::string *Result) const override {
150 // The `Describe` operation can be applied to any node, not just
151 // expressions, so it is handled here, separately.
152 if (Op == UnaryNodeOperator::Describe)
153 return printNode(Id, Match, Result);
154
155 const auto *E = Match.Nodes.getNodeAs<Expr>(ID: Id);
156 if (E == nullptr)
157 return llvm::make_error<StringError>(Args: errc::invalid_argument,
158 Args: "Id not bound or not Expr: " + Id);
159 std::optional<std::string> Source;
160 switch (Op) {
161 case UnaryNodeOperator::Parens:
162 Source = tooling::buildParens(E: *E, Context: *Match.Context);
163 break;
164 case UnaryNodeOperator::Deref:
165 Source = tooling::buildDereference(E: *E, Context: *Match.Context);
166 break;
167 case UnaryNodeOperator::MaybeDeref:
168 if (E->getType()->isAnyPointerType() ||
169 tooling::isKnownPointerLikeType(Ty: E->getType(), Context&: *Match.Context)) {
170 // Strip off any operator->. This can only occur inside an actual arrow
171 // member access, so we treat it as equivalent to an actual object
172 // expression.
173 if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(Val: E)) {
174 if (OpCall->getOperator() == clang::OO_Arrow &&
175 OpCall->getNumArgs() == 1) {
176 E = OpCall->getArg(0);
177 }
178 }
179 Source = tooling::buildDereference(E: *E, Context: *Match.Context);
180 break;
181 }
182 *Result += tooling::getText(Node: *E, Context: *Match.Context);
183 return Error::success();
184 case UnaryNodeOperator::AddressOf:
185 Source = tooling::buildAddressOf(E: *E, Context: *Match.Context);
186 break;
187 case UnaryNodeOperator::MaybeAddressOf:
188 if (E->getType()->isAnyPointerType() ||
189 tooling::isKnownPointerLikeType(Ty: E->getType(), Context&: *Match.Context)) {
190 // Strip off any operator->. This can only occur inside an actual arrow
191 // member access, so we treat it as equivalent to an actual object
192 // expression.
193 if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(Val: E)) {
194 if (OpCall->getOperator() == clang::OO_Arrow &&
195 OpCall->getNumArgs() == 1) {
196 E = OpCall->getArg(0);
197 }
198 }
199 *Result += tooling::getText(Node: *E, Context: *Match.Context);
200 return Error::success();
201 }
202 Source = tooling::buildAddressOf(E: *E, Context: *Match.Context);
203 break;
204 case UnaryNodeOperator::Describe:
205 llvm_unreachable("This case is handled at the start of the function");
206 }
207 if (!Source)
208 return llvm::make_error<StringError>(
209 Args: errc::invalid_argument,
210 Args: "Could not construct expression source from ID: " + Id);
211 *Result += *Source;
212 return Error::success();
213 }
214};
215
216// The fragment of code corresponding to the selected range.
217class SelectorStencil : public StencilInterface {
218 RangeSelector Selector;
219
220public:
221 explicit SelectorStencil(RangeSelector S) : Selector(std::move(S)) {}
222
223 std::string toString() const override { return "selection(...)"; }
224
225 Error eval(const MatchFinder::MatchResult &Match,
226 std::string *Result) const override {
227 auto RawRange = Selector(Match);
228 if (!RawRange)
229 return RawRange.takeError();
230 CharSourceRange Range = Lexer::makeFileCharRange(
231 Range: *RawRange, SM: *Match.SourceManager, LangOpts: Match.Context->getLangOpts());
232 if (Range.isInvalid()) {
233 // Validate the original range to attempt to get a meaningful error
234 // message. If it's valid, then something else is the cause and we just
235 // return the generic failure message.
236 if (auto Err = tooling::validateRange(Range: *RawRange, SM: *Match.SourceManager,
237 /*AllowSystemHeaders=*/true))
238 return handleErrors(E: std::move(Err), Hs: [](std::unique_ptr<StringError> E) {
239 assert(E->convertToErrorCode() ==
240 llvm::make_error_code(errc::invalid_argument) &&
241 "Validation errors must carry the invalid_argument code");
242 return llvm::createStringError(
243 EC: errc::invalid_argument,
244 S: "selected range could not be resolved to a valid source range; " +
245 E->getMessage());
246 });
247 return llvm::createStringError(
248 EC: errc::invalid_argument,
249 S: "selected range could not be resolved to a valid source range");
250 }
251 // Validate `Range`, because `makeFileCharRange` accepts some ranges that
252 // `validateRange` rejects.
253 if (auto Err = tooling::validateRange(Range, SM: *Match.SourceManager,
254 /*AllowSystemHeaders=*/true))
255 return joinErrors(
256 E1: llvm::createStringError(EC: errc::invalid_argument,
257 S: "selected range is not valid for editing"),
258 E2: std::move(Err));
259 *Result += tooling::getText(Range, Context: *Match.Context);
260 return Error::success();
261 }
262};
263
264// A stencil operation to build a member access `e.m` or `e->m`, as appropriate.
265class AccessStencil : public StencilInterface {
266 std::string BaseId;
267 Stencil Member;
268
269public:
270 AccessStencil(StringRef BaseId, Stencil Member)
271 : BaseId(std::string(BaseId)), Member(std::move(Member)) {}
272
273 std::string toString() const override {
274 return (llvm::Twine("access(\"") + BaseId + "\", " + Member->toString() +
275 ")")
276 .str();
277 }
278
279 Error eval(const MatchFinder::MatchResult &Match,
280 std::string *Result) const override {
281 const auto *E = Match.Nodes.getNodeAs<Expr>(ID: BaseId);
282 if (E == nullptr)
283 return llvm::make_error<StringError>(Args: errc::invalid_argument,
284 Args: "Id not bound: " + BaseId);
285 std::optional<std::string> S = tooling::buildAccess(E: *E, Context&: *Match.Context);
286 if (!S)
287 return llvm::make_error<StringError>(
288 Args: errc::invalid_argument,
289 Args: "Could not construct object text from ID: " + BaseId);
290 *Result += *S;
291 return Member->eval(Match, Result);
292 }
293};
294
295class IfBoundStencil : public StencilInterface {
296 std::string Id;
297 Stencil TrueStencil;
298 Stencil FalseStencil;
299
300public:
301 IfBoundStencil(StringRef Id, Stencil TrueStencil, Stencil FalseStencil)
302 : Id(std::string(Id)), TrueStencil(std::move(TrueStencil)),
303 FalseStencil(std::move(FalseStencil)) {}
304
305 std::string toString() const override {
306 return (llvm::Twine("ifBound(\"") + Id + "\", " + TrueStencil->toString() +
307 ", " + FalseStencil->toString() + ")")
308 .str();
309 }
310
311 Error eval(const MatchFinder::MatchResult &Match,
312 std::string *Result) const override {
313 auto &M = Match.Nodes.getMap();
314 return (M.find(x: Id) != M.end() ? TrueStencil : FalseStencil)
315 ->eval(Match, Result);
316 }
317};
318
319class SelectBoundStencil : public clang::transformer::StencilInterface {
320 static bool containsNoNullStencils(
321 const std::vector<std::pair<std::string, Stencil>> &Cases) {
322 for (const auto &S : Cases)
323 if (S.second == nullptr)
324 return false;
325 return true;
326 }
327
328public:
329 SelectBoundStencil(std::vector<std::pair<std::string, Stencil>> Cases,
330 Stencil Default)
331 : CaseStencils(std::move(Cases)), DefaultStencil(std::move(Default)) {
332 assert(containsNoNullStencils(CaseStencils) &&
333 "cases of selectBound may not be null");
334 }
335 ~SelectBoundStencil() override {}
336
337 llvm::Error eval(const MatchFinder::MatchResult &match,
338 std::string *result) const override {
339 const BoundNodes::IDToNodeMap &NodeMap = match.Nodes.getMap();
340 for (const auto &S : CaseStencils) {
341 if (NodeMap.count(x: S.first) > 0) {
342 return S.second->eval(Match: match, Result: result);
343 }
344 }
345
346 if (DefaultStencil != nullptr) {
347 return DefaultStencil->eval(Match: match, Result: result);
348 }
349
350 llvm::SmallVector<llvm::StringRef, 2> CaseIDs;
351 CaseIDs.reserve(N: CaseStencils.size());
352 for (const auto &S : CaseStencils)
353 CaseIDs.emplace_back(Args: S.first);
354
355 return llvm::createStringError(
356 EC: errc::result_out_of_range,
357 S: llvm::Twine("selectBound failed: no cases bound and no default: {") +
358 llvm::join(R&: CaseIDs, Separator: ", ") + "}");
359 }
360
361 std::string toString() const override {
362 std::string Buffer;
363 llvm::raw_string_ostream Stream(Buffer);
364 Stream << "selectBound({";
365 bool First = true;
366 for (const auto &S : CaseStencils) {
367 if (First)
368 First = false;
369 else
370 Stream << "}, ";
371 Stream << "{\"" << S.first << "\", " << S.second->toString();
372 }
373 Stream << "}}";
374 if (DefaultStencil != nullptr) {
375 Stream << ", " << DefaultStencil->toString();
376 }
377 Stream << ")";
378 return Buffer;
379 }
380
381private:
382 std::vector<std::pair<std::string, Stencil>> CaseStencils;
383 Stencil DefaultStencil;
384};
385
386class SequenceStencil : public StencilInterface {
387 std::vector<Stencil> Stencils;
388
389public:
390 SequenceStencil(std::vector<Stencil> Stencils)
391 : Stencils(std::move(Stencils)) {}
392
393 std::string toString() const override {
394 llvm::SmallVector<std::string, 2> Parts;
395 Parts.reserve(N: Stencils.size());
396 for (const auto &S : Stencils)
397 Parts.push_back(Elt: S->toString());
398 return (llvm::Twine("seq(") + llvm::join(R&: Parts, Separator: ", ") + ")").str();
399 }
400
401 Error eval(const MatchFinder::MatchResult &Match,
402 std::string *Result) const override {
403 for (const auto &S : Stencils)
404 if (auto Err = S->eval(Match, Result))
405 return Err;
406 return Error::success();
407 }
408};
409
410class RunStencil : public StencilInterface {
411 MatchConsumer<std::string> Consumer;
412
413public:
414 explicit RunStencil(MatchConsumer<std::string> C) : Consumer(std::move(C)) {}
415
416 std::string toString() const override { return "run(...)"; }
417
418 Error eval(const MatchFinder::MatchResult &Match,
419 std::string *Result) const override {
420
421 Expected<std::string> Value = Consumer(Match);
422 if (!Value)
423 return Value.takeError();
424 *Result += *Value;
425 return Error::success();
426 }
427};
428} // namespace
429
430Stencil transformer::detail::makeStencil(StringRef Text) {
431 return std::make_shared<RawTextStencil>(args: std::string(Text));
432}
433
434Stencil transformer::detail::makeStencil(RangeSelector Selector) {
435 return std::make_shared<SelectorStencil>(args: std::move(Selector));
436}
437
438Stencil transformer::dPrint(StringRef Id) {
439 return std::make_shared<DebugPrintNodeStencil>(args: std::string(Id));
440}
441
442Stencil transformer::expression(llvm::StringRef Id) {
443 return std::make_shared<UnaryOperationStencil>(args: UnaryNodeOperator::Parens,
444 args: std::string(Id));
445}
446
447Stencil transformer::deref(llvm::StringRef ExprId) {
448 return std::make_shared<UnaryOperationStencil>(args: UnaryNodeOperator::Deref,
449 args: std::string(ExprId));
450}
451
452Stencil transformer::maybeDeref(llvm::StringRef ExprId) {
453 return std::make_shared<UnaryOperationStencil>(args: UnaryNodeOperator::MaybeDeref,
454 args: std::string(ExprId));
455}
456
457Stencil transformer::addressOf(llvm::StringRef ExprId) {
458 return std::make_shared<UnaryOperationStencil>(args: UnaryNodeOperator::AddressOf,
459 args: std::string(ExprId));
460}
461
462Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) {
463 return std::make_shared<UnaryOperationStencil>(
464 args: UnaryNodeOperator::MaybeAddressOf, args: std::string(ExprId));
465}
466
467Stencil transformer::describe(StringRef Id) {
468 return std::make_shared<UnaryOperationStencil>(args: UnaryNodeOperator::Describe,
469 args: std::string(Id));
470}
471
472Stencil transformer::access(StringRef BaseId, Stencil Member) {
473 return std::make_shared<AccessStencil>(args&: BaseId, args: std::move(Member));
474}
475
476Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil,
477 Stencil FalseStencil) {
478 return std::make_shared<IfBoundStencil>(args&: Id, args: std::move(TrueStencil),
479 args: std::move(FalseStencil));
480}
481
482Stencil transformer::selectBound(
483 std::vector<std::pair<std::string, Stencil>> CaseStencils,
484 Stencil DefaultStencil) {
485 return std::make_shared<SelectBoundStencil>(args: std::move(CaseStencils),
486 args: std::move(DefaultStencil));
487}
488
489Stencil transformer::run(MatchConsumer<std::string> Fn) {
490 return std::make_shared<RunStencil>(args: std::move(Fn));
491}
492
493Stencil transformer::catVector(std::vector<Stencil> Parts) {
494 // Only one argument, so don't wrap in sequence.
495 if (Parts.size() == 1)
496 return std::move(Parts[0]);
497 return std::make_shared<SequenceStencil>(args: std::move(Parts));
498}
499

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang/lib/Tooling/Transformer/Stencil.cpp