blob: c600babcd9b854002fb1d34c939e773c72a4cff4 [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 "nullability/pointer_nullability.h"
#include <memory>
#include "nullability/type_nullability.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/Specifiers.h"
#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
namespace clang::tidy::nullability {
namespace {
using dataflow::Arena;
using dataflow::Formula;
class NullabilityPropertiesTest : public ::testing::Test {
protected:
dataflow::PointerValue &makePointer(PointerTypeNullability N) {
auto &P = Env.create<dataflow::PointerValue>(
DACtx.createStorageLocation(QualType()));
initPointerNullState(P, DACtx, N);
return P;
}
dataflow::PointerValue &makeNullPointer() {
auto &P = Env.create<dataflow::PointerValue>(
DACtx.createStorageLocation(QualType()));
initNullPointer(P, DACtx);
return P;
}
dataflow::DataflowAnalysisContext DACtx = dataflow::DataflowAnalysisContext(
std::make_unique<dataflow::WatchedLiteralsSolver>());
dataflow::Environment Env = dataflow::Environment(DACtx);
Arena &A = DACtx.arena();
};
using IsNullableTest = NullabilityPropertiesTest;
TEST_F(IsNullableTest, NullPtr) {
EXPECT_TRUE(isNullable(makeNullPointer(), Env));
}
TEST_F(IsNullableTest, NullableButNotNull) {
auto &NullableButNotNull = makePointer(NullabilityKind::Nullable);
EXPECT_TRUE(isNullable(NullableButNotNull, Env));
auto *IsNull = getPointerNullState(NullableButNotNull).IsNull;
ASSERT_NE(IsNull, nullptr);
Env.assume(A.makeNot(*IsNull));
EXPECT_FALSE(isNullable(NullableButNotNull, Env));
}
TEST_F(IsNullableTest, NullableAndNull) {
auto &NullableAndNull = makePointer(NullabilityKind::Nullable);
auto *IsNull = getPointerNullState(NullableAndNull).IsNull;
ASSERT_NE(IsNull, nullptr);
Env.assume(*IsNull);
EXPECT_TRUE(isNullable(NullableAndNull, Env));
}
TEST_F(IsNullableTest, NonnullAndNotNull) {
auto &NonnullAndNotNull = makePointer(NullabilityKind::NonNull);
EXPECT_FALSE(isNullable(NonnullAndNotNull, Env));
auto *IsNull = getPointerNullState(NonnullAndNotNull).IsNull;
ASSERT_NE(IsNull, nullptr);
// `IsNull` should not just be provably false but an actual false literal.
ASSERT_EQ(IsNull, &A.makeLiteral(false));
// Assuming the pointer is non-null is a no-op, but make sure it doesn't
// change the result of `isNullable()`.
Env.assume(A.makeNot(*IsNull));
EXPECT_FALSE(isNullable(NonnullAndNotNull, Env));
}
TEST_F(IsNullableTest, NonnullAndNull) {
// If a pointer comes from a non-null source but is dynamically discovered to
// be definitely null, we don't consider it nullable, because we're in an
// environment with false flow conditions.
auto &NonnullAndNull = makePointer(NullabilityKind::NonNull);
auto *IsNull = getPointerNullState(NonnullAndNull).IsNull;
ASSERT_NE(IsNull, nullptr);
Env.assume(*IsNull);
EXPECT_FALSE(isNullable(NonnullAndNull, Env));
}
TEST_F(IsNullableTest, UnknownAndNull) {
// If a pointer comes from an unknown source but is dynamically discovered to
// be definitely null, we consider it nullable.
auto &UnknownAndNull = makePointer(NullabilityKind::Unspecified);
auto *IsNull = getPointerNullState(UnknownAndNull).IsNull;
ASSERT_NE(IsNull, nullptr);
Env.assume(*IsNull);
EXPECT_TRUE(isNullable(UnknownAndNull, Env));
}
TEST_F(IsNullableTest, AdditionalConstraints) {
auto &P = makePointer(NullabilityKind::Nullable);
EXPECT_TRUE(isNullable(P, Env));
auto *IsNull = getPointerNullState(P).IsNull;
ASSERT_NE(IsNull, nullptr);
auto *NotNull = &DACtx.arena().makeNot(*IsNull);
EXPECT_FALSE(isNullable(P, Env, NotNull));
}
TEST_F(NullabilityPropertiesTest, GetNullabilityAdditionalConstraints) {
auto &P = makePointer(NullabilityKind::Nullable);
EXPECT_EQ(getNullability(P, Env), NullabilityKind::Nullable);
auto *IsNull = getPointerNullState(P).IsNull;
ASSERT_NE(IsNull, nullptr);
auto *NotNull = &DACtx.arena().makeNot(*IsNull);
EXPECT_EQ(getNullability(P, Env, NotNull), NullabilityKind::NonNull);
}
TEST_F(NullabilityPropertiesTest, InitNullabilityPropertiesWithFormulas) {
auto &P = Env.create<dataflow::PointerValue>(
DACtx.createStorageLocation(QualType()));
Arena &A = DACtx.arena();
const Formula &FromNullable = A.makeAtomRef(A.makeAtom());
const Formula &IsNull = A.makeAtomRef(A.makeAtom());
initPointerNullState(P, DACtx, PointerNullState{&FromNullable, &IsNull});
ASSERT_EQ(getPointerNullState(P).FromNullable, &FromNullable);
ASSERT_EQ(getPointerNullState(P).IsNull, &IsNull);
}
TEST_F(NullabilityPropertiesTest, InitNullabilityPropertiesWithTop) {
auto &P = Env.create<dataflow::PointerValue>(
DACtx.createStorageLocation(QualType()));
initPointerNullState(P, DACtx, PointerNullState{nullptr, nullptr});
ASSERT_EQ(getPointerNullState(P).FromNullable, nullptr);
ASSERT_EQ(getPointerNullState(P).IsNull, nullptr);
}
} // namespace
} // namespace clang::tidy::nullability