blob: 3a597b82b34d4a3aef3bf4161cc9077eafa6e984 [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
#ifndef CRUBIT_NULLABILITY_INFERENCE_COLLECT_EVIDENCE_H_
#define CRUBIT_NULLABILITY_INFERENCE_COLLECT_EVIDENCE_H_
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "nullability/inference/inference.proto.h"
#include "nullability/inference/slot_fingerprint.h"
#include "nullability/inference/usr_cache.h"
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/pragma.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Analysis/FlowSensitive/Solver.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
namespace clang::tidy::nullability {
/// Describes the direction of flow for a piece of evidence between a virtual
/// method and its overrides.
enum class VirtualMethodEvidenceFlowDirection {
kFromBaseToDerived,
kFromDerivedToBase,
kBoth,
};
/// Returns the direction of flow for a piece of evidence between a virtual
/// method and its overrides.
///
/// The direction is determined by whether the evidence points towards Nonnull
/// or Nullable and is for a return slot or a parameter slot.
VirtualMethodEvidenceFlowDirection getFlowDirection(Evidence::Kind Kind,
bool ForReturnSlot);
using RelatedVirtualMethodsMap = llvm::StringMap<llvm::StringSet<>>;
// Summarizes a method for the purpose of aligning evidence across
// virtual-method overrides.
struct MethodSummary {
int SlotCount;
// USRs of all methods that (transitively) override this method.
llvm::StringSet<> OverridingUSRs;
};
struct VirtualMethodIndex {
VirtualMethodIndex() = default;
// These are expected to often be very large containers, so disallow copying.
VirtualMethodIndex(const VirtualMethodIndex &) = delete;
VirtualMethodIndex &operator=(const VirtualMethodIndex &) = delete;
VirtualMethodIndex(VirtualMethodIndex &&) = default;
VirtualMethodIndex &operator=(VirtualMethodIndex &&) = default;
// Map from virtual methods in a TU to the set of methods that they
// override. Methods are identified by USRs.
RelatedVirtualMethodsMap Bases;
// Map from virtual methods in a TU to the set of methods that override them
// in that TU. Methods are identified by USRs.
llvm::StringMap<MethodSummary> Overrides;
};
/// Index the relationships between virtual methods in the TU.
VirtualMethodIndex getVirtualMethodIndex(ASTContext &Ctx, USRCache &UC);
class SortedFingerprintVector {
public:
SortedFingerprintVector() = default;
// These are expected to often be very large containers, so disallow copying.
SortedFingerprintVector(const SortedFingerprintVector &) = delete;
SortedFingerprintVector &operator=(const SortedFingerprintVector &) = delete;
explicit SortedFingerprintVector(std::vector<SlotFingerprint> &&V)
: Vector(std::move(V)) {
if (!std::is_sorted(Vector.begin(), Vector.end())) {
// Performance is much improved if the incoming vector is already sorted,
// but this is not a requirement.
llvm::errs() << "Previous inferences are not sorted. Performance may be "
"degraded.\n";
std::sort(Vector.begin(), Vector.end());
}
const auto &FirstDuplicate =
std::adjacent_find(Vector.begin(), Vector.end());
if (FirstDuplicate != Vector.end()) {
// Duplicate fingerprints are not expected, and can cause incorrect
// inference results, but only for symbols that have the same fingerprint.
// Do not crash, to avoid invalidating all the other results, but do log
// as much debugging information as possible in case this very unexpected
// event occurs.
llvm::errs() << "Found duplicate fingerprints in previous inferences.\n";
llvm::DenseMap<SlotFingerprint, int> AppearanceCounts;
// Because the vector is sorted, we can count the number of appearances
// starting from FirstDuplicate and know that there will be no duplicates
// of any of the fingerprints from Vector.begin() until FirstDuplicate.
for (auto It = FirstDuplicate; It != Vector.end(); ++It) {
AppearanceCounts[*It]++;
}
for (const auto &[Fingerprint, Count] : AppearanceCounts) {
if (Count > 1) {
llvm::errs() << "Fingerprint " << Fingerprint << " appears " << Count
<< " times.\n";
}
}
// Remove the duplicates before continuing.
Vector.erase(std::unique(Vector.begin(), Vector.end()), Vector.end());
}
}
bool contains(SlotFingerprint Fingerprint) const {
return std::binary_search(Vector.begin(), Vector.end(), Fingerprint);
}
private:
std::vector<SlotFingerprint> Vector;
};
struct PreviousInferences {
const std::shared_ptr<const SortedFingerprintVector> absl_nonnull Nullable =
std::make_shared<const SortedFingerprintVector>();
const std::shared_ptr<const SortedFingerprintVector> absl_nonnull Nonnull =
std::make_shared<const SortedFingerprintVector>();
};
/// Creates a solver with default parameters that is suitable for passing to
/// `collectEvidenceFromDefinition()`.
std::unique_ptr<dataflow::Solver> makeDefaultSolverForInference();
/// Callback used to report collected nullability evidence.
using EvidenceEmitter = void(Evidence);
/// Creates an EvidenceEmitter that handles propagation of evidence for virtual
/// methods. This emitter caches USR generation, and should be reused for the
/// whole AST. All parameters must outlive the returned EvidenceEmitter.
llvm::unique_function<EvidenceEmitter> evidenceEmitterWithPropagation(
llvm::unique_function<EvidenceEmitter> Emit, USRCache &USRCache,
ASTContext &Ctx);
/// Creates an EvidenceEmitter as above, but allows re-use of a
/// VirtualMethodIndex.
llvm::unique_function<EvidenceEmitter> evidenceEmitterWithPropagation(
llvm::unique_function<EvidenceEmitter> Emit, VirtualMethodIndex Index);
/// Analyze code (such as a function body or variable initializer) to infer
/// nullability.
///
/// Produces Evidence constraining the nullability slots of the symbols that
/// the code interacts with, such as the function's own parameters.
/// This is based on the code's behavior and our definition of null-safety.
///
/// It is up to the caller to ensure the definition is eligible for inference
/// (function has a body, is not dependent, etc).
llvm::Error collectEvidenceFromDefinition(
const Decl &, llvm::function_ref<EvidenceEmitter>, USRCache &USRCache,
const NullabilityPragmas &Pragmas,
const PreviousInferences &PreviousInferences = {},
const SolverFactory &MakeSolver = makeDefaultSolverForInference);
/// Gathers evidence of a symbol's nullability from a declaration of it.
///
/// These are trivial "inferences" of what's already written in the code. e.g:
/// void foo(Nullable<int*>);
/// The first parameter of foo must be nullable.
///
/// It is the caller's responsibility to ensure that the symbol is inferable.
void collectEvidenceFromTargetDeclaration(const clang::Decl &,
llvm::function_ref<EvidenceEmitter>,
USRCache &USRCache,
const NullabilityPragmas &Pragmas);
/// Describes locations within an AST that provide evidence for use in
/// inference.
struct EvidenceSites {
/// Declarations of inferable symbols.
llvm::DenseSet<const Decl *absl_nonnull> Declarations;
/// Definitions (e.g. function body, variable initializer) that can be
/// analyzed.
/// This will always be concrete code, not a template pattern. These may be
/// passed to collectEvidenceFromDefinition().
llvm::DenseSet<const Decl *absl_nonnull> Definitions;
/// Find the evidence sites within the provided AST. If
/// RestrictToMainFileOrHeader is true, only looks for evidence sites in the
/// main file or its associated header. Implicit declarations are never
/// considered to be in the main file or header.
static EvidenceSites discover(ASTContext &,
bool RestrictToMainFileOrHeader = false);
using ForEach = llvm::function_ref<void(const Decl &)>;
/// For each evidence site with the provided AST, calls the provided
/// callback(s). A single Decl may be passed to both callbacks if it is also a
/// useful definition. If RestrictToMainFileOrHeader is true, only looks for
/// evidence sites in the main file or its associated header. Implicit
/// declarations are never considered to be in the main file or header.
static void forDefinitionsAndForDeclarations(
ForEach ForDefinitions, ForEach ForDeclarations, ASTContext &Ctx,
bool RestrictToMainFileOrHeader = false);
};
/// Returns the slot number for the I'th parameter (0-based).
inline Slot paramSlot(unsigned I) { return static_cast<Slot>(SLOT_PARAM + I); }
} // namespace clang::tidy::nullability
#endif // CRUBIT_NULLABILITY_INFERENCE_COLLECT_EVIDENCE_H_