blob: 37be25721102a3f049e045758510a4788bcda749 [file] [log] [blame] [edit]
// 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 basic functionality (simple dereferences without control flow).
#include <memory>
#include "nullability/pointer_nullability_diagnosis.h"
#include "nullability/pragma.h"
#include "nullability/test/check_diagnostics.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/Basic/LLVM.h"
#include "clang/Frontend/ASTUnit.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Testing/Support/Error.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 ::testing::IsEmpty;
using ::testing::SizeIs;
TEST(PointerNullabilityTest, NoPointerOperations) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() { 1 + 2; }
)cc"));
}
TEST(PointerNullabilityTest, DerefNullPtr) {
// nullptr
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() {
int *x = nullptr;
*x; // [[unsafe]]
}
)cc"));
// 0
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() {
int *x = 0;
*x; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, DerefAddrOf) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() {
int i;
int *x = &i;
*x;
}
)cc"));
// transitive
EXPECT_TRUE(checkDiagnostics(R"cc(
void target() {
int i;
int *x = &i;
int *y = x;
*y;
}
)cc"));
}
TEST(PointerNullabilityTest, DerefPtrAnnotatedNonNullWithoutACheck) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull x) { *x; }
)cc"));
// transitive
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull x) {
int *y = x;
*y;
}
)cc"));
}
TEST(PointerNullabilityTest, DerefPtrAnnotatedNullableWithoutACheck) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nullable x) {
*x; // [[unsafe]]
}
)cc"));
// transitive
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nullable x) {
int *y = x;
*y; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, DerefUnknownPtrWithoutACheck) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *x) { *x; }
)cc"));
// transitive
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *x) {
int *y = x;
*y;
}
)cc"));
}
TEST(PointerNullabilityTest, DoubleDereference) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int **p) {
*p;
**p;
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int **_Nonnull p) {
*p;
**p;
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull *p) {
*p;
**p;
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull *_Nonnull p) {
*p;
**p;
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int **_Nullable p) {
*p; // [[unsafe]]
**p; // [[unsafe]]
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nullable *p) {
*p;
**p; // [[unsafe]]
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nullable *_Nullable p) {
*p; // [[unsafe]]
**p; // [[unsafe]]
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nullable *_Nonnull p) {
*p;
**p; // [[unsafe]]
}
)cc"));
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull *_Nullable p) {
*p; // [[unsafe]]
**p; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ArrowOperatorOnNonNullPtr) {
// (->) member field
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo;
};
void target(Foo *_Nonnull foo) { foo->foo; }
)cc"));
// (->) member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo();
};
void target(Foo *_Nonnull foo) { foo->foo(); }
)cc"));
}
TEST(PointerNullabilityTest, ArrowOperatorOnNullablePtr) {
// (->) member field
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo;
};
void target(Foo *_Nullable foo) {
foo->foo; // [[unsafe]]
if (foo) {
foo->foo;
} else {
foo->foo; // [[unsafe]]
}
foo->foo; // [[unsafe]]
}
)cc"));
// (->) member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo();
};
void target(Foo *_Nullable foo) {
foo->foo(); // [[unsafe]]
if (foo) {
foo->foo();
} else {
foo->foo(); // [[unsafe]]
}
foo->foo(); // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ArrowOperatorOnUnknownPtr) {
// (->) member field
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo;
};
void target(Foo *foo) { foo->foo; }
)cc"));
// (->) member function
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Foo {
Foo *foo();
};
void target(Foo *foo) { foo->foo(); }
)cc"));
}
TEST(PointerNullabilityTest, ArraySubscript) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull nonnull, int *_Nullable nullable, int *unknown) {
nonnull[0];
nullable[0]; // [[unsafe]]
unknown[0];
0 [nonnull];
0 [nullable]; // [[unsafe]]
0 [unknown];
}
)cc"));
}
TEST(PointerNullabilityTest, Assignment) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *_Nonnull nonnull, int *_Nullable nullable, int *unknown) {
nonnull = new int;
nonnull = nullptr; // [[unsafe]]
nullable = new int;
nullable = nullptr;
unknown = new int;
unknown = nullptr;
// Check that we can handle cases where there isn't just a simple
// `DeclRefExpr` on the left-hand side.
*(&nonnull) = nullptr; // [[unsafe]]
}
)cc"));
}
TEST(PointerNullabilityTest, ForwardDeclaration) {
// Check that we handle a function with a forward declaration correctly. This
// is a regression test for a bug where we erroneously used the `ParmVarDecl`s
// of the first declaration when creating initial values for the parameters;
// the body uses the `ParmVarDecl`s of the second declaration, so it would not
// see these initial values.
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int* _Nullable p);
void target(int* _Nullable p) {
if (p == nullptr) return;
*p;
}
)cc"));
}
TEST(PointerNullabilityTest, AnalyzeFunctionWithForwardDeclarationOnlyOnce) {
std::unique_ptr<ASTUnit> Unit = tooling::buildASTFromCode(R"cc(
// Check that we analyze a function with a forward declaration only once
// (for the definition), and not for every redeclaration that we encounter.
void target();
void target() {
int *p = nullptr;
*p;
}
)cc");
NullabilityPragmas NoPragmas;
ASTContext &Context = Unit->getASTContext();
DeclContextLookupResult Result =
Context.getTranslationUnitDecl()->lookup(&Context.Idents.get("target"));
ASSERT_TRUE(Result.isSingleResult());
auto *Target = cast<FunctionDecl>(Result.front());
SmallVector<FunctionDecl *> Redecls(Target->redecls());
ASSERT_EQ(Redecls.size(), 2);
EXPECT_TRUE(Redecls[0]->doesThisDeclarationHaveABody());
EXPECT_THAT_EXPECTED(diagnosePointerNullability(Redecls[0], NoPragmas),
llvm::HasValue(SizeIs(1)));
EXPECT_FALSE(Redecls[1]->doesThisDeclarationHaveABody());
EXPECT_THAT_EXPECTED(diagnosePointerNullability(Redecls[1], NoPragmas),
llvm::HasValue(IsEmpty()));
}
TEST(PointerNullabilityTest, CheckMacro) {
EXPECT_TRUE(checkDiagnostics(R"cc(
#define CHECK(x) \
if (!x) __builtin_abort();
void target(int* _Nullable p) {
CHECK(p);
*p;
}
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability