blob: 9539198810110a276d8bdb48c11d2fe334cf9cfa [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
// Tests for merging different nullability types.
#include <memory>
#include <string>
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/test/check_diagnostics.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Testing/Support/Error.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
TEST(PointerNullabilityTest, MergeNullAndNonNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullAndNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullAndUnknown) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNonNullAndNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNonNullAndNonNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNonNullAndNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNonNullAndUnknown) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullableAndNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullableAndNonNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullableAndNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeNullableAndUnknown) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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]]
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeUnknownAndNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeUnknownAndNonNull) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeUnknownAndNullable) {
EXPECT_TRUE(checkDiagnostics(R"cc(
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;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergeUnknownAndUnknown) {
EXPECT_TRUE(checkDiagnostics(R"cc(
void target(int *y, int *z, bool b) {
int *x = y;
if (b) {
*x;
x = z;
*x;
}
*x;
if (b) {
*x;
} else {
*x;
}
}
)cc"));
}
TEST(PointerNullabilityTest, MergePointerLValues) {
EXPECT_TRUE(checkDiagnostics(R"cc(
struct Node {
Node* next;
};
bool b();
void target(Node* first) {
for (Node* cur = first; cur; cur = cur->next) {
// We used to crash here: `PointerAnalysis::merge()` assumed `Value`s
// of pointer type were always `PointerValue`.
//
// Here the `MemberExpr` is a glvalue and produces a `ReferenceValue`
// of type `Node *`.
// When we merge the first and second loop iteration,
// `Environment::join()` calls `PointerAnalysis::merge()` to combine
// the two `ReferenceValue`s.
cur->next;
// The rest of this function exists to actually trigger a situation
// where we perform a merge and the two `ReferenceValue`s to be merged
// are actually different. (Otherwise, we will never call through to
// `PointerNullabilityAnalysis::merge()` in the first place.)
// This code is unfortunately pretty arbitrary, because it relies on the
// specific order in which the framework processes blocks in the CFG.
// This is unsatisfactory, but will be moot when `ReferenceValue` is
// eliminated (see https://discourse.llvm.org/t/70086 for details).
// At that point, this test should be converted into a test that checks
// that the analysis converges (which is also not the case, see below).
if (b())
;
else {
// The merge that used to trigger the crash happens at the top of this
// loop where the edge that comes from outside the loop joins the edge
// that comes from the bottom of the loop.
for (int i = 0; i < 10; ++i) {
}
}
}
}
)cc"));
}
} // namespace
} // namespace clang::tidy::nullability