forked from OSchip/llvm-project
539 lines
17 KiB
C++
539 lines
17 KiB
C++
//===- unittest/Tooling/StencilTest.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/Stencil.h"
|
|
#include "clang/AST/ASTTypeTraits.h"
|
|
#include "clang/ASTMatchers/ASTMatchers.h"
|
|
#include "clang/Tooling/FixIt.h"
|
|
#include "clang/Tooling/Tooling.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace clang;
|
|
using namespace transformer;
|
|
using namespace ast_matchers;
|
|
|
|
namespace {
|
|
using ::llvm::Failed;
|
|
using ::llvm::HasValue;
|
|
using ::llvm::StringError;
|
|
using ::testing::AllOf;
|
|
using ::testing::HasSubstr;
|
|
using MatchResult = MatchFinder::MatchResult;
|
|
|
|
// Create a valid translation-unit from a statement.
|
|
static std::string wrapSnippet(StringRef StatementCode) {
|
|
return ("struct S { int field; }; auto stencil_test_snippet = []{" +
|
|
StatementCode + "};")
|
|
.str();
|
|
}
|
|
|
|
static DeclarationMatcher wrapMatcher(const StatementMatcher &Matcher) {
|
|
return varDecl(hasName("stencil_test_snippet"),
|
|
hasDescendant(compoundStmt(hasAnySubstatement(Matcher))));
|
|
}
|
|
|
|
struct TestMatch {
|
|
// The AST unit from which `result` is built. We bundle it because it backs
|
|
// the result. Users are not expected to access it.
|
|
std::unique_ptr<ASTUnit> AstUnit;
|
|
// The result to use in the test. References `ast_unit`.
|
|
MatchResult Result;
|
|
};
|
|
|
|
// Matches `Matcher` against the statement `StatementCode` and returns the
|
|
// result. Handles putting the statement inside a function and modifying the
|
|
// matcher correspondingly. `Matcher` should match one of the statements in
|
|
// `StatementCode` exactly -- that is, produce exactly one match. However,
|
|
// `StatementCode` may contain other statements not described by `Matcher`.
|
|
static llvm::Optional<TestMatch> matchStmt(StringRef StatementCode,
|
|
StatementMatcher Matcher) {
|
|
auto AstUnit = tooling::buildASTFromCode(wrapSnippet(StatementCode));
|
|
if (AstUnit == nullptr) {
|
|
ADD_FAILURE() << "AST construction failed";
|
|
return llvm::None;
|
|
}
|
|
ASTContext &Context = AstUnit->getASTContext();
|
|
auto Matches = ast_matchers::match(
|
|
traverse(ast_type_traits::TK_AsIs, wrapMatcher(Matcher)), Context);
|
|
// We expect a single, exact match for the statement.
|
|
if (Matches.size() != 1) {
|
|
ADD_FAILURE() << "Wrong number of matches: " << Matches.size();
|
|
return llvm::None;
|
|
}
|
|
return TestMatch{std::move(AstUnit), MatchResult(Matches[0], &Context)};
|
|
}
|
|
|
|
class StencilTest : public ::testing::Test {
|
|
protected:
|
|
// Verifies that the given stencil fails when evaluated on a valid match
|
|
// result. Binds a statement to "stmt", a (non-member) ctor-initializer to
|
|
// "init", an expression to "expr" and a (nameless) declaration to "decl".
|
|
void testError(const Stencil &Stencil,
|
|
::testing::Matcher<std::string> Matcher) {
|
|
const std::string Snippet = R"cc(
|
|
struct A {};
|
|
class F : public A {
|
|
public:
|
|
F(int) {}
|
|
};
|
|
F(1);
|
|
)cc";
|
|
auto StmtMatch = matchStmt(
|
|
Snippet,
|
|
stmt(hasDescendant(
|
|
cxxConstructExpr(
|
|
hasDeclaration(decl(hasDescendant(cxxCtorInitializer(
|
|
isBaseInitializer())
|
|
.bind("init")))
|
|
.bind("decl")))
|
|
.bind("expr")))
|
|
.bind("stmt"));
|
|
ASSERT_TRUE(StmtMatch);
|
|
if (auto ResultOrErr = Stencil->eval(StmtMatch->Result)) {
|
|
ADD_FAILURE() << "Expected failure but succeeded: " << *ResultOrErr;
|
|
} else {
|
|
auto Err = llvm::handleErrors(ResultOrErr.takeError(),
|
|
[&Matcher](const StringError &Err) {
|
|
EXPECT_THAT(Err.getMessage(), Matcher);
|
|
});
|
|
if (Err) {
|
|
ADD_FAILURE() << "Unhandled error: " << llvm::toString(std::move(Err));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests failures caused by references to unbound nodes. `unbound_id` is the
|
|
// id that will cause the failure.
|
|
void testUnboundNodeError(const Stencil &Stencil, StringRef UnboundId) {
|
|
testError(Stencil,
|
|
AllOf(HasSubstr(std::string(UnboundId)), HasSubstr("not bound")));
|
|
}
|
|
};
|
|
|
|
TEST_F(StencilTest, SingleStatement) {
|
|
StringRef Condition("C"), Then("T"), Else("E");
|
|
const std::string Snippet = R"cc(
|
|
if (true)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
)cc";
|
|
auto StmtMatch = matchStmt(
|
|
Snippet, ifStmt(hasCondition(expr().bind(Condition)),
|
|
hasThen(stmt().bind(Then)), hasElse(stmt().bind(Else))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
// Invert the if-then-else.
|
|
auto Stencil =
|
|
cat("if (!", node(std::string(Condition)), ") ",
|
|
statement(std::string(Else)), " else ", statement(std::string(Then)));
|
|
EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
|
|
HasValue("if (!true) return 0; else return 1;"));
|
|
}
|
|
|
|
TEST_F(StencilTest, UnboundNode) {
|
|
const std::string Snippet = R"cc(
|
|
if (true)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
)cc";
|
|
auto StmtMatch = matchStmt(Snippet, ifStmt(hasCondition(stmt().bind("a1")),
|
|
hasThen(stmt().bind("a2"))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
auto Stencil = cat("if(!", node("a1"), ") ", node("UNBOUND"), ";");
|
|
auto ResultOrErr = Stencil->eval(StmtMatch->Result);
|
|
EXPECT_TRUE(llvm::errorToBool(ResultOrErr.takeError()))
|
|
<< "Expected unbound node, got " << *ResultOrErr;
|
|
}
|
|
|
|
// Tests that a stencil with a single parameter (`Id`) evaluates to the expected
|
|
// string, when `Id` is bound to the expression-statement in `Snippet`.
|
|
void testExpr(StringRef Id, StringRef Snippet, const Stencil &Stencil,
|
|
StringRef Expected) {
|
|
auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
|
|
ASSERT_TRUE(StmtMatch);
|
|
EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
|
|
HasValue(std::string(Expected)));
|
|
}
|
|
|
|
void testFailure(StringRef Id, StringRef Snippet, const Stencil &Stencil,
|
|
testing::Matcher<std::string> MessageMatcher) {
|
|
auto StmtMatch = matchStmt(Snippet, expr().bind(Id));
|
|
ASSERT_TRUE(StmtMatch);
|
|
EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
|
|
Failed<StringError>(testing::Property(
|
|
&StringError::getMessage, MessageMatcher)));
|
|
}
|
|
|
|
TEST_F(StencilTest, SelectionOp) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "3;", cat(node(std::string(Id))), "3");
|
|
}
|
|
|
|
TEST_F(StencilTest, IfBoundOpBound) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "3;", ifBound(Id, cat("5"), cat("7")), "5");
|
|
}
|
|
|
|
TEST_F(StencilTest, IfBoundOpUnbound) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "3;", ifBound("other", cat("5"), cat("7")), "7");
|
|
}
|
|
|
|
TEST_F(StencilTest, ExpressionOpNoParens) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "3;", expression(Id), "3");
|
|
}
|
|
|
|
// Don't parenthesize a parens expression.
|
|
TEST_F(StencilTest, ExpressionOpNoParensParens) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "(3);", expression(Id), "(3)");
|
|
}
|
|
|
|
TEST_F(StencilTest, ExpressionOpBinaryOpParens) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "3+4;", expression(Id), "(3+4)");
|
|
}
|
|
|
|
// `expression` shares code with other ops, so we get sufficient coverage of the
|
|
// error handling code with this test. If that changes in the future, more error
|
|
// tests should be added.
|
|
TEST_F(StencilTest, ExpressionOpUnbound) {
|
|
StringRef Id = "id";
|
|
testFailure(Id, "3;", expression("ACACA"),
|
|
AllOf(HasSubstr("ACACA"), HasSubstr("not bound")));
|
|
}
|
|
|
|
TEST_F(StencilTest, DerefPointer) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; x;", deref(Id), "*x");
|
|
}
|
|
|
|
TEST_F(StencilTest, DerefBinOp) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; x + 1;", deref(Id), "*(x + 1)");
|
|
}
|
|
|
|
TEST_F(StencilTest, DerefAddressExpr) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; &x;", deref(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, AddressOfValue) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; x;", addressOf(Id), "&x");
|
|
}
|
|
|
|
TEST_F(StencilTest, AddressOfDerefExpr) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; *x;", addressOf(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeDerefValue) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; x;", maybeDeref(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeDerefPointer) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; x;", maybeDeref(Id), "*x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeDerefBinOp) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; x + 1;", maybeDeref(Id), "*(x + 1)");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeDerefAddressExpr) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; &x;", maybeDeref(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeAddressOfPointer) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; x;", maybeAddressOf(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeAddressOfValue) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; x;", addressOf(Id), "&x");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeAddressOfBinOp) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int x; x + 1;", maybeAddressOf(Id), "&(x + 1)");
|
|
}
|
|
|
|
TEST_F(StencilTest, MaybeAddressOfDerefExpr) {
|
|
StringRef Id = "id";
|
|
testExpr(Id, "int *x; *x;", addressOf(Id), "x");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpValue) {
|
|
StringRef Snippet = R"cc(
|
|
S x;
|
|
x;
|
|
)cc";
|
|
StringRef Id = "id";
|
|
testExpr(Id, Snippet, access(Id, "field"), "x.field");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpValueExplicitText) {
|
|
StringRef Snippet = R"cc(
|
|
S x;
|
|
x;
|
|
)cc";
|
|
StringRef Id = "id";
|
|
testExpr(Id, Snippet, access(Id, cat("field")), "x.field");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpValueAddress) {
|
|
StringRef Snippet = R"cc(
|
|
S x;
|
|
&x;
|
|
)cc";
|
|
StringRef Id = "id";
|
|
testExpr(Id, Snippet, access(Id, "field"), "x.field");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpPointer) {
|
|
StringRef Snippet = R"cc(
|
|
S *x;
|
|
x;
|
|
)cc";
|
|
StringRef Id = "id";
|
|
testExpr(Id, Snippet, access(Id, "field"), "x->field");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpPointerDereference) {
|
|
StringRef Snippet = R"cc(
|
|
S *x;
|
|
*x;
|
|
)cc";
|
|
StringRef Id = "id";
|
|
testExpr(Id, Snippet, access(Id, "field"), "x->field");
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpExplicitThis) {
|
|
using clang::ast_matchers::hasObjectExpression;
|
|
using clang::ast_matchers::memberExpr;
|
|
|
|
// Set up the code so we can bind to a use of this.
|
|
StringRef Snippet = R"cc(
|
|
class C {
|
|
public:
|
|
int x;
|
|
int foo() { return this->x; }
|
|
};
|
|
)cc";
|
|
auto StmtMatch = matchStmt(
|
|
Snippet, traverse(ast_type_traits::TK_AsIs,
|
|
returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
|
|
hasObjectExpression(expr().bind("obj"))))))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
const Stencil Stencil = access("obj", "field");
|
|
EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result),
|
|
HasValue("this->field"));
|
|
}
|
|
|
|
TEST_F(StencilTest, AccessOpImplicitThis) {
|
|
using clang::ast_matchers::hasObjectExpression;
|
|
using clang::ast_matchers::memberExpr;
|
|
|
|
// Set up the code so we can bind to a use of (implicit) this.
|
|
StringRef Snippet = R"cc(
|
|
class C {
|
|
public:
|
|
int x;
|
|
int foo() { return x; }
|
|
};
|
|
)cc";
|
|
auto StmtMatch =
|
|
matchStmt(Snippet, returnStmt(hasReturnValue(ignoringImplicit(memberExpr(
|
|
hasObjectExpression(expr().bind("obj")))))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
const Stencil Stencil = access("obj", "field");
|
|
EXPECT_THAT_EXPECTED(Stencil->eval(StmtMatch->Result), HasValue("field"));
|
|
}
|
|
|
|
TEST_F(StencilTest, RunOp) {
|
|
StringRef Id = "id";
|
|
auto SimpleFn = [Id](const MatchResult &R) {
|
|
return std::string(R.Nodes.getNodeAs<Stmt>(Id) != nullptr ? "Bound"
|
|
: "Unbound");
|
|
};
|
|
testExpr(Id, "3;", run(SimpleFn), "Bound");
|
|
}
|
|
|
|
TEST_F(StencilTest, CatOfMacroRangeSucceeds) {
|
|
StringRef Snippet = R"cpp(
|
|
#define MACRO 3.77
|
|
double foo(double d);
|
|
foo(MACRO);)cpp";
|
|
|
|
auto StmtMatch =
|
|
matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))),
|
|
argumentCountIs(1),
|
|
hasArgument(0, expr().bind("arg"))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
Stencil S = cat(node("arg"));
|
|
EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("MACRO"));
|
|
}
|
|
|
|
TEST_F(StencilTest, CatOfMacroArgRangeSucceeds) {
|
|
StringRef Snippet = R"cpp(
|
|
#define MACRO(a, b) a + b
|
|
MACRO(2, 3);)cpp";
|
|
|
|
auto StmtMatch =
|
|
matchStmt(Snippet, binaryOperator(hasRHS(expr().bind("rhs"))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
Stencil S = cat(node("rhs"));
|
|
EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3"));
|
|
}
|
|
|
|
TEST_F(StencilTest, CatOfMacroArgSubRangeSucceeds) {
|
|
StringRef Snippet = R"cpp(
|
|
#define MACRO(a, b) a + b
|
|
int foo(int);
|
|
MACRO(2, foo(3));)cpp";
|
|
|
|
auto StmtMatch = matchStmt(
|
|
Snippet, binaryOperator(hasRHS(callExpr(
|
|
callee(functionDecl(hasName("foo"))), argumentCountIs(1),
|
|
hasArgument(0, expr().bind("arg"))))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
Stencil S = cat(node("arg"));
|
|
EXPECT_THAT_EXPECTED(S->eval(StmtMatch->Result), HasValue("3"));
|
|
}
|
|
|
|
TEST_F(StencilTest, CatOfInvalidRangeFails) {
|
|
StringRef Snippet = R"cpp(
|
|
#define MACRO (3.77)
|
|
double foo(double d);
|
|
foo(MACRO);)cpp";
|
|
|
|
auto StmtMatch =
|
|
matchStmt(Snippet, callExpr(callee(functionDecl(hasName("foo"))),
|
|
argumentCountIs(1),
|
|
hasArgument(0, expr().bind("arg"))));
|
|
ASSERT_TRUE(StmtMatch);
|
|
Stencil S = cat(node("arg"));
|
|
Expected<std::string> Result = S->eval(StmtMatch->Result);
|
|
ASSERT_THAT_EXPECTED(Result, Failed<StringError>());
|
|
llvm::handleAllErrors(Result.takeError(), [](const llvm::StringError &E) {
|
|
EXPECT_THAT(E.getMessage(), AllOf(HasSubstr("selected range"),
|
|
HasSubstr("macro expansion")));
|
|
});
|
|
}
|
|
|
|
TEST(StencilToStringTest, RawTextOp) {
|
|
auto S = cat("foo bar baz");
|
|
StringRef Expected = R"("foo bar baz")";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, RawTextOpEscaping) {
|
|
auto S = cat("foo \"bar\" baz\\n");
|
|
StringRef Expected = R"("foo \"bar\" baz\\n")";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, DebugPrintNodeOp) {
|
|
auto S = dPrint("Id");
|
|
StringRef Expected = R"repr(dPrint("Id"))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, ExpressionOp) {
|
|
auto S = expression("Id");
|
|
StringRef Expected = R"repr(expression("Id"))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, DerefOp) {
|
|
auto S = deref("Id");
|
|
StringRef Expected = R"repr(deref("Id"))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, AddressOfOp) {
|
|
auto S = addressOf("Id");
|
|
StringRef Expected = R"repr(addressOf("Id"))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, SelectionOp) {
|
|
auto S1 = cat(node("node1"));
|
|
EXPECT_EQ(S1->toString(), "selection(...)");
|
|
}
|
|
|
|
TEST(StencilToStringTest, AccessOpText) {
|
|
auto S = access("Id", "memberData");
|
|
StringRef Expected = R"repr(access("Id", "memberData"))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, AccessOpSelector) {
|
|
auto S = access("Id", cat(name("otherId")));
|
|
StringRef Expected = R"repr(access("Id", selection(...)))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, AccessOpStencil) {
|
|
auto S = access("Id", cat("foo_", "bar"));
|
|
StringRef Expected = R"repr(access("Id", seq("foo_", "bar")))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, IfBoundOp) {
|
|
auto S = ifBound("Id", cat("trueText"), access("exprId", "memberData"));
|
|
StringRef Expected =
|
|
R"repr(ifBound("Id", "trueText", access("exprId", "memberData")))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, RunOp) {
|
|
auto F1 = [](const MatchResult &R) { return "foo"; };
|
|
auto S1 = run(F1);
|
|
EXPECT_EQ(S1->toString(), "run(...)");
|
|
}
|
|
|
|
TEST(StencilToStringTest, Sequence) {
|
|
auto S = cat("foo", access("x", "m()"), "bar",
|
|
ifBound("x", cat("t"), access("e", "f")));
|
|
StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr"
|
|
R"repr(ifBound("x", "t", access("e", "f"))))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, SequenceEmpty) {
|
|
auto S = cat();
|
|
StringRef Expected = "seq()";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, SequenceSingle) {
|
|
auto S = cat("foo");
|
|
StringRef Expected = "\"foo\"";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
|
|
TEST(StencilToStringTest, SequenceFromVector) {
|
|
auto S = catVector({cat("foo"), access("x", "m()"), cat("bar"),
|
|
ifBound("x", cat("t"), access("e", "f"))});
|
|
StringRef Expected = R"repr(seq("foo", access("x", "m()"), "bar", )repr"
|
|
R"repr(ifBound("x", "t", access("e", "f"))))repr";
|
|
EXPECT_EQ(S->toString(), Expected);
|
|
}
|
|
} // namespace
|