| // 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 |
| |
| // Tests for diagnosis on function calls. |
| |
| #include "nullability/test/check_diagnostics.h" |
| #include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h" |
| |
| namespace clang::tidy::nullability { |
| namespace { |
| |
| TEST(PointerNullabilityTest, CallExprParamAssignment) { |
| // free function with single param |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void takeNonnull(int *_Nonnull); |
| void takeNullable(int *_Nullable); |
| void takeUnannotated(int *); |
| void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable, |
| int *ptr_unannotated) { |
| takeNonnull(nullptr); // [[unsafe]] |
| takeNonnull(ptr_nonnull); |
| takeNonnull(ptr_nullable); // [[unsafe]] |
| takeNonnull(ptr_unannotated); |
| |
| takeNullable(nullptr); |
| takeNullable(ptr_nonnull); |
| takeNullable(ptr_nullable); |
| takeNullable(ptr_unannotated); |
| |
| takeUnannotated(nullptr); |
| takeUnannotated(ptr_nonnull); |
| takeUnannotated(ptr_nullable); |
| takeUnannotated(ptr_unannotated); |
| } |
| )cc")); |
| |
| // free function with multiple params of mixed nullability |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void takeMixed(int *, int *_Nullable, int *_Nonnull); |
| void target() { |
| takeMixed(nullptr, nullptr, nullptr); // [[unsafe]] |
| } |
| )cc")); |
| |
| // member function |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct Foo { |
| void takeNonnull(int *_Nonnull); |
| void takeNullable(int *_Nullable); |
| void takeUnannotated(int *); |
| }; |
| void target(Foo foo) { |
| foo.takeNonnull(nullptr); // [[unsafe]] |
| foo.takeNullable(nullptr); |
| foo.takeUnannotated(nullptr); |
| } |
| )cc")); |
| |
| // function pointer |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(void (*takeNonnull)(int *_Nonnull), |
| void (*takeNullable)(int *_Nullable), |
| void (*takeUnannotated)(int *)) { |
| takeNonnull(nullptr); // [[unsafe]] |
| takeNullable(nullptr); |
| takeUnannotated(nullptr); |
| } |
| )cc")); |
| |
| // pointer to function pointer |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void target(void (**takeNonnull)(int *_Nonnull), |
| void (**takeNullable)(int *_Nullable), |
| void (**takeUnannotated)(int *)) { |
| (*takeNonnull)(nullptr); // [[unsafe]] |
| (*takeNullable)(nullptr); |
| (*takeUnannotated)(nullptr); |
| } |
| )cc")); |
| |
| // function returned from function |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| typedef void (*takeNonnullF)(int *_Nonnull); |
| typedef void (*takeNullableF)(int *_Nullable); |
| typedef void (*takeUnannotatedF)(int *); |
| void target(takeNonnullF (*takeNonnull)(), takeNullableF (*takeNullable)(), |
| takeUnannotatedF (*takeUnannotated)()) { |
| (*takeNonnull)()(nullptr); // [[unsafe]] |
| (*takeNullable)()(nullptr); |
| (*takeUnannotated)()(nullptr); |
| } |
| )cc")); |
| |
| // passing a reference to a nonnull pointer |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void takeNonnullRef(int* _Nonnull&); |
| void takeNullableRef(int* _Nullable&); |
| void takeUnannotatedRef(int*&); |
| void target(int* _Nonnull ptr_nonnull) { |
| takeNonnullRef(ptr_nonnull); |
| *ptr_nonnull; |
| |
| takeNullableRef(ptr_nonnull); // [[unsafe]] |
| *ptr_nonnull; |
| |
| takeUnannotatedRef(ptr_nonnull); |
| *ptr_nonnull; |
| } |
| )cc")); |
| |
| // passing a reference to a nullable pointer |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void takeNonnullRef(int *_Nonnull &); |
| void takeNullableRef(int *_Nullable &); |
| void takeUnannotatedRef(int *&); |
| void target(int *_Nullable ptr_nullable) { |
| takeNonnullRef(ptr_nullable); // [[unsafe]] |
| *ptr_nullable; // [[unsafe]] |
| |
| takeNullableRef(ptr_nullable); |
| *ptr_nullable; // [[unsafe]] |
| |
| takeUnannotatedRef(ptr_nullable); |
| *ptr_nullable; |
| } |
| )cc")); |
| |
| // passing a reference to an unannotated pointer |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void takeNonnullRef(int* _Nonnull&); |
| void takeNullableRef(int* _Nullable&); |
| void takeUnannotatedRef(int*&); |
| void target(int* ptr_unannotated) { |
| int* UnannotatedNullValue = nullptr; |
| takeNonnullRef(UnannotatedNullValue); // [[unsafe]] |
| |
| takeNonnullRef(ptr_unannotated); |
| *ptr_unannotated; |
| |
| takeNullableRef(ptr_unannotated); |
| *ptr_unannotated; // false-negative? The unannotated pointer could be |
| // considered nullable if it has been used as a |
| // nullable pointer. |
| |
| takeUnannotatedRef(ptr_unannotated); |
| *ptr_unannotated; |
| } |
| )cc")); |
| } |
| |
| // Test that relevant diagnostics are produced for declarations with templated |
| // annotations. |
| TEST(PointerNullabilityTest, CallExprParamAssignmentTemplateAnnotations) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| template <typename T> |
| using Nonnull = T _Nonnull; |
| template <typename T> |
| using Nullable = T _Nullable; |
| template <typename T> |
| using NullabilityUnknown = T; |
| |
| void takeNonnull(Nonnull<int *>); |
| void takeNullable(Nullable<int *>); |
| void takeUnknown(NullabilityUnknown<int *>); |
| |
| void target(Nonnull<int *> ptr_nonnull, Nullable<int *> ptr_nullable, |
| NullabilityUnknown<int *> ptr_unknown) { |
| takeNonnull(nullptr); // [[unsafe]] |
| takeNonnull(ptr_nonnull); |
| takeNonnull(ptr_nullable); // [[unsafe]] |
| takeNonnull(ptr_unknown); |
| |
| takeNullable(nullptr); |
| takeNullable(ptr_nonnull); |
| takeNullable(ptr_nullable); |
| takeNullable(ptr_unknown); |
| |
| takeUnknown(nullptr); |
| takeUnknown(ptr_nonnull); |
| takeUnknown(ptr_nullable); |
| takeUnknown(ptr_unknown); |
| } |
| )cc")); |
| } |
| |
| // Test that templated annotations work interchangeably, in diagnosis, with the |
| // built-in Clang annotations. |
| TEST(PointerNullabilityTest, CallExprParamAssignmentTemplateBuiltinMixed) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| template <typename T> |
| using Nonnull = T _Nonnull; |
| template <typename T> |
| using Nullable = T _Nullable; |
| template <typename T> |
| using NullabilityUnknown = T; |
| |
| void takeNonnull(int *_Nonnull); |
| void takeNullable(int *_Nullable); |
| void takeUnannotated(int *); |
| |
| void target(Nonnull<int *> ptr_nonnull, Nullable<int *> ptr_nullable, |
| NullabilityUnknown<int *> ptr_unknown) { |
| takeNonnull(ptr_nonnull); |
| takeNonnull(ptr_nullable); // [[unsafe]] |
| takeNonnull(ptr_unknown); |
| |
| takeNullable(ptr_nonnull); |
| takeNullable(ptr_nullable); |
| takeNullable(ptr_unknown); |
| |
| takeUnannotated(ptr_nonnull); |
| takeUnannotated(ptr_nullable); |
| takeUnannotated(ptr_unknown); |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallExprMultiNonnullParams) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void take(int *_Nonnull, int *_Nullable, int *_Nonnull); |
| void target() { |
| take(nullptr, // [[unsafe]] |
| nullptr, |
| nullptr); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallVariadicFunction) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| void variadic(int *_Nonnull, ...); |
| void target() { |
| int i = 0; |
| variadic(&i, nullptr, &i); |
| variadic(nullptr, nullptr, &i); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallVariadicConstructor) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct S { |
| S(int* _Nonnull, ...); |
| }; |
| void target() { |
| int i = 0; |
| S(&i, nullptr, &i); |
| S(nullptr, nullptr, &i); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallMemberOperatorOneParam) { |
| // overloaded operator with single param |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| // map<int * _Nonnull, int> |
| struct MapWithNonnullKeys { |
| int &operator[](int *_Nonnull key); |
| }; |
| // map<int * _Nullable, int> |
| struct MapWithNullableKeys { |
| int &operator[](int *_Nullable key); |
| }; |
| // map<int *, int> |
| struct MapWithUnannotatedKeys { |
| int &operator[](int *key); |
| }; |
| void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable, |
| int *ptr_unannotated) { |
| MapWithNonnullKeys nonnull_keys; |
| nonnull_keys[nullptr] = 42; // [[unsafe]] |
| nonnull_keys[ptr_nonnull] = 42; |
| nonnull_keys[ptr_nullable] = 42; // [[unsafe]] |
| nonnull_keys[ptr_unannotated] = 42; |
| |
| MapWithNullableKeys nullable_keys; |
| nullable_keys[nullptr] = 42; |
| nullable_keys[ptr_nonnull] = 42; |
| nullable_keys[ptr_nullable] = 42; |
| nullable_keys[ptr_unannotated] = 42; |
| |
| MapWithUnannotatedKeys unannotated_keys; |
| unannotated_keys[nullptr] = 42; |
| unannotated_keys[ptr_nonnull] = 42; |
| unannotated_keys[ptr_nullable] = 42; |
| unannotated_keys[ptr_unannotated] = 42; |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallMemberOperatorMultipleParams) { |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct TakeMixed { |
| void operator()(int *, int *_Nullable, int *_Nonnull); |
| }; |
| void target() { |
| TakeMixed takeMixed; |
| takeMixed(nullptr, nullptr, nullptr); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, CallNoreturnDestructor) { |
| // This test demonstrates that the check considers program execution to end |
| // when a `noreturn` destructor is called. |
| // Among other things, this is intended to demonstrate that Abseil's logging |
| // instruction `LOG(FATAL)` (which creates an object with a `noreturn` |
| // destructor) is correctly interpreted as terminating the program. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct Fatal { |
| __attribute__((noreturn)) ~Fatal(); |
| void method(int); |
| }; |
| void target(int* _Nullable p) { |
| // Do warn here, as the `*p` dereference happens before the `Fatal` object |
| // is destroyed. |
| Fatal().method(*p); // [[unsafe]] |
| // Don't warn here: We know that this code never gets executed. |
| *p; |
| } |
| )cc")); |
| } |
| |
| // This is a crash repro. |
| TEST(PointerNullabilityTest, NonConstMethodClearsPointerMembersInExpr) { |
| EXPECT_TRUE(checkDiagnosticsHasUntracked(R"cc( |
| void f(char* _Nonnull const&, char* const&); |
| |
| template <class T> |
| struct S { |
| void target() { |
| // This used to cause a crash because of a very specific sequence of |
| // events: |
| // - We visit `p` and initialize its nullability properties. |
| // - We visit `returnsPtr()`, causing us to reset all pointer-type |
| // fields (in this case, `p`). When we did this, we used to create |
| // fresh `PointerValue`s for the fields, but without nullability |
| // properties. This would cause a crash in the next step (see below). |
| // (Instead, we now simply clear the values associated with the |
| // fields.) |
| // - We visit the function call and check that `p` is non-null, which |
| // used to crash because `p` had a `PointerValue` associated with it |
| // that didn't have nullability properties. |
| // Diagnosis produces a "pointer value not modeled" warning on this line |
| // because the value for `p` has been cleared. (This only happens if |
| // `S` is a template, which is why `S` has a template parameter that is |
| // otherwise unused.) |
| f(p, returnsPtr()); // [[unsafe]] |
| } |
| |
| char* returnsPtr(); |
| |
| char* p; |
| }; |
| |
| template class S<int>; |
| )cc")); |
| } |
| |
| TEST(SmartPointerTest, JoinCausesLossOfNullabilityPropertiesAtExit) { |
| // This is a regression test for the crash seen in b/414348238. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| class A { |
| public: |
| void target(bool b) { |
| if (b) return; |
| // Calling a non-const member function clears out the framework-produced |
| // value for `p_` but (crucially) without mentioning `p_` in an |
| // expression (which would initialize the nullability properties for the |
| // previous value of `p_`). |
| non_const_member_fn(); |
| // We now mention `p_` in an expression to make sure the nullability |
| // properties for `p_` are initialized. |
| p_; |
| // At function exit, we see the joined state from two blocks: |
| // - The block containing the return statement above. Here, `p_` has the |
| // value that the framework initialized it with. Because we never |
| // saw the expression `p_` on this path, we didn't initialize the |
| // nullability properties for this value. |
| // - The block that follows the if statement. Here, `p_` has a different |
| // value, which is associated with nullablility properties, as |
| // explained above. |
| // When we join these two values, because one of them does not have |
| // nullability properties, we also don't associate nullability |
| // properties with the joined value. It's important to test for this; |
| // our failure to do this previously resulted in the crash. |
| } |
| |
| void non_const_member_fn(); |
| |
| private: |
| int* _Nonnull p_; |
| }; |
| )cc")); |
| } |
| |
| TEST(PointerNullabilityTest, Accessor_BaseObjectReturnedByReference) { |
| // Crash repro: |
| // If the base object of the accessor call expression is a reference returned |
| // from a function call, we have a storage location for the object but no |
| // values for its fields. Check that we don't crash in this case. |
| EXPECT_TRUE(checkDiagnostics(R"cc( |
| struct C { |
| int *_Nullable property() const { return x; } |
| int *_Nullable x = nullptr; |
| }; |
| C &foo(); |
| void target() { |
| if (foo().property() != nullptr) int x = *foo().property(); // [[unsafe]] |
| } |
| )cc")); |
| } |
| |
| } // namespace |
| } // namespace clang::tidy::nullability |