forked from OSchip/llvm-project
821 lines
24 KiB
C++
821 lines
24 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/Stencil.h"
|
|
#include "llvm/Support/Errc.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace clang;
|
|
using namespace tooling;
|
|
using namespace ast_matchers;
|
|
namespace {
|
|
using ::testing::IsEmpty;
|
|
using transformer::cat;
|
|
using transformer::changeTo;
|
|
using transformer::RewriteRule;
|
|
|
|
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 llvm::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));
|
|
}
|
|
|
|
llvm::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 None;
|
|
}
|
|
if (ErrorCount != 0) {
|
|
llvm::errs() << "Generating changes failed.\n";
|
|
return None;
|
|
}
|
|
auto ChangedCode =
|
|
applyAtomicChanges("input.cc", Code, Changes, ApplyChangesSpec());
|
|
if (!ChangedCode) {
|
|
llvm::errs() << "Applying changes failed: "
|
|
<< llvm::toString(ChangedCode.takeError()) << "\n";
|
|
return None;
|
|
}
|
|
return *ChangedCode;
|
|
}
|
|
|
|
Transformer::ChangeConsumer consumer() {
|
|
return [this](Expected<AtomicChange> C) {
|
|
if (C) {
|
|
Changes.push_back(std::move(*C));
|
|
} else {
|
|
consumeError(C.takeError());
|
|
++ErrorCount;
|
|
}
|
|
};
|
|
}
|
|
|
|
template <typename R>
|
|
void testRule(R Rule, StringRef Input, StringRef Expected) {
|
|
Transformer T(std::move(Rule), consumer());
|
|
T.registerMatchers(&MatchFinder);
|
|
compareSnippets(Expected, rewrite(Input));
|
|
}
|
|
|
|
clang::ast_matchers::MatchFinder MatchFinder;
|
|
// Records whether any errors occurred in individual changes.
|
|
int ErrorCount = 0;
|
|
AtomicChanges Changes;
|
|
|
|
private:
|
|
FileContentMappings FileContents = {{"header.h", ""}};
|
|
};
|
|
|
|
class TransformerTest : public ClangRefactoringTestBase {
|
|
protected:
|
|
TransformerTest() { appendToHeader(KHeaderContents); }
|
|
};
|
|
|
|
// Given string s, change strlen($s.c_str()) to REPLACED.
|
|
static RewriteRule 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")))),
|
|
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, AddIncludeAngled) {
|
|
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(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, 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, 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, OrderedRuleUnrelated) {
|
|
StringRef Flag = "flag";
|
|
RewriteRule FlagRule = makeRule(
|
|
cxxMemberCallExpr(on(expr(hasType(cxxRecordDecl(
|
|
hasName("proto::ProtoCommandLineFlag"))))
|
|
.bind(Flag)),
|
|
unless(callee(cxxMethodDecl(hasName("GetProto"))))),
|
|
changeTo(node(std::string(Flag)), cat("PROTO")));
|
|
|
|
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);
|
|
}
|
|
|
|
//
|
|
// 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);
|
|
}
|
|
|
|
// 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
|
|
} // namespace
|