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