| // 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 <optional> |
| #include <utility> |
| |
| #include "absl/log/check.h" |
| #include "nullability/inference/inference.proto.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/STLExtras.h" |
| |
| namespace clang::tidy::nullability { |
| namespace { |
| |
| static void mergeSampleLocations(Partial::SampleLocations &LHS, |
| const Partial::SampleLocations &RHS) { |
| static constexpr unsigned Limit = 3; |
| // We don't care which we pick, but they should be unique. |
| // Multiple instantiations of the same template are not interesting. |
| for (const auto &Loc : RHS.location()) { |
| if (LHS.location_size() >= Limit) break; |
| // Linear scan is fine because Limit is tiny. |
| if (!llvm::is_contained(LHS.location(), Loc)) LHS.add_location(Loc); |
| } |
| } |
| |
| static void mergeSlotPartials(Partial::SlotPartial &LHS, |
| const Partial::SlotPartial &RHS) { |
| for (auto [Kind, Count] : RHS.kind_count()) |
| (*LHS.mutable_kind_count())[Kind] += Count; |
| for (const auto &[Kind, Samples] : RHS.kind_samples()) |
| mergeSampleLocations((*LHS.mutable_kind_samples())[Kind], Samples); |
| } |
| |
| } // namespace |
| |
| Partial partialFromEvidence(const Evidence &E) { |
| Partial P; |
| *P.mutable_symbol() = E.symbol(); |
| // We want to set P.slot[E.slot], so populate previous slots. |
| while (P.slot_size() < E.slot()) P.add_slot(); |
| auto *S = P.add_slot(); |
| ++(*S->mutable_kind_count())[E.kind()]; |
| if (E.has_location()) |
| (*S->mutable_kind_samples())[E.kind()].add_location(E.location()); |
| return P; |
| } |
| |
| void mergePartials(Partial &LHS, const Partial &RHS) { |
| CHECK_EQ(LHS.symbol().usr(), RHS.symbol().usr()); |
| auto *Slots = LHS.mutable_slot(); |
| while (RHS.slot_size() > Slots->size()) Slots->Add(); |
| for (unsigned I = 0; I < RHS.slot_size(); ++I) |
| mergeSlotPartials(*LHS.mutable_slot(I), RHS.slot(I)); |
| } |
| |
| // Form nullability conclusions from a set of evidence. |
| Inference finalize(const Partial &P) { |
| Inference Result; |
| *Result.mutable_symbol() = P.symbol(); |
| for (unsigned I = 0; I < P.slot_size(); ++I) { |
| if (P.slot(I).kind_count_size() == 0) continue; |
| auto &Slot = *Result.add_slot_inference(); |
| Slot.set_slot(I); |
| |
| // Reconstitute samples, if we have them. |
| for (const auto &[Kind, Samples] : P.slot(I).kind_samples()) { |
| for (const auto &Loc : Samples.location()) { |
| auto *Sample = Slot.add_sample_evidence(); |
| Sample->set_location(Loc); |
| Sample->set_kind(static_cast<Evidence::Kind>(Kind)); |
| } |
| } |
| llvm::stable_sort(*Slot.mutable_sample_evidence(), [&](auto &L, auto &R) { |
| return std::forward_as_tuple(L.kind(), L.location()) < |
| std::forward_as_tuple(R.kind(), R.location()); |
| }); |
| |
| std::array<unsigned, Evidence::Kind_MAX + 1> KindCounts = {}; |
| for (auto [Kind, Count] : P.slot(I).kind_count()) KindCounts[Kind] = Count; |
| auto Result = infer(KindCounts); |
| Slot.set_nullability(Result.Nullability); |
| if (Result.Conflict) Slot.set_conflict(true); |
| if (Result.Trivial) Slot.set_trivial(true); |
| } |
| return Result; |
| } |
| |
| namespace { |
| void update(std::optional<InferResult> &Result, |
| Nullability ImpliedNullability) { |
| if (!Result) { |
| Result = {ImpliedNullability}; |
| return; |
| } |
| if (Result->Nullability != ImpliedNullability) |
| // Leave the existing Nullability. |
| Result->Conflict = true; |
| } |
| } // namespace |
| |
| InferResult infer(llvm::ArrayRef<unsigned> Counts) { |
| CHECK_EQ(Counts.size(), Evidence::Kind_MAX + 1); |
| // Annotations take precedence over everything. |
| // If some other evidence is incompatible with an annotation, that's not |
| // an inference conflict, just an error to be caught by verification. |
| if (Counts[Evidence::ANNOTATED_NONNULL] && |
| Counts[Evidence::ANNOTATED_NULLABLE]) { |
| return {Nullability::UNKNOWN, /*Conflict=*/true}; |
| } |
| if (Counts[Evidence::ANNOTATED_NONNULL]) |
| return {Nullability::NONNULL, /*Conflict=*/false, /*Trivial=*/true}; |
| if (Counts[Evidence::ANNOTATED_NULLABLE]) |
| return {Nullability::NULLABLE, /*Conflict=*/false, /*Trivial=*/true}; |
| |
| // Mandatory inference rules, required by type-checking. |
| // Ordered from most confident to least. |
| std::optional<InferResult> Result; |
| if (Counts[Evidence::UNCHECKED_DEREFERENCE]) |
| update(Result, Nullability::NONNULL); |
| if (Counts[Evidence::NULLABLE_ARGUMENT]) |
| update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::NULLABLE_REFERENCE_ARGUMENT]) |
| update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::NONNULL_REFERENCE_ARGUMENT]) |
| update(Result, Nullability::NONNULL); |
| if (Counts[Evidence::ASSIGNED_FROM_NULLABLE]) |
| update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::NULLABLE_RETURN]) update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::NULLABLE_REFERENCE_RETURN]) |
| update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::NONNULL_REFERENCE_RETURN]) |
| update(Result, Nullability::NONNULL); |
| if (Counts[Evidence::ASSIGNED_TO_NONNULL]) |
| update(Result, Nullability::NONNULL); |
| if (Counts[Evidence::ASSIGNED_TO_MUTABLE_NULLABLE]) |
| update(Result, Nullability::NULLABLE); |
| if (Counts[Evidence::ABORT_IF_NULL]) update(Result, Nullability::NONNULL); |
| if (Counts[Evidence::ARITHMETIC]) update(Result, Nullability::NONNULL); |
| if (Result) return *Result; |
| |
| // Optional "soft" inference heuristics. |
| // These do not report conflicts. |
| if (Counts[Evidence::GCC_NONNULL_ATTRIBUTE]) return {Nullability::NONNULL}; |
| if (!Counts[Evidence::NULLABLE_RETURN] && !Counts[Evidence::UNKNOWN_RETURN] && |
| Counts[Evidence::NONNULL_RETURN]) |
| return {Nullability::NONNULL}; |
| if (!Counts[Evidence::NULLABLE_ARGUMENT] && |
| !Counts[Evidence::UNKNOWN_ARGUMENT] && Counts[Evidence::NONNULL_ARGUMENT]) |
| return {Nullability::NONNULL}; |
| if (Counts[Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER]) |
| return {Nullability::NULLABLE}; |
| if (!Counts[Evidence::ASSIGNED_FROM_NULLABLE] && |
| !Counts[Evidence::ASSIGNED_FROM_UNKNOWN] && |
| Counts[Evidence::ASSIGNED_FROM_NONNULL]) |
| return {Nullability::NONNULL}; |
| |
| return {Nullability::UNKNOWN}; |
| } |
| |
| } // namespace clang::tidy::nullability |