| // 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 <algorithm> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #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/AST/TypeLoc.h" | |
| #include "clang/ASTMatchers/ASTMatchFinder.h" | |
| #include "clang/ASTMatchers/ASTMatchers.h" | |
| #include "clang/Basic/SourceLocation.h" | |
| #include "clang/Basic/Specifiers.h" | |
| #include "clang/Frontend/CompilerInstance.h" | |
| #include "clang/Frontend/FrontendAction.h" | |
| #include "clang/Frontend/FrontendActions.h" | |
| #include "clang/Lex/Lexer.h" | |
| #include "clang/Testing/CommandLineArgs.h" | |
| #include "clang/Testing/TestAST.h" | |
| #include "clang/Tooling/Transformer/SourceCode.h" | |
| #include "llvm/ADT/StringRef.h" | |
| #include "llvm/Testing/Annotations/Annotations.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 ::clang::ast_matchers::hasName; | |
| using ::clang::ast_matchers::match; | |
| using ::clang::ast_matchers::selectFirst; | |
| using ::clang::ast_matchers::typeAliasDecl; | |
| using ::llvm::Annotations; | |
| using ::testing::ElementsAre; | |
| using ::testing::FieldsAre; | |
| using ::testing::IsEmpty; | |
| using ::testing::Matcher; | |
| using ::testing::Optional; | |
| 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 <typename T> | |
| struct _Nullable UserDefinedSmartPointerWithAttribute{}; | |
| using UserDefinedWithAttribute = | |
| UserDefinedSmartPointerWithAttribute<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("UserDefinedWithAttribute", 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 PrivateDerived : private std::unique_ptr<T> {}; | |
| template <typename T> | |
| struct UserDefinedSmartPointer { | |
| using absl_nullability_compatible = void; | |
| using pointer = char *; | |
| }; | |
| template <typename T> | |
| struct _Nullable UserDefinedSmartPointerWithAttribute { | |
| 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 PrivateDerivedPointer = PrivateDerived<int>; | |
| using UserDefined = UserDefinedSmartPointer<int>; | |
| using UserDefinedWithAttribute = UserDefinedSmartPointerWithAttribute<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 PrivateDerived<int>; | |
| template class UserDefinedSmartPointer<int>; | |
| template class UserDefinedSmartPointerWithAttribute<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_TRUE(underlyingRawPointerType(underlying("PrivateDerivedPointer", AST)) | |
| .isNull()); | |
| EXPECT_EQ(underlyingRawPointerType(underlying("PrivateDerivedPointer", AST), | |
| AS_private), | |
| PointerToCharTy); | |
| EXPECT_EQ(underlyingRawPointerType(underlying("UserDefined", AST)), | |
| PointerToCharTy); | |
| EXPECT_EQ( | |
| underlyingRawPointerType(underlying("UserDefinedWithAttribute", 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> | |
| struct PrivateDerived : private std::unique_ptr<T> {}; | |
| using PrivateDerivedPointer = PrivateDerived<int>; | |
| template <typename T> | |
| struct _Nullable UserDefinedSmartPointerWithAttribute; | |
| using UserDefinedWithAttribute = UserDefinedSmartPointerWithAttribute<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_TRUE(underlyingRawPointerType(underlying("PrivateDerivedPointer", AST)) | |
| .isNull()); | |
| EXPECT_EQ(underlyingRawPointerType(underlying("PrivateDerivedPointer", AST), | |
| AS_private), | |
| PointerToIntTy); | |
| EXPECT_EQ( | |
| underlyingRawPointerType(underlying("UserDefinedWithAttribute", AST)), | |
| PointerToIntTy); | |
| EXPECT_EQ(underlyingRawPointerType(underlying("NullableUniquePointer", AST)), | |
| PointerToIntTy); | |
| EXPECT_TRUE(underlyingRawPointerType(underlying("Recursive2", AST)).isNull()); | |
| EXPECT_TRUE( | |
| underlyingRawPointerType(underlying("IndirectRecursive2", AST)).isNull()); | |
| } | |
| TEST_F(UnderlyingRawPointerTest, BaseClassIsTemplateTemplateParameter) { | |
| // This is a crash repro for b/339236507. | |
| TestAST AST(R"cc( | |
| struct Other {}; | |
| template <template <typename> typename Base> | |
| struct Derived : public Base<Other> { | |
| using Target = Derived; | |
| }; | |
| )cc"); | |
| const auto *Target = selectFirst<TypeAliasDecl>( | |
| "T", match(typeAliasDecl(hasName("Target")).bind("T"), AST.context())); | |
| EXPECT_EQ(underlyingRawPointerType(Target->getUnderlyingType()), QualType()); | |
| } | |
| std::function<std::unique_ptr<FrontendAction>()> makeRegisterPragmasAction( | |
| NullabilityPragmas &Pragmas) { | |
| return [&Pragmas]() { | |
| 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); | |
| }; | |
| } | |
| // Returns the RHS of the typedef named "Target" | |
| QualType getTypedefTarget(TestAST &AST) { | |
| auto Target = AST.context().getTranslationUnitDecl()->lookup( | |
| &AST.context().Idents.get("Target")); | |
| if (!Target.isSingleResult()) { | |
| ADD_FAILURE() << "Expected single typedef named 'Target'\n" | |
| << AST.sourceManager().getBufferData( | |
| AST.sourceManager().getMainFileID()); | |
| return QualType(); | |
| } | |
| return Target.find_first<TypeAliasDecl>()->getUnderlyingType(); | |
| } | |
| class GetTypeNullabilityTest : public ::testing::Test { | |
| protected: | |
| // C++ declarations prepended before parsing type in nullVec(). | |
| TestInputs Inputs; | |
| std::string &Header; | |
| std::string Preamble; | |
| GetTypeNullabilityTest() : Header(Inputs.ExtraFiles["header.h"]) { | |
| Inputs.ExtraArgs.push_back("-include"); | |
| Inputs.ExtraArgs.push_back("nullability.h"); | |
| Inputs.ExtraArgs.push_back("-include"); | |
| Inputs.ExtraArgs.push_back("header.h"); | |
| // TODO: b/357760487 -- use the flag until the issue is resolved or we find | |
| // a workaround. | |
| Inputs.ExtraArgs.push_back( | |
| "-fretain-subst-template-type-parm-type-ast-nodes"); | |
| Inputs.ExtraFiles["nullability.h"] = R"cpp( | |
| template <class X> | |
| using Nullable [[clang::annotate("Nullable")]] = X; | |
| template <class X> | |
| using Nonnull [[clang::annotate("Nonnull")]] = X; | |
| template <class X> | |
| using Unknown [[clang::annotate("Nullability_Unspecified")]] = X; | |
| )cpp"; | |
| } | |
| // Parses `Type` and returns getTypeNullability(). | |
| TypeNullability nullVec(llvm::StringRef Type) { | |
| NullabilityPragmas Pragmas; | |
| Inputs.Code = (Preamble + "\nusing Target = " + Type + ";").str(); | |
| Inputs.MakeAction = makeRegisterPragmasAction(Pragmas); | |
| TestAST AST(Inputs); | |
| return getTypeNullability(getTypedefTarget(AST), | |
| AST.sourceManager().getMainFileID(), | |
| TypeNullabilityDefaults(AST.context(), Pragmas)); | |
| } | |
| }; | |
| // GetTypeNullabilityLocsTests below cover much of the same functionality as | |
| // GetTypeNullabilityTest could cover, as long as they both use | |
| // NullabilityWalker under the hood, so we only add additional | |
| // GetTypeNullabilityTests for differences in their coverage, | |
| // namely pragma consideration and Unspecified as a default nullability. | |
| TEST_F(GetTypeNullabilityTest, UnannotatedGivesDefault) { | |
| EXPECT_THAT(nullVec("int *"), ElementsAre(NullabilityKind::Unspecified)); | |
| } | |
| TEST_F(GetTypeNullabilityTest, 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(GetTypeNullabilityTest, 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(GetTypeNullabilityTest, 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(GetTypeNullabilityTest, 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(GetTypeNullabilityTest, 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)); | |
| } | |
| TEST_F(GetTypeNullabilityTest, UnknownOptsOutOfPragma) { | |
| Preamble = R"cpp( | |
| #pragma nullability file_default nonnull | |
| using Ptr = int*; | |
| using UPtr = Unknown<int*>; | |
| using UPtr2 = int* _Null_unspecified; | |
| template <class T> | |
| using PtrTmpl = T*; | |
| template <class T> | |
| using UPtrTmpl = Unknown<T*>; | |
| template <class T> | |
| using UPtrTmpl2 = T* _Null_unspecified; | |
| )cpp"; | |
| EXPECT_THAT(nullVec("Ptr"), ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("PtrTmpl<int>"), ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("UPtr"), ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("UPtrTmpl<int>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("UPtr2"), ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("UPtrTmpl2<int>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| } | |
| TEST_F(GetTypeNullabilityTest, Overrides) { | |
| EXPECT_THAT(nullVec("Nullable<Unknown<int*>>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Unknown<Nullable<int*>>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Nullable<int* _Null_unspecified>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Unknown<Nullable<int*>>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Nullable<int*> _Null_unspecified"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Nonnull<Nullable<int*>>"), | |
| ElementsAre(NullabilityKind::NonNull)); | |
| Header = R"cpp( | |
| #pragma nullability file_default nullable | |
| using IntPtr = int*; // nullable | |
| )cpp"; | |
| EXPECT_THAT(nullVec("Unknown<IntPtr>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| } | |
| TEST_F(GetTypeNullabilityTest, PragmaSmartPointers) { | |
| Inputs.ExtraFiles["smart.h"] = R"cpp( | |
| namespace std { | |
| template <class T> | |
| class unique_ptr {}; | |
| }; // namespace std | |
| )cpp"; | |
| Header = R"cpp( | |
| #include "smart.h" | |
| #pragma nullability file_default nullable | |
| namespace directive { | |
| using namespace std; | |
| } | |
| namespace declaration { | |
| using std::unique_ptr; | |
| } | |
| namespace transparent { | |
| template <typename T> | |
| using unique_ptr = std::unique_ptr<T>; | |
| } | |
| namespace opaque { | |
| template <typename T> | |
| using unique_ptr = | |
| std::unique_ptr<T> [[clang::annotate_type("hello_world")]]; | |
| } | |
| )cpp"; | |
| Preamble = R"cpp( | |
| #pragma nullability file_default nonnull | |
| )cpp"; | |
| EXPECT_THAT(nullVec("std::unique_ptr<int>"), | |
| ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("Unknown<std::unique_ptr<int>>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("directive::unique_ptr<int>"), | |
| ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("Unknown<directive::unique_ptr<int>>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("declaration::unique_ptr<int>"), | |
| ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("Unknown<declaration::unique_ptr<int>>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("transparent::unique_ptr<int>"), | |
| ElementsAre(NullabilityKind::NonNull)); | |
| EXPECT_THAT(nullVec("Unknown<transparent::unique_ptr<int>>"), | |
| ElementsAre(NullabilityKind::Unspecified)); | |
| EXPECT_THAT(nullVec("opaque::unique_ptr<int>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| EXPECT_THAT(nullVec("Unknown<opaque::unique_ptr<int>>"), | |
| ElementsAre(NullabilityKind::Nullable)); | |
| } | |
| TEST(IsUnknownValidOnTest, All) { | |
| std::string Preamble = R"cpp( | |
| namespace std { | |
| template <class T, class Deleter = void> | |
| class unique_ptr {}; | |
| template <class T, int N = 5> | |
| class shared_ptr {}; | |
| } // namespace std | |
| using Int = int; | |
| using IntPtr = int*; | |
| namespace using_decl { | |
| using Int = int; | |
| using std::unique_ptr; | |
| } // namespace using_decl | |
| namespace using_directive { | |
| using namespace std; | |
| } | |
| namespace template_aliases { | |
| template <class A, class B> | |
| using up_transparent = std::unique_ptr<A, B>; | |
| template <class A> | |
| using up_withdefault = std::unique_ptr<A>; | |
| template <class A, class B, class = void> | |
| using up_withsfinae = std::unique_ptr<A, B>; | |
| template <class A, class B> | |
| using up_reversed = std::unique_ptr<B, A>; | |
| template <class A, class B> | |
| using up_twolayers = up_transparent<A, B>; | |
| template <class A> | |
| using up_withsugar = std::unique_ptr<A> [[clang::annotate_type("hi")]]; | |
| template <class A, int N> | |
| using sp_withnttp = std::shared_ptr<A, N>; | |
| } // namespace template_aliases | |
| )cpp"; | |
| for (std::string Valid : { | |
| "int*", | |
| "Int*", | |
| "std::unique_ptr<int>", | |
| "std::shared_ptr<int>", | |
| "using_decl::unique_ptr<int>", | |
| "using_directive::unique_ptr<int>", | |
| "template_aliases::up_transparent<int, void>", | |
| "template_aliases::up_withdefault<int>", | |
| "template_aliases::up_withsfinae<int, void>", | |
| "template_aliases::up_twolayers<int, void>", | |
| "template_aliases::sp_withnttp<int, 4>", | |
| }) { | |
| TestAST AST(Preamble + "\nusing Target=" + Valid + ";"); | |
| EXPECT_TRUE(isUnknownValidOn(getTypedefTarget(AST))) << Valid; | |
| } | |
| for (std::string Invalid : { | |
| "int", | |
| "int * _Nonnull", | |
| "std::unique_ptr<int> _Nonnull", | |
| "IntPtr", | |
| "using_decl::Int", | |
| "template_aliases::up_reversed<int, void>", | |
| "template_aliases::up_withsugar<int>", | |
| }) { | |
| TestAST AST(Preamble + "\nusing Target=" + Invalid + ";"); | |
| EXPECT_FALSE(isUnknownValidOn(getTypedefTarget(AST))) << Invalid; | |
| } | |
| } | |
| 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()); | |
| return printWithNullability(getTypedefTarget(AST), 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]"); | |
| } | |
| using MissingLocSlots = std::vector< | |
| std::pair<unsigned, Matcher<std::optional<clang::NullabilityKind>>>>; | |
| using ComparableNullabilityLoc = | |
| std::tuple<unsigned, std::optional<clang::NullabilityKind>, | |
| std::optional<Annotations::Range>>; | |
| std::optional<Annotations::Range> getRange(std::optional<TypeLoc> L, | |
| TestAST &AST) { | |
| if (!L) return std::nullopt; | |
| const auto &SM = AST.sourceManager(); | |
| std::optional<CharSourceRange> ActualRange = tooling::getFileRange( | |
| CharSourceRange::getTokenRange(L->getSourceRange()), AST.context(), | |
| /*IncludeMacroExpansion=*/true); | |
| if (!ActualRange) { | |
| ADD_FAILURE() << "unable to retrieve source range for TypeLoc "; | |
| return std::nullopt; | |
| } | |
| return Annotations::Range{SM.getFileOffset(ActualRange->getBegin()), | |
| SM.getFileOffset(ActualRange->getEnd())}; | |
| } | |
| // Snippet should be a string of code contain a `using Target = ` typedef, | |
| // with the aliased type annotated with ranges representing each expected | |
| // TypeNullabilityLoc. Each range should have a numeric name equal to the Slot | |
| // number expected for that TypeNullabilityLoc. | |
| // | |
| // Require passing of Snippet into this function instead of using the test | |
| // fixture's snippet to make each expectation read more typically, i.e. in the | |
| // format EXPECT_THAT(functionOf(Input), matchesExpectations()), as opposed to | |
| // having Input also passed to the expectations or not passed to functionOf. | |
| std::vector<ComparableNullabilityLoc> getComparableNullabilityLocs( | |
| llvm::StringRef Snippet, llvm::StringRef HeaderWithAttributes = "") { | |
| Annotations AnnotatedInput(Snippet); | |
| NullabilityPragmas Pragmas; | |
| TestInputs Inputs(AnnotatedInput.code()); | |
| Inputs.MakeAction = makeRegisterPragmasAction(Pragmas); | |
| Inputs.Language = TestLanguage::Lang_CXX17; | |
| // TODO: b/357760487 -- use the flag until the issue is resolved or we find a | |
| // workaround. | |
| Inputs.ExtraArgs.push_back( | |
| "-fretain-subst-template-type-parm-type-ast-nodes"); | |
| if (!HeaderWithAttributes.empty()) { | |
| Inputs.ExtraFiles["header.h"] = HeaderWithAttributes; | |
| Inputs.ExtraArgs.push_back("-include"); | |
| Inputs.ExtraArgs.push_back("header.h"); | |
| } | |
| TestAST AST{Inputs}; | |
| auto Target = AST.context().getTranslationUnitDecl()->lookup( | |
| &AST.context().Idents.get("Target")); | |
| CHECK(Target.isSingleResult()); | |
| std::vector<TypeNullabilityLoc> NullabilityLocs = getTypeNullabilityLocs( | |
| Target.find_first<TypeAliasDecl>()->getTypeSourceInfo()->getTypeLoc(), | |
| TypeNullabilityDefaults(AST.context(), Pragmas)); | |
| std::vector<ComparableNullabilityLoc> ComparableOutputs; | |
| for (const auto &Output : NullabilityLocs) { | |
| ComparableOutputs.push_back( | |
| {Output.Slot, Output.ExistingAnnotation, getRange(Output.Loc, AST)}); | |
| } | |
| return ComparableOutputs; | |
| } | |
| class GetTypeNullabilityLocsTest : public ::testing::Test { | |
| protected: | |
| // Snippet should be a string of code contain a `using Target = ` typedef, | |
| // with the aliased type annotated with ranges representing each expected | |
| // TypeNullabilityLoc. Each range should have a numeric name equal to the Slot | |
| // number expected for that TypeNullabilityLoc. | |
| std::string Snippet; | |
| Matcher<std::vector<ComparableNullabilityLoc>> matchesRanges( | |
| MissingLocSlots SlotsWithNoLocs = {}) { | |
| if (Snippet.empty()) { | |
| ADD_FAILURE() << "Snippet is empty. You probably need to set it before " | |
| "creating this matcher."; | |
| return IsEmpty(); | |
| } | |
| Annotations AnnotatedInput(Snippet); | |
| auto Ranges = AnnotatedInput.all_ranges(); | |
| std::vector<std::pair<unsigned, Matcher<ComparableNullabilityLoc>>> | |
| MatchersBySlotNumber; | |
| for (const auto &RangesForKey : Ranges) { | |
| if (RangesForKey.getValue().size() != 1) { | |
| ADD_FAILURE() | |
| << "Input should contain ranges named with Slot numbers, e.g. " | |
| "$0[[int*]], with only one range for each name."; | |
| return IsEmpty(); | |
| } | |
| unsigned Slot; | |
| if (RangesForKey.getKey().consumeInteger(10, Slot)) { | |
| ADD_FAILURE() | |
| << "Unable to parse Slot number from annotated range name: " | |
| << RangesForKey.getKey().str(); | |
| return IsEmpty(); | |
| } | |
| llvm::StringRef Payload = | |
| AnnotatedInput.rangeWithPayload(RangesForKey.getKey()).second; | |
| std::optional<clang::NullabilityKind> ExpectedNullabilityKind = | |
| std::nullopt; | |
| if (!Payload.empty()) { | |
| clang::NullabilityKind KindFromPayload; | |
| if (Payload == "Nullable") { | |
| KindFromPayload = NullabilityKind::Nullable; | |
| } else if (Payload == "NonNull") { | |
| KindFromPayload = NullabilityKind::NonNull; | |
| } else if (Payload == "Unspecified") { | |
| KindFromPayload = NullabilityKind::Unspecified; | |
| } else { | |
| ADD_FAILURE() | |
| << "Payload is not empty but does not match one of the expected " | |
| "values indicating a nullability kind."; | |
| } | |
| ExpectedNullabilityKind = KindFromPayload; | |
| } | |
| Annotations::Range ExpectedRange = RangesForKey.getValue().front(); | |
| MatchersBySlotNumber.push_back( | |
| {Slot, FieldsAre(Slot, ExpectedNullabilityKind, ExpectedRange)}); | |
| } | |
| for (auto &[Slot, NKMatcher] : SlotsWithNoLocs) { | |
| MatchersBySlotNumber.push_back( | |
| {Slot, FieldsAre(Slot, NKMatcher, std::nullopt)}); | |
| } | |
| // We get much better error messages with an ordered container matcher, | |
| // which means we need to first sort the Matchers by Slot number. Locs | |
| // should be assembled in Slot number order anyway. | |
| std::stable_sort( | |
| MatchersBySlotNumber.begin(), MatchersBySlotNumber.end(), | |
| [](const auto &A, const auto &B) { return A.first < B.first; }); | |
| std::vector<Matcher<ComparableNullabilityLoc>> SortedMatchers; | |
| std::for_each(MatchersBySlotNumber.begin(), MatchersBySlotNumber.end(), | |
| [&SortedMatchers](const auto &A) { | |
| SortedMatchers.push_back(A.second); | |
| }); | |
| return ElementsAreArray(SortedMatchers); | |
| } | |
| }; | |
| TEST_F(GetTypeNullabilityLocsTest, Pointers) { | |
| std::string Using = "using Target = "; | |
| Snippet = Using + R"(int;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0[[int *]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0[[$1[[int *]]*]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0[[$1[[$2[[int *]]*]]*]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0(Nullable)[[int *]] _Nullable;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0(NonNull)[[int *]] _Nonnull;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0(Unspecified)[[int *]] _Null_unspecified;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = | |
| Using + R"($0(Nullable)[[$1(NonNull)[[int *]] _Nonnull *]] _Nullable;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, Sugar) { | |
| std::string Header = R"cpp(using X = int* _Nonnull;)cpp"; | |
| Snippet = Header + R"( | |
| using Target = $0(NonNull)[[X]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = $0(Nullable)[[$1(NonNull)[[X]] *]] _Nullable; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = $0[[$1(NonNull)[[X]](*)]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, References) { | |
| std::string Using = "using Target = "; | |
| // Top-level references can't be expression types, but we support them anyway | |
| Snippet = Using + R"($0(NonNull)[[int *]] _Nonnull &;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Using + R"($0(NonNull)[[int *]] _Nonnull &&;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| // ... and other types involving references can appear in expressions | |
| Snippet = | |
| Using + | |
| R"($0(NonNull)[[$1(Nullable)[[int *]] _Nullable & (* _Nonnull)()]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = | |
| Using + | |
| R"($0(NonNull)[[$1(Nullable)[[int *]] _Nullable && (* _Nonnull)()]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, Arrays) { | |
| Snippet = R"(using Target = $0(NonNull)[[int *]] _Nonnull [][2];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, AliasTemplates) { | |
| Snippet = R"( | |
| template <typename T> | |
| using Nullable = T _Nullable; | |
| template <typename T> | |
| using Nonnull = T _Nonnull; | |
| using Target = | |
| Nullable<$0(Nullable)[[Nullable<$1(Nullable)[[Nonnull<$2(NonNull)[[int*]]>*]]>*]]>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = R"( | |
| template <typename T> | |
| using Alias = T; | |
| using Target = Alias<$0(Nullable)[[int *]] _Nullable>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = R"( | |
| template <typename T, typename U> | |
| struct Pair; | |
| template <typename T> | |
| using Two = Pair<T, T>; | |
| using Target = Two<$0(Nullable)[[$1(Nullable)[[int *]]]] _Nullable>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = R"( | |
| template <typename T1> | |
| using A = T1 * _Nullable; | |
| template <typename T2> | |
| using B = A<T2> * _Nonnull; | |
| using Target = $0[[$1(NonNull)[[B<int>]] *]]; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{2, Optional(NullabilityKind::Nullable)}})); | |
| Snippet = R"( | |
| template <typename T, typename U, typename V> | |
| struct Triple; | |
| template <typename A, typename... Rest> | |
| using TripleAlias = Triple<A _Nonnull, Rest...>; | |
| using Target = TripleAlias<$0(NonNull)[[int *]], $1(Nullable)[[int *]] | |
| _Nullable, $2[[int *]]>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = R"( | |
| template <class... Ts> | |
| using First = __type_pack_element<0, Ts...>; | |
| using Target = First<$0(NonNull)[[int *]] _Nonnull>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, DependentAlias) { | |
| // Simple dependent type-aliases. | |
| Snippet = R"( | |
| template <typename T> | |
| struct Nullable { | |
| using type = T _Nullable; | |
| }; | |
| using Target = Nullable<$0(Nullable)[[$1(NonNull)[[int *]] _Nonnull *]]>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, NestedClassTemplate) { | |
| // Simple struct inside template. | |
| Snippet = R"( | |
| template <class T> | |
| struct Outer { | |
| struct Inner; | |
| }; | |
| using Target = Outer<$0(NonNull)[[int *]] _Nonnull>::Inner; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, NestedClassInstantiation) { | |
| std::string Header = R"cpp( | |
| template <class T, class U> | |
| struct Pair; | |
| template <class T, class U> | |
| struct PairWrapper { | |
| using type = Pair<T _Nullable, U>; | |
| }; | |
| )cpp"; | |
| Snippet = Header + R"( | |
| using Target = PairWrapper<$0(Nullable)[[int *]], | |
| $1(NonNull)[[int *]] _Nonnull>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = PairWrapper<$0(Nullable)[[int *]] _Nonnull, | |
| $1[[int *]]>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = PairWrapper<$0(Nullable)[[PairWrapper<$1(Nullable)[[int *]], | |
| $2(NonNull)[[int *]] _Nonnull | |
| >::type *]], | |
| $3[[PairWrapper<$4(Nullable)[[int *]] _Nonnull, | |
| $5[[int *]] | |
| >::type *]] | |
| >::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, ReferenceOuterTemplateParam) { | |
| // Referencing type-params from indirectly-enclosing template. | |
| Snippet = R"( | |
| template <class A, class B> | |
| struct Pair; | |
| template <class T> | |
| struct Outer { | |
| template <class U> | |
| struct Inner { | |
| using type = Pair<U, T>; | |
| }; | |
| }; | |
| using Target = Outer<$1(Nullable)[[int *]] _Nullable>::Inner< | |
| $0(NonNull)[[int *]] _Nonnull>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| // Same where Inner is an alias template. | |
| Snippet = R"( | |
| template <class A, class B> | |
| struct Pair; | |
| template <class T> | |
| struct Outer { | |
| template <class U> | |
| using Inner = Pair<U, T>; | |
| }; | |
| using Target = Outer<$1(Nullable)[[int *]] _Nullable>::Inner< | |
| $0(NonNull)[[int *]] _Nonnull>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, MixedQualiferChain) { | |
| std::string 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"; | |
| Snippet = Header + R"( | |
| using Target = Outer1::Middle<$0(Nullable)[[int *]] _Nullable>::Inner< | |
| $1(NonNull)[[int *]] _Nonnull>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = Outer2<$0(Nullable)[[int *]] _Nullable>::Middle::Inner< | |
| $1(NonNull)[[int *]] _Nonnull>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| using Target = Outer3<$0(Nullable)[[int *]] _Nullable>::Middle< | |
| $1(NonNull)[[int *]] _Nonnull>::Inner::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, DependentlyNamedTemplate) { | |
| // Instantiation of dependent-named template | |
| Snippet = R"( | |
| struct Wrapper { | |
| template <class T> | |
| using Nullable = T _Nullable; | |
| }; | |
| template <class U, class WrapT> | |
| struct S { | |
| using type = typename WrapT::template Nullable<U> *_Nonnull; | |
| }; | |
| using Target = $0(NonNull)[[S<$1(Nullable)[[int *]], Wrapper>::type]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, PartialSpecialization) { | |
| Snippet = R"( | |
| template <class> | |
| struct S; | |
| template <class T> | |
| struct S<T *> { | |
| using Alias = T; | |
| }; | |
| using Target = S<int *>::Alias; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, TemplateTemplateParams) { | |
| // Template template params | |
| std::string 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"; | |
| Snippet = Header + | |
| R"(using Target = $0(Nullable)[[Pointer<Nullable, int>::type]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + | |
| R"(using Target = $0(Nullable)[[Pointer<Nullable, | |
| $1(NonNull)[[Pointer<Nonnull, int>::type]]>::type]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| // 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"; | |
| Snippet = Header + | |
| R"(using Target = $0(Nullable)[[Pointer<Nullable, int>::type]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + | |
| R"(using Target = $0(Nullable)[[Pointer<Nullable, | |
| $1(NonNull)[[Pointer<Nonnull, int>::type]]>::type]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, ClassTemplateParamPack) { | |
| // Parameter packs | |
| std::string Header = R"cpp( | |
| template <typename... X> | |
| struct TupleWrapper { | |
| class Tuple; | |
| }; | |
| )cpp"; | |
| Snippet = Header + R"( | |
| using Target = TupleWrapper<$0[[int *]], | |
| $1(NonNull)[[int *]] _Nonnull>::Tuple; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"( | |
| template <typename... X> | |
| struct NullableTuple { | |
| using type = TupleWrapper<X _Nullable...>::Tuple; | |
| }; | |
| using Target = NullableTuple<$0(Nullable)[[int *]], | |
| $1(Nullable)[[int *]] _Nonnull>::type; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, AliasTemplateWithDefaultArg) { | |
| // TODO(b/281474380): The range should only enclose the `int *`, but we don't | |
| // yet handle default argument sugar correctly. | |
| Snippet = R"( | |
| template <typename T1, typename T2 = T1> | |
| using AliasTemplate = T2; | |
| using Target = $0(Nullable)[[AliasTemplate<int * _Nullable>]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, ClassTemplateWithDefaultArg) { | |
| // TODO(b/281474380): This should be two nullable slots with the same range, | |
| // but we don't yet handle default argument sugar correctly. | |
| Snippet = R"( | |
| template <typename T1, typename T2 = T1> | |
| class ClassTemplate {}; | |
| using Target = ClassTemplate<$0(Nullable)[[int *]] _Nullable>; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{ | |
| {1, Matcher<std::optional<NullabilityKind>>(std::nullopt)}})); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, TemplateArgsBehindAlias) { | |
| // TODO: NullabilityKind should be Nullable, but we don't assemble template | |
| // contexts behind an alias. | |
| Snippet = R"( | |
| template <class X> | |
| struct Outer { | |
| using Inner = X; | |
| }; | |
| using OuterNullable = Outer<int *_Nullable>; | |
| using Target = $0[[OuterNullable::Inner]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, AnnotateNullable) { | |
| std::string HeaderWithAttributes = 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"; | |
| Snippet = R"(using Target = custom::Nullable<$0(Nullable)[[int *]]>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| Snippet = R"(using Target = custom::NonNull<$0(NonNull)[[int *]]>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| Snippet = | |
| R"(using Target = pair<custom::NonNull<$0(NonNull)[[int *]]>, custom::Nullable<$1(Nullable)[[int *]]>>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| Snippet = | |
| R"(using Target = twice<custom::NonNull<$0(NonNull)[[$1(NonNull)[[int *]]]]>>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| // Should still work if aliases *do* apply _Nullable. | |
| HeaderWithAttributes = 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"; | |
| Snippet = R"(using Target = custom::Nullable<$0(Nullable)[[int *]]>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| Snippet = R"(using Target = custom::NonNull<$0(NonNull)[[int *]]>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, HeaderWithAttributes), | |
| matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, SmartPointers) { | |
| std::string Header = R"cpp( | |
| namespace std { | |
| template <typename T> | |
| class unique_ptr {}; | |
| } // namespace std | |
| )cpp"; | |
| Snippet = Header + R"(using Target = int;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = Header + R"(using Target = $0[[std::unique_ptr<int>]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| Snippet = | |
| Header + | |
| R"(using Target = $0[[std::unique_ptr<$1[[std::unique_ptr<int>]]>]];)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| std::string MoreHeaderWithAttributes = R"cpp( | |
| template <typename T> | |
| using Nullable [[clang::annotate("Nullable")]] = T; | |
| template <typename T> | |
| using NonNull [[clang::annotate("Nonnull")]] = T; | |
| )cpp"; | |
| Snippet = | |
| Header + | |
| R"(using Target = NonNull<$0(NonNull)[[std::unique_ptr<Nullable<$1(Nullable)[[std::unique_ptr<int>]]>>]]>;)"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet, MoreHeaderWithAttributes), | |
| matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, ArrayBehindElaboratedAlias) { | |
| Snippet = R"( | |
| namespace ns { using T = int * _Nullable [5]; } | |
| using Target = ns::T; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{0, Optional(NullabilityKind::Nullable)}})); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, | |
| FunctionPrototypeBehindElaboratedAliasWithPointerReturn) { | |
| Snippet = R"( | |
| namespace ns { using T = int * _Nullable (* _Nonnull)(); } | |
| using Target = $0(NonNull)[[ns::T]]; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{1, Optional(NullabilityKind::Nullable)}})); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, | |
| FunctionPrototypeBehindElaboratedAliasWithPointerParam) { | |
| Snippet = R"( | |
| namespace ns { using T = void (* _Nonnull)(int * _Nullable); } | |
| using Target = $0(NonNull)[[ns::T]]; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{1, Optional(NullabilityKind::Nullable)}})); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, | |
| FunctionPrototypePointerParamAfterElaboratedNonPointerParam) { | |
| Snippet = R"( | |
| namespace ns { | |
| using MyInt = int; | |
| } //namespace ns | |
| using Target = $0(NonNull)[[void (* _Nonnull)(ns::MyInt, | |
| $1(Nullable)[[int *]] _Nullable)]]; | |
| )"; | |
| EXPECT_THAT(getComparableNullabilityLocs(Snippet), matchesRanges()); | |
| } | |
| TEST_F(GetTypeNullabilityLocsTest, | |
| DeclContextTemplateArgsWithElaboratedAliases) { | |
| // We record the correct Locs for decl context template args when the decl | |
| // context is behind an elaborated alias. | |
| std::string Header = R"cpp( | |
| namespace ns { | |
| template <typename T> | |
| struct Outer { | |
| template <typename U> | |
| struct InnerTemplated {}; | |
| struct Inner {}; | |
| }; | |
| using MyInnerTemplated = Outer<int* _Nullable>::InnerTemplated<int>; | |
| using MyInner = Outer<int* _Nullable>::Inner; | |
| } // namespace ns | |
| )cpp"; | |
| Snippet = Header + R"(using Target = ns::MyInnerTemplated;)"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{0, Optional(NullabilityKind::Nullable)}})); | |
| Snippet = Header + R"(using Target = ns::MyInner;)"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges(MissingLocSlots{{0, Optional(NullabilityKind::Nullable)}})); | |
| // We record the correct Locs for decl context template args when we have | |
| // pointer args after elaborated alias args. | |
| Header = R"cpp( | |
| namespace ns { | |
| using MyInt = int; | |
| } // namespace ns | |
| template <typename T, typename U, typename V, typename W = int* _Nonnull> | |
| struct Outer { | |
| template <typename X> | |
| struct InnerTemplated {}; | |
| struct Inner {}; | |
| }; | |
| )cpp"; | |
| Snippet = Header + R"( | |
| using Target = Outer<ns::MyInt, $0(Nullable)[[int *]] _Nullable, | |
| ns::MyInt>::InnerTemplated<int>; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges( | |
| // TODO(b/281474380) Should be NonNull, but we don't yet resugar | |
| // default arguments. | |
| MissingLocSlots{{1, std::optional<NullabilityKind>(std::nullopt)}})); | |
| Snippet = Header + R"( | |
| using Target = Outer<ns::MyInt, $0(Nullable)[[int *]] _Nullable, | |
| ns::MyInt>::Inner; | |
| )"; | |
| EXPECT_THAT( | |
| getComparableNullabilityLocs(Snippet), | |
| matchesRanges( | |
| // TODO(b/281474380) Should be NonNull, but we don't yet resugar | |
| // default arguments. | |
| MissingLocSlots{{1, std::optional<NullabilityKind>(std::nullopt)}})); | |
| } | |
| } // namespace | |
| } // namespace clang::tidy::nullability |