//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "clang/Tooling/Transformer/Stencil.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Expr.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/Lexer.h" #include "clang/Tooling/Transformer/SourceCode.h" #include "clang/Tooling/Transformer/SourceCodeBuilders.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include #include #include using namespace clang; using namespace transformer; using ast_matchers::MatchFinder; using llvm::errc; using llvm::Error; using llvm::Expected; using llvm::StringError; static llvm::Expected getNode(const ast_matchers::BoundNodes &Nodes, StringRef Id) { auto &NodesMap = Nodes.getMap(); auto It = NodesMap.find(Id); if (It == NodesMap.end()) return llvm::make_error(llvm::errc::invalid_argument, "Id not bound: " + Id); return It->second; } static Error printNode(StringRef Id, const MatchFinder::MatchResult &Match, std::string *Result) { std::string Output; llvm::raw_string_ostream Os(Output); auto NodeOrErr = getNode(Match.Nodes, Id); if (auto Err = NodeOrErr.takeError()) return Err; NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts())); *Result += Os.str(); return Error::success(); } // FIXME: Consider memoizing this function using the `ASTContext`. static bool isSmartPointerType(QualType Ty, ASTContext &Context) { using namespace ::clang::ast_matchers; // Optimization: hard-code common smart-pointer types. This can/should be // removed if we start caching the results of this function. auto KnownSmartPointer = cxxRecordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); const auto QuacksLikeASmartPointer = cxxRecordDecl( hasMethod(cxxMethodDecl(hasOverloadedOperatorName("->"), returns(qualType(pointsTo(type()))))), hasMethod(cxxMethodDecl(hasOverloadedOperatorName("*"), returns(qualType(references(type())))))); const auto SmartPointer = qualType(hasDeclaration( cxxRecordDecl(anyOf(KnownSmartPointer, QuacksLikeASmartPointer)))); return match(SmartPointer, Ty, Context).size() > 0; } // Identifies use of `operator*` on smart pointers, and returns the underlying // smart-pointer expression; otherwise, returns null. static const Expr *isSmartDereference(const Expr &E, ASTContext &Context) { using namespace ::clang::ast_matchers; const auto HasOverloadedArrow = cxxRecordDecl(hasMethod(cxxMethodDecl( hasOverloadedOperatorName("->"), returns(qualType(pointsTo(type())))))); // Verify it is a smart pointer by finding `operator->` in the class // declaration. auto Deref = cxxOperatorCallExpr( hasOverloadedOperatorName("*"), hasUnaryOperand(expr().bind("arg")), callee(cxxMethodDecl(ofClass(HasOverloadedArrow)))); return selectFirst("arg", match(Deref, E, Context)); } namespace { // An arbitrary fragment of code within a stencil. class RawTextStencil : public StencilInterface { std::string Text; public: explicit RawTextStencil(std::string T) : Text(std::move(T)) {} std::string toString() const override { std::string Result; llvm::raw_string_ostream OS(Result); OS << "\""; OS.write_escaped(Text); OS << "\""; OS.flush(); return Result; } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { Result->append(Text); return Error::success(); } }; // A debugging operation to dump the AST for a particular (bound) AST node. class DebugPrintNodeStencil : public StencilInterface { std::string Id; public: explicit DebugPrintNodeStencil(std::string S) : Id(std::move(S)) {} std::string toString() const override { return (llvm::Twine("dPrint(\"") + Id + "\")").str(); } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { return printNode(Id, Match, Result); } }; // Operators that take a single node Id as an argument. enum class UnaryNodeOperator { Parens, Deref, MaybeDeref, AddressOf, MaybeAddressOf, Describe, }; // Generic container for stencil operations with a (single) node-id argument. class UnaryOperationStencil : public StencilInterface { UnaryNodeOperator Op; std::string Id; public: UnaryOperationStencil(UnaryNodeOperator Op, std::string Id) : Op(Op), Id(std::move(Id)) {} std::string toString() const override { StringRef OpName; switch (Op) { case UnaryNodeOperator::Parens: OpName = "expression"; break; case UnaryNodeOperator::Deref: OpName = "deref"; break; case UnaryNodeOperator::MaybeDeref: OpName = "maybeDeref"; break; case UnaryNodeOperator::AddressOf: OpName = "addressOf"; break; case UnaryNodeOperator::MaybeAddressOf: OpName = "maybeAddressOf"; break; case UnaryNodeOperator::Describe: OpName = "describe"; break; } return (OpName + "(\"" + Id + "\")").str(); } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { // The `Describe` operation can be applied to any node, not just // expressions, so it is handled here, separately. if (Op == UnaryNodeOperator::Describe) return printNode(Id, Match, Result); const auto *E = Match.Nodes.getNodeAs(Id); if (E == nullptr) return llvm::make_error(errc::invalid_argument, "Id not bound or not Expr: " + Id); llvm::Optional Source; switch (Op) { case UnaryNodeOperator::Parens: Source = tooling::buildParens(*E, *Match.Context); break; case UnaryNodeOperator::Deref: Source = tooling::buildDereference(*E, *Match.Context); break; case UnaryNodeOperator::MaybeDeref: if (E->getType()->isAnyPointerType() || isSmartPointerType(E->getType(), *Match.Context)) { // Strip off any operator->. This can only occur inside an actual arrow // member access, so we treat it as equivalent to an actual object // expression. if (const auto *OpCall = dyn_cast(E)) { if (OpCall->getOperator() == clang::OO_Arrow && OpCall->getNumArgs() == 1) { E = OpCall->getArg(0); } } Source = tooling::buildDereference(*E, *Match.Context); break; } *Result += tooling::getText(*E, *Match.Context); return Error::success(); case UnaryNodeOperator::AddressOf: Source = tooling::buildAddressOf(*E, *Match.Context); break; case UnaryNodeOperator::MaybeAddressOf: if (E->getType()->isAnyPointerType() || isSmartPointerType(E->getType(), *Match.Context)) { // Strip off any operator->. This can only occur inside an actual arrow // member access, so we treat it as equivalent to an actual object // expression. if (const auto *OpCall = dyn_cast(E)) { if (OpCall->getOperator() == clang::OO_Arrow && OpCall->getNumArgs() == 1) { E = OpCall->getArg(0); } } *Result += tooling::getText(*E, *Match.Context); return Error::success(); } Source = tooling::buildAddressOf(*E, *Match.Context); break; case UnaryNodeOperator::Describe: llvm_unreachable("This case is handled at the start of the function"); } if (!Source) return llvm::make_error( errc::invalid_argument, "Could not construct expression source from ID: " + Id); *Result += *Source; return Error::success(); } }; // The fragment of code corresponding to the selected range. class SelectorStencil : public StencilInterface { RangeSelector Selector; public: explicit SelectorStencil(RangeSelector S) : Selector(std::move(S)) {} std::string toString() const override { return "selection(...)"; } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { auto RawRange = Selector(Match); if (!RawRange) return RawRange.takeError(); CharSourceRange Range = Lexer::makeFileCharRange( *RawRange, *Match.SourceManager, Match.Context->getLangOpts()); if (Range.isInvalid()) { // Validate the original range to attempt to get a meaningful error // message. If it's valid, then something else is the cause and we just // return the generic failure message. if (auto Err = tooling::validateEditRange(*RawRange, *Match.SourceManager)) return handleErrors(std::move(Err), [](std::unique_ptr E) { assert(E->convertToErrorCode() == llvm::make_error_code(errc::invalid_argument) && "Validation errors must carry the invalid_argument code"); return llvm::createStringError( errc::invalid_argument, "selected range could not be resolved to a valid source range; " + E->getMessage()); }); return llvm::createStringError( errc::invalid_argument, "selected range could not be resolved to a valid source range"); } // Validate `Range`, because `makeFileCharRange` accepts some ranges that // `validateEditRange` rejects. if (auto Err = tooling::validateEditRange(Range, *Match.SourceManager)) return joinErrors( llvm::createStringError(errc::invalid_argument, "selected range is not valid for editing"), std::move(Err)); *Result += tooling::getText(Range, *Match.Context); return Error::success(); } }; // A stencil operation to build a member access `e.m` or `e->m`, as appropriate. class AccessStencil : public StencilInterface { std::string BaseId; Stencil Member; public: AccessStencil(StringRef BaseId, Stencil Member) : BaseId(std::string(BaseId)), Member(std::move(Member)) {} std::string toString() const override { return (llvm::Twine("access(\"") + BaseId + "\", " + Member->toString() + ")") .str(); } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { const auto *E = Match.Nodes.getNodeAs(BaseId); if (E == nullptr) return llvm::make_error(errc::invalid_argument, "Id not bound: " + BaseId); if (!E->isImplicitCXXThis()) { llvm::Optional S; if (E->getType()->isAnyPointerType() || isSmartPointerType(E->getType(), *Match.Context)) { // Strip off any operator->. This can only occur inside an actual arrow // member access, so we treat it as equivalent to an actual object // expression. if (const auto *OpCall = dyn_cast(E)) { if (OpCall->getOperator() == clang::OO_Arrow && OpCall->getNumArgs() == 1) { E = OpCall->getArg(0); } } S = tooling::buildArrow(*E, *Match.Context); } else if (const auto *Operand = isSmartDereference(*E, *Match.Context)) { // `buildDot` already handles the built-in dereference operator, so we // only need to catch overloaded `operator*`. S = tooling::buildArrow(*Operand, *Match.Context); } else { S = tooling::buildDot(*E, *Match.Context); } if (S.hasValue()) *Result += *S; else return llvm::make_error( errc::invalid_argument, "Could not construct object text from ID: " + BaseId); } return Member->eval(Match, Result); } }; class IfBoundStencil : public StencilInterface { std::string Id; Stencil TrueStencil; Stencil FalseStencil; public: IfBoundStencil(StringRef Id, Stencil TrueStencil, Stencil FalseStencil) : Id(std::string(Id)), TrueStencil(std::move(TrueStencil)), FalseStencil(std::move(FalseStencil)) {} std::string toString() const override { return (llvm::Twine("ifBound(\"") + Id + "\", " + TrueStencil->toString() + ", " + FalseStencil->toString() + ")") .str(); } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { auto &M = Match.Nodes.getMap(); return (M.find(Id) != M.end() ? TrueStencil : FalseStencil) ->eval(Match, Result); } }; class SequenceStencil : public StencilInterface { std::vector Stencils; public: SequenceStencil(std::vector Stencils) : Stencils(std::move(Stencils)) {} std::string toString() const override { llvm::SmallVector Parts; Parts.reserve(Stencils.size()); for (const auto &S : Stencils) Parts.push_back(S->toString()); return (llvm::Twine("seq(") + llvm::join(Parts, ", ") + ")").str(); } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { for (const auto &S : Stencils) if (auto Err = S->eval(Match, Result)) return Err; return Error::success(); } }; class RunStencil : public StencilInterface { MatchConsumer Consumer; public: explicit RunStencil(MatchConsumer C) : Consumer(std::move(C)) {} std::string toString() const override { return "run(...)"; } Error eval(const MatchFinder::MatchResult &Match, std::string *Result) const override { Expected Value = Consumer(Match); if (!Value) return Value.takeError(); *Result += *Value; return Error::success(); } }; } // namespace Stencil transformer::detail::makeStencil(StringRef Text) { return std::make_shared(std::string(Text)); } Stencil transformer::detail::makeStencil(RangeSelector Selector) { return std::make_shared(std::move(Selector)); } Stencil transformer::dPrint(StringRef Id) { return std::make_shared(std::string(Id)); } Stencil transformer::expression(llvm::StringRef Id) { return std::make_shared(UnaryNodeOperator::Parens, std::string(Id)); } Stencil transformer::deref(llvm::StringRef ExprId) { return std::make_shared(UnaryNodeOperator::Deref, std::string(ExprId)); } Stencil transformer::maybeDeref(llvm::StringRef ExprId) { return std::make_shared(UnaryNodeOperator::MaybeDeref, std::string(ExprId)); } Stencil transformer::addressOf(llvm::StringRef ExprId) { return std::make_shared(UnaryNodeOperator::AddressOf, std::string(ExprId)); } Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) { return std::make_shared( UnaryNodeOperator::MaybeAddressOf, std::string(ExprId)); } Stencil transformer::describe(StringRef Id) { return std::make_shared(UnaryNodeOperator::Describe, std::string(Id)); } Stencil transformer::access(StringRef BaseId, Stencil Member) { return std::make_shared(BaseId, std::move(Member)); } Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil, Stencil FalseStencil) { return std::make_shared(Id, std::move(TrueStencil), std::move(FalseStencil)); } Stencil transformer::run(MatchConsumer Fn) { return std::make_shared(std::move(Fn)); } Stencil transformer::catVector(std::vector Parts) { // Only one argument, so don't wrap in sequence. if (Parts.size() == 1) return std::move(Parts[0]); return std::make_shared(std::move(Parts)); }