blob: 5866b67b54f1f2b2dc94eafe7390ab5605676f44 [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/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