| // 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 "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" |
| #include "third_party/protobuf/text_format.h" |
| |
| namespace clang::tidy::nullability { |
| namespace { |
| |
| template <typename T> |
| T proto(llvm::StringRef Text) { |
| T Result; |
| CHECK(proto2::TextFormat::ParseFromString(Text, &Result)); |
| return Result; |
| } |
| |
| TEST(MergeEvidenceTest, PartialFromEvidence) { |
| EXPECT_THAT(partialFromEvidence(proto<Evidence>(R"pb( |
| symbol { usr: "func" } |
| slot: 1 |
| kind: UNCHECKED_DEREFERENCE |
| location: "foo.cc:42" |
| )pb")), |
| EqualsProto(R"pb( |
| symbol { usr: "func" } |
| slot {} |
| slot { |
| kind_count { key: 3 value: 1 } |
| kind_samples { |
| key: 3 |
| value { location: "foo.cc:42" } |
| } |
| } |
| )pb")); |
| } |
| |
| TEST(MergeEvidenceTest, MergePartials) { |
| auto L = proto<Partial>(R"pb( |
| symbol { usr: "func" } |
| slot { |
| kind_count { key: 1 value: 1 } |
| kind_count { key: 0 value: 2 } |
| kind_samples { |
| key: 0 |
| value { location: "a" location: "b" } |
| } |
| } |
| slot { kind_count { key: 0 value: 1 } } |
| )pb"); |
| |
| auto R = proto<Partial>(R"pb( |
| symbol { usr: "func" } |
| slot { |
| kind_count { key: 2 value: 1 } |
| kind_count { key: 0 value: 2 } |
| kind_samples { |
| key: 0 |
| value { location: "c" location: "a" location: "d" } |
| } |
| } |
| slot {} |
| slot { kind_count { key: 3 value: 1 } } |
| )pb"); |
| |
| mergePartials(L, R); |
| EXPECT_THAT(L, EqualsProto(R"pb( |
| symbol { usr: "func" } |
| slot { |
| 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" } |
| } |
| } |
| slot { kind_count { key: 0 value: 1 } } |
| slot { kind_count { key: 3 value: 1 } } |
| )pb")); |
| } |
| |
| TEST(MergeEvidenceTest, Finalize) { |
| EXPECT_THAT(finalize(proto<Partial>(R"pb( |
| symbol { usr: "func" } |
| slot { |
| 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" } |
| } |
| } |
| slot {} |
| slot { |
| kind_count { key: 3 value: 1 } # UNCHECKED_DEREFERENCE |
| } |
| slot { kind_count { key: 0 value: 1 } } # ANNOTATED_UNKNOWN |
| slot { kind_count { key: 1 value: 1 } } # ANNOTATED_NULLABLE |
| )pb")), |
| EqualsProto(R"pb( |
| symbol { usr: "func" } |
| slot_inference { |
| slot: 0 |
| nullability: UNKNOWN |
| conflict: true |
| sample_evidence { kind: ANNOTATED_NULLABLE location: "decl" } |
| sample_evidence { kind: ANNOTATED_NONNULL location: "def" } |
| } |
| slot_inference { slot: 2 nullability: NONNULL } |
| slot_inference { slot: 3 nullability: UNKNOWN } |
| slot_inference { slot: 4 nullability: NULLABLE trivial: true } |
| )pb")); |
| } |
| |
| TEST(MergeEvidenceTest, TreeShapedMerge) { |
| Partial P1 = |
| partialFromEvidence(proto<Evidence>("slot: 0 " |
| "kind: ANNOTATED_NULLABLE")); |
| Partial P2 = |
| partialFromEvidence(proto<Evidence>("slot: 2 " |
| "kind: UNCHECKED_DEREFERENCE")); |
| Partial P3 = |
| partialFromEvidence(proto<Evidence>("slot: 4 " |
| "kind: ANNOTATED_NULLABLE")); |
| Partial P4 = |
| partialFromEvidence(proto<Evidence>("slot: 0 " |
| "kind: ANNOTATED_NONNULL")); |
| |
| auto IsExpected = EqualsProto(R"pb( |
| symbol {} |
| slot_inference { slot: 0 nullability: UNKNOWN conflict: true } |
| slot_inference { slot: 2 nullability: NONNULL } |
| slot_inference { slot: 4 nullability: NULLABLE trivial: true } |
| )pb"); |
| |
| EXPECT_THAT( |
| [&] { |
| Partial P = P1; |
| mergePartials(P, P2); |
| mergePartials(P, P3); |
| mergePartials(P, P4); |
| return finalize(P); |
| }(), |
| IsExpected); |
| |
| EXPECT_THAT( |
| [&] { |
| Partial P = P4; |
| mergePartials(P, P3); |
| mergePartials(P, P2); |
| mergePartials(P, P1); |
| return finalize(P); |
| }(), |
| IsExpected); |
| |
| EXPECT_THAT( |
| [&] { |
| Partial PA = P1; |
| mergePartials(PA, P2); |
| Partial PB = P3; |
| mergePartials(PB, P4); |
| mergePartials(PA, PB); |
| return finalize(PA); |
| }(), |
| IsExpected); |
| } |
| |
| 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, 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()); |
| } |
| |
| } // namespace |
| } // namespace clang::tidy::nullability |