blob: 4650c66d733a9bff20c03b2fc80b15082b6fefa9 [file] [log] [blame]
// 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 "nullability/type_nullability.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/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Basic/LLVM.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
#include "third_party/llvm/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;
test::EnableSmartPointers Enable;
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 custom_smart_ptr {
using absl_nullability_compatible = void;
using pointer = T*;
};
)cc";
TEST(IsInferenceTargetTest, GlobalVariables) {
TestAST AST((SmartPointerHeader + R"cc(
int* Pointer;
std::unique_ptr<int> StdSmartPointer;
custom_smart_ptr<int> CustomSmartPointer;
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_FALSE(isInferenceTarget(lookup("NotPointer", Ctx)));
}
TEST(IsInferenceTargetTest, Functions) {
TestAST AST((SmartPointerHeader + R"cc(
int* func(int*, int**, std::unique_ptr<int>,
custom_smart_ptr<int>) {
int* Local;
static int* StaticLocal;
std::unique_ptr<int> StdSmartLocal;
custom_smart_ptr<int> CustomSmartLocal;
}
void empty() {}
auto Lambda = []() {};
)cc")
.str());
auto &Ctx = AST.context();
EXPECT_TRUE(isInferenceTarget(lookup("func", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("Local", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("StaticLocal", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("StdSmartLocal", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("CustomSmartLocal", Ctx)));
EXPECT_TRUE(isInferenceTarget(lookup("empty", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("Lambda", Ctx)));
EXPECT_FALSE(isInferenceTarget(lookup("operator()", Ctx)));
}
TEST(IsInferenceTargetTest, ClassAndMembers) {
TestAST AST((SmartPointerHeader + R"cc(
class C {
void method();
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_TRUE(isInferenceTarget(lookup("method", 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*) {}
auto& FuncTmplSpec = funcTmpl<2>;
)cc");
auto &Ctx = AST.context();
// A function template is not an inference target.
const FunctionTemplateDecl &FuncTmpl =
lookup<FunctionTemplateDecl>("funcTmpl", Ctx);
EXPECT_FALSE(isInferenceTarget(FuncTmpl));
EXPECT_FALSE(isInferenceTarget(*FuncTmpl.getTemplatedDecl()));
// The function template specialization is *also* not an inference target.
const VarDecl &FuncTmplSpec = lookup<VarDecl>("FuncTmplSpec", Ctx);
EXPECT_FALSE(isInferenceTarget(FuncTmplSpec));
const ValueDecl &FuncTmpl2 =
*cast<DeclRefExpr>(FuncTmplSpec.getInit()->IgnoreImplicit())->getDecl();
EXPECT_FALSE(isInferenceTarget(FuncTmpl2));
}
TEST(IsInferenceTargetTest, ClassTemplateAndMembers) {
TestAST AST(R"cc(
template <typename T>
struct ClassTemplate {
T NonPtrField;
T* PtrField;
static T* StaticField;
};
ClassTemplate<int> I;
int A = I.NonPtrField;
int* B = I.PtrField;
int* C = I.StaticField;
)cc");
auto &Ctx = AST.context();
// Class templates and their fields 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())));
// Class template specializations and their fields are also not inference
// targets.
EXPECT_FALSE(isInferenceTarget(
*lookup<VarDecl>("I", Ctx).getType()->getAsRecordDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("A", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("B", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
EXPECT_FALSE(isInferenceTarget(
*cast<MemberExpr>(lookup<VarDecl>("C", Ctx).getInit()->IgnoreImplicit())
->getMemberDecl()));
}
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)));
}
} // namespace
} // namespace clang::tidy::nullability