Path: blob/main/contrib/llvm-project/clang/lib/Tooling/Transformer/Stencil.cpp
35294 views
//===--- Stencil.cpp - Stencil implementation -------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/Tooling/Transformer/Stencil.h"9#include "clang/AST/ASTContext.h"10#include "clang/AST/ASTTypeTraits.h"11#include "clang/AST/Expr.h"12#include "clang/ASTMatchers/ASTMatchFinder.h"13#include "clang/Basic/SourceLocation.h"14#include "clang/Lex/Lexer.h"15#include "clang/Tooling/Transformer/SourceCode.h"16#include "clang/Tooling/Transformer/SourceCodeBuilders.h"17#include "llvm/ADT/SmallVector.h"18#include "llvm/ADT/Twine.h"19#include "llvm/Support/Errc.h"20#include "llvm/Support/Error.h"21#include <atomic>22#include <memory>23#include <string>2425using namespace clang;26using namespace transformer;2728using ast_matchers::BoundNodes;29using ast_matchers::MatchFinder;30using llvm::errc;31using llvm::Error;32using llvm::Expected;33using llvm::StringError;3435static llvm::Expected<DynTypedNode> getNode(const BoundNodes &Nodes,36StringRef Id) {37auto &NodesMap = Nodes.getMap();38auto It = NodesMap.find(Id);39if (It == NodesMap.end())40return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,41"Id not bound: " + Id);42return It->second;43}4445static Error printNode(StringRef Id, const MatchFinder::MatchResult &Match,46std::string *Result) {47std::string Output;48llvm::raw_string_ostream Os(Output);49auto NodeOrErr = getNode(Match.Nodes, Id);50if (auto Err = NodeOrErr.takeError())51return Err;52NodeOrErr->print(Os, PrintingPolicy(Match.Context->getLangOpts()));53*Result += Output;54return Error::success();55}5657namespace {58// An arbitrary fragment of code within a stencil.59class RawTextStencil : public StencilInterface {60std::string Text;6162public:63explicit RawTextStencil(std::string T) : Text(std::move(T)) {}6465std::string toString() const override {66std::string Result;67llvm::raw_string_ostream OS(Result);68OS << "\"";69OS.write_escaped(Text);70OS << "\"";71OS.flush();72return Result;73}7475Error eval(const MatchFinder::MatchResult &Match,76std::string *Result) const override {77Result->append(Text);78return Error::success();79}80};8182// A debugging operation to dump the AST for a particular (bound) AST node.83class DebugPrintNodeStencil : public StencilInterface {84std::string Id;8586public:87explicit DebugPrintNodeStencil(std::string S) : Id(std::move(S)) {}8889std::string toString() const override {90return (llvm::Twine("dPrint(\"") + Id + "\")").str();91}9293Error eval(const MatchFinder::MatchResult &Match,94std::string *Result) const override {95return printNode(Id, Match, Result);96}97};9899// Operators that take a single node Id as an argument.100enum class UnaryNodeOperator {101Parens,102Deref,103MaybeDeref,104AddressOf,105MaybeAddressOf,106Describe,107};108109// Generic container for stencil operations with a (single) node-id argument.110class UnaryOperationStencil : public StencilInterface {111UnaryNodeOperator Op;112std::string Id;113114public:115UnaryOperationStencil(UnaryNodeOperator Op, std::string Id)116: Op(Op), Id(std::move(Id)) {}117118std::string toString() const override {119StringRef OpName;120switch (Op) {121case UnaryNodeOperator::Parens:122OpName = "expression";123break;124case UnaryNodeOperator::Deref:125OpName = "deref";126break;127case UnaryNodeOperator::MaybeDeref:128OpName = "maybeDeref";129break;130case UnaryNodeOperator::AddressOf:131OpName = "addressOf";132break;133case UnaryNodeOperator::MaybeAddressOf:134OpName = "maybeAddressOf";135break;136case UnaryNodeOperator::Describe:137OpName = "describe";138break;139}140return (OpName + "(\"" + Id + "\")").str();141}142143Error eval(const MatchFinder::MatchResult &Match,144std::string *Result) const override {145// The `Describe` operation can be applied to any node, not just146// expressions, so it is handled here, separately.147if (Op == UnaryNodeOperator::Describe)148return printNode(Id, Match, Result);149150const auto *E = Match.Nodes.getNodeAs<Expr>(Id);151if (E == nullptr)152return llvm::make_error<StringError>(errc::invalid_argument,153"Id not bound or not Expr: " + Id);154std::optional<std::string> Source;155switch (Op) {156case UnaryNodeOperator::Parens:157Source = tooling::buildParens(*E, *Match.Context);158break;159case UnaryNodeOperator::Deref:160Source = tooling::buildDereference(*E, *Match.Context);161break;162case UnaryNodeOperator::MaybeDeref:163if (E->getType()->isAnyPointerType() ||164tooling::isKnownPointerLikeType(E->getType(), *Match.Context)) {165// Strip off any operator->. This can only occur inside an actual arrow166// member access, so we treat it as equivalent to an actual object167// expression.168if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(E)) {169if (OpCall->getOperator() == clang::OO_Arrow &&170OpCall->getNumArgs() == 1) {171E = OpCall->getArg(0);172}173}174Source = tooling::buildDereference(*E, *Match.Context);175break;176}177*Result += tooling::getText(*E, *Match.Context);178return Error::success();179case UnaryNodeOperator::AddressOf:180Source = tooling::buildAddressOf(*E, *Match.Context);181break;182case UnaryNodeOperator::MaybeAddressOf:183if (E->getType()->isAnyPointerType() ||184tooling::isKnownPointerLikeType(E->getType(), *Match.Context)) {185// Strip off any operator->. This can only occur inside an actual arrow186// member access, so we treat it as equivalent to an actual object187// expression.188if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(E)) {189if (OpCall->getOperator() == clang::OO_Arrow &&190OpCall->getNumArgs() == 1) {191E = OpCall->getArg(0);192}193}194*Result += tooling::getText(*E, *Match.Context);195return Error::success();196}197Source = tooling::buildAddressOf(*E, *Match.Context);198break;199case UnaryNodeOperator::Describe:200llvm_unreachable("This case is handled at the start of the function");201}202if (!Source)203return llvm::make_error<StringError>(204errc::invalid_argument,205"Could not construct expression source from ID: " + Id);206*Result += *Source;207return Error::success();208}209};210211// The fragment of code corresponding to the selected range.212class SelectorStencil : public StencilInterface {213RangeSelector Selector;214215public:216explicit SelectorStencil(RangeSelector S) : Selector(std::move(S)) {}217218std::string toString() const override { return "selection(...)"; }219220Error eval(const MatchFinder::MatchResult &Match,221std::string *Result) const override {222auto RawRange = Selector(Match);223if (!RawRange)224return RawRange.takeError();225CharSourceRange Range = Lexer::makeFileCharRange(226*RawRange, *Match.SourceManager, Match.Context->getLangOpts());227if (Range.isInvalid()) {228// Validate the original range to attempt to get a meaningful error229// message. If it's valid, then something else is the cause and we just230// return the generic failure message.231if (auto Err = tooling::validateRange(*RawRange, *Match.SourceManager,232/*AllowSystemHeaders=*/true))233return handleErrors(std::move(Err), [](std::unique_ptr<StringError> E) {234assert(E->convertToErrorCode() ==235llvm::make_error_code(errc::invalid_argument) &&236"Validation errors must carry the invalid_argument code");237return llvm::createStringError(238errc::invalid_argument,239"selected range could not be resolved to a valid source range; " +240E->getMessage());241});242return llvm::createStringError(243errc::invalid_argument,244"selected range could not be resolved to a valid source range");245}246// Validate `Range`, because `makeFileCharRange` accepts some ranges that247// `validateRange` rejects.248if (auto Err = tooling::validateRange(Range, *Match.SourceManager,249/*AllowSystemHeaders=*/true))250return joinErrors(251llvm::createStringError(errc::invalid_argument,252"selected range is not valid for editing"),253std::move(Err));254*Result += tooling::getText(Range, *Match.Context);255return Error::success();256}257};258259// A stencil operation to build a member access `e.m` or `e->m`, as appropriate.260class AccessStencil : public StencilInterface {261std::string BaseId;262Stencil Member;263264public:265AccessStencil(StringRef BaseId, Stencil Member)266: BaseId(std::string(BaseId)), Member(std::move(Member)) {}267268std::string toString() const override {269return (llvm::Twine("access(\"") + BaseId + "\", " + Member->toString() +270")")271.str();272}273274Error eval(const MatchFinder::MatchResult &Match,275std::string *Result) const override {276const auto *E = Match.Nodes.getNodeAs<Expr>(BaseId);277if (E == nullptr)278return llvm::make_error<StringError>(errc::invalid_argument,279"Id not bound: " + BaseId);280std::optional<std::string> S = tooling::buildAccess(*E, *Match.Context);281if (!S)282return llvm::make_error<StringError>(283errc::invalid_argument,284"Could not construct object text from ID: " + BaseId);285*Result += *S;286return Member->eval(Match, Result);287}288};289290class IfBoundStencil : public StencilInterface {291std::string Id;292Stencil TrueStencil;293Stencil FalseStencil;294295public:296IfBoundStencil(StringRef Id, Stencil TrueStencil, Stencil FalseStencil)297: Id(std::string(Id)), TrueStencil(std::move(TrueStencil)),298FalseStencil(std::move(FalseStencil)) {}299300std::string toString() const override {301return (llvm::Twine("ifBound(\"") + Id + "\", " + TrueStencil->toString() +302", " + FalseStencil->toString() + ")")303.str();304}305306Error eval(const MatchFinder::MatchResult &Match,307std::string *Result) const override {308auto &M = Match.Nodes.getMap();309return (M.find(Id) != M.end() ? TrueStencil : FalseStencil)310->eval(Match, Result);311}312};313314class SelectBoundStencil : public clang::transformer::StencilInterface {315static bool containsNoNullStencils(316const std::vector<std::pair<std::string, Stencil>> &Cases) {317for (const auto &S : Cases)318if (S.second == nullptr)319return false;320return true;321}322323public:324SelectBoundStencil(std::vector<std::pair<std::string, Stencil>> Cases,325Stencil Default)326: CaseStencils(std::move(Cases)), DefaultStencil(std::move(Default)) {327assert(containsNoNullStencils(CaseStencils) &&328"cases of selectBound may not be null");329}330~SelectBoundStencil() override {}331332llvm::Error eval(const MatchFinder::MatchResult &match,333std::string *result) const override {334const BoundNodes::IDToNodeMap &NodeMap = match.Nodes.getMap();335for (const auto &S : CaseStencils) {336if (NodeMap.count(S.first) > 0) {337return S.second->eval(match, result);338}339}340341if (DefaultStencil != nullptr) {342return DefaultStencil->eval(match, result);343}344345llvm::SmallVector<llvm::StringRef, 2> CaseIDs;346CaseIDs.reserve(CaseStencils.size());347for (const auto &S : CaseStencils)348CaseIDs.emplace_back(S.first);349350return llvm::createStringError(351errc::result_out_of_range,352llvm::Twine("selectBound failed: no cases bound and no default: {") +353llvm::join(CaseIDs, ", ") + "}");354}355356std::string toString() const override {357std::string Buffer;358llvm::raw_string_ostream Stream(Buffer);359Stream << "selectBound({";360bool First = true;361for (const auto &S : CaseStencils) {362if (First)363First = false;364else365Stream << "}, ";366Stream << "{\"" << S.first << "\", " << S.second->toString();367}368Stream << "}}";369if (DefaultStencil != nullptr) {370Stream << ", " << DefaultStencil->toString();371}372Stream << ")";373return Buffer;374}375376private:377std::vector<std::pair<std::string, Stencil>> CaseStencils;378Stencil DefaultStencil;379};380381class SequenceStencil : public StencilInterface {382std::vector<Stencil> Stencils;383384public:385SequenceStencil(std::vector<Stencil> Stencils)386: Stencils(std::move(Stencils)) {}387388std::string toString() const override {389llvm::SmallVector<std::string, 2> Parts;390Parts.reserve(Stencils.size());391for (const auto &S : Stencils)392Parts.push_back(S->toString());393return (llvm::Twine("seq(") + llvm::join(Parts, ", ") + ")").str();394}395396Error eval(const MatchFinder::MatchResult &Match,397std::string *Result) const override {398for (const auto &S : Stencils)399if (auto Err = S->eval(Match, Result))400return Err;401return Error::success();402}403};404405class RunStencil : public StencilInterface {406MatchConsumer<std::string> Consumer;407408public:409explicit RunStencil(MatchConsumer<std::string> C) : Consumer(std::move(C)) {}410411std::string toString() const override { return "run(...)"; }412413Error eval(const MatchFinder::MatchResult &Match,414std::string *Result) const override {415416Expected<std::string> Value = Consumer(Match);417if (!Value)418return Value.takeError();419*Result += *Value;420return Error::success();421}422};423} // namespace424425Stencil transformer::detail::makeStencil(StringRef Text) {426return std::make_shared<RawTextStencil>(std::string(Text));427}428429Stencil transformer::detail::makeStencil(RangeSelector Selector) {430return std::make_shared<SelectorStencil>(std::move(Selector));431}432433Stencil transformer::dPrint(StringRef Id) {434return std::make_shared<DebugPrintNodeStencil>(std::string(Id));435}436437Stencil transformer::expression(llvm::StringRef Id) {438return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Parens,439std::string(Id));440}441442Stencil transformer::deref(llvm::StringRef ExprId) {443return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Deref,444std::string(ExprId));445}446447Stencil transformer::maybeDeref(llvm::StringRef ExprId) {448return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::MaybeDeref,449std::string(ExprId));450}451452Stencil transformer::addressOf(llvm::StringRef ExprId) {453return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::AddressOf,454std::string(ExprId));455}456457Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) {458return std::make_shared<UnaryOperationStencil>(459UnaryNodeOperator::MaybeAddressOf, std::string(ExprId));460}461462Stencil transformer::describe(StringRef Id) {463return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Describe,464std::string(Id));465}466467Stencil transformer::access(StringRef BaseId, Stencil Member) {468return std::make_shared<AccessStencil>(BaseId, std::move(Member));469}470471Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil,472Stencil FalseStencil) {473return std::make_shared<IfBoundStencil>(Id, std::move(TrueStencil),474std::move(FalseStencil));475}476477Stencil transformer::selectBound(478std::vector<std::pair<std::string, Stencil>> CaseStencils,479Stencil DefaultStencil) {480return std::make_shared<SelectBoundStencil>(std::move(CaseStencils),481std::move(DefaultStencil));482}483484Stencil transformer::run(MatchConsumer<std::string> Fn) {485return std::make_shared<RunStencil>(std::move(Fn));486}487488Stencil transformer::catVector(std::vector<Stencil> Parts) {489// Only one argument, so don't wrap in sequence.490if (Parts.size() == 1)491return std::move(Parts[0]);492return std::make_shared<SequenceStencil>(std::move(Parts));493}494495496