| // 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"); |
| 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; |
| 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 NullabilityKind should be Nullable and 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[[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 |