Files
clang-p2996/clang/lib/Tooling/Transformer/Stencil.cpp
Yitzhak Mandelbaum 675a2973ee [libTooling] Add support for smart pointers to relevant Transformer Stencils.
Stencils `maybeDeref` and `maybeAddressOf` are designed to handle nodes that may
be pointers. Currently, they only handle native pointers. This patch extends the
support to recognize smart pointers and handle them as well.

Differential Revision: https://reviews.llvm.org/D93637
2021-01-05 17:57:41 +00:00

442 lines
15 KiB
C++

//===--- 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 <atomic>
#include <memory>
#include <string>
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<DynTypedNode>
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::StringError>(llvm::errc::invalid_argument,
"Id not bound: " + Id);
return It->second;
}
namespace {
// An arbitrary fragment of code within a stencil.
struct RawTextData {
explicit RawTextData(std::string T) : Text(std::move(T)) {}
std::string Text;
};
// A debugging operation to dump the AST for a particular (bound) AST node.
struct DebugPrintNodeData {
explicit DebugPrintNodeData(std::string S) : Id(std::move(S)) {}
std::string Id;
};
// 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.
struct UnaryOperationData {
UnaryOperationData(UnaryNodeOperator Op, std::string Id)
: Op(Op), Id(std::move(Id)) {}
UnaryNodeOperator Op;
std::string Id;
};
// The fragment of code corresponding to the selected range.
struct SelectorData {
explicit SelectorData(RangeSelector S) : Selector(std::move(S)) {}
RangeSelector Selector;
};
// A stencil operation to build a member access `e.m` or `e->m`, as appropriate.
struct AccessData {
AccessData(StringRef BaseId, Stencil Member)
: BaseId(std::string(BaseId)), Member(std::move(Member)) {}
std::string BaseId;
Stencil Member;
};
struct IfBoundData {
IfBoundData(StringRef Id, Stencil TrueStencil, Stencil FalseStencil)
: Id(std::string(Id)), TrueStencil(std::move(TrueStencil)),
FalseStencil(std::move(FalseStencil)) {}
std::string Id;
Stencil TrueStencil;
Stencil FalseStencil;
};
struct SequenceData {
SequenceData(std::vector<Stencil> Stencils) : Stencils(std::move(Stencils)) {}
std::vector<Stencil> Stencils;
};
std::string toStringData(const RawTextData &Data) {
std::string Result;
llvm::raw_string_ostream OS(Result);
OS << "\"";
OS.write_escaped(Data.Text);
OS << "\"";
OS.flush();
return Result;
}
std::string toStringData(const DebugPrintNodeData &Data) {
return (llvm::Twine("dPrint(\"") + Data.Id + "\")").str();
}
std::string toStringData(const UnaryOperationData &Data) {
StringRef OpName;
switch (Data.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 + "(\"" + Data.Id + "\")").str();
}
std::string toStringData(const SelectorData &) { return "selection(...)"; }
std::string toStringData(const AccessData &Data) {
return (llvm::Twine("access(\"") + Data.BaseId + "\", " +
Data.Member->toString() + ")")
.str();
}
std::string toStringData(const IfBoundData &Data) {
return (llvm::Twine("ifBound(\"") + Data.Id + "\", " +
Data.TrueStencil->toString() + ", " + Data.FalseStencil->toString() +
")")
.str();
}
std::string toStringData(const MatchConsumer<std::string> &) {
return "run(...)";
}
std::string toStringData(const SequenceData &Data) {
llvm::SmallVector<std::string, 2> Parts;
Parts.reserve(Data.Stencils.size());
for (const auto &S : Data.Stencils)
Parts.push_back(S->toString());
return (llvm::Twine("seq(") + llvm::join(Parts, ", ") + ")").str();
}
// The `evalData()` overloads evaluate the given stencil data to a string, given
// the match result, and append it to `Result`. We define an overload for each
// type of stencil data.
Error evalData(const RawTextData &Data, const MatchFinder::MatchResult &,
std::string *Result) {
Result->append(Data.Text);
return Error::success();
}
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();
}
Error evalData(const DebugPrintNodeData &Data,
const MatchFinder::MatchResult &Match, std::string *Result) {
return printNode(Data.Id, Match, Result);
}
// 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;
}
Error evalData(const UnaryOperationData &Data,
const MatchFinder::MatchResult &Match, std::string *Result) {
// The `Describe` operation can be applied to any node, not just expressions,
// so it is handled here, separately.
if (Data.Op == UnaryNodeOperator::Describe)
return printNode(Data.Id, Match, Result);
const auto *E = Match.Nodes.getNodeAs<Expr>(Data.Id);
if (E == nullptr)
return llvm::make_error<StringError>(
errc::invalid_argument, "Id not bound or not Expr: " + Data.Id);
llvm::Optional<std::string> Source;
switch (Data.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<clang::CXXOperatorCallExpr>(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<clang::CXXOperatorCallExpr>(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<StringError>(
errc::invalid_argument,
"Could not construct expression source from ID: " + Data.Id);
*Result += *Source;
return Error::success();
}
Error evalData(const SelectorData &Data, const MatchFinder::MatchResult &Match,
std::string *Result) {
auto RawRange = Data.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<StringError> 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();
}
Error evalData(const AccessData &Data, const MatchFinder::MatchResult &Match,
std::string *Result) {
const auto *E = Match.Nodes.getNodeAs<Expr>(Data.BaseId);
if (E == nullptr)
return llvm::make_error<StringError>(errc::invalid_argument,
"Id not bound: " + Data.BaseId);
if (!E->isImplicitCXXThis()) {
if (llvm::Optional<std::string> S =
E->getType()->isAnyPointerType()
? tooling::buildArrow(*E, *Match.Context)
: tooling::buildDot(*E, *Match.Context))
*Result += *S;
else
return llvm::make_error<StringError>(
errc::invalid_argument,
"Could not construct object text from ID: " + Data.BaseId);
}
return Data.Member->eval(Match, Result);
}
Error evalData(const IfBoundData &Data, const MatchFinder::MatchResult &Match,
std::string *Result) {
auto &M = Match.Nodes.getMap();
return (M.find(Data.Id) != M.end() ? Data.TrueStencil : Data.FalseStencil)
->eval(Match, Result);
}
Error evalData(const MatchConsumer<std::string> &Fn,
const MatchFinder::MatchResult &Match, std::string *Result) {
Expected<std::string> Value = Fn(Match);
if (!Value)
return Value.takeError();
*Result += *Value;
return Error::success();
}
Error evalData(const SequenceData &Data, const MatchFinder::MatchResult &Match,
std::string *Result) {
for (const auto &S : Data.Stencils)
if (auto Err = S->eval(Match, Result))
return Err;
return Error::success();
}
template <typename T> class StencilImpl : public StencilInterface {
T Data;
public:
template <typename... Ps>
explicit StencilImpl(Ps &&... Args) : Data(std::forward<Ps>(Args)...) {}
Error eval(const MatchFinder::MatchResult &Match,
std::string *Result) const override {
return evalData(Data, Match, Result);
}
std::string toString() const override { return toStringData(Data); }
};
} // namespace
Stencil transformer::detail::makeStencil(StringRef Text) {
return std::make_shared<StencilImpl<RawTextData>>(std::string(Text));
}
Stencil transformer::detail::makeStencil(RangeSelector Selector) {
return std::make_shared<StencilImpl<SelectorData>>(std::move(Selector));
}
Stencil transformer::dPrint(StringRef Id) {
return std::make_shared<StencilImpl<DebugPrintNodeData>>(std::string(Id));
}
Stencil transformer::expression(llvm::StringRef Id) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::Parens, std::string(Id));
}
Stencil transformer::deref(llvm::StringRef ExprId) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::Deref, std::string(ExprId));
}
Stencil transformer::maybeDeref(llvm::StringRef ExprId) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::MaybeDeref, std::string(ExprId));
}
Stencil transformer::addressOf(llvm::StringRef ExprId) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::AddressOf, std::string(ExprId));
}
Stencil transformer::maybeAddressOf(llvm::StringRef ExprId) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::MaybeAddressOf, std::string(ExprId));
}
Stencil transformer::describe(StringRef Id) {
return std::make_shared<StencilImpl<UnaryOperationData>>(
UnaryNodeOperator::Describe, std::string(Id));
}
Stencil transformer::access(StringRef BaseId, Stencil Member) {
return std::make_shared<StencilImpl<AccessData>>(BaseId, std::move(Member));
}
Stencil transformer::ifBound(StringRef Id, Stencil TrueStencil,
Stencil FalseStencil) {
return std::make_shared<StencilImpl<IfBoundData>>(Id, std::move(TrueStencil),
std::move(FalseStencil));
}
Stencil transformer::run(MatchConsumer<std::string> Fn) {
return std::make_shared<StencilImpl<MatchConsumer<std::string>>>(
std::move(Fn));
}
Stencil transformer::catVector(std::vector<Stencil> Parts) {
// Only one argument, so don't wrap in sequence.
if (Parts.size() == 1)
return std::move(Parts[0]);
return std::make_shared<StencilImpl<SequenceData>>(std::move(Parts));
}