blob: b5af9d17e9bd93cd1806adf40c17f916c9db4646 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <string>
#include "nullability_verification/pointer_nullability_analysis.h"
#include "nullability_verification/pointer_nullability_diagnosis.h"
#include "clang/Basic/SourceManager.h"
#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include "llvm/Testing/Support/Error.h"
#include "third_party/llvm/llvm-project/llvm/utils/unittest/googletest/include/gtest/gtest.h"
namespace clang {
namespace tidy {
namespace nullability {
namespace {
using dataflow::Environment;
using dataflow::TypeErasedDataflowAnalysisState;
using dataflow::test::AnalysisInputs;
using dataflow::test::AnalysisOutputs;
using dataflow::test::checkDataflow;
using ::testing::ContainerEq;
using ::testing::Test;
void checkDiagnostics(llvm::StringRef SourceCode) {
std::vector<const Stmt *> Diagnostics;
PointerNullabilityDiagnoser Diagnoser;
ASSERT_THAT_ERROR(
checkDataflow<PointerNullabilityAnalysis>(
AnalysisInputs<PointerNullabilityAnalysis>(
SourceCode, ast_matchers::hasName("target"),
[](ASTContext &ASTCtx, Environment &) {
return PointerNullabilityAnalysis(ASTCtx);
})
.withPostVisitCFG(
[&Diagnostics, &Diagnoser](
ASTContext &Ctx, const CFGElement &Elt,
const TypeErasedDataflowAnalysisState &State) {
auto EltDiagnostics =
Diagnoser.diagnose(&Elt, Ctx, State.Env);
if (EltDiagnostics.has_value()) {
Diagnostics.push_back(EltDiagnostics.value());
}
})
.withASTBuildArgs({"-fsyntax-only", "-std=c++17",
"-Wno-unused-value", "-Wno-nonnull"}),
[&Diagnostics](
const llvm::DenseMap<unsigned, std::string> &Annotations,
const AnalysisOutputs &AnalysisData) {
llvm::DenseSet<unsigned> ExpectedLines, ActualLines;
for (const auto &[Line, _] : Annotations) {
ExpectedLines.insert(Line);
}
auto &SrcMgr = AnalysisData.ASTCtx.getSourceManager();
for (auto *Stmt : Diagnostics) {
ActualLines.insert(
SrcMgr.getPresumedLineNumber(Stmt->getBeginLoc()));
}
EXPECT_THAT(ActualLines, ContainerEq(ExpectedLines));
}),
llvm::Succeeded());
}
TEST(PointerNullabilityTest, NoPointerOperations) {
checkDiagnostics(R"(
void target() {
1 + 2;
}
)");
}
TEST(PointerNullabilityTest, DerefNullPtr) {
// nullptr
checkDiagnostics(R"(
void target() {
int *x = nullptr;
*x; // [[unsafe]]
}
)");
// 0
checkDiagnostics(R"(
void target() {
int *x = 0;
*x; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, DerefAddrOf) {
checkDiagnostics(R"(
void target() {
int i;
int *x = &i;
*x;
}
)");
// transitive
checkDiagnostics(R"(
void target() {
int i;
int *x = &i;
int *y = x;
*y;
}
)");
}
TEST(PointerNullabilityTest, DerefPtrAnnotatedNonNullWithoutACheck) {
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
}
)");
// transitive
checkDiagnostics(R"(
void target(int * _Nonnull x) {
int *y = x;
*y;
}
)");
}
TEST(PointerNullabilityTest, DerefPtrAnnotatedNullableWithoutACheck) {
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
}
)");
// transitive
checkDiagnostics(R"(
void target(int * _Nullable x) {
int *y = x;
*y; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, DerefUnknownPtrWithoutACheck) {
checkDiagnostics(R"(
void target(int *x) {
*x;
}
)");
// transitive
checkDiagnostics(R"(
void target(int *x) {
int *y = x;
*y;
}
)");
}
// TODO(b/233582219): Implement diagnosis of unreachable program points
TEST(PointerNullabilityTest, NonNullPtrImplicitCastToBool) {
// x
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (x) {
*x;
} else {
*x; // unreachable
}
*x;
}
)");
// !x
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (!x) {
*x; // unreachable
} else {
*x;
}
*x;
}
)");
}
TEST(PointerNullabilityTest, NullablePtrImplicitCastToBool) {
// x
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (x) {
*x;
} else {
*x; // [[unsafe]]
}
*x; // [[unsafe]]
}
)");
// !x
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (!x) {
*x; // [[unsafe]]
} else {
*x;
}
*x; // [[unsafe]]
}
)");
}
// TODO(b/233582219): Fix false negatives. Casting the pointer to boolean is
// evidence of the author considering null a possibility, hence the unnannotated
// pointer should be considered nullable and emit warnings where it fails or is
// not null checked.
TEST(PointerNullabilityTest, UnknownPtrImplicitCastToBool) {
// x
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (x) {
*x;
} else {
*x; // false-negative
}
*x; // false-negative
}
)");
// !x
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (!x) {
*x; // false-negative
} else {
*x;
}
*x; // false-negative
}
)");
}
TEST(PointerNullabilityTest, CompareNonNullPtrAndNonNullPtr) {
// nonnull == nonnull
checkDiagnostics(R"(
void target(int * _Nonnull x, int * _Nonnull y) {
*x;
*y;
if (x == y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
// nonnull != nonnull
checkDiagnostics(R"(
void target(int * _Nonnull x, int * _Nonnull y) {
*x;
*y;
if (x != y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
}
TEST(PointerNullabilityTest, CompareNullablePtrAndNullablePtr) {
// nullable == nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (x == y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
} else {
*x; // [[unsafe]]
*y; // [[unsafe]]
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
// nullable != nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (x != y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
} else {
*x; // [[unsafe]]
*y; // [[unsafe]]
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, CompareUnknownPtrAndUnknownPtr) {
// unknown == unknown
checkDiagnostics(R"(
void target(int *x, int *y) {
*x;
*y;
if (x == y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
// unknown != unknown
checkDiagnostics(R"(
void target(int *x, int *y) {
*x;
*y;
if (x != y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
}
// TODO(b/233582219): Implement diagnosis of unreachable program points
TEST(PointerNullabilityTest, CompareNonNullPtrAndNullPtr) {
// nonnull == nullptr
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (x == nullptr) {
*x; // unreachable
} else {
*x;
}
*x;
}
)");
// nullptr == nonnull
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (nullptr == x) {
*x; // unreachable
} else {
*x;
}
*x;
}
)");
// nonnull != nullptr
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (x != nullptr) {
*x;
} else {
*x; // unreachable
}
*x;
}
)");
// nullptr != nonnull
checkDiagnostics(R"(
void target(int * _Nonnull x) {
*x;
if (nullptr != x) {
*x;
} else {
*x; // unreachable
}
*x;
}
)");
}
TEST(PointerNullabilityTest, CompareNullablePtrAndNullPtr) {
// nullable == nullptr
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (x == nullptr) {
*x; // [[unsafe]]
} else {
*x;
}
*x; // [[unsafe]]
}
)");
// nullptr == nullable
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (nullptr == x) {
*x; // [[unsafe]]
} else {
*x;
}
*x; // [[unsafe]]
}
)");
// nullable != nullptr
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (x != nullptr) {
*x;
} else {
*x; // [[unsafe]]
}
*x; // [[unsafe]]
}
)");
// nullptr != nullable
checkDiagnostics(R"(
void target(int * _Nullable x) {
*x; // [[unsafe]]
if (nullptr != x) {
*x;
} else {
*x; // [[unsafe]]
}
*x; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, CompareNullablePtrAndNonNullPtr) {
// nullable == nonnull
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nonnull y) {
*x; // [[unsafe]]
*y;
if (x == y) {
*x;
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// nonnull == nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nonnull y) {
*x; // [[unsafe]]
*y;
if (y == x) {
*x;
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// nullable != nonnull
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nonnull y) {
*x; // [[unsafe]]
*y;
if (x != y) {
*x; // [[unsafe]]
*y;
} else {
*x;
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// nonnull != nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nonnull y) {
*x; // [[unsafe]]
*y;
if (y != x) {
*x; // [[unsafe]]
*y;
} else {
*x;
*y;
}
*x; // [[unsafe]]
*y;
}
)");
}
TEST(PointerNullabilityTest, CompareNullablePtrAndUnknownPtr) {
// nullable == unknown
checkDiagnostics(R"(
void target(int * _Nullable x, int *y) {
*x; // [[unsafe]]
*y;
if (x == y) {
*x; // [[unsafe]]
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// unknown == nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int *y) {
*x; // [[unsafe]]
*y;
if (y == x) {
*x; // [[unsafe]]
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// nullable != unknown
checkDiagnostics(R"(
void target(int * _Nullable x, int *y) {
*x; // [[unsafe]]
*y;
if (x != y) {
*x; // [[unsafe]]
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
// unknown != nullable
checkDiagnostics(R"(
void target(int * _Nullable x, int *y) {
*x; // [[unsafe]]
*y;
if (y != x) {
*x; // [[unsafe]]
*y;
} else {
*x; // [[unsafe]]
*y;
}
*x; // [[unsafe]]
*y;
}
)");
}
// TODO(b/233582219): Fix false negatives. The pointer is compared to nullptr,
// hence the unnannotated pointer should be considered nullable and emit
// warnings where it fails or is not null checked.
TEST(PointerNullabilityTest, CompareUnknownPtrAndNullPtr) {
// unknown == nullptr
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (x == nullptr) {
*x; // false-negative
} else {
*x;
}
*x; // false-negative
}
)");
// nullptr == unknown
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (nullptr == x) {
*x; // false-negative
} else {
*x;
}
*x; // false-negative
}
)");
// unknown != nullptr
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (x != nullptr) {
*x;
} else {
*x; // false-negative
}
*x; // false-negative
}
)");
// nullptr != unknown
checkDiagnostics(R"(
void target(int *x) {
*x; // false-negative
if (nullptr != x) {
*x;
} else {
*x; // false-negative
}
*x; // false-negative
}
)");
}
TEST(PointerNullabilityTest, CompareUnknownPtrAndNonNullPtr) {
// unknown == nonnull
checkDiagnostics(R"(
void target(int *x, int * _Nonnull y) {
*x;
*y;
if (x == y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
// nonnull == unknown
checkDiagnostics(R"(
void target(int *x, int * _Nonnull y) {
*x;
*y;
if (y == x) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
// unknown != nonnull
checkDiagnostics(R"(
void target(int *x, int * _Nonnull y) {
*x;
*y;
if (x != y) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
// nonnull != unknown
checkDiagnostics(R"(
void target(int *x, int * _Nonnull y) {
*x;
*y;
if (y != x) {
*x;
*y;
} else {
*x;
*y;
}
*x;
*y;
}
)");
}
TEST(PointerNullabilityTest, TransitiveNullCheck) {
checkDiagnostics(R"(
void target(int * _Nullable x) {
int *y = x;
*x; // [[unsafe]]
if (y) {
*x;
} else {
*x; // [[unsafe]]
}
*x; // [[unsafe]]
}
)");
checkDiagnostics(R"(
void target(int * _Nullable x) {
int *y = x;
*y; // [[unsafe]]
if (x) {
*y;
} else {
*y; // [[unsafe]]
}
*y; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, BinaryExpressions) {
// x && y
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (x && y) {
*x;
*y;
} else {
*x; // [[unsafe]]
*y; // [[unsafe]]
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
// x || y
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (x || y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
} else {
*x; // [[unsafe]]
*y; // [[unsafe]]
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
// !x && !y
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (!x && !y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
} else {
*x; // [[unsafe]]
*y; // [[unsafe]]
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
// !x || !y
checkDiagnostics(R"(
void target(int * _Nullable x, int * _Nullable y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
if (!x || !y) {
*x; // [[unsafe]]
*y; // [[unsafe]]
} else {
*x;
*y;
}
*x; // [[unsafe]]
*y; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, ArrowOperatorOnNonNullPtr) {
// (->) member field
checkDiagnostics(R"(
struct Foo {
Foo *foo;
};
void target(Foo * _Nonnull foo) {
foo->foo;
}
)");
// (->) member function
checkDiagnostics(R"(
struct Foo {
Foo *foo();
};
void target(Foo * _Nonnull foo) {
foo->foo();
}
)");
}
TEST(PointerNullabilityTest, ArrowOperatorOnNullablePtr) {
// (->) member field
checkDiagnostics(R"(
struct Foo {
Foo *foo;
};
void target(Foo * _Nullable foo) {
foo->foo; // [[unsafe]]
if (foo) {
foo->foo;
} else {
foo->foo; // [[unsafe]]
}
foo->foo; // [[unsafe]]
}
)");
// (->) member function
checkDiagnostics(R"(
struct Foo {
Foo *foo();
};
void target(Foo * _Nullable foo) {
foo->foo(); // [[unsafe]]
if (foo) {
foo->foo();
} else {
foo->foo(); // [[unsafe]]
}
foo->foo(); // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, ArrowOperatorOnUnknownPtr) {
// (->) member field
checkDiagnostics(R"(
struct Foo {
Foo *foo;
};
void target(Foo *foo) {
foo->foo;
}
)");
// (->) member function
checkDiagnostics(R"(
struct Foo {
Foo *foo();
};
void target(Foo *foo) {
foo->foo();
}
)");
}
TEST(PointerNullabilityTest, ThisPointer) {
// (->) implicit `this`
checkDiagnostics(R"(
struct Foo {
void foo();
void target() {
foo();
}
};
)");
// (->) explicit `this`
checkDiagnostics(R"(
struct Foo {
void foo();
void target() {
this->foo();
}
};
)");
}
TEST(PointerNullabilityTest, NonNullFieldsOfPointerType) {
// dereference field of pointer type
checkDiagnostics(R"(
struct Foo {
Foo * _Nonnull ptr;
};
void target(Foo foo) {
*foo.ptr;
}
)");
// dereference field of pointer type in member function
checkDiagnostics(R"(
struct Foo {
Foo * _Nonnull ptr;
void target() {
*ptr;
}
};
)");
}
TEST(PointerNullabilityTest, NullableFieldsOfPointerType) {
// dereference field of pointer type
checkDiagnostics(R"(
struct Foo {
Foo * _Nullable ptr;
};
void target(Foo foo) {
*foo.ptr; // [[unsafe]]
if (foo.ptr) {
*foo.ptr;
} else {
*foo.ptr; // [[unsafe]]
}
*foo.ptr; // [[unsafe]]
}
)");
// dereference field of pointer type in member function
checkDiagnostics(R"(
struct Foo {
Foo * _Nullable ptr;
void target() {
*ptr; // [[unsafe]]
if (ptr) {
*ptr;
} else {
*ptr; // [[unsafe]]
}
*ptr; // [[unsafe]]
}
};
)");
}
TEST(PointerNullabilityTest, UnknownFieldsOfPointerType) {
// dereference field of pointer type
checkDiagnostics(R"(
struct Foo {
Foo *ptr;
};
void target(Foo foo) {
*foo.ptr;
}
)");
// dereference field of pointer type in member function
checkDiagnostics(R"(
struct Foo {
Foo *ptr;
void target() {
*ptr;
}
};
)");
}
TEST(PointerNullabilityTest, MergeNullAndNonNull) {
checkDiagnostics(R"(
void target(int * _Nonnull y, bool b) {
int *x = nullptr;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = y;
*x;
}
*x; // [[unsafe]]
if (b) {
*x;
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullAndNullable) {
checkDiagnostics(R"(
void target(int * _Nullable y, bool b) {
int *x = nullptr;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = y;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullAndUnknown) {
checkDiagnostics(R"(
void target(int *y, bool b) {
int *x = nullptr;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = y;
*x;
}
*x; // [[unsafe]]
if (b) {
*x;
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNonNullAndNull) {
checkDiagnostics(R"(
void target(int * _Nonnull y, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = nullptr;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeNonNullAndNonNull) {
checkDiagnostics(R"(
void target(int * _Nonnull y, int * _Nonnull z, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = z;
*x;
}
*x;
if (b) {
*x;
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeNonNullAndNullable) {
checkDiagnostics(R"(
void target(int * _Nonnull y, int * _Nullable z, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = z;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeNonNullAndUnknown) {
checkDiagnostics(R"(
void target(int * _Nonnull y, int *z, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = z;
*x;
}
*x;
if (b) {
*x;
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullableAndNull) {
checkDiagnostics(R"(
void target(int * _Nullable y, bool b) {
int *x = y;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = nullptr;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullableAndNonNull) {
checkDiagnostics(R"(
void target(int * _Nullable y, int * _Nonnull z, bool b) {
int *x = y;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = z;
*x;
}
*x; // [[unsafe]]
if (b) {
*x;
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullableAndNullable) {
checkDiagnostics(R"(
void target(int * _Nullable y, int * _Nullable z, bool b) {
int *x = y;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = z;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeNullableAndUnknown) {
checkDiagnostics(R"(
void target(int * _Nullable y, int *z, bool b) {
int *x = y;
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
x = z;
*x;
}
*x; // [[unsafe]]
if (b) {
*x;
} else {
*x; // [[unsafe]]
}
}
)");
}
TEST(PointerNullabilityTest, MergeUnknownAndNull) {
checkDiagnostics(R"(
void target(int *y, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = nullptr;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeUnknownAndNonNull) {
checkDiagnostics(R"(
void target(int *y, int * _Nonnull z, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = z;
*x;
}
*x;
if (b) {
*x;
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeUnknownAndNullable) {
checkDiagnostics(R"(
void target(int *y, int * _Nullable z, bool b) {
int *x = y;
*x;
if (b) {
*x;
x = z;
*x; // [[unsafe]]
}
*x; // [[unsafe]]
if (b) {
*x; // [[unsafe]]
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, MergeUnknownAndUnknown) {
checkDiagnostics(R"(
void target(int *y, int *z, bool b) {
int *x = y;
if (b) {
*x;
x = z;
*x;
}
*x;
if (b) {
*x;
} else {
*x;
}
}
)");
}
TEST(PointerNullabilityTest, CallExprWithPointerReturnType) {
// free function
checkDiagnostics(R"(
int * _Nonnull makeNonnull();
int * _Nullable makeNullable();
int *makeUnannotated();
void target() {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
}
)");
// member function
checkDiagnostics(R"(
struct Foo {
int * _Nonnull makeNonnull();
int * _Nullable makeNullable();
int *makeUnannotated();
};
void target(Foo foo) {
*foo.makeNonnull();
*foo.makeNullable(); // [[unsafe]]
*foo.makeUnannotated();
}
)");
// overloaded operator call
checkDiagnostics(R"(
struct MakeNonnull {
int * _Nonnull operator()();
};
struct MakeNullable {
int * _Nullable operator()();
};
struct MakeUnannotated {
int *operator()();
};
void target() {
MakeNonnull makeNonnull;
*makeNonnull();
MakeNullable makeNullable;
*makeNullable(); // [[unsafe]]
MakeUnannotated makeUnannotated;
*makeUnannotated();
}
)");
// function pointer
checkDiagnostics(R"(
void target(int * _Nonnull (*makeNonnull)(),
int * _Nullable (*makeNullable)(),
int * (*makeUnannotated)()) {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
}
)");
// pointer to function pointer
checkDiagnostics(R"(
void target(int * _Nonnull (**makeNonnull)(),
int * _Nullable (**makeNullable)(),
int * (**makeUnannotated)()) {
*(*makeNonnull)();
*(*makeNullable)(); // [[unsafe]]
*(*makeUnannotated)();
}
)");
// function returning a function pointer which returns a pointer
checkDiagnostics(R"(
typedef int * _Nonnull (*MakeNonnullT)();
typedef int * _Nullable (*MakeNullableT)();
typedef int * (*MakeUnannotatedT)();
void target(MakeNonnullT (*makeNonnull)(),
MakeNullableT (*makeNullable)(),
MakeUnannotatedT (*makeUnannotated)()) {
*(*makeNonnull)()();
*(*makeNullable)()(); // [[unsafe]]
*(*makeUnannotated)()();
}
)");
// free function returns reference to pointer
checkDiagnostics(R"(
int * _Nonnull & makeNonnull();
int * _Nullable & makeNullable();
int *&makeUnannotated();
void target() {
*makeNonnull();
*makeNullable(); // [[unsafe]]
*makeUnannotated();
}
)");
// function called in loop
//
// TODO(b/233582219): Fix false negative. The pointer is only null-checked and
// therefore safe to dereference on the first iteration of the loop. On
// subsequent iterations of the loop, the pointer dereference is unsafe due to
// the lack of null check. The diagnoser currently fails to catch the
// unsafe dereference as it only evaluates the statement once.
checkDiagnostics(R"(
int * _Nullable makeNullable();
bool makeBool();
void target() {
bool first = true;
while(true) {
int *x = makeNullable();
if (first && x == nullptr) return;
first = false;
*x; // false-negative
}
}
)");
}
TEST(PointerNullabilityTest, CallExprParamAssignment) {
// free function with single param
checkDiagnostics(R"(
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);
}
)");
// overloaded operator with single param
checkDiagnostics(R"(
// 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;
}
)");
// free function with multiple params of mixed nullability
checkDiagnostics(R"(
void takeMixed(int *, int * _Nullable, int * _Nonnull);
void target() {
takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
}
)");
// overloaded operator with multiple params of mixed nullability
checkDiagnostics(R"(
struct TakeMixed {
void operator()(int *, int * _Nullable, int * _Nonnull);
};
void target() {
TakeMixed takeMixed;
takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
}
)");
// member function
checkDiagnostics(R"(
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);
}
)");
// function pointer
checkDiagnostics(R"(
void target(void (*takeNonnull)(int * _Nonnull),
void (*takeNullable)(int * _Nullable),
void (*takeUnannotated)(int *)) {
takeNonnull(nullptr); // [[unsafe]]
takeNullable(nullptr);
takeUnannotated(nullptr);
}
)");
// pointer to function pointer
//
// TODO(b/233582219): Fix false negative. Implement support for retrieving
// parameter types from a pointer to function pointer.
checkDiagnostics(R"(
void target(void (**takeNonnull)(int * _Nonnull),
void (**takeNullable)(int * _Nullable),
void (**takeUnannotated)(int *)) {
(*takeNonnull)(nullptr); // false-negative
(*takeNullable)(nullptr);
(*takeUnannotated)(nullptr);
}
)");
// function returned from function
//
// TODO(b/233582219): Fix false negative. Implement support for retrieving
// parameter types for functions returned by another function.
checkDiagnostics(R"(
typedef void (*takeNonnullF)(int * _Nonnull);
typedef void (*takeNullableF)(int * _Nullable);
typedef void (*takeUnannotatedF)(int *);
void target(takeNonnullF (*takeNonnull)(),
takeNullableF (*takeNullable)(),
takeUnannotatedF (*takeUnannotated)()) {
(*takeNonnull)()(nullptr); // false-negative
(*takeNullable)()(nullptr);
(*takeUnannotated)()(nullptr);
}
)");
// passing a reference to a nonnull pointer
//
// TODO(b/233582219): Fix false negative. When the nonnull pointer is passed
// by reference into the callee which takes a nullable parameter, its value
// may be changed to null, making it unsafe to dereference when we return from
// the function call. Some possible approaches for handling this case:
// (1) Disallow passing a nonnull pointer as a nullable reference - and warn
// at the function call.
// (2) Assume in worst case the nonnull pointer becomes nullable after the
// call - and warn at the dereference.
// (3) Sacrifice soundness for reduction in noise, and skip the warning.
checkDiagnostics(R"(
void takeNonnullRef(int * _Nonnull &);
void takeNullableRef(int * _Nullable &);
void takeUnannotatedRef(int *&);
void target(int * _Nonnull ptr_nonnull) {
takeNonnullRef(ptr_nonnull);
*ptr_nonnull;
// false-negative
takeNullableRef(ptr_nonnull);
*ptr_nonnull;
takeUnannotatedRef(ptr_nonnull);
*ptr_nonnull;
}
)");
// passing a reference to a nullable pointer
checkDiagnostics(R"(
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; // [[unsafe]]
}
)");
// passing a reference to an unannotated pointer
//
// TODO(b/233582219): Fix false negative. The unannotated pointer should be
// considered nullable if it has been used as a nullable pointer.
checkDiagnostics(R"(
void takeNonnullRef(int * _Nonnull &);
void takeNullableRef(int * _Nullable &);
void takeUnannotatedRef(int *&);
void target(int *ptr_unannotated) {
takeNonnullRef(ptr_unannotated);
*ptr_unannotated;
takeNullableRef(ptr_unannotated);
*ptr_unannotated; // false-negative
takeUnannotatedRef(ptr_unannotated);
*ptr_unannotated;
}
)");
}
TEST(PointerNullabilityTest, ReturnStatements) {
// nonnull return type
checkDiagnostics(R"(
int * _Nonnull target() {
return nullptr; // [[unsafe]]
}
)");
checkDiagnostics(R"(
int * _Nonnull target(int * _Nonnull ptr_nonnull) {
return ptr_nonnull;
}
)");
checkDiagnostics(R"(
int * _Nonnull target(int * _Nullable ptr_nullable) {
return ptr_nullable; // [[unsafe]]
}
)");
checkDiagnostics(R"(
int * _Nonnull target(int *ptr_unannotated) {
return ptr_unannotated;
}
)");
// nullable return type
checkDiagnostics(R"(
int * _Nullable target() {
return nullptr;
}
)");
checkDiagnostics(R"(
int * _Nullable target(int * _Nonnull ptr_nonnull) {
return ptr_nonnull;
}
)");
checkDiagnostics(R"(
int * _Nullable target(int * _Nullable ptr_nullable) {
return ptr_nullable;
}
)");
checkDiagnostics(R"(
int * _Nullable target(int *ptr_unannotated) {
return ptr_unannotated;
}
)");
// unannotated return type
checkDiagnostics(R"(
int * target() {
return nullptr;
}
)");
checkDiagnostics(R"(
int * target(int * _Nonnull ptr_nonnull) {
return ptr_nonnull;
}
)");
checkDiagnostics(R"(
int * target(int * _Nullable ptr_nullable) {
return ptr_nullable;
}
)");
checkDiagnostics(R"(
int * target(int *ptr_unannotated) {
return ptr_unannotated;
}
)");
// multiple return statements
checkDiagnostics(R"(
int * _Nonnull target(bool b, int * _Nonnull ptr_nonnull) {
if (b) {
return nullptr; // [[unsafe]]
}
return ptr_nonnull;
}
)");
checkDiagnostics(R"(
int * _Nonnull target(int * _Nullable ptr_nullable,
int * _Nonnull ptr_nonnull) {
if (ptr_nullable) {
return ptr_nullable;
}
return ptr_nonnull;
}
)");
checkDiagnostics(R"(
int * _Nonnull target(int * _Nullable ptr_nullable_1,
int * _Nullable ptr_nullable_2) {
if (ptr_nullable_1) {
return ptr_nullable_2; // [[unsafe]]
}
return ptr_nullable_1; // [[unsafe]]
}
)");
// return result of merging 2 pointer values
checkDiagnostics(R"(
int * _Nonnull target(bool b, int i) {
int *ptr;
if (b) {
ptr = &i;
} else {
ptr = nullptr;
}
return ptr; // [[unsafe]]
}
)");
}
TEST(PointerNullabilityTest, ConstructExpr) {
// Constructor call assigned to local variable.
checkDiagnostics(R"(
struct TakeNonnull {
explicit TakeNonnull(int * _Nonnull) {}
};
struct TakeNullable {
explicit TakeNullable(int * _Nullable) {}
};
struct TakeUnannotated {
explicit TakeUnannotated(int *) {}
};
int * _Nonnull makeNonnull();
int * _Nullable makeNullable();
int *makeUnannotated();
void target() {
auto NN1 = TakeNonnull(makeNonnull());
auto NN2 = TakeNonnull(makeNullable()); // [[unsafe]]
auto NN3 = TakeNonnull(makeUnannotated());
auto NB1 = TakeNullable(makeNonnull());
auto NB2 = TakeNullable(makeNullable());
auto NB3 = TakeNullable(makeUnannotated());
auto UN1 = TakeUnannotated(makeNonnull());
auto UN2 = TakeUnannotated(makeNullable());
auto UN3 = TakeUnannotated(makeUnannotated());
}
)");
// Constructor call in a base initializer.
checkDiagnostics(R"(
struct TakeNonnull {
explicit TakeNonnull(int * _Nonnull);
};
struct target: TakeNonnull {
target(int * _Nullable ptr_nullable): TakeNonnull(ptr_nullable) {} // [[unsafe]]
};
)");
// Call to a delegating constructor
checkDiagnostics(R"(
int * _Nullable makeNullable();
struct target {
target(int * _Nonnull);
target(): target(makeNullable()) {} // [[unsafe]]
};
)");
}
TEST(PointerNullabilityTest, ConstructorMemberInitializer) {
checkDiagnostics(R"(
int * _Nullable makeNullable();
struct target {
int * _Nonnull ptr_nonnull;
int * _Nullable ptr_nullable;
int * ptr_unannotated;
target(): ptr_nonnull(makeNullable()), // [[unsafe]]
ptr_nullable(makeNullable()),
ptr_unannotated(makeNullable()) {}
};
)");
checkDiagnostics(R"(
int * _Nonnull makeNonnull();
struct target {
int * _Nonnull ptr_nonnull;
int * _Nullable ptr_nullable;
int * ptr_unannotated;
target(): ptr_nonnull(makeNonnull()),
ptr_nullable(makeNonnull()),
ptr_unannotated(makeNonnull()) {}
};
)");
checkDiagnostics(R"(
int *makeUnannotated();
struct target {
int * _Nonnull ptr_nonnull;
int * _Nullable ptr_nullable;
int * ptr_unannotated;
target(): ptr_nonnull(makeUnannotated()),
ptr_nullable(makeUnannotated()),
ptr_unannotated(makeUnannotated()) {}
};
)");
}
} // namespace
} // namespace nullability
} // namespace tidy
} // namespace clang