blob: 70ba180e6f6e316a1b943fd3c988f78f753905c6 [file] [log] [blame] [edit]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "nullability/inference/inferable.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
#include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using ::clang::ast_matchers::anything;
using ::clang::ast_matchers::equalsNode;
using ::clang::ast_matchers::hasDeclContext;
using ::clang::ast_matchers::hasName;
using ::clang::ast_matchers::isImplicit;
using ::clang::ast_matchers::match;
using ::clang::ast_matchers::namedDecl;
using ::clang::ast_matchers::unless;
template <class T = NamedDecl>
const T &lookup(llvm::StringRef Name, ASTContext &Ctx,
const Decl *DeclContext = nullptr) {
const auto &ContextMatcher =
DeclContext ? hasDeclContext(equalsNode(DeclContext)) : anything();
const auto &BoundNodes =
match(namedDecl(hasName(Name), ContextMatcher, unless(isImplicit()))
.bind("decl"),
Ctx);
const T *Match = nullptr;
for (const auto &N : BoundNodes) {
if (const auto *NAsT = N.getNodeAs<T>("decl")) {
if (Match)
ADD_FAILURE() << "Found more than one matching node for " << Name;
Match = NAsT;
}
}
EXPECT_NE(Match, nullptr) << Name;
return *Match;
}
constexpr llvm::StringRef SmartPointerHeader = R"cc(
namespace std {
template <typename T>
struct unique_ptr {
using pointer = T*;
};
} // namespace std
template <typename T>
struct _Nullable custom_smart_ptr {
using pointer = T*;
};
class _Nullable custom_smart_int_ptr { using pointer = int*; };
)cc";
TEST(IsInferenceTargetTest, GlobalVariables) {
TestAST AST((SmartPointerHeader + R"cc(
int* Pointer;
std::unique_ptr<int> StdSmartPointer;
custom_smart_ptr<int> CustomSmartPointer;
custom_smart_int_ptr CustomSmartIntPointer;
int NotPointer;
)cc")
.str());
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(lookup("Pointer", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StdSmartPointer", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("CustomSmartPointer", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("CustomSmartIntPointer", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("NotPointer", Ctx)));
}
TEST(IsInferenceTargetTest, Functions) {
TestAST AST((SmartPointerHeader + R"cc(
int* func(int* Param, int** NestedParam,
std::unique_ptr<int> StdSmartParam,
custom_smart_ptr<int> CustomSmartParam) {
int* Local;
static int* StaticLocal;
std::unique_ptr<int> StdSmartLocal;
custom_smart_ptr<int> CustomSmartLocal;
}
void empty() {}
auto Lambda = []() {};
auto LambdaWithPtr = [](int*) {};
)cc")
.str());
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(lookup("func", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("Param", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("NestedParam", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("StdSmartParam", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("CustomSmartParam", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("Local", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StaticLocal", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StdSmartLocal", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("CustomSmartLocal", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("empty", Ctx)));
auto &Lambda = lookup<VarDecl>("Lambda", Ctx);
auto *LambdaCtx = cast<LambdaExpr>(Lambda.getInit())->getLambdaClass();
EXPECT_FALSE(isInferenceTarget(Lambda));
EXPECT_FALSE(isInferenceTarget(lookup("operator()", Ctx, LambdaCtx)));
auto &LambdaWithPtr = lookup<VarDecl>("LambdaWithPtr", Ctx);
auto *LambdaWithPtrCtx =
cast<LambdaExpr>(LambdaWithPtr.getInit())->getLambdaClass();
EXPECT_FALSE(isInferenceTarget(LambdaWithPtr));
EXPECT_TRUE(isInferenceTarget(lookup("operator()", Ctx, LambdaWithPtrCtx)));
}
TEST(IsInferenceTargetTest, ClassAndMembers) {
TestAST AST((SmartPointerHeader + R"cc(
class C {
void method();
int* methodWithPtr();
int NonPtrField;
int* PtrField;
static int* StaticField;
std::unique_ptr<int> StdSmartField;
custom_smart_ptr<int> CustomSmartField;
};
)cc")
.str());
auto &Ctx = AST.context();
EXPECT_FALSE(isInferenceTarget(lookup<CXXRecordDecl>("C", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("method", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("methodWithPtr", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("NonPtrField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("PtrField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StaticField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("StdSmartField", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("CustomSmartField", Ctx)));
}
TEST(IsInferenceTargetTest, FunctionTemplate) {
TestAST AST(R"cc(
template <int X>
void funcTmpl(int*) {
int* LocalInTmpl;
struct StructInTmpl {
void funcInStructInTmpl(int* P) { int* LocalInStructInTmpl; }
int* FieldInStructInTmpl;
};
}
auto& FuncTmplInst = funcTmpl<2>;
)cc");
auto &Ctx = AST.context();
// A function template is not an inference target, nor are functions or fields
// or local variables contained within.
const FunctionTemplateDecl &FuncTmpl =
lookup<FunctionTemplateDecl>("funcTmpl", Ctx);
EXPECT_FALSE(isInferenceTarget(FuncTmpl));
EXPECT_FALSE(isInferenceTarget(*FuncTmpl.getTemplatedDecl()));
EXPECT_FALSE(isInferenceTarget(
lookup("LocalInTmpl", Ctx, FuncTmpl.getTemplatedDecl())));
auto *StructInTmpl =
&lookup<CXXRecordDecl>("StructInTmpl", Ctx, FuncTmpl.getTemplatedDecl());
EXPECT_FALSE(
isInferenceTarget(lookup("FieldInStructInTmpl", Ctx, StructInTmpl)));
auto &FuncInStructInTmpl = lookup("funcInStructInTmpl", Ctx, StructInTmpl);
EXPECT_FALSE(isInferenceTarget(FuncInStructInTmpl));
EXPECT_FALSE(isInferenceTarget(
lookup("LocalInStructInTmpl", Ctx, &FuncInStructInTmpl)));
// The function template instantiation *is* an inference target, as are
// functions, fields, and local variables within.
const ValueDecl &Instantiation =
*cast<DeclRefExpr>(
lookup<VarDecl>("FuncTmplInst", Ctx).getInit()->IgnoreImplicit())
->getDecl();
EXPECT_TRUE(isInferenceTarget(Instantiation));
EXPECT_TRUE(isInferenceTarget(lookup("LocalInTmpl", Ctx, &Instantiation)));
auto *StructInInstantiation =
&lookup<CXXRecordDecl>("StructInTmpl", Ctx, &Instantiation);
EXPECT_TRUE(isInferenceTarget(
lookup("FieldInStructInTmpl", Ctx, StructInInstantiation)));
auto &FuncInStructInInstantiation =
lookup("funcInStructInTmpl", Ctx, StructInInstantiation);
EXPECT_TRUE(isInferenceTarget(FuncInStructInInstantiation));
EXPECT_TRUE(isInferenceTarget(
lookup("LocalInStructInTmpl", Ctx, &FuncInStructInInstantiation)));
}
TEST(IsInferenceTargetTest, ClassTemplateAndMembers) {
TestAST AST(R"cc(
template <typename T>
struct ClassTemplate {
T NonPtrField;
T* PtrField;
static T* StaticField;
T method();
T* methodWithPtr();
struct Nested {
struct NestedTwo {
T* NestedStructPtrField;
bool* nestedStructMethod();
};
NestedTwo NestedStructTwo;
};
Nested NestedStruct;
};
ClassTemplate<int> IntClass;
int IntA = IntClass.NonPtrField;
int* IntB = IntClass.PtrField;
int* IntC = IntClass.StaticField;
int IntD = IntClass.method();
int* IntE = IntClass.methodWithPtr();
int* IntF = IntClass.NestedStruct.NestedStructTwo.NestedStructPtrField;
bool* IntG = IntClass.NestedStruct.NestedStructTwo.nestedStructMethod();
ClassTemplate<int*> IntStarClass;
int* IntStarA = IntStarClass.NonPtrField;
int** IntStarB = IntStarClass.PtrField;
int** IntStarC = IntStarClass.StaticField;
int* IntStarD = IntStarClass.method();
int** IntStarE = IntStarClass.methodWithPtr();
int** IntStarF = IntStarClass.NestedStruct.NestedStructTwo.NestedStructPtrField;
bool* IntStarG = IntStarClass.NestedStruct.NestedStructTwo.nestedStructMethod();
)cc");
auto &Ctx = AST.context();
// Class templates and fields and functions inside them are not inference
// targets.
auto &ClassTemplate = lookup<ClassTemplateDecl>("ClassTemplate", Ctx);
EXPECT_FALSE(isInferenceTarget(ClassTemplate));
EXPECT_FALSE(isInferenceTarget(*ClassTemplate.getTemplatedDecl()));
EXPECT_FALSE(isInferenceTarget(
lookup("NonPtrField", Ctx, ClassTemplate.getTemplatedDecl())));
EXPECT_FALSE(isInferenceTarget(
lookup("PtrField", Ctx, ClassTemplate.getTemplatedDecl())));
EXPECT_FALSE(isInferenceTarget(
lookup("StaticField", Ctx, ClassTemplate.getTemplatedDecl())));
EXPECT_FALSE(isInferenceTarget(
lookup("method", Ctx, ClassTemplate.getTemplatedDecl())));
EXPECT_FALSE(isInferenceTarget(
lookup("methodWithPtr", Ctx, ClassTemplate.getTemplatedDecl())));
auto *NestedTwoInClassTemplate = &lookup<CXXRecordDecl>(
"NestedTwo", Ctx,
&lookup<CXXRecordDecl>("Nested", Ctx, ClassTemplate.getTemplatedDecl()));
EXPECT_FALSE(isInferenceTarget(
lookup("NestedStructPtrField", Ctx, NestedTwoInClassTemplate)));
EXPECT_FALSE(isInferenceTarget(
lookup("nestedStructMethod", Ctx, NestedTwoInClassTemplate)));
// Class template instantiations are inference targets, as are fields
// and functions inside, if they have inferable types, i.e. they are pointers
// and the pointer-ness is specified by the template.
// For the int template argument:
EXPECT_FALSE(isInferenceTarget(
*lookup<VarDecl>("IntClass", Ctx).getType()->getAsRecordDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntA", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntB", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntC", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntD", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntE", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntF", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntG", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
// For the int* template argument, notably, we get the same results, even
// though the canonical types of more fields are pointers:
EXPECT_FALSE(isInferenceTarget(
*lookup<VarDecl>("IntStarClass", Ctx).getType()->getAsRecordDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntStarA", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntStarB", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntStarC", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntStarD", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntStarE", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("IntStarF", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<CXXMemberCallExpr>(
lookup<VarDecl>("IntStarG", Ctx).getInit()->IgnoreImplicit())
->getMethodDecl()));
}
TEST(InferableTest, CountInferableSlots) {
TestAST AST((SmartPointerHeader + R"cc(
using Pointer = int *;
template <class T>
struct S;
struct T;
void f1(int *);
void f2(Pointer);
void f3(int **);
void f4(Pointer *);
void f5(int *&);
void f6(int (*)()); // function pointer
void f7(std::unique_ptr<int>);
void f8(custom_smart_ptr<int>);
int *g1(int);
Pointer g2(int);
std::unique_ptr<int> g3(int);
custom_smart_ptr<int> g4(int);
void h1(S<int *>);
void h2(int T::*); // pointer to data member
void h3(int (T::*)()); // pointer to member function
)cc")
.str());
auto &Ctx = AST.context();
// All the 'f's have a single pointer arg.
EXPECT_EQ(1, countInferableSlots(lookup("f1", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f2", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f3", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f4", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f5", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f6", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f7", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("f8", Ctx)));
// All the 'g's have a pointer return.
EXPECT_EQ(1, countInferableSlots(lookup("g1", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("g2", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("g3", Ctx)));
EXPECT_EQ(1, countInferableSlots(lookup("g4", Ctx)));
// The 'h's have types that aren't really pointers.
EXPECT_EQ(0, countInferableSlots(lookup("h1", Ctx)));
EXPECT_EQ(0, countInferableSlots(lookup("h2", Ctx)));
EXPECT_EQ(0, countInferableSlots(lookup("h3", Ctx)));
}
TEST(InferableTest, InnerPointersNotInferable) {
TestAST AST(R"cc(
int*** ThreePointersOneInferable;
)cc");
auto &Ctx = AST.context();
EXPECT_EQ(1, countInferableSlots(lookup("ThreePointersOneInferable", Ctx)));
}
TEST(InferableTest, TemplateArgumentPointersNotInferable) {
TestAST AST(R"cc(
template <typename T>
struct S {};
S<int*> NotInferable;
)cc");
auto &Ctx = AST.context();
EXPECT_EQ(0, countInferableSlots(lookup("NotInferable", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("NotInferable", Ctx)));
}
TEST(InferableTest, TemplateInstantiationOnlyArgWithStarIsInferable) {
TestAST AST((SmartPointerHeader + R"cc(
template <typename T>
struct S {
T* Inferable;
T NotInferable;
using U = T;
U NotInferableThroughAlias;
};
S<int*> AnS;
int** FromTStarField = AnS.Inferable;
int* FromTField = AnS.NotInferable;
int* FromTFieldAlias = AnS.NotInferableThroughAlias;
S<std::unique_ptr<int>> AnSSmart;
std::unique_ptr<int>* FromTStarFieldSmart = AnSSmart.Inferable;
std::unique_ptr<int>& FromTFieldSmart = AnSSmart.NotInferable;
std::unique_ptr<int>& FromTFieldAliasSmart = AnSSmart.NotInferableThroughAlias;
)cc")
.str());
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromTStarField", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromTField", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromTFieldAlias", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("FromTStarFieldSmart", Ctx)
.getInit()
->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromTFieldSmart", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("FromTFieldAliasSmart", Ctx)
.getInit()
->IgnoreImplicit())
->getMemberDecl()));
}
TEST(InferableTest, TypeAliasTemplate) {
TestAST AST(R"cc(
template <typename T>
using PtrAlias = T*;
template <typename T>
using Alias = T;
template <typename U>
struct S {
Alias<U*> inferable;
Alias<U> not_inferable;
PtrAlias<U> inferable_pointer_in_alias;
};
S<int*> AnS;
int** FromInferable = AnS.inferable;
int* FromNotInferable = AnS.not_inferable;
int** FromInferablePointerInAlias = AnS.inferable_pointer_in_alias;
)cc");
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromInferable", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromNotInferable", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("FromInferablePointerInAlias", Ctx)
.getInit()
->IgnoreImplicit())
->getMemberDecl()));
}
TEST(InferableTest, NestedTemplates) {
TestAST AST(R"cc(
template <typename T>
struct S {
template <typename U>
struct Nested {
U* inferable_u;
U not_inferable_u;
T* inferable_t;
T not_inferable_t;
};
Nested<bool*> ANested;
};
S<int*> AnS;
bool** FromInferableU = AnS.ANested.inferable_u;
bool* FromNotInferableU = AnS.ANested.not_inferable_u;
int** FromInferableT = AnS.ANested.inferable_t;
int* FromNotInferableT = AnS.ANested.not_inferable_t;
)cc");
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromInferableU", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("FromNotInferableU", Ctx)
.getInit()
->IgnoreImplicit())
->getMemberDecl()));
EXPECT_TRUE(isInferenceTarget(
*cast<MemberExpr>(
lookup<VarDecl>("FromInferableT", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("FromNotInferableT", Ctx)
.getInit()
->IgnoreImplicit())
->getMemberDecl()));
}
} // namespace
} // namespace clang::tidy::nullability