blob: 6e726d694bbce4a551a940113bb31950e961427b [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
#include "nullability/inference/merge.h"
#include <array>
#include "absl/log/check.h"
#include "nullability/inference/inference.proto.h"
#include "nullability/proto_matchers.h"
#include "llvm/ADT/StringRef.h"
#include "external/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
#include "external/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
#include "google/protobuf/text_format.h"
namespace clang::tidy::nullability {
namespace {
template <typename T>
T proto(llvm::StringRef Text) {
T Result;
CHECK(google::protobuf::TextFormat::ParseFromString(Text, &Result));
return Result;
}
TEST(PartialFromEvidenceTest, ContainsEvidenceInfo) {
EXPECT_THAT(partialFromEvidence(proto<Evidence>(R"pb(
symbol { usr: "func" }
slot: 1
kind: UNCHECKED_DEREFERENCE
location: "foo.cc:42"
)pb")),
EqualsProto(R"pb(
kind_count { key: 3 value: 1 }
kind_samples {
key: 3
value { location: "foo.cc:42" }
}
)pb"));
}
TEST(MergePartialsTest, BothContainEvidence) {
auto L = proto<SlotPartial>(R"pb(
kind_count { key: 1 value: 1 }
kind_count { key: 0 value: 2 }
kind_samples {
key: 0
value { location: "a" location: "b" }
}
)pb");
auto R = proto<SlotPartial>(
R"pb(kind_count { key: 2 value: 1 }
kind_count { key: 0 value: 2 }
kind_samples {
key: 0
value { location: "c" location: "a" location: "d" }
})pb");
mergePartials(L, R);
EXPECT_THAT(L, EqualsProto(R"pb(
kind_count { key: 0 value: 4 }
kind_count { key: 2 value: 1 }
kind_count { key: 1 value: 1 }
kind_samples {
key: 0
value { location: "a" location: "b" location: "c" }
}
)pb"));
}
TEST(MergePartialsTest, RightEmpty) {
auto L = proto<SlotPartial>(R"pb(kind_count { key: 0 value: 1 })pb");
SlotPartial R;
mergePartials(L, R);
EXPECT_THAT(L, EqualsProto(L));
}
TEST(MergePartialsTest, LeftEmpty) {
SlotPartial L;
auto R = proto<SlotPartial>(R"pb(kind_count { key: 3 value: 1 })pb");
mergePartials(L, R);
EXPECT_THAT(L, EqualsProto(R));
}
TEST(FinalizeTest, ConflictingAnnotations) {
EXPECT_THAT(finalize(proto<SlotPartial>(R"pb(
kind_count { key: 1 value: 1 } # ANNOTATED_NULLABLE
kind_count { key: 2 value: 1 } # ANNOTATED_NONNULL
kind_samples {
key: 1
value { location: "decl" }
}
kind_samples {
key: 2
value { location: "def" }
}
)pb")),
EqualsProto(R"pb(
nullability: UNKNOWN
conflict: true
sample_evidence { kind: ANNOTATED_NULLABLE location: "decl" }
sample_evidence { kind: ANNOTATED_NONNULL location: "def" }
)pb"));
}
TEST(FinalizeTest, Empty) {
EXPECT_THAT(finalize(SlotPartial()), EqualsProto(SlotInference()));
}
TEST(FinalizeTest, TypicalEvidence) {
EXPECT_THAT(finalize(proto<SlotPartial>(R"pb(
kind_count { key: 3 value: 1 } # UNCHECKED_DEREFERENCE
)pb")),
EqualsProto(R"pb(nullability: NONNULL)pb"));
}
TEST(FinalizeTest, AnnotatedUnknown) {
EXPECT_THAT(finalize(proto<SlotPartial>(R"pb(
kind_count { key: 0 value: 1 } # ANNOTATED_UNKNOWN
)pb")),
EqualsProto(R"pb(nullability: UNKNOWN)pb"));
}
TEST(FinalizeTest, AnnotatedNonUnknown) {
EXPECT_THAT(finalize(proto<SlotPartial>(R"pb(
kind_count { key: 1 value: 1 } # ANNOTATED_NULLABLE
)pb")),
EqualsProto(R"pb(nullability: NULLABLE trivial: true)pb"));
}
TEST(MergeEvidenceTest, TreeShapedMerge) {
SlotPartial P1 = partialFromEvidence(
proto<Evidence>(R"pb(kind: ASSIGNED_FROM_UNKNOWN)pb"));
SlotPartial P2 = partialFromEvidence(
proto<Evidence>(R"pb(kind: ASSIGNED_FROM_NONNULL)pb"));
SlotPartial P3 = partialFromEvidence(
proto<Evidence>(R"pb(kind: GCC_NONNULL_ATTRIBUTE)pb"));
SlotPartial P4 = partialFromEvidence(
proto<Evidence>(R"pb(kind: ASSIGNED_FROM_NULLABLE)pb"));
auto Expected = EqualsProto(R"pb(nullability: NULLABLE)pb");
EXPECT_THAT(
[&] {
SlotPartial P = P1;
mergePartials(P, P2);
mergePartials(P, P3);
mergePartials(P, P4);
return finalize(P);
}(),
Expected);
EXPECT_THAT(
[&] {
SlotPartial P = P4;
mergePartials(P, P3);
mergePartials(P, P2);
mergePartials(P, P1);
return finalize(P);
}(),
Expected);
EXPECT_THAT(
[&] {
SlotPartial PA = P1;
mergePartials(PA, P2);
SlotPartial PB = P3;
mergePartials(PB, P4);
mergePartials(PA, PB);
return finalize(PA);
}(),
Expected);
}
class InferTest : public ::testing::Test {
std::array<unsigned, Evidence::Kind_MAX + 1> Counts = {};
protected:
void add(Evidence::Kind E, int N = 1) { Counts[E] += N; }
Nullability infer(bool ExpectConflict = false, bool ExpectTrivial = false) {
auto Result = nullability::infer(Counts);
EXPECT_EQ(ExpectConflict, Result.Conflict);
EXPECT_EQ(ExpectTrivial, Result.Trivial);
return Result.Nullability;
}
};
TEST_F(InferTest, NoEvidence) { EXPECT_EQ(Nullability::UNKNOWN, infer()); }
TEST_F(InferTest, Annotated) {
add(Evidence::ANNOTATED_NULLABLE);
EXPECT_EQ(Nullability::NULLABLE,
infer(/*ExpectConflict=*/false, /*ExpectTrivial=*/true));
add(Evidence::UNCHECKED_DEREFERENCE); // No conflict, annotation wins.
EXPECT_EQ(Nullability::NULLABLE,
infer(/*ExpectConflict=*/false, /*ExpectTrivial=*/true));
add(Evidence::ANNOTATED_NONNULL); // Conflicting annotations!
EXPECT_EQ(Nullability::UNKNOWN, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, Deref) {
add(Evidence::UNCHECKED_DEREFERENCE);
EXPECT_EQ(Nullability::NONNULL, infer());
}
TEST_F(InferTest, NullableArgumentPassed) {
add(Evidence::NULLABLE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::NONNULL_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::UNKNOWN_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::UNCHECKED_DEREFERENCE);
EXPECT_EQ(Nullability::NONNULL, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, OnlyNonnullArgumentsPassed) {
add(Evidence::NONNULL_ARGUMENT);
EXPECT_EQ(Nullability::NONNULL, infer());
}
TEST_F(InferTest, NonnullAndUnknownArgumentsPassed) {
add(Evidence::NONNULL_ARGUMENT);
add(Evidence::UNKNOWN_ARGUMENT);
EXPECT_EQ(Nullability::UNKNOWN, infer());
}
TEST_F(InferTest, AssignedFromNullable) {
add(Evidence::ASSIGNED_FROM_NULLABLE);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::UNCHECKED_DEREFERENCE);
EXPECT_EQ(Nullability::NONNULL, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, ReturnValues) {
add(Evidence::NONNULL_RETURN);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::UNKNOWN_RETURN);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::NULLABLE_RETURN);
EXPECT_EQ(Nullability::NULLABLE, infer());
}
TEST_F(InferTest, PassedToNonnull) {
add(Evidence::ASSIGNED_TO_NONNULL);
EXPECT_EQ(Nullability::NONNULL, infer());
}
TEST_F(InferTest, PassedToMutableNullable) {
add(Evidence::ASSIGNED_TO_MUTABLE_NULLABLE);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::ASSIGNED_TO_NONNULL);
EXPECT_EQ(Nullability::NONNULL, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, AbortIfNull) {
add(Evidence::ABORT_IF_NULL);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::NULLABLE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, Arithmetic) {
add(Evidence::ARITHMETIC);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::NULLABLE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, ArraySubscript) {
add(Evidence::ARRAY_SUBSCRIPT);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::NULLABLE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, NullableDefaultMemberInitializer) {
add(Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER);
EXPECT_EQ(Nullability::NULLABLE, infer());
add(Evidence::UNCHECKED_DEREFERENCE);
EXPECT_EQ(Nullability::NONNULL, infer());
}
TEST_F(InferTest, MixedAssignments) {
add(Evidence::ASSIGNED_FROM_NONNULL);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::ASSIGNED_FROM_UNKNOWN);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::ASSIGNED_FROM_NULLABLE);
EXPECT_EQ(Nullability::NULLABLE, infer());
}
TEST_F(InferTest, MixedConstReferenceReturns) {
add(Evidence::NONNULL_REFERENCE_RETURN_AS_CONST);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::UNKNOWN_REFERENCE_RETURN);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::NULLABLE_REFERENCE_RETURN);
EXPECT_EQ(Nullability::NULLABLE, infer());
}
TEST_F(InferTest, MixedReferenceReturns) {
add(Evidence::UNKNOWN_REFERENCE_RETURN);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::NONNULL_REFERENCE_RETURN);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::NULLABLE_REFERENCE_RETURN);
EXPECT_EQ(Nullability::NULLABLE, infer(/*ExpectConflict=*/true));
}
TEST_F(InferTest, MixedConstReferenceArguments) {
add(Evidence::NONNULL_REFERENCE_ARGUMENT_AS_CONST);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::UNKNOWN_REFERENCE_ARGUMENT);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::NULLABLE_REFERENCE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer());
}
TEST_F(InferTest, MixedReferenceArguments) {
add(Evidence::UNKNOWN_REFERENCE_ARGUMENT);
EXPECT_EQ(Nullability::UNKNOWN, infer());
add(Evidence::NONNULL_REFERENCE_ARGUMENT);
EXPECT_EQ(Nullability::NONNULL, infer());
add(Evidence::NULLABLE_REFERENCE_ARGUMENT);
EXPECT_EQ(Nullability::NULLABLE, infer(/*ExpectConflict=*/true));
}
} // namespace
} // namespace clang::tidy::nullability