blob: 735e611cca6be3e22b9a99a498ecc00bd8e46a8c [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/type_nullability.h"
#include <memory>
#include <string>
#include "absl/log/check.h"
#include "nullability/pragma.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Type.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringRef.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using testing::ElementsAre;
static test::EnableSmartPointers Enable;
class PointerTypeTest : public ::testing::Test {
protected:
QualType underlying(llvm::StringRef Name, TestAST& AST) {
auto Lookup = AST.context().getTranslationUnitDecl()->lookup(
&AST.context().Idents.get(Name));
EXPECT_TRUE(Lookup.isSingleResult());
return Lookup.find_first<TypeAliasDecl>()->getUnderlyingType();
}
};
TEST_F(PointerTypeTest, IsSupportedRawPointerType) {
TestAST AST(R"cpp(
using NotPointer = int;
using Pointer = NotPointer*;
using FuncPointer = Pointer (*)(Pointer);
using SugaredPointer = Pointer;
struct S;
using PointerDataMember = Pointer S::*;
using PointerMemberFunction = Pointer (S::*)(Pointer);
@class X;
using ObjCPointer = X;
template <class>
struct Container;
using ContainsPointers = Container<int*>;
namespace std {
template <typename T>
class unique_ptr;
}
using UniquePointer = std::unique_ptr<NotPointer>;
)cpp");
EXPECT_FALSE(isSupportedRawPointerType(underlying("NotPointer", AST)));
EXPECT_TRUE(isSupportedRawPointerType(underlying("Pointer", AST)));
EXPECT_TRUE(isSupportedRawPointerType(underlying("FuncPointer", AST)));
EXPECT_TRUE(isSupportedRawPointerType(underlying("SugaredPointer", AST)));
EXPECT_FALSE(isSupportedRawPointerType(underlying("PointerDataMember", AST)));
EXPECT_FALSE(
isSupportedRawPointerType(underlying("PointerMemberFunction", AST)));
EXPECT_FALSE(isSupportedRawPointerType(underlying("ObjCPointer", AST)));
EXPECT_FALSE(isSupportedRawPointerType(underlying("ContainsPointers", AST)));
EXPECT_FALSE(isSupportedRawPointerType(underlying("UniquePointer", AST)));
}
TEST_F(PointerTypeTest, IsSupportedSmartPointerType) {
TestAST AST(R"cpp(
namespace std {
template <typename T>
class unique_ptr;
template <typename T>
class shared_ptr;
template <typename T>
class weak_ptr;
} // namespace std
template <typename T>
class unique_ptr;
using NotPointer = int;
using UniquePointer = std::unique_ptr<NotPointer>;
using SharedPointer = std::shared_ptr<NotPointer>;
using WeakPointer = std::weak_ptr<NotPointer>;
using UniquePointerWrongNamespace = ::unique_ptr<NotPointer>;
using SugaredPointer = UniquePointer;
template <typename T>
struct PublicDerived : public std::unique_ptr<T> {};
template <typename T>
struct PrivateDerived : private std::unique_ptr<T> {};
using PublicDerivedPointer = PublicDerived<int>;
using PrivateDerivedPointer = PrivateDerived<int>;
template <typename T>
struct UserDefinedSmartPointer {
using absl_nullability_compatible = void;
};
using UserDefined = UserDefinedSmartPointer<NotPointer>;
template <class>
struct Container;
using ContainsPointers = Container<std::unique_ptr<int>>;
)cpp");
EXPECT_FALSE(isSupportedSmartPointerType(underlying("NotPointer", AST)));
EXPECT_TRUE(isSupportedSmartPointerType(underlying("UniquePointer", AST)));
EXPECT_TRUE(isSupportedSmartPointerType(underlying("SharedPointer", AST)));
EXPECT_FALSE(isSupportedSmartPointerType(underlying("WeakPointer", AST)));
EXPECT_FALSE(isSupportedSmartPointerType(
underlying("UniquePointerWrongNamespace", AST)));
EXPECT_TRUE(isSupportedSmartPointerType(underlying("SugaredPointer", AST)));
EXPECT_TRUE(isSupportedSmartPointerType(underlying("UserDefined", AST)));
EXPECT_TRUE(
isSupportedSmartPointerType(underlying("PublicDerivedPointer", AST)));
EXPECT_FALSE(
isSupportedSmartPointerType(underlying("PrivateDerivedPointer", AST)));
EXPECT_FALSE(
isSupportedSmartPointerType(underlying("ContainsPointers", AST)));
}
using UnderlyingRawPointerTest = PointerTypeTest;
TEST_F(UnderlyingRawPointerTest, Instantiated) {
// Test the case where the smart pointer type is instantiated and
// `underlyingRawPointerType()` therefore looks at the type aliases `pointer`
// or `element_type`.
// To test that we're really looking at these type aliases, make them refer to
// `char *` / `char`, independent of the template argument.
TestAST AST(R"cpp(
namespace std {
template <typename T>
class unique_ptr {
using pointer = char *;
};
template <typename T>
class shared_ptr {
using element_type = char;
};
} // namespace std
template <typename T>
struct PublicDerived : public std::unique_ptr<T> {};
template <typename T>
struct UserDefinedSmartPointer {
using absl_nullability_compatible = void;
using pointer = char *;
};
template <int i>
struct Recursive : public Recursive<i - 1> {};
template <>
class Recursive<0> {};
template <int i>
struct IndirectRecursive;
template <int i>
struct Base : public IndirectRecursive<i - 1> {};
template <int i>
struct IndirectRecursive : public Base<i> {};
template <>
struct IndirectRecursive<0> {};
using UniquePointer = std::unique_ptr<int>;
using SharedPointer = std::shared_ptr<int>;
using PublicDerivedPointer = PublicDerived<int>;
using UserDefined = UserDefinedSmartPointer<int>;
using Recursive2 = Recursive<2>;
using IndirectRecursive2 = IndirectRecursive<2>;
// Force the compiler to instantiate the templates. Otherwise, the
// `ClassTemplateSpecializationDecl` won't contain a `TypedefNameDecl` for
// `pointer` or `element_type`, and `underlyingRawPointerType()` will
// use the fallback behavior of looking at the template argument.
template class std::unique_ptr<int>;
template class std::shared_ptr<int>;
template class PublicDerived<int>;
template class UserDefinedSmartPointer<int>;
template class Recursive<2>;
template class IndirectRecursive<2>;
)cpp");
QualType PointerToCharTy = AST.context().getPointerType(AST.context().CharTy);
EXPECT_EQ(underlyingRawPointerType(underlying("UniquePointer", AST)),
PointerToCharTy);
EXPECT_EQ(underlyingRawPointerType(underlying("SharedPointer", AST)),
PointerToCharTy);
EXPECT_EQ(underlyingRawPointerType(underlying("PublicDerivedPointer", AST)),
PointerToCharTy);
EXPECT_EQ(underlyingRawPointerType(underlying("UserDefined", AST)),
PointerToCharTy);
EXPECT_TRUE(underlyingRawPointerType(underlying("Recursive2", AST)).isNull());
EXPECT_TRUE(
underlyingRawPointerType(underlying("IndirectRecursive2", AST)).isNull());
}
TEST_F(UnderlyingRawPointerTest, NotInstantiated) {
// Test the fallback behavior for `underlyingRawPointerType()` where the smart
// pointer type is not instantiated. (In fact, we can't even see the template
// definition here.)
TestAST AST(R"cpp(
namespace std {
template <typename T>
class unique_ptr;
template <typename T>
class shared_ptr;
} // namespace std
using UniquePointer = std::unique_ptr<int>;
using ArrayUniquePointer = std::unique_ptr<int[]>;
using SharedPointer = std::shared_ptr<int>;
using ArraySharedPointer = std::shared_ptr<int[]>;
template <typename T>
struct PublicDerived : public std::unique_ptr<T> {};
using PublicDerivedPointer = PublicDerived<int>;
template <typename T>
using Nullable [[clang::annotate("Nullable")]] = T;
using NullableUniquePointer = Nullable<std::unique_ptr<int>>;
template <int i>
struct Recursive : public Recursive<i - 1> {};
template <>
class Recursive<0> {};
using Recursive2 = Recursive<2>;
template <int i>
struct IndirectRecursive;
template <int i>
struct Base : public IndirectRecursive<i - 1> {};
template <int i>
struct IndirectRecursive : public Base<i> {};
template <>
struct IndirectRecursive<0> {};
using IndirectRecursive2 = IndirectRecursive<2>;
)cpp");
QualType PointerToIntTy = AST.context().getPointerType(AST.context().IntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("UniquePointer", AST)),
PointerToIntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("ArrayUniquePointer", AST)),
PointerToIntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("SharedPointer", AST)),
PointerToIntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("ArraySharedPointer", AST)),
PointerToIntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("PublicDerivedPointer", AST)),
PointerToIntTy);
EXPECT_EQ(underlyingRawPointerType(underlying("NullableUniquePointer", AST)),
PointerToIntTy);
EXPECT_TRUE(underlyingRawPointerType(underlying("Recursive2", AST)).isNull());
EXPECT_TRUE(
underlyingRawPointerType(underlying("IndirectRecursive2", AST)).isNull());
}
class GetNullabilityAnnotationsFromTypeTest : public ::testing::Test {
protected:
// C++ declarations prepended before parsing type in nullVec().
TestInputs Inputs;
std::string &Header;
std::string Preamble;
GetNullabilityAnnotationsFromTypeTest()
: Header(Inputs.ExtraFiles["header.h"]) {
Inputs.ExtraArgs.push_back("-include");
Inputs.ExtraArgs.push_back("header.h");
}
// Parses `Type` and returns getNullabilityAnnotationsFromType().
TypeNullability nullVec(llvm::StringRef Type) {
NullabilityPragmas Pragmas;
Inputs.Code = (Preamble + "\nusing Target = " + Type + ";").str();
Inputs.MakeAction = [&] {
struct Action : public SyntaxOnlyAction {
NullabilityPragmas &Pragmas;
Action(NullabilityPragmas &Pragmas) : Pragmas(Pragmas) {}
std::unique_ptr<ASTConsumer> CreateASTConsumer(
CompilerInstance &CI, llvm::StringRef File) override {
registerPragmaHandler(CI.getPreprocessor(), Pragmas);
return SyntaxOnlyAction::CreateASTConsumer(CI, File);
}
};
return std::make_unique<Action>(Pragmas);
};
TestAST AST(Inputs);
auto Target = AST.context().getTranslationUnitDecl()->lookup(
&AST.context().Idents.get("Target"));
CHECK(Target.isSingleResult());
return getTypeNullability(*Target.find_first<TypeAliasDecl>(),
TypeNullabilityDefaults(AST.context(), Pragmas));
}
};
TEST_F(GetNullabilityAnnotationsFromTypeTest, Pointers) {
EXPECT_THAT(nullVec("int"), ElementsAre());
EXPECT_THAT(nullVec("int *"), ElementsAre(NullabilityKind::Unspecified));
EXPECT_THAT(nullVec("int **"), ElementsAre(NullabilityKind::Unspecified,
NullabilityKind::Unspecified));
EXPECT_THAT(nullVec("int *_Nullable*_Nonnull"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, Sugar) {
Header = "using X = int* _Nonnull;";
EXPECT_THAT(nullVec("X"), ElementsAre(NullabilityKind::NonNull));
EXPECT_THAT(nullVec("X*"), ElementsAre(NullabilityKind::Unspecified,
NullabilityKind::NonNull));
EXPECT_THAT(nullVec("X(*)"), ElementsAre(NullabilityKind::Unspecified,
NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, References) {
// Top-level references can't be expression types, but we support them anyway
EXPECT_THAT(nullVec("int * _Nonnull &"),
ElementsAre(NullabilityKind::NonNull));
EXPECT_THAT(nullVec("int * _Nonnull &&"),
ElementsAre(NullabilityKind::NonNull));
// ... and other types involving references can appear in expressions
EXPECT_THAT(nullVec("int * _Nullable& (* _Nonnull)()"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
EXPECT_THAT(nullVec("int * _Nullable&& (* _Nonnull)()"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, Arrays) {
EXPECT_THAT(nullVec("int * _Nonnull[][2]"),
ElementsAre(NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, AliasTemplates) {
Header = R"cpp(
template <typename T>
using Nullable = T _Nullable;
template <typename T>
using Nonnull = T _Nonnull;
)cpp";
EXPECT_THAT(nullVec("Nullable<int*>"),
ElementsAre(NullabilityKind::Nullable));
EXPECT_THAT(
nullVec("Nullable<Nullable<int*>*>"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable));
EXPECT_THAT(nullVec("Nullable<Nullable<Nonnull<int*>*>*>"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable,
NullabilityKind::NonNull));
Header = R"cpp(
template <typename T, typename U>
struct Pair;
template <typename T>
using Two = Pair<T, T>;
)cpp";
EXPECT_THAT(
nullVec("Two<int* _Nullable>"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable));
Header = R"cpp(
template <typename T1>
using A = T1 *_Nullable;
template <typename T2>
using B = A<T2> *_Nonnull;
)cpp";
EXPECT_THAT(nullVec("B<int>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
Header = R"cpp(
template <typename T, typename U, typename V>
struct Triple;
template <typename A, typename... Rest>
using TripleAlias = Triple<A _Nonnull, Rest...>;
)cpp";
EXPECT_THAT(nullVec("TripleAlias<int *, int *_Nullable, int*>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable,
NullabilityKind::Unspecified));
Header = R"cpp(
template <class... Ts>
using First = __type_pack_element<0, Ts...>;
)cpp";
EXPECT_THAT(nullVec("First<int * _Nonnull>"),
ElementsAre(NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, DependentAlias) {
// Simple dependent type-aliases.
Header = R"cpp(
template <class T>
struct Nullable {
using type = T _Nullable;
};
)cpp";
EXPECT_THAT(nullVec("Nullable<int* _Nonnull *>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, NestedClassTemplate) {
// Simple struct inside template.
Header = R"cpp(
template <class T>
struct Outer {
struct Inner;
};
using OuterNullableInner = Outer<int *_Nonnull>::Inner;
)cpp";
// TODO: should be [NonNull]
EXPECT_THAT(nullVec("Outer<int* _Nonnull>::Inner"),
ElementsAre(NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, NestedClassInstantiation) {
Header = R"cpp(
template <class T, class U>
struct Pair;
template <class T, class U>
struct PairWrapper {
using type = Pair<T _Nullable, U>;
};
)cpp";
EXPECT_THAT(nullVec("PairWrapper<int*, int* _Nonnull>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
EXPECT_THAT(
nullVec("PairWrapper<int* _Nonnull, int*>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Unspecified));
EXPECT_THAT(
nullVec("PairWrapper<PairWrapper<int*, int* _Nonnull>::type*, "
" PairWrapper<int* _Nonnull, int*>::type*>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable,
NullabilityKind::NonNull,
NullabilityKind::Unspecified, NullabilityKind::Nullable,
NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, ReferenceOuterTemplateParam) {
// Referencing type-params from indirectly-enclosing template.
Header = R"cpp(
template <class A, class B>
struct Pair;
template <class T>
struct Outer {
template <class U>
struct Inner {
using type = Pair<U, T>;
};
};
)cpp";
EXPECT_THAT(nullVec("Outer<int *_Nullable>::Inner<int *_Nonnull>::type"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
// Same where Inner is an alias template.
Header = R"cpp(
template <class A, class B>
struct Pair;
template <class T>
struct Outer {
template <class U>
using Inner = Pair<U, T>;
};
)cpp";
EXPECT_THAT(nullVec("Outer<int *_Nullable>::Inner<int *_Nonnull>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, MixedQualiferChain) {
Header = R"cpp(
template <class A, class B>
class Pair;
struct Outer1 {
template <class T>
struct Middle {
template <class U>
struct Inner {
using type = Pair<T, U>;
};
};
};
template <class T>
struct Outer2 {
struct Middle {
template <class U>
struct Inner {
using type = Pair<T, U>;
};
};
};
template <class T>
struct Outer3 {
template <class U>
struct Middle {
struct Inner {
using type = Pair<T, U>;
};
};
};
)cpp";
EXPECT_THAT(
nullVec("Outer1::Middle<int * _Nullable>::Inner<int * _Nonnull>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
EXPECT_THAT(
nullVec("Outer2<int * _Nullable>::Middle::Inner<int * _Nonnull>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
EXPECT_THAT(
nullVec("Outer3<int * _Nullable>::Middle<int * _Nonnull>::Inner::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
};
TEST_F(GetNullabilityAnnotationsFromTypeTest, DependentlyNamedTemplate) {
// Instantiation of dependent-named template
Header = R"cpp(
struct Wrapper {
template <class T>
using Nullable = T _Nullable;
};
template <class U, class WrapT>
struct S {
using type = typename WrapT::template Nullable<U> *_Nonnull;
};
)cpp";
EXPECT_THAT(nullVec("S<int *, Wrapper>::type"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, PartialSpecialization) {
Header = R"cpp(
template <class>
struct S;
template <class T>
struct S<T *> {
using Alias = T;
};
)cpp";
EXPECT_THAT(nullVec("S<int*>::Alias"), testing::IsEmpty());
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, TemplateTemplateParams) {
// Template template params
Header = R"cpp(
template <class X>
struct Nullable {
using type = X _Nullable;
};
template <class X>
struct Nonnull {
using type = X _Nonnull;
};
template <template <class> class Nullability, class T>
struct Pointer {
using type = typename Nullability<T *>::type;
};
)cpp";
EXPECT_THAT(nullVec("Pointer<Nullable, int>::type"),
ElementsAre(NullabilityKind::Nullable));
EXPECT_THAT(nullVec("Pointer<Nullable, Pointer<Nonnull, int>::type>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
// Same thing, but with alias templates.
Header = R"cpp(
template <class X>
using Nullable = X _Nullable;
template <class X>
using Nonnull = X _Nonnull;
template <template <class> class Nullability, class T>
struct Pointer {
using type = Nullability<T *>;
};
)cpp";
EXPECT_THAT(nullVec("Pointer<Nullable, int>::type"),
ElementsAre(NullabilityKind::Nullable));
EXPECT_THAT(nullVec("Pointer<Nullable, Pointer<Nonnull, int>::type>::type"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, ClassTemplateParamPack) {
// Parameter packs
Header = R"cpp(
template <class... X>
struct TupleWrapper {
class Tuple;
};
template <class... X>
struct NullableTuple {
using type = TupleWrapper<X _Nullable...>::Tuple;
};
)cpp";
// TODO: should be [Unspecified, Nonnull]
EXPECT_THAT(
nullVec("TupleWrapper<int*, int* _Nonnull>::Tuple"),
ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
// TODO: should be [Nullable, Nullable]
EXPECT_THAT(
nullVec("NullableTuple<int*, int* _Nonnull>::type"),
ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, AliasTemplateWithDefaultArg) {
Header = "template <typename T1, typename T2 = T1> using AliasTemplate = T2;";
// TODO(b/281474380): This should be [Nullable], but we don't yet handle
// default arguments correctly.
EXPECT_THAT(nullVec("AliasTemplate<int * _Nullable>"),
ElementsAre(NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, ClassTemplateWithDefaultArg) {
Header = "template <typename T1, typename T2 = T1> class ClassTemplate {};";
// TODO(b/281474380): This should be [Nullable, Nullable], but we don't yet
// handle default arguments correctly.
EXPECT_THAT(
nullVec("ClassTemplate<int * _Nullable>"),
ElementsAre(NullabilityKind::Nullable, NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, TemplateArgsBehindAlias) {
Header = R"cpp(
template <class X>
struct Outer {
using Inner = X;
};
using OuterNullable = Outer<int *_Nullable>;
)cpp";
// TODO: should be [Nullable]
EXPECT_THAT(nullVec("OuterNullable::Inner"),
ElementsAre(NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, AnnotateNullable) {
Header = R"cpp(
namespace custom {
template <class T>
using Nullable [[clang::annotate("Nullable")]] = T;
template <class T>
using NonNull [[clang::annotate("Nonnull")]] = T;
} // namespace custom
template <class T, class U>
class pair;
template <class X>
using twice = pair<X, X>;
)cpp";
EXPECT_THAT(nullVec("custom::Nullable<int*>"),
ElementsAre(NullabilityKind::Nullable));
EXPECT_THAT(nullVec("custom::NonNull<int*>"),
ElementsAre(NullabilityKind::NonNull));
EXPECT_THAT(nullVec("pair<custom::NonNull<int*>, custom::Nullable<int*>>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
EXPECT_THAT(nullVec("twice<custom::NonNull<int*>>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::NonNull));
// Should still work if aliases *do* apply _Nullable.
Header = R"cpp(
namespace custom {
template <class T>
using Nullable [[clang::annotate("Nullable")]] = T _Nullable;
template <class T>
using NonNull [[clang::annotate("Nonnull")]] = T _Nonnull;
} // namespace custom
)cpp";
EXPECT_THAT(nullVec("custom::Nullable<int*>"),
ElementsAre(NullabilityKind::Nullable));
EXPECT_THAT(nullVec("custom::NonNull<int*>"),
ElementsAre(NullabilityKind::NonNull));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, SmartPointers) {
Header = R"cpp(
namespace std {
template <class T>
class unique_ptr {};
} // namespace std
template <class T>
using Nullable [[clang::annotate("Nullable")]] = T;
template <class T>
using NonNull [[clang::annotate("Nonnull")]] = T;
)cpp";
EXPECT_THAT(nullVec("int"), ElementsAre());
EXPECT_THAT(nullVec("std::unique_ptr<int>"),
ElementsAre(NullabilityKind::Unspecified));
EXPECT_THAT(
nullVec("std::unique_ptr<std::unique_ptr<int>>"),
ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
EXPECT_THAT(
nullVec("NonNull<std::unique_ptr<Nullable<std::unique_ptr<int>>>>"),
ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, Pragma) {
EXPECT_THAT(nullVec("int*"), ElementsAre(NullabilityKind::Unspecified));
Preamble = "#pragma nullability file_default nonnull";
EXPECT_THAT(nullVec("int*"), ElementsAre(NullabilityKind::NonNull));
Preamble = "#pragma nullability file_default nullable";
EXPECT_THAT(nullVec("int*"), ElementsAre(NullabilityKind::Nullable));
Preamble = "#pragma nullability file_default unspecified";
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, PragmaTypedef) {
Inputs.ExtraFiles["p.h"] = R"cpp(
#pragma nullability file_default nullable
typedef int *P;
)cpp";
Header = R"cpp(
#include "p.h"
#pragma nullability file_default nonnull
using PP = P*;
)cpp";
EXPECT_THAT(nullVec("PP*"),
ElementsAre(NullabilityKind::Unspecified,
NullabilityKind::NonNull, NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, PragmaMacroUsesExpansionLoc) {
Header = R"cpp(
#pragma nullability file_default nonnull
#define P int*
#define PTR(X) X*
)cpp";
Preamble = "#pragma nullability file_default nullable";
// Ideally we'd track the spelling location of the `*`, but instead we just
// use the expansion location.
EXPECT_THAT(nullVec("P*"), ElementsAre(NullabilityKind::Nullable,
NullabilityKind::Nullable));
EXPECT_THAT(nullVec("PTR(int*)"), ElementsAre(NullabilityKind::Nullable,
NullabilityKind::Nullable));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, PragmaTemplate) {
Header = R"cpp(
#pragma nullability file_default nonnull
template <class X>
using P = X*;
template <class X>
struct S {
using P = X*;
};
)cpp";
// int* is written in the main file, so the main file's "unspecified" applies.
EXPECT_THAT(nullVec("P<int*>"), ElementsAre(NullabilityKind::NonNull,
NullabilityKind::Unspecified));
EXPECT_THAT(nullVec("S<int*>::P"), ElementsAre(NullabilityKind::NonNull,
NullabilityKind::Unspecified));
}
TEST_F(GetNullabilityAnnotationsFromTypeTest, LostSugarCausesWrongType) {
Preamble = "#pragma nullability file_default nonnull";
Header = R"cpp(
#pragma nullability file_default nullable
using NullablePointer = int*;
auto identity(auto X) { return X; }
)cpp";
Inputs.ExtraArgs.push_back("-std=c++20");
// identity() destroys sugar, so we incorrectly use main-file's "nonnull".
EXPECT_THAT(nullVec("decltype(identity(NullablePointer{}))"),
ElementsAre(NullabilityKind::NonNull));
}
class PrintWithNullabilityTest : public ::testing::Test {
protected:
// C++ declarations prepended before parsing type in nullVec().
std::string Preamble;
// Parses `Type`, augments it with Nulls, and prints the result.
std::string print(llvm::StringRef Type, const TypeNullability &Nulls) {
clang::TestAST AST((Preamble + "\n using Target = " + Type + ";").str());
auto Target = AST.context().getTranslationUnitDecl()->lookup(
&AST.context().Idents.get("Target"));
CHECK(Target.isSingleResult());
QualType TargetType =
AST.context().getTypedefType(Target.find_first<TypeAliasDecl>());
return printWithNullability(TargetType, Nulls, AST.context());
}
};
TEST_F(PrintWithNullabilityTest, Pointers) {
EXPECT_EQ(print("int*", {NullabilityKind::Nullable}), "int * _Nullable");
EXPECT_EQ(
print("int***", {NullabilityKind::Nullable, NullabilityKind::NonNull,
NullabilityKind::Unspecified}),
"int ** _Nonnull * _Nullable");
}
TEST_F(PrintWithNullabilityTest, Sugar) {
Preamble = R"cpp(
template <class T>
using Ptr = T *;
using Int = int;
using IntPtr = Ptr<Int>;
)cpp";
EXPECT_EQ(print("IntPtr", {NullabilityKind::Nullable}), "int * _Nullable");
}
TEST_F(PrintWithNullabilityTest, Templates) {
Preamble = R"cpp(
template <class>
struct vector;
template <class, class>
struct pair;
)cpp";
EXPECT_EQ(print("vector<pair<int*, int*>*>",
{NullabilityKind::Nullable, NullabilityKind::NonNull,
NullabilityKind::Unspecified}),
"vector<pair<int * _Nonnull, int *> * _Nullable>");
}
TEST_F(PrintWithNullabilityTest, Functions) {
EXPECT_EQ(print("float*(*)(double*, double*)",
{NullabilityKind::Nullable, NullabilityKind::NonNull,
NullabilityKind::NonNull, NullabilityKind::Unspecified}),
"float * _Nonnull (* _Nullable)(double * _Nonnull, double *)");
}
TEST_F(PrintWithNullabilityTest, Arrays) {
EXPECT_EQ(print("int*[][2]", {NullabilityKind::Nullable}),
"int * _Nullable[][2]");
// variable length array not allowed at file scope, wrap in a function...
Preamble = R"cpp(
int n;
auto &makeArray() {
float *array[n];
return array;
}
)cpp";
EXPECT_EQ(print("decltype(makeArray())", {NullabilityKind::Nullable}),
"float * _Nullable (&)[n]");
}
} // namespace
} // namespace clang::tidy::nullability