| // 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/pointer_nullability_matchers.h" |
| |
| #include "nullability/type_nullability.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Testing/TestAST.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h" |
| |
| namespace clang::tidy::nullability { |
| namespace { |
| |
| // Static initializer turns on support for smart pointers. |
| test::EnableSmartPointers Enable; |
| |
| using ast_matchers::match; |
| |
| template <typename MatcherT> |
| bool matches(llvm::StringRef base_input, llvm::StringRef test_input, |
| MatcherT Matcher) { |
| TestAST InputAST(base_input.str() + test_input.str()); |
| return !match(Matcher, InputAST.context()).empty(); |
| } |
| |
| TEST(PointerNullabilityTest, MatchMemberFunctions) { |
| llvm::StringRef Input(R"cc( |
| struct DummyStruct { |
| int *p; |
| }; |
| class C { |
| public: |
| int *_Nullable get() const { return x; } |
| int *_Nullable get_member_in_parens() const { return (x); } |
| int *_Nullable get_this_in_parens() const { return (this)->x; } |
| int *_Nullable get(int i) const { return x; } |
| int *_Nullable get_nonconst() { return x; } |
| int *_Nullable get_external() { return ds.p; } |
| void may_mutate(){}; |
| |
| private: |
| int *x; |
| DummyStruct ds; |
| }; |
| C foo() { return C(); } |
| )cc"); |
| |
| EXPECT_TRUE(matches(Input, "void target(){ C().get(); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ C().get_member_in_parens(); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ C().get_this_in_parens(); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ C().get(0); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ C().get_nonconst(); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ foo().get(); }", |
| isSupportedPointerAccessorCall())); |
| |
| EXPECT_FALSE(matches(Input, "void target(){ C().may_mutate(); }", |
| isSupportedPointerAccessorCall())); |
| EXPECT_FALSE(matches(Input, "void target(){ C().get_external(); }", |
| isSupportedPointerAccessorCall())); |
| } |
| |
| TEST(PointerNullabilityTest, MatchConstMemberFunctions) { |
| llvm::StringRef Input(R"cc( |
| class C { |
| public: |
| int *_Nullable get() const; |
| int *_Nullable get(int i) const; |
| int *_Nullable get_with_default_arg(int i = 0) const; |
| int *_Nullable get_nonconst(); |
| }; |
| C foo() { return C(); } |
| )cc"); |
| EXPECT_TRUE(matches(Input, "void target(){ C().get(); }", |
| isZeroParamConstMemberCall())); |
| EXPECT_TRUE(matches(Input, "void target(){ foo().get(); }", |
| isZeroParamConstMemberCall())); |
| |
| EXPECT_FALSE(matches(Input, "void target(){ C().get(0); }", |
| isZeroParamConstMemberCall())); |
| EXPECT_FALSE(matches(Input, "void target(){ C().get_with_default_arg(); }", |
| isZeroParamConstMemberCall())); |
| EXPECT_FALSE(matches(Input, "void target(){ C().get_nonconst(); }", |
| isZeroParamConstMemberCall())); |
| } |
| |
| TEST(PointerNullabilityTest, MatchSmartPointerMethodCall) { |
| llvm::StringRef Input(R"cc( |
| namespace std { |
| template <class T> |
| struct unique_ptr { |
| using pointer = T *; |
| T *get() const; |
| }; |
| } // namespace std |
| template <class T> |
| struct MyUniquePtr { |
| using pointer = T *; |
| T *get() const; |
| }; |
| )cc"); |
| // Call using `.method()` syntax. |
| EXPECT_TRUE(matches(Input, "void target(){ std::unique_ptr<int>().get(); }", |
| isSmartPointerMethodCall("get"))); |
| // Call using `->method()` syntax. |
| EXPECT_TRUE(matches(Input, |
| "void target(std::unique_ptr<int> *p) { p->get(); }", |
| isSmartPointerMethodCall("get"))); |
| // Querying for wrong method name. |
| EXPECT_FALSE(matches(Input, "void target(){ std::unique_ptr<int>().get(); }", |
| isSmartPointerMethodCall("reset"))); |
| // Not a supported smart pointer type. |
| EXPECT_FALSE(matches(Input, "void target(){ MyUniquePtr<int>().get(); }", |
| isSmartPointerMethodCall("get"))); |
| } |
| |
| TEST(PointerNullabilityTest, MatchSmartPointerBoolConversionCall) { |
| llvm::StringRef Input(R"cc( |
| namespace std { |
| template <class T> |
| struct unique_ptr { |
| using pointer = T*; |
| explicit operator bool() const; |
| }; |
| } // namespace std |
| template <class T> |
| struct MyUniquePtr { |
| using pointer = T*; |
| explicit operator bool() const; |
| }; |
| )cc"); |
| // Call using `static_cast<bool>()` syntax. |
| EXPECT_TRUE(matches( |
| Input, "void target(){ static_cast<bool>(std::unique_ptr<int>()); }", |
| isSmartPointerBoolConversionCall())); |
| // Explicit call using `.method()` syntax. |
| EXPECT_TRUE( |
| matches(Input, "void target(){ std::unique_ptr<int>().operator bool(); }", |
| isSmartPointerBoolConversionCall())); |
| // Explicit call using `->method()` syntax. |
| EXPECT_TRUE(matches( |
| Input, "void target(std::unique_ptr<int> *p) { p->operator bool(); }", |
| isSmartPointerBoolConversionCall())); |
| // Not a supported smart pointer type. |
| EXPECT_FALSE( |
| matches(Input, "void target(){ static_cast<bool>(MyUniquePtr<int>()); }", |
| isSmartPointerBoolConversionCall())); |
| } |
| |
| TEST(PointerNullabilityTest, MatchOptionalOperatorArrowCall) { |
| llvm::StringRef Input(R"cc( |
| namespace std { |
| template <class T> |
| struct optional { |
| T* operator->(); |
| T& operator*(); |
| }; |
| } // namespace std |
| |
| namespace absl { |
| template <class T> |
| struct optional { |
| T* operator->(); |
| }; |
| } // namespace absl |
| |
| namespace folly { |
| template <class T> |
| struct Optional { |
| T* operator->(); |
| }; |
| } // namespace folly |
| |
| // Lots of libraries that used to define their own optional now make it an |
| // alias for `std::optional`, so we want to support this too. |
| template <class T> |
| using StdOptionalAlias = std::optional<T>; |
| |
| template <class T> |
| struct optional { |
| T* operator->(); |
| }; |
| |
| struct S { |
| int i; |
| }; |
| )cc"); |
| |
| EXPECT_TRUE(matches(Input, "void target(std::optional<S> o){ o->i; }", |
| isOptionalOperatorArrowCall())); |
| EXPECT_TRUE(matches(Input, "void target(absl::optional<S> o){ o->i; }", |
| isOptionalOperatorArrowCall())); |
| EXPECT_TRUE(matches(Input, "void target(folly::Optional<S> o){ o->i; }", |
| isOptionalOperatorArrowCall())); |
| EXPECT_TRUE(matches(Input, "void target(StdOptionalAlias<S> o){ o->i; }", |
| isOptionalOperatorArrowCall())); |
| |
| // Not one of the supported optional classes. |
| EXPECT_FALSE(matches(Input, "void target(optional<S> o){ o->i; }", |
| isOptionalOperatorArrowCall())); |
| |
| // Not `operator->`. |
| EXPECT_FALSE(matches(Input, "void target(std::optional<S> o){ *o; }", |
| isOptionalOperatorArrowCall())); |
| } |
| |
| } // namespace |
| } // namespace clang::tidy::nullability |