llvm-project/clang/unittests/Tooling/TransformerTest.cpp
Jan Svoboda 5523fefb01 [clang][lex] Use preferred path separator in includer-relative lookup
There is a long-standing FIXME in `HeaderSearch.cpp` to use the path separator preferred by the platform instead of forward slash. There was an attempt to fix that (1cf6c28a) which got reverted (cf385dc8). I couldn't find an explanation, but my guess is that some tests assuming forward slash started failing.

This commit fixes tests with that assumption.

This is intended to be NFC, but there are two exceptions to that:
* Some diagnostic messages might now contain backslash instead of forward slash.
* Arguments to the "-remap-file" option that use forward slash might stop kicking in. Separators between potential includer path and header name need to be replaced by backslash in that case.
2023-09-08 16:38:08 -07:00

1753 lines
57 KiB
C++

//===- unittest/Tooling/TransformerTest.cpp -------------------------------===//
//
// 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/Transformer.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/Transformer/RangeSelector.h"
#include "clang/Tooling/Transformer/RewriteRule.h"
#include "clang/Tooling/Transformer/Stencil.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
using namespace clang;
using namespace tooling;
using namespace ast_matchers;
namespace {
using ::clang::transformer::addInclude;
using ::clang::transformer::applyFirst;
using ::clang::transformer::before;
using ::clang::transformer::cat;
using ::clang::transformer::changeTo;
using ::clang::transformer::editList;
using ::clang::transformer::makeRule;
using ::clang::transformer::member;
using ::clang::transformer::name;
using ::clang::transformer::node;
using ::clang::transformer::noEdits;
using ::clang::transformer::remove;
using ::clang::transformer::rewriteDescendants;
using ::clang::transformer::RewriteRule;
using ::clang::transformer::RewriteRuleWith;
using ::clang::transformer::statement;
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::ResultOf;
using ::testing::UnorderedElementsAre;
constexpr char KHeaderContents[] = R"cc(
struct string {
string(const char*);
char* c_str();
int size();
};
int strlen(const char*);
namespace proto {
struct PCFProto {
int foo();
};
struct ProtoCommandLineFlag : PCFProto {
PCFProto& GetProto();
};
} // namespace proto
class Logger {};
void operator<<(Logger& l, string msg);
Logger& log(int level);
)cc";
static ast_matchers::internal::Matcher<clang::QualType>
isOrPointsTo(const clang::ast_matchers::DeclarationMatcher &TypeMatcher) {
return anyOf(hasDeclaration(TypeMatcher), pointsTo(TypeMatcher));
}
static std::string format(StringRef Code) {
const std::vector<Range> Ranges(1, Range(0, Code.size()));
auto Style = format::getLLVMStyle();
const auto Replacements = format::reformat(Style, Code, Ranges);
auto Formatted = applyAllReplacements(Code, Replacements);
if (!Formatted) {
ADD_FAILURE() << "Could not format code: "
<< llvm::toString(Formatted.takeError());
return std::string();
}
return *Formatted;
}
static void compareSnippets(StringRef Expected,
const std::optional<std::string> &MaybeActual) {
ASSERT_TRUE(MaybeActual) << "Rewrite failed. Expecting: " << Expected;
auto Actual = *MaybeActual;
std::string HL = "#include \"header.h\"\n";
auto I = Actual.find(HL);
if (I != std::string::npos)
Actual.erase(I, HL.size());
EXPECT_EQ(format(Expected), format(Actual));
}
// FIXME: consider separating this class into its own file(s).
class ClangRefactoringTestBase : public testing::Test {
protected:
void appendToHeader(StringRef S) { FileContents[0].second += S; }
void addFile(StringRef Filename, StringRef Content) {
FileContents.emplace_back(std::string(Filename), std::string(Content));
}
std::optional<std::string> rewrite(StringRef Input) {
std::string Code = ("#include \"header.h\"\n" + Input).str();
auto Factory = newFrontendActionFactory(&MatchFinder);
if (!runToolOnCodeWithArgs(
Factory->create(), Code, std::vector<std::string>(), "input.cc",
"clang-tool", std::make_shared<PCHContainerOperations>(),
FileContents)) {
llvm::errs() << "Running tool failed.\n";
return std::nullopt;
}
if (ErrorCount != 0) {
llvm::errs() << "Generating changes failed.\n";
return std::nullopt;
}
auto ChangedCode =
applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec());
if (!ChangedCode) {
llvm::errs() << "Applying changes failed: "
<< llvm::toString(ChangedCode.takeError()) << "\n";
return std::nullopt;
}
return *ChangedCode;
}
Transformer::ChangeSetConsumer consumer() {
return [this](Expected<MutableArrayRef<AtomicChange>> C) {
if (C) {
Changes.insert(Changes.end(), std::make_move_iterator(C->begin()),
std::make_move_iterator(C->end()));
} else {
// FIXME: stash this error rather than printing.
llvm::errs() << "Error generating changes: "
<< llvm::toString(C.takeError()) << "\n";
++ErrorCount;
}
};
}
auto consumerWithStringMetadata() {
return [this](Expected<TransformerResult<std::string>> C) {
if (C) {
Changes.insert(Changes.end(),
std::make_move_iterator(C->Changes.begin()),
std::make_move_iterator(C->Changes.end()));
StringMetadata.push_back(std::move(C->Metadata));
} else {
// FIXME: stash this error rather than printing.
llvm::errs() << "Error generating changes: "
<< llvm::toString(C.takeError()) << "\n";
++ErrorCount;
}
};
}
void testRule(RewriteRule Rule, StringRef Input, StringRef Expected) {
Transformers.push_back(
std::make_unique<Transformer>(std::move(Rule), consumer()));
Transformers.back()->registerMatchers(&MatchFinder);
compareSnippets(Expected, rewrite(Input));
}
void testRule(RewriteRuleWith<std::string> Rule, StringRef Input,
StringRef Expected) {
Transformers.push_back(std::make_unique<Transformer>(
std::move(Rule), consumerWithStringMetadata()));
Transformers.back()->registerMatchers(&MatchFinder);
compareSnippets(Expected, rewrite(Input));
}
void testRuleFailure(RewriteRule Rule, StringRef Input) {
Transformers.push_back(
std::make_unique<Transformer>(std::move(Rule), consumer()));
Transformers.back()->registerMatchers(&MatchFinder);
ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
}
void testRuleFailure(RewriteRuleWith<std::string> Rule, StringRef Input) {
Transformers.push_back(std::make_unique<Transformer>(
std::move(Rule), consumerWithStringMetadata()));
Transformers.back()->registerMatchers(&MatchFinder);
ASSERT_FALSE(rewrite(Input)) << "Expected failure to rewrite code";
}
// Transformers are referenced by MatchFinder.
std::vector<std::unique_ptr<Transformer>> Transformers;
clang::ast_matchers::MatchFinder MatchFinder;
// Records whether any errors occurred in individual changes.
int ErrorCount = 0;
AtomicChanges Changes;
std::vector<std::string> StringMetadata;
private:
FileContentMappings FileContents = {{"header.h", ""}};
};
class TransformerTest : public ClangRefactoringTestBase {
protected:
TransformerTest() { appendToHeader(KHeaderContents); }
};
// Given string s, change strlen($s.c_str()) to REPLACED.
static RewriteRuleWith<std::string> ruleStrlenSize() {
StringRef StringExpr = "strexpr";
auto StringType = namedDecl(hasAnyName("::basic_string", "::string"));
auto R = makeRule(
callExpr(callee(functionDecl(hasName("strlen"))),
hasArgument(0, cxxMemberCallExpr(
on(expr(hasType(isOrPointsTo(StringType)))
.bind(StringExpr)),
callee(cxxMethodDecl(hasName("c_str")))))),
changeTo(cat("REPLACED")), cat("Use size() method directly on string."));
return R;
}
TEST_F(TransformerTest, StrlenSize) {
std::string Input = "int f(string s) { return strlen(s.c_str()); }";
std::string Expected = "int f(string s) { return REPLACED; }";
testRule(ruleStrlenSize(), Input, Expected);
}
// Tests that no change is applied when a match is not expected.
TEST_F(TransformerTest, NoMatch) {
std::string Input = "int f(string s) { return s.size(); }";
testRule(ruleStrlenSize(), Input, Input);
}
// Tests replacing an expression.
TEST_F(TransformerTest, Flag) {
StringRef Flag = "flag";
RewriteRule Rule = makeRule(
cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
hasName("proto::ProtoCommandLineFlag"))))
.bind(Flag)),
unless(callee(cxxMethodDecl(hasName("GetProto"))))),
changeTo(node(std::string(Flag)), cat("EXPR")));
std::string Input = R"cc(
proto::ProtoCommandLineFlag flag;
int x = flag.foo();
int y = flag.GetProto().foo();
)cc";
std::string Expected = R"cc(
proto::ProtoCommandLineFlag flag;
int x = EXPR.foo();
int y = flag.GetProto().foo();
)cc";
testRule(std::move(Rule), Input, Expected);
}
TEST_F(TransformerTest, AddIncludeQuoted) {
RewriteRule Rule =
makeRule(callExpr(callee(functionDecl(hasName("f")))),
{addInclude("clang/OtherLib.h"), changeTo(cat("other()"))});
std::string Input = R"cc(
int f(int x);
int h(int x) { return f(x); }
)cc";
std::string Expected = R"cc(#include "clang/OtherLib.h"
int f(int x);
int h(int x) { return other(); }
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, AddIncludeAngled) {
RewriteRule Rule = makeRule(
callExpr(callee(functionDecl(hasName("f")))),
{addInclude("clang/OtherLib.h", transformer::IncludeFormat::Angled),
changeTo(cat("other()"))});
std::string Input = R"cc(
int f(int x);
int h(int x) { return f(x); }
)cc";
std::string Expected = R"cc(#include <clang/OtherLib.h>
int f(int x);
int h(int x) { return other(); }
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, AddIncludeQuotedForRule) {
RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))),
changeTo(cat("other()")));
addInclude(Rule, "clang/OtherLib.h");
std::string Input = R"cc(
int f(int x);
int h(int x) { return f(x); }
)cc";
std::string Expected = R"cc(#include "clang/OtherLib.h"
int f(int x);
int h(int x) { return other(); }
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, AddIncludeAngledForRule) {
RewriteRule Rule = makeRule(callExpr(callee(functionDecl(hasName("f")))),
changeTo(cat("other()")));
addInclude(Rule, "clang/OtherLib.h", transformer::IncludeFormat::Angled);
std::string Input = R"cc(
int f(int x);
int h(int x) { return f(x); }
)cc";
std::string Expected = R"cc(#include <clang/OtherLib.h>
int f(int x);
int h(int x) { return other(); }
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, NodePartNameNamedDecl) {
StringRef Fun = "fun";
RewriteRule Rule = makeRule(functionDecl(hasName("bad")).bind(Fun),
changeTo(name(std::string(Fun)), cat("good")));
std::string Input = R"cc(
int bad(int x);
int bad(int x) { return x * x; }
)cc";
std::string Expected = R"cc(
int good(int x);
int good(int x) { return x * x; }
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, NodePartNameDeclRef) {
std::string Input = R"cc(
template <typename T>
T bad(T x) {
return x;
}
int neutral(int x) { return bad<int>(x) * x; }
)cc";
std::string Expected = R"cc(
template <typename T>
T bad(T x) {
return x;
}
int neutral(int x) { return good<int>(x) * x; }
)cc";
StringRef Ref = "ref";
testRule(makeRule(declRefExpr(to(functionDecl(hasName("bad")))).bind(Ref),
changeTo(name(std::string(Ref)), cat("good"))),
Input, Expected);
}
TEST_F(TransformerTest, NodePartNameDeclRefFailure) {
std::string Input = R"cc(
struct Y {
int operator*();
};
int neutral(int x) {
Y y;
int (Y::*ptr)() = &Y::operator*;
return *y + x;
}
)cc";
StringRef Ref = "ref";
Transformer T(makeRule(declRefExpr(to(functionDecl())).bind(Ref),
changeTo(name(std::string(Ref)), cat("good"))),
consumer());
T.registerMatchers(&MatchFinder);
EXPECT_FALSE(rewrite(Input));
}
TEST_F(TransformerTest, NodePartMember) {
StringRef E = "expr";
RewriteRule Rule =
makeRule(memberExpr(clang::ast_matchers::member(hasName("bad"))).bind(E),
changeTo(member(std::string(E)), cat("good")));
std::string Input = R"cc(
struct S {
int bad;
};
int g() {
S s;
return s.bad;
}
)cc";
std::string Expected = R"cc(
struct S {
int bad;
};
int g() {
S s;
return s.good;
}
)cc";
testRule(Rule, Input, Expected);
}
TEST_F(TransformerTest, NodePartMemberQualified) {
std::string Input = R"cc(
struct S {
int bad;
int good;
};
struct T : public S {
int bad;
};
int g() {
T t;
return t.S::bad;
}
)cc";
std::string Expected = R"cc(
struct S {
int bad;
int good;
};
struct T : public S {
int bad;
};
int g() {
T t;
return t.S::good;
}
)cc";
StringRef E = "expr";
testRule(makeRule(memberExpr().bind(E),
changeTo(member(std::string(E)), cat("good"))),
Input, Expected);
}
TEST_F(TransformerTest, NodePartMemberMultiToken) {
std::string Input = R"cc(
struct Y {
int operator*();
int good();
template <typename T> void foo(T t);
};
int neutral(int x) {
Y y;
y.template foo<int>(3);
return y.operator *();
}
)cc";
std::string Expected = R"cc(
struct Y {
int operator*();
int good();
template <typename T> void foo(T t);
};
int neutral(int x) {
Y y;
y.template good<int>(3);
return y.good();
}
)cc";
StringRef MemExpr = "member";
testRule(makeRule(memberExpr().bind(MemExpr),
changeTo(member(std::string(MemExpr)), cat("good"))),
Input, Expected);
}
TEST_F(TransformerTest, NoEdits) {
using transformer::noEdits;
std::string Input = "int f(int x) { return x; }";
testRule(makeRule(returnStmt().bind("return"), noEdits()), Input, Input);
}
TEST_F(TransformerTest, NoopEdit) {
using transformer::node;
using transformer::noopEdit;
std::string Input = "int f(int x) { return x; }";
testRule(makeRule(returnStmt().bind("return"), noopEdit(node("return"))),
Input, Input);
}
TEST_F(TransformerTest, IfBound2Args) {
using transformer::ifBound;
std::string Input = "int f(int x) { return x; }";
std::string Expected = "int f(int x) { CHANGE; }";
testRule(makeRule(returnStmt().bind("return"),
ifBound("return", changeTo(cat("CHANGE;")))),
Input, Expected);
}
TEST_F(TransformerTest, IfBound3Args) {
using transformer::ifBound;
std::string Input = "int f(int x) { return x; }";
std::string Expected = "int f(int x) { CHANGE; }";
testRule(makeRule(returnStmt().bind("return"),
ifBound("nothing", changeTo(cat("ERROR")),
changeTo(cat("CHANGE;")))),
Input, Expected);
}
TEST_F(TransformerTest, ShrinkTo) {
using transformer::shrinkTo;
std::string Input = "int f(int x) { return x; }";
std::string Expected = "return x;";
testRule(makeRule(functionDecl(hasDescendant(returnStmt().bind("return")))
.bind("function"),
shrinkTo(node("function"), node("return"))),
Input, Expected);
}
// Rewrite various Stmts inside a Decl.
TEST_F(TransformerTest, RewriteDescendantsDeclChangeStmt) {
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(makeRule(functionDecl(hasName("f")).bind("fun"),
rewriteDescendants("fun", InlineX)),
Input, Expected);
}
// Rewrite various TypeLocs inside a Decl.
TEST_F(TransformerTest, RewriteDescendantsDeclChangeTypeLoc) {
std::string Input = "int f(int *x) { return *x; }";
std::string Expected = "char f(char *x) { return *x; }";
auto IntToChar = makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))),
changeTo(cat("char")));
testRule(makeRule(functionDecl(hasName("f")).bind("fun"),
rewriteDescendants("fun", IntToChar)),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsStmt) {
// Add an unrelated definition to the header that also has a variable named
// "x", to test that the rewrite is limited to the scope we intend.
appendToHeader(R"cc(int g(int x) { return x; })cc");
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
rewriteDescendants("body", InlineX)),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsStmtWithAdditionalChange) {
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int newName(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(
makeRule(
functionDecl(hasName("f"), hasBody(stmt().bind("body"))).bind("f"),
flatten(changeTo(name("f"), cat("newName")),
rewriteDescendants("body", InlineX))),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsTypeLoc) {
std::string Input = "int f(int *x) { return *x; }";
std::string Expected = "int f(char *x) { return *x; }";
auto IntToChar =
makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"),
changeTo(cat("char")));
testRule(
makeRule(functionDecl(hasName("f"),
hasParameter(0, varDecl(hasTypeLoc(
typeLoc().bind("parmType"))))),
rewriteDescendants("parmType", IntToChar)),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsReferToParentBinding) {
std::string Input =
"int f(int p) { int y = p; { int z = p * p; } return p; }";
std::string Expected =
"int f(int p) { int y = 3; { int z = 3 * 3; } return 3; }";
std::string VarId = "var";
auto InlineVar = makeRule(declRefExpr(to(varDecl(equalsBoundNode(VarId)))),
changeTo(cat("3")));
testRule(makeRule(functionDecl(hasName("f"),
hasParameter(0, varDecl().bind(VarId)))
.bind("fun"),
rewriteDescendants("fun", InlineVar)),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsUnboundNode) {
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
Transformer T(makeRule(functionDecl(hasName("f")),
rewriteDescendants("UNBOUND", InlineX)),
consumer());
T.registerMatchers(&MatchFinder);
EXPECT_FALSE(rewrite(Input));
EXPECT_THAT(Changes, IsEmpty());
EXPECT_EQ(ErrorCount, 1);
}
TEST_F(TransformerTest, RewriteDescendantsInvalidNodeType) {
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
auto IntToChar =
makeRule(qualType(isInteger(), builtinType()), changeTo(cat("char")));
Transformer T(
makeRule(functionDecl(
hasName("f"),
hasParameter(0, varDecl(hasType(qualType().bind("type"))))),
rewriteDescendants("type", IntToChar)),
consumer());
T.registerMatchers(&MatchFinder);
EXPECT_FALSE(rewrite(Input));
EXPECT_THAT(Changes, IsEmpty());
EXPECT_EQ(ErrorCount, 1);
}
//
// We include one test per typed overload. We don't test extensively since that
// is already covered by the tests above.
//
TEST_F(TransformerTest, RewriteDescendantsTypedStmt) {
// Add an unrelated definition to the header that also has a variable named
// "x", to test that the rewrite is limited to the scope we intend.
appendToHeader(R"cc(int g(int x) { return x; })cc");
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
[&InlineX](const MatchFinder::MatchResult &R) {
const auto *Node = R.Nodes.getNodeAs<Stmt>("body");
assert(Node != nullptr && "body must be bound");
return transformer::detail::rewriteDescendants(
*Node, InlineX, R);
}),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsTypedDecl) {
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(makeRule(functionDecl(hasName("f")).bind("fun"),
[&InlineX](const MatchFinder::MatchResult &R) {
const auto *Node = R.Nodes.getNodeAs<Decl>("fun");
assert(Node != nullptr && "fun must be bound");
return transformer::detail::rewriteDescendants(
*Node, InlineX, R);
}),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsTypedTypeLoc) {
std::string Input = "int f(int *x) { return *x; }";
std::string Expected = "int f(char *x) { return *x; }";
auto IntToChar =
makeRule(typeLoc(loc(qualType(isInteger(), builtinType()))).bind("loc"),
changeTo(cat("char")));
testRule(
makeRule(
functionDecl(
hasName("f"),
hasParameter(0, varDecl(hasTypeLoc(typeLoc().bind("parmType"))))),
[&IntToChar](const MatchFinder::MatchResult &R) {
const auto *Node = R.Nodes.getNodeAs<TypeLoc>("parmType");
assert(Node != nullptr && "parmType must be bound");
return transformer::detail::rewriteDescendants(*Node, IntToChar, R);
}),
Input, Expected);
}
TEST_F(TransformerTest, RewriteDescendantsTypedDynTyped) {
// Add an unrelated definition to the header that also has a variable named
// "x", to test that the rewrite is limited to the scope we intend.
appendToHeader(R"cc(int g(int x) { return x; })cc");
std::string Input =
"int f(int x) { int y = x; { int z = x * x; } return x; }";
std::string Expected =
"int f(int x) { int y = 3; { int z = 3 * 3; } return 3; }";
auto InlineX =
makeRule(declRefExpr(to(varDecl(hasName("x")))), changeTo(cat("3")));
testRule(
makeRule(functionDecl(hasName("f"), hasBody(stmt().bind("body"))),
[&InlineX](const MatchFinder::MatchResult &R) {
auto It = R.Nodes.getMap().find("body");
assert(It != R.Nodes.getMap().end() && "body must be bound");
return transformer::detail::rewriteDescendants(It->second,
InlineX, R);
}),
Input, Expected);
}
TEST_F(TransformerTest, InsertBeforeEdit) {
std::string Input = R"cc(
int f() {
return 7;
}
)cc";
std::string Expected = R"cc(
int f() {
int y = 3;
return 7;
}
)cc";
StringRef Ret = "return";
testRule(
makeRule(returnStmt().bind(Ret),
insertBefore(statement(std::string(Ret)), cat("int y = 3;"))),
Input, Expected);
}
TEST_F(TransformerTest, InsertAfterEdit) {
std::string Input = R"cc(
int f() {
int x = 5;
return 7;
}
)cc";
std::string Expected = R"cc(
int f() {
int x = 5;
int y = 3;
return 7;
}
)cc";
StringRef Decl = "decl";
testRule(
makeRule(declStmt().bind(Decl),
insertAfter(statement(std::string(Decl)), cat("int y = 3;"))),
Input, Expected);
}
TEST_F(TransformerTest, RemoveEdit) {
std::string Input = R"cc(
int f() {
int x = 5;
return 7;
}
)cc";
std::string Expected = R"cc(
int f() {
return 7;
}
)cc";
StringRef Decl = "decl";
testRule(
makeRule(declStmt().bind(Decl), remove(statement(std::string(Decl)))),
Input, Expected);
}
TEST_F(TransformerTest, WithMetadata) {
auto makeMetadata = [](const MatchFinder::MatchResult &R) -> llvm::Any {
int N =
R.Nodes.getNodeAs<IntegerLiteral>("int")->getValue().getLimitedValue();
return N;
};
std::string Input = R"cc(
int f() {
int x = 5;
return 7;
}
)cc";
Transformer T(
makeRule(
declStmt(containsDeclaration(0, varDecl(hasInitializer(
integerLiteral().bind("int")))))
.bind("decl"),
withMetadata(remove(statement(std::string("decl"))), makeMetadata)),
consumer());
T.registerMatchers(&MatchFinder);
auto Factory = newFrontendActionFactory(&MatchFinder);
EXPECT_TRUE(runToolOnCodeWithArgs(
Factory->create(), Input, std::vector<std::string>(), "input.cc",
"clang-tool", std::make_shared<PCHContainerOperations>(), {}));
ASSERT_EQ(Changes.size(), 1u);
const llvm::Any &Metadata = Changes[0].getMetadata();
ASSERT_TRUE(llvm::any_cast<int>(&Metadata));
EXPECT_THAT(llvm::any_cast<int>(Metadata), 5);
}
TEST_F(TransformerTest, MultiChange) {
std::string Input = R"cc(
void foo() {
if (10 > 1.0)
log(1) << "oh no!";
else
log(0) << "ok";
}
)cc";
std::string Expected = R"(
void foo() {
if (true) { /* then */ }
else { /* else */ }
}
)";
StringRef C = "C", T = "T", E = "E";
testRule(
makeRule(ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)),
hasElse(stmt().bind(E))),
{changeTo(node(std::string(C)), cat("true")),
changeTo(statement(std::string(T)), cat("{ /* then */ }")),
changeTo(statement(std::string(E)), cat("{ /* else */ }"))}),
Input, Expected);
}
TEST_F(TransformerTest, EditList) {
std::string Input = R"cc(
void foo() {
if (10 > 1.0)
log(1) << "oh no!";
else
log(0) << "ok";
}
)cc";
std::string Expected = R"(
void foo() {
if (true) { /* then */ }
else { /* else */ }
}
)";
StringRef C = "C", T = "T", E = "E";
testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
editList({changeTo(node(std::string(C)), cat("true")),
changeTo(statement(std::string(T)),
cat("{ /* then */ }")),
changeTo(statement(std::string(E)),
cat("{ /* else */ }"))})),
Input, Expected);
}
TEST_F(TransformerTest, Flatten) {
std::string Input = R"cc(
void foo() {
if (10 > 1.0)
log(1) << "oh no!";
else
log(0) << "ok";
}
)cc";
std::string Expected = R"(
void foo() {
if (true) { /* then */ }
else { /* else */ }
}
)";
StringRef C = "C", T = "T", E = "E";
testRule(
makeRule(
ifStmt(hasCondition(expr().bind(C)), hasThen(stmt().bind(T)),
hasElse(stmt().bind(E))),
flatten(changeTo(node(std::string(C)), cat("true")),
changeTo(statement(std::string(T)), cat("{ /* then */ }")),
changeTo(statement(std::string(E)), cat("{ /* else */ }")))),
Input, Expected);
}
TEST_F(TransformerTest, FlattenWithMixedArgs) {
using clang::transformer::editList;
std::string Input = R"cc(
void foo() {
if (10 > 1.0)
log(1) << "oh no!";
else
log(0) << "ok";
}
)cc";
std::string Expected = R"(
void foo() {
if (true) { /* then */ }
else { /* else */ }
}
)";
StringRef C = "C", T = "T", E = "E";
testRule(makeRule(ifStmt(hasCondition(expr().bind(C)),
hasThen(stmt().bind(T)), hasElse(stmt().bind(E))),
flatten(changeTo(node(std::string(C)), cat("true")),
edit(changeTo(statement(std::string(T)),
cat("{ /* then */ }"))),
editList({changeTo(statement(std::string(E)),
cat("{ /* else */ }"))}))),
Input, Expected);
}
TEST_F(TransformerTest, OrderedRuleUnrelated) {
StringRef Flag = "flag";
RewriteRuleWith<std::string> FlagRule = makeRule(
cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
hasName("proto::ProtoCommandLineFlag"))))
.bind(Flag)),
unless(callee(cxxMethodDecl(hasName("GetProto"))))),
changeTo(node(std::string(Flag)), cat("PROTO")), cat(""));
std::string Input = R"cc(
proto::ProtoCommandLineFlag flag;
int x = flag.foo();
int y = flag.GetProto().foo();
int f(string s) { return strlen(s.c_str()); }
)cc";
std::string Expected = R"cc(
proto::ProtoCommandLineFlag flag;
int x = PROTO.foo();
int y = flag.GetProto().foo();
int f(string s) { return REPLACED; }
)cc";
testRule(applyFirst({ruleStrlenSize(), FlagRule}), Input, Expected);
}
TEST_F(TransformerTest, OrderedRuleRelated) {
std::string Input = R"cc(
void f1();
void f2();
void call_f1() { f1(); }
void call_f2() { f2(); }
)cc";
std::string Expected = R"cc(
void f1();
void f2();
void call_f1() { REPLACE_F1; }
void call_f2() { REPLACE_F1_OR_F2; }
)cc";
RewriteRule ReplaceF1 =
makeRule(callExpr(callee(functionDecl(hasName("f1")))),
changeTo(cat("REPLACE_F1")));
RewriteRule ReplaceF1OrF2 =
makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))),
changeTo(cat("REPLACE_F1_OR_F2")));
testRule(applyFirst({ReplaceF1, ReplaceF1OrF2}), Input, Expected);
}
// Change the order of the rules to get a different result. When `ReplaceF1OrF2`
// comes first, it applies for both uses, so `ReplaceF1` never applies.
TEST_F(TransformerTest, OrderedRuleRelatedSwapped) {
std::string Input = R"cc(
void f1();
void f2();
void call_f1() { f1(); }
void call_f2() { f2(); }
)cc";
std::string Expected = R"cc(
void f1();
void f2();
void call_f1() { REPLACE_F1_OR_F2; }
void call_f2() { REPLACE_F1_OR_F2; }
)cc";
RewriteRule ReplaceF1 =
makeRule(callExpr(callee(functionDecl(hasName("f1")))),
changeTo(cat("REPLACE_F1")));
RewriteRule ReplaceF1OrF2 =
makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))),
changeTo(cat("REPLACE_F1_OR_F2")));
testRule(applyFirst({ReplaceF1OrF2, ReplaceF1}), Input, Expected);
}
// Verify that a set of rules whose matchers have different base kinds works
// properly, including that `applyFirst` produces multiple matchers. We test
// two different kinds of rules: Expr and Decl. We place the Decl rule in the
// middle to test that `buildMatchers` works even when the kinds aren't grouped
// together.
TEST_F(TransformerTest, OrderedRuleMultipleKinds) {
std::string Input = R"cc(
void f1();
void f2();
void call_f1() { f1(); }
void call_f2() { f2(); }
)cc";
std::string Expected = R"cc(
void f1();
void DECL_RULE();
void call_f1() { REPLACE_F1; }
void call_f2() { REPLACE_F1_OR_F2; }
)cc";
RewriteRule ReplaceF1 =
makeRule(callExpr(callee(functionDecl(hasName("f1")))),
changeTo(cat("REPLACE_F1")));
RewriteRule ReplaceF1OrF2 =
makeRule(callExpr(callee(functionDecl(hasAnyName("f1", "f2")))),
changeTo(cat("REPLACE_F1_OR_F2")));
RewriteRule DeclRule = makeRule(functionDecl(hasName("f2")).bind("fun"),
changeTo(name("fun"), cat("DECL_RULE")));
RewriteRule Rule = applyFirst({ReplaceF1, DeclRule, ReplaceF1OrF2});
EXPECT_EQ(transformer::detail::buildMatchers(Rule).size(), 2UL);
testRule(Rule, Input, Expected);
}
// Verifies that a rule with a top-level matcher for an implicit node (like
// `implicitCastExpr`) works correctly -- the implicit nodes are not skipped.
TEST_F(TransformerTest, OrderedRuleImplicitMatched) {
std::string Input = R"cc(
void f1();
int f2();
void call_f1() { f1(); }
float call_f2() { return f2(); }
)cc";
std::string Expected = R"cc(
void f1();
int f2();
void call_f1() { REPLACE_F1; }
float call_f2() { return REPLACE_F2; }
)cc";
RewriteRule ReplaceF1 =
makeRule(callExpr(callee(functionDecl(hasName("f1")))),
changeTo(cat("REPLACE_F1")));
RewriteRule ReplaceF2 =
makeRule(implicitCastExpr(hasSourceExpression(callExpr())),
changeTo(cat("REPLACE_F2")));
testRule(applyFirst({ReplaceF1, ReplaceF2}), Input, Expected);
}
//
// Negative tests (where we expect no transformation to occur).
//
// Tests for a conflict in edits from a single match for a rule.
TEST_F(TransformerTest, TextGeneratorFailure) {
std::string Input = "int conflictOneRule() { return 3 + 7; }";
// Try to change the whole binary-operator expression AND one its operands:
StringRef O = "O";
class AlwaysFail : public transformer::MatchComputation<std::string> {
llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &,
std::string *) const override {
return llvm::createStringError(llvm::errc::invalid_argument, "ERROR");
}
std::string toString() const override { return "AlwaysFail"; }
};
Transformer T(
makeRule(binaryOperator().bind(O),
changeTo(node(std::string(O)), std::make_shared<AlwaysFail>())),
consumer());
T.registerMatchers(&MatchFinder);
EXPECT_FALSE(rewrite(Input));
EXPECT_THAT(Changes, IsEmpty());
EXPECT_EQ(ErrorCount, 1);
}
// Tests for a conflict in edits from a single match for a rule.
TEST_F(TransformerTest, OverlappingEditsInRule) {
std::string Input = "int conflictOneRule() { return 3 + 7; }";
// Try to change the whole binary-operator expression AND one its operands:
StringRef O = "O", L = "L";
Transformer T(makeRule(binaryOperator(hasLHS(expr().bind(L))).bind(O),
{changeTo(node(std::string(O)), cat("DELETE_OP")),
changeTo(node(std::string(L)), cat("DELETE_LHS"))}),
consumer());
T.registerMatchers(&MatchFinder);
EXPECT_FALSE(rewrite(Input));
EXPECT_THAT(Changes, IsEmpty());
EXPECT_EQ(ErrorCount, 1);
}
// Tests for a conflict in edits across multiple matches (of the same rule).
TEST_F(TransformerTest, OverlappingEditsMultipleMatches) {
std::string Input = "int conflictOneRule() { return -7; }";
// Try to change the whole binary-operator expression AND one its operands:
StringRef E = "E";
Transformer T(makeRule(expr().bind(E),
changeTo(node(std::string(E)), cat("DELETE_EXPR"))),
consumer());
T.registerMatchers(&MatchFinder);
// The rewrite process fails because the changes conflict with each other...
EXPECT_FALSE(rewrite(Input));
// ... but two changes were produced.
EXPECT_EQ(Changes.size(), 2u);
EXPECT_EQ(ErrorCount, 0);
}
TEST_F(TransformerTest, ErrorOccurredMatchSkipped) {
// Syntax error in the function body:
std::string Input = "void errorOccurred() { 3 }";
Transformer T(makeRule(functionDecl(hasName("errorOccurred")),
changeTo(cat("DELETED;"))),
consumer());
T.registerMatchers(&MatchFinder);
// The rewrite process itself fails...
EXPECT_FALSE(rewrite(Input));
// ... and no changes or errors are produced in the process.
EXPECT_THAT(Changes, IsEmpty());
EXPECT_EQ(ErrorCount, 0);
}
TEST_F(TransformerTest, ImplicitNodes_ConstructorDecl) {
std::string OtherStructPrefix = R"cpp(
struct Other {
)cpp";
std::string OtherStructSuffix = "};";
std::string CopyableStructName = "struct Copyable";
std::string BrokenStructName = "struct explicit Copyable";
std::string CodeSuffix = R"cpp(
{
Other m_i;
Copyable();
};
)cpp";
std::string CopyCtor = "Other(const Other&) = default;";
std::string ExplicitCopyCtor = "explicit Other(const Other&) = default;";
std::string BrokenExplicitCopyCtor =
"explicit explicit explicit Other(const Other&) = default;";
std::string RewriteInput = OtherStructPrefix + CopyCtor + OtherStructSuffix +
CopyableStructName + CodeSuffix;
std::string ExpectedRewriteOutput = OtherStructPrefix + ExplicitCopyCtor +
OtherStructSuffix + CopyableStructName +
CodeSuffix;
std::string BrokenRewriteOutput = OtherStructPrefix + BrokenExplicitCopyCtor +
OtherStructSuffix + BrokenStructName +
CodeSuffix;
auto MatchedRecord =
cxxConstructorDecl(isCopyConstructor()).bind("copyConstructor");
auto RewriteRule =
changeTo(before(node("copyConstructor")), cat("explicit "));
testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
RewriteRule),
RewriteInput, ExpectedRewriteOutput);
testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
RewriteInput, BrokenRewriteOutput);
}
TEST_F(TransformerTest, ImplicitNodes_RangeFor) {
std::string CodePrefix = R"cpp(
struct Container
{
int* begin() const;
int* end() const;
int* cbegin() const;
int* cend() const;
};
void foo()
{
const Container c;
)cpp";
std::string BeginCallBefore = " c.begin();";
std::string BeginCallAfter = " c.cbegin();";
std::string ForLoop = "for (auto i : c)";
std::string BrokenForLoop = "for (auto i :.cbegin() c)";
std::string CodeSuffix = R"cpp(
{
}
}
)cpp";
std::string RewriteInput =
CodePrefix + BeginCallBefore + ForLoop + CodeSuffix;
std::string ExpectedRewriteOutput =
CodePrefix + BeginCallAfter + ForLoop + CodeSuffix;
std::string BrokenRewriteOutput =
CodePrefix + BeginCallAfter + BrokenForLoop + CodeSuffix;
auto MatchedRecord =
cxxMemberCallExpr(on(expr(hasType(qualType(isConstQualified(),
hasDeclaration(cxxRecordDecl(
hasName("Container"))))))
.bind("callTarget")),
callee(cxxMethodDecl(hasName("begin"))))
.bind("constBeginCall");
auto RewriteRule =
changeTo(node("constBeginCall"), cat(name("callTarget"), ".cbegin()"));
testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedRecord),
RewriteRule),
RewriteInput, ExpectedRewriteOutput);
testRule(makeRule(traverse(TK_AsIs, MatchedRecord), RewriteRule),
RewriteInput, BrokenRewriteOutput);
}
TEST_F(TransformerTest, ImplicitNodes_ForStmt) {
std::string CodePrefix = R"cpp(
struct NonTrivial {
NonTrivial() {}
NonTrivial(NonTrivial&) {}
NonTrivial& operator=(NonTrivial const&) { return *this; }
~NonTrivial() {}
};
struct ContainsArray {
NonTrivial arr[2];
ContainsArray& operator=(ContainsArray const&) = default;
};
void testIt()
{
ContainsArray ca1;
ContainsArray ca2;
ca2 = ca1;
)cpp";
auto CodeSuffix = "}";
auto LoopBody = R"cpp(
{
}
)cpp";
auto RawLoop = "for (auto i = 0; i != 5; ++i)";
auto RangeLoop = "for (auto i : boost::irange(5))";
// Expect to rewrite the raw loop to the ranged loop.
// This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs
// mode also matches the hidden for loop generated in the copy assignment
// operator of ContainsArray. Transformer then fails to transform the code at
// all.
auto RewriteInput =
CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix;
auto RewriteOutput =
CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix;
auto MatchedLoop = forStmt(
has(declStmt(hasSingleDecl(
varDecl(hasInitializer(integerLiteral(equals(0)))).bind("loopVar")))),
has(binaryOperator(hasOperatorName("!="),
hasLHS(ignoringImplicit(declRefExpr(
to(varDecl(equalsBoundNode("loopVar")))))),
hasRHS(expr().bind("upperBoundExpr")))),
has(unaryOperator(hasOperatorName("++"),
hasUnaryOperand(declRefExpr(
to(varDecl(equalsBoundNode("loopVar"))))))
.bind("incrementOp")));
auto RewriteRule =
changeTo(transformer::enclose(node("loopVar"), node("incrementOp")),
cat("auto ", name("loopVar"), " : boost::irange(",
node("upperBoundExpr"), ")"));
testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop),
RewriteRule),
RewriteInput, RewriteOutput);
testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule),
RewriteInput);
}
TEST_F(TransformerTest, ImplicitNodes_ForStmt2) {
std::string CodePrefix = R"cpp(
struct NonTrivial {
NonTrivial() {}
NonTrivial(NonTrivial&) {}
NonTrivial& operator=(NonTrivial const&) { return *this; }
~NonTrivial() {}
};
struct ContainsArray {
NonTrivial arr[2];
ContainsArray& operator=(ContainsArray const&) = default;
};
void testIt()
{
ContainsArray ca1;
ContainsArray ca2;
ca2 = ca1;
)cpp";
auto CodeSuffix = "}";
auto LoopBody = R"cpp(
{
}
)cpp";
auto RawLoop = "for (auto i = 0; i != 5; ++i)";
auto RangeLoop = "for (auto i : boost::irange(5))";
// Expect to rewrite the raw loop to the ranged loop.
// This works in TK_IgnoreUnlessSpelledInSource mode, but TK_AsIs
// mode also matches the hidden for loop generated in the copy assignment
// operator of ContainsArray. Transformer then fails to transform the code at
// all.
auto RewriteInput =
CodePrefix + RawLoop + LoopBody + RawLoop + LoopBody + CodeSuffix;
auto RewriteOutput =
CodePrefix + RangeLoop + LoopBody + RangeLoop + LoopBody + CodeSuffix;
auto MatchedLoop = forStmt(
hasLoopInit(declStmt(hasSingleDecl(
varDecl(hasInitializer(integerLiteral(equals(0)))).bind("loopVar")))),
hasCondition(binaryOperator(hasOperatorName("!="),
hasLHS(ignoringImplicit(declRefExpr(to(
varDecl(equalsBoundNode("loopVar")))))),
hasRHS(expr().bind("upperBoundExpr")))),
hasIncrement(unaryOperator(hasOperatorName("++"),
hasUnaryOperand(declRefExpr(
to(varDecl(equalsBoundNode("loopVar"))))))
.bind("incrementOp")));
auto RewriteRule =
changeTo(transformer::enclose(node("loopVar"), node("incrementOp")),
cat("auto ", name("loopVar"), " : boost::irange(",
node("upperBoundExpr"), ")"));
testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedLoop),
RewriteRule),
RewriteInput, RewriteOutput);
testRuleFailure(makeRule(traverse(TK_AsIs, MatchedLoop), RewriteRule),
RewriteInput);
}
TEST_F(TransformerTest, TemplateInstantiation) {
std::string NonTemplatesInput = R"cpp(
struct S {
int m_i;
};
)cpp";
std::string NonTemplatesExpected = R"cpp(
struct S {
safe_int m_i;
};
)cpp";
std::string TemplatesInput = R"cpp(
template<typename T>
struct TemplStruct {
TemplStruct() {}
~TemplStruct() {}
private:
T m_t;
};
void instantiate()
{
TemplStruct<int> ti;
}
)cpp";
auto MatchedField = fieldDecl(hasType(asString("int"))).bind("theField");
// Changes the 'int' in 'S', but not the 'T' in 'TemplStruct':
testRule(makeRule(traverse(TK_IgnoreUnlessSpelledInSource, MatchedField),
changeTo(cat("safe_int ", name("theField"), ";"))),
NonTemplatesInput + TemplatesInput,
NonTemplatesExpected + TemplatesInput);
// In AsIs mode, template instantiations are modified, which is
// often not desired:
std::string IncorrectTemplatesExpected = R"cpp(
template<typename T>
struct TemplStruct {
TemplStruct() {}
~TemplStruct() {}
private:
safe_int m_t;
};
void instantiate()
{
TemplStruct<int> ti;
}
)cpp";
// Changes the 'int' in 'S', and (incorrectly) the 'T' in 'TemplStruct':
testRule(makeRule(traverse(TK_AsIs, MatchedField),
changeTo(cat("safe_int ", name("theField"), ";"))),
NonTemplatesInput + TemplatesInput,
NonTemplatesExpected + IncorrectTemplatesExpected);
}
// Transformation of macro source text when the change encompasses the entirety
// of the expanded text.
TEST_F(TransformerTest, SimpleMacro) {
std::string Input = R"cc(
#define ZERO 0
int f(string s) { return ZERO; }
)cc";
std::string Expected = R"cc(
#define ZERO 0
int f(string s) { return 999; }
)cc";
StringRef zero = "zero";
RewriteRule R = makeRule(integerLiteral(equals(0)).bind(zero),
changeTo(node(std::string(zero)), cat("999")));
testRule(R, Input, Expected);
}
// Transformation of macro source text when the change encompasses the entirety
// of the expanded text, for the case of function-style macros.
TEST_F(TransformerTest, FunctionMacro) {
std::string Input = R"cc(
#define MACRO(str) strlen((str).c_str())
int f(string s) { return MACRO(s); }
)cc";
std::string Expected = R"cc(
#define MACRO(str) strlen((str).c_str())
int f(string s) { return REPLACED; }
)cc";
testRule(ruleStrlenSize(), Input, Expected);
}
// Tests that expressions in macro arguments can be rewritten.
TEST_F(TransformerTest, MacroArg) {
std::string Input = R"cc(
#define PLUS(e) e + 1
int f(string s) { return PLUS(strlen(s.c_str())); }
)cc";
std::string Expected = R"cc(
#define PLUS(e) e + 1
int f(string s) { return PLUS(REPLACED); }
)cc";
testRule(ruleStrlenSize(), Input, Expected);
}
// Tests that expressions in macro arguments can be rewritten, even when the
// macro call occurs inside another macro's definition.
TEST_F(TransformerTest, MacroArgInMacroDef) {
std::string Input = R"cc(
#define NESTED(e) e
#define MACRO(str) NESTED(strlen((str).c_str()))
int f(string s) { return MACRO(s); }
)cc";
std::string Expected = R"cc(
#define NESTED(e) e
#define MACRO(str) NESTED(strlen((str).c_str()))
int f(string s) { return REPLACED; }
)cc";
testRule(ruleStrlenSize(), Input, Expected);
}
// Tests the corner case of the identity macro, specifically that it is
// discarded in the rewrite rather than preserved (like PLUS is preserved in the
// previous test). This behavior is of dubious value (and marked with a FIXME
// in the code), but we test it to verify (and demonstrate) how this case is
// handled.
TEST_F(TransformerTest, IdentityMacro) {
std::string Input = R"cc(
#define ID(e) e
int f(string s) { return ID(strlen(s.c_str())); }
)cc";
std::string Expected = R"cc(
#define ID(e) e
int f(string s) { return REPLACED; }
)cc";
testRule(ruleStrlenSize(), Input, Expected);
}
// Tests that two changes in a single macro expansion do not lead to conflicts
// in applying the changes.
TEST_F(TransformerTest, TwoChangesInOneMacroExpansion) {
std::string Input = R"cc(
#define PLUS(a,b) (a) + (b)
int f() { return PLUS(3, 4); }
)cc";
std::string Expected = R"cc(
#define PLUS(a,b) (a) + (b)
int f() { return PLUS(LIT, LIT); }
)cc";
testRule(makeRule(integerLiteral(), changeTo(cat("LIT"))), Input, Expected);
}
// Tests case where the rule's match spans both source from the macro and its
// arg, with the begin location (the "anchor") being the arg.
TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNot) {
std::string Input = R"cc(
#define PLUS_ONE(a) a + 1
int f() { return PLUS_ONE(3); }
)cc";
std::string Expected = R"cc(
#define PLUS_ONE(a) a + 1
int f() { return PLUS_ONE(LIT); }
)cc";
StringRef E = "expr";
testRule(makeRule(binaryOperator(hasLHS(expr().bind(E))),
changeTo(node(std::string(E)), cat("LIT"))),
Input, Expected);
}
// Tests case where the rule's match spans both source from the macro and its
// arg, with the begin location (the "anchor") being inside the macro.
TEST_F(TransformerTest, MatchSpansMacroTextButChangeDoesNotAnchoredInMacro) {
std::string Input = R"cc(
#define PLUS_ONE(a) 1 + a
int f() { return PLUS_ONE(3); }
)cc";
std::string Expected = R"cc(
#define PLUS_ONE(a) 1 + a
int f() { return PLUS_ONE(LIT); }
)cc";
StringRef E = "expr";
testRule(makeRule(binaryOperator(hasRHS(expr().bind(E))),
changeTo(node(std::string(E)), cat("LIT"))),
Input, Expected);
}
// No rewrite is applied when the changed text does not encompass the entirety
// of the expanded text. That is, the edit would have to be applied to the
// macro's definition to succeed and editing the expansion point would not
// suffice.
TEST_F(TransformerTest, NoPartialRewriteOMacroExpansion) {
std::string Input = R"cc(
#define ZERO_PLUS 0 + 3
int f(string s) { return ZERO_PLUS; })cc";
StringRef zero = "zero";
RewriteRule R = makeRule(integerLiteral(equals(0)).bind(zero),
changeTo(node(std::string(zero)), cat("0")));
testRule(R, Input, Input);
}
// This test handles the corner case where a macro expands within another macro
// to matching code, but that code is an argument to the nested macro call. A
// simple check of isMacroArgExpansion() vs. isMacroBodyExpansion() will get
// this wrong, and transform the code.
TEST_F(TransformerTest, NoPartialRewriteOfMacroExpansionForMacroArgs) {
std::string Input = R"cc(
#define NESTED(e) e
#define MACRO(str) 1 + NESTED(strlen((str).c_str()))
int f(string s) { return MACRO(s); }
)cc";
testRule(ruleStrlenSize(), Input, Input);
}
#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST
// Verifies that `Type` and `QualType` are not allowed as top-level matchers in
// rules.
TEST(TransformerDeathTest, OrderedRuleTypes) {
RewriteRule QualTypeRule = makeRule(qualType(), changeTo(cat("Q")));
EXPECT_DEATH(transformer::detail::buildMatchers(QualTypeRule),
"Matcher must be.*node matcher");
RewriteRule TypeRule = makeRule(arrayType(), changeTo(cat("T")));
EXPECT_DEATH(transformer::detail::buildMatchers(TypeRule),
"Matcher must be.*node matcher");
}
#endif
// Edits are able to span multiple files; in this case, a header and an
// implementation file.
TEST_F(TransformerTest, MultipleFiles) {
std::string Header = R"cc(void RemoveThisFunction();)cc";
std::string Source = R"cc(#include "input.h"
void RemoveThisFunction();)cc";
Transformer T(
makeRule(functionDecl(hasName("RemoveThisFunction")), changeTo(cat(""))),
consumer());
T.registerMatchers(&MatchFinder);
auto Factory = newFrontendActionFactory(&MatchFinder);
EXPECT_TRUE(runToolOnCodeWithArgs(
Factory->create(), Source, std::vector<std::string>(), "input.cc",
"clang-tool", std::make_shared<PCHContainerOperations>(),
{{"input.h", Header}}));
llvm::sort(Changes, [](const AtomicChange &L, const AtomicChange &R) {
return L.getFilePath() < R.getFilePath();
});
ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()),
"./input.h");
EXPECT_THAT(Changes[0].getInsertedHeaders(), IsEmpty());
EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty());
llvm::Expected<std::string> UpdatedCode =
clang::tooling::applyAllReplacements(Header,
Changes[0].getReplacements());
ASSERT_TRUE(static_cast<bool>(UpdatedCode))
<< "Could not update code: " << llvm::toString(UpdatedCode.takeError());
EXPECT_EQ(format(*UpdatedCode), "");
ASSERT_EQ(Changes[1].getFilePath(), "input.cc");
EXPECT_THAT(Changes[1].getInsertedHeaders(), IsEmpty());
EXPECT_THAT(Changes[1].getRemovedHeaders(), IsEmpty());
UpdatedCode = clang::tooling::applyAllReplacements(
Source, Changes[1].getReplacements());
ASSERT_TRUE(static_cast<bool>(UpdatedCode))
<< "Could not update code: " << llvm::toString(UpdatedCode.takeError());
EXPECT_EQ(format(*UpdatedCode), format("#include \"input.h\"\n"));
}
TEST_F(TransformerTest, AddIncludeMultipleFiles) {
std::string Header = R"cc(void RemoveThisFunction();)cc";
std::string Source = R"cc(#include "input.h"
void Foo() {RemoveThisFunction();})cc";
Transformer T(
makeRule(callExpr(callee(
functionDecl(hasName("RemoveThisFunction")).bind("fun"))),
addInclude(node("fun"), "header.h")),
consumer());
T.registerMatchers(&MatchFinder);
auto Factory = newFrontendActionFactory(&MatchFinder);
EXPECT_TRUE(runToolOnCodeWithArgs(
Factory->create(), Source, std::vector<std::string>(), "input.cc",
"clang-tool", std::make_shared<PCHContainerOperations>(),
{{"input.h", Header}}));
ASSERT_EQ(Changes.size(), 1U);
ASSERT_EQ(llvm::sys::path::convert_to_slash(Changes[0].getFilePath()),
"./input.h");
EXPECT_THAT(Changes[0].getInsertedHeaders(), ElementsAre("header.h"));
EXPECT_THAT(Changes[0].getRemovedHeaders(), IsEmpty());
llvm::Expected<std::string> UpdatedCode =
clang::tooling::applyAllReplacements(Header,
Changes[0].getReplacements());
ASSERT_TRUE(static_cast<bool>(UpdatedCode))
<< "Could not update code: " << llvm::toString(UpdatedCode.takeError());
EXPECT_EQ(format(*UpdatedCode), format(Header));
}
// A single change set can span multiple files.
TEST_F(TransformerTest, MultiFileEdit) {
// NB: The fixture is unused for this test, but kept for the test suite name.
std::string Header = R"cc(void Func(int id);)cc";
std::string Source = R"cc(#include "input.h"
void Caller() {
int id = 0;
Func(id);
})cc";
int ErrorCount = 0;
std::vector<AtomicChanges> ChangeSets;
clang::ast_matchers::MatchFinder MatchFinder;
Transformer T(
makeRule(callExpr(callee(functionDecl(hasName("Func"))),
forEachArgumentWithParam(expr().bind("arg"),
parmVarDecl().bind("param"))),
{changeTo(node("arg"), cat("ARG")),
changeTo(node("param"), cat("PARAM"))}),
[&](Expected<MutableArrayRef<AtomicChange>> Changes) {
if (Changes)
ChangeSets.push_back(AtomicChanges(Changes->begin(), Changes->end()));
else
++ErrorCount;
});
T.registerMatchers(&MatchFinder);
auto Factory = newFrontendActionFactory(&MatchFinder);
EXPECT_TRUE(runToolOnCodeWithArgs(
Factory->create(), Source, std::vector<std::string>(), "input.cc",
"clang-tool", std::make_shared<PCHContainerOperations>(),
{{"input.h", Header}}));
auto GetPathWithSlashes = [](const AtomicChange &C) {
return llvm::sys::path::convert_to_slash(C.getFilePath());
};
EXPECT_EQ(ErrorCount, 0);
EXPECT_THAT(ChangeSets, UnorderedElementsAre(UnorderedElementsAre(
ResultOf(GetPathWithSlashes, "input.cc"),
ResultOf(GetPathWithSlashes, "./input.h"))));
}
TEST_F(TransformerTest, GeneratesMetadata) {
std::string Input = R"cc(int target = 0;)cc";
std::string Expected = R"cc(REPLACE)cc";
RewriteRuleWith<std::string> Rule = makeRule(
varDecl(hasName("target")), changeTo(cat("REPLACE")), cat("METADATA"));
testRule(std::move(Rule), Input, Expected);
EXPECT_EQ(ErrorCount, 0);
EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA"));
}
TEST_F(TransformerTest, GeneratesMetadataWithNoEdits) {
std::string Input = R"cc(int target = 0;)cc";
RewriteRuleWith<std::string> Rule = makeRule(
varDecl(hasName("target")).bind("var"), noEdits(), cat("METADATA"));
testRule(std::move(Rule), Input, Input);
EXPECT_EQ(ErrorCount, 0);
EXPECT_THAT(StringMetadata, UnorderedElementsAre("METADATA"));
}
TEST_F(TransformerTest, PropagateMetadataErrors) {
class AlwaysFail : public transformer::MatchComputation<std::string> {
llvm::Error eval(const ast_matchers::MatchFinder::MatchResult &,
std::string *) const override {
return llvm::createStringError(llvm::errc::invalid_argument, "ERROR");
}
std::string toString() const override { return "AlwaysFail"; }
};
std::string Input = R"cc(int target = 0;)cc";
RewriteRuleWith<std::string> Rule = makeRule<std::string>(
varDecl(hasName("target")).bind("var"), changeTo(cat("REPLACE")),
std::make_shared<AlwaysFail>());
testRuleFailure(std::move(Rule), Input);
EXPECT_EQ(ErrorCount, 1);
}
} // namespace