499 lines · cpp
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-exception6//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(Id);39 if (It == NodesMap.end())40 return llvm::make_error<llvm::StringError>(llvm::errc::invalid_argument,41 "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(Text);76 OS << "\"";77 return Result;78 }79 80 Error eval(const MatchFinder::MatchResult &Match,81 std::string *Result) const override {82 Result->append(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 just151 // 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);156 if (E == nullptr)157 return llvm::make_error<StringError>(errc::invalid_argument,158 "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, *Match.Context);163 break;164 case UnaryNodeOperator::Deref:165 Source = tooling::buildDereference(*E, *Match.Context);166 break;167 case UnaryNodeOperator::MaybeDeref:168 if (E->getType()->isAnyPointerType() ||169 tooling::isKnownPointerLikeType(E->getType(), *Match.Context)) {170 // Strip off any operator->. This can only occur inside an actual arrow171 // member access, so we treat it as equivalent to an actual object172 // expression.173 if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(E)) {174 if (OpCall->getOperator() == clang::OO_Arrow &&175 OpCall->getNumArgs() == 1) {176 E = OpCall->getArg(0);177 }178 }179 Source = tooling::buildDereference(*E, *Match.Context);180 break;181 }182 *Result += tooling::getText(*E, *Match.Context);183 return Error::success();184 case UnaryNodeOperator::AddressOf:185 Source = tooling::buildAddressOf(*E, *Match.Context);186 break;187 case UnaryNodeOperator::MaybeAddressOf:188 if (E->getType()->isAnyPointerType() ||189 tooling::isKnownPointerLikeType(E->getType(), *Match.Context)) {190 // Strip off any operator->. This can only occur inside an actual arrow191 // member access, so we treat it as equivalent to an actual object192 // expression.193 if (const auto *OpCall = dyn_cast<clang::CXXOperatorCallExpr>(E)) {194 if (OpCall->getOperator() == clang::OO_Arrow &&195 OpCall->getNumArgs() == 1) {196 E = OpCall->getArg(0);197 }198 }199 *Result += tooling::getText(*E, *Match.Context);200 return Error::success();201 }202 Source = tooling::buildAddressOf(*E, *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 errc::invalid_argument,210 "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 *RawRange, *Match.SourceManager, Match.Context->getLangOpts());232 if (Range.isInvalid()) {233 // Validate the original range to attempt to get a meaningful error234 // message. If it's valid, then something else is the cause and we just235 // return the generic failure message.236 if (auto Err = tooling::validateRange(*RawRange, *Match.SourceManager,237 /*AllowSystemHeaders=*/true))238 return handleErrors(std::move(Err), [](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 errc::invalid_argument,244 "selected range could not be resolved to a valid source range; " +245 E->getMessage());246 });247 return llvm::createStringError(248 errc::invalid_argument,249 "selected range could not be resolved to a valid source range");250 }251 // Validate `Range`, because `makeFileCharRange` accepts some ranges that252 // `validateRange` rejects.253 if (auto Err = tooling::validateRange(Range, *Match.SourceManager,254 /*AllowSystemHeaders=*/true))255 return joinErrors(256 llvm::createStringError(errc::invalid_argument,257 "selected range is not valid for editing"),258 std::move(Err));259 *Result += tooling::getText(Range, *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>(BaseId);282 if (E == nullptr)283 return llvm::make_error<StringError>(errc::invalid_argument,284 "Id not bound: " + BaseId);285 std::optional<std::string> S = tooling::buildAccess(*E, *Match.Context);286 if (!S)287 return llvm::make_error<StringError>(288 errc::invalid_argument,289 "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(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(S.first) > 0) {342 return S.second->eval(match, result);343 }344 }345 346 if (DefaultStencil != nullptr) {347 return DefaultStencil->eval(match, result);348 }349 350 llvm::SmallVector<llvm::StringRef, 2> CaseIDs;351 CaseIDs.reserve(CaseStencils.size());352 for (const auto &S : CaseStencils)353 CaseIDs.emplace_back(S.first);354 355 return llvm::createStringError(356 errc::result_out_of_range,357 llvm::Twine("selectBound failed: no cases bound and no default: {") +358 llvm::join(CaseIDs, ", ") + "}");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 else370 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(Stencils.size());396 for (const auto &S : Stencils)397 Parts.push_back(S->toString());398 return (llvm::Twine("seq(") + llvm::join(Parts, ", ") + ")").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} // namespace429 430Stencil transformer::detail::makeStencil(StringRef Text) {431 return std::make_shared<RawTextStencil>(std::string(Text));432}433 434Stencil transformer::detail::makeStencil(RangeSelector Selector) {435 return std::make_shared<SelectorStencil>(std::move(Selector));436}437 438Stencil transformer::dPrint(StringRef Id) {439 return std::make_shared<DebugPrintNodeStencil>(std::string(Id));440}441 442Stencil transformer::expression(llvm::StringRef Id) {443 return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Parens,444 std::string(Id));445}446 447Stencil transformer::deref(llvm::StringRef ExprId) {448 return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Deref,449 std::string(ExprId));450}451 452Stencil transformer::maybeDeref(llvm::StringRef ExprId) {453 return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::MaybeDeref,454 std::string(ExprId));455}456 457Stencil transformer::addressOf(llvm::StringRef ExprId) {458 return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::AddressOf,459 std::string(ExprId));460}461 462Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) {463 return std::make_shared<UnaryOperationStencil>(464 UnaryNodeOperator::MaybeAddressOf, std::string(ExprId));465}466 467Stencil transformer::describe(StringRef Id) {468 return std::make_shared<UnaryOperationStencil>(UnaryNodeOperator::Describe,469 std::string(Id));470}471 472Stencil transformer::access(StringRef BaseId, Stencil Member) {473 return std::make_shared<AccessStencil>(BaseId, std::move(Member));474}475 476Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil,477 Stencil FalseStencil) {478 return std::make_shared<IfBoundStencil>(Id, std::move(TrueStencil),479 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>(std::move(CaseStencils),486 std::move(DefaultStencil));487}488 489Stencil transformer::run(MatchConsumer<std::string> Fn) {490 return std::make_shared<RunStencil>(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>(std::move(Parts));498}499