blob: 751079ce9b4e9a679addff0815d79187687fe289 [file]
// 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/collect_evidence.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "nullability/inference/inference.proto.h"
#include "nullability/pointer_nullability.h"
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/pointer_nullability_lattice.h"
#include "nullability/type_nullability.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/ControlFlowContext.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Index/USRGeneration.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/FunctionExtras.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/raw_ostream.h"
namespace clang::tidy::nullability {
using ::clang::dataflow::DataflowAnalysisContext;
using ::clang::dataflow::Environment;
llvm::unique_function<EvidenceEmitter> evidenceEmitter(
llvm::unique_function<void(const Evidence &) const> Emit) {
class EvidenceEmitterImpl {
public:
EvidenceEmitterImpl(
llvm::unique_function<void(const Evidence &) const> Emit)
: Emit(std::move(Emit)) {}
void operator()(const Decl &Target, Slot S, NullabilityConstraint C) const {
Evidence E;
S.Swap(E.mutable_slot());
C.Swap(E.mutable_constraint());
auto [It, Inserted] = USRCache.try_emplace(&Target);
if (Inserted) {
llvm::SmallString<128> USR;
if (!index::generateUSRForDecl(&Target, USR)) It->second = USR.str();
}
if (It->second.empty()) return; // Can't emit without a USR
E.mutable_symbol()->set_usr(It->second);
Emit(E);
}
private:
mutable llvm::DenseMap<const Decl *, std::string> USRCache;
llvm::unique_function<void(const Evidence &) const> Emit;
};
return EvidenceEmitterImpl(std::move(Emit));
}
namespace {
void collectEvidenceFromDereference(
std::vector<std::pair<PointerTypeNullability, Slot>> InferrableSlots,
const CFGElement &Element, const PointerNullabilityLattice &Lattice,
const dataflow::Environment &Env,
llvm::function_ref<EvidenceEmitter> Emit) {
// Is this CFGElement a dereference of a pointer?
auto CFGStmt = Element.getAs<clang::CFGStmt>();
if (!CFGStmt) return;
auto *Op = dyn_cast_or_null<UnaryOperator>(CFGStmt->getStmt());
if (!Op || Op->getOpcode() != UO_Deref) return;
auto *DereferencedExpr = Op->getSubExpr();
if (!DereferencedExpr || !DereferencedExpr->getType()->isPointerType())
return;
// It is a dereference of a pointer. Now gather evidence from it.
dataflow::PointerValue *DereferencedValue =
getPointerValueFromExpr(DereferencedExpr, Env);
if (!DereferencedValue) return;
auto &A = Env.getDataflowAnalysisContext().arena();
auto &NotIsNull =
A.makeNot(getPointerNullState(*DereferencedValue).second.formula());
// If the flow conditions already imply the dereferenced value is not null,
// then we don't have any new evidence of a necessary annotation.
if (Env.flowConditionImplies(NotIsNull)) return;
// Otherwise, if an inferrable slot being annotated Nonnull would imply that
// the dereferenced value is not null, then we have evidence suggesting that
// slot should be annotated. For now, we simply choose the first such slot,
// sidestepping complexities around the possibility of multiple such slots,
// any one of which would be sufficient if annotated Nonnull.
for (auto &[Nullability, Slot] : InferrableSlots) {
auto &SlotNonnullImpliesDerefValueNonnull =
A.makeImplies(Nullability.Nonnull->formula(), NotIsNull);
if (Env.flowConditionImplies(SlotNonnullImpliesDerefValueNonnull)) {
NullabilityConstraint Constraint;
Constraint.set_must_be_nonnull(true);
Emit(*Env.getCurrentFunc(), Slot, std::move(Constraint));
}
}
}
void collectEvidenceFromElement(
std::vector<std::pair<PointerTypeNullability, Slot>> InferrableSlots,
const CFGElement &Element, const PointerNullabilityLattice &Lattice,
const Environment &Env, llvm::function_ref<EvidenceEmitter> Emit) {
collectEvidenceFromDereference(InferrableSlots, Element, Lattice, Env, Emit);
// TODO: add more heuristic collections here
}
std::optional<NullabilityConstraint> constraintFromDeclaredType(QualType T) {
if (!T.getNonReferenceType()->isPointerType()) return std::nullopt;
auto Nullability = getNullabilityAnnotationsFromType(T);
switch (Nullability.front()) {
default:
return std::nullopt;
case NullabilityKind::NonNull: {
NullabilityConstraint C;
C.set_must_be_nonnull(true);
return C;
}
case NullabilityKind::Nullable: {
NullabilityConstraint C;
C.set_must_be_nullable(true);
return C;
}
}
}
} // namespace
llvm::Error collectEvidenceFromImplementation(
const Decl &Decl, llvm::function_ref<EvidenceEmitter> Emit) {
const FunctionDecl *Func = dyn_cast<FunctionDecl>(&Decl);
if (!Func || !Func->doesThisDeclarationHaveABody()) {
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
"Implementation must be a function with a body.");
}
llvm::Expected<dataflow::ControlFlowContext> ControlFlowContext =
dataflow::ControlFlowContext::build(*Func);
if (!ControlFlowContext) return ControlFlowContext.takeError();
DataflowAnalysisContext AnalysisContext(
std::make_unique<dataflow::WatchedLiteralsSolver>());
Environment Environment(AnalysisContext, *Func);
PointerNullabilityAnalysis Analysis(
Decl.getDeclContext()->getParentASTContext());
std::vector<std::pair<PointerTypeNullability, Slot>> InferrableSlots;
auto Parameters = Func->parameters();
for (auto i = 0; i < Parameters.size(); ++i) {
if (Parameters[i]->getType().getNonReferenceType()->isPointerType()) {
// TODO: Skip assigning variables for already-annotated parameters,
// potentially configurably.
Slot slot;
slot.set_parameter(i);
InferrableSlots.push_back(
std::make_pair(Analysis.assignNullabilityVariable(
Parameters[i], AnalysisContext.arena()),
std::move(slot)));
}
}
std::vector<Evidence> AllEvidence;
llvm::Expected<std::vector<std::optional<
dataflow::DataflowAnalysisState<PointerNullabilityLattice>>>>
BlockToOutputStateOrError = dataflow::runDataflowAnalysis(
*ControlFlowContext, Analysis, Environment,
[&](const CFGElement &Element,
const dataflow::DataflowAnalysisState<PointerNullabilityLattice>
&State) {
collectEvidenceFromElement(InferrableSlots, Element, State.Lattice,
State.Env, Emit);
});
return llvm::Error::success();
}
void collectEvidenceFromTargetDeclaration(
const clang::Decl &D, llvm::function_ref<EvidenceEmitter> Emit) {
// For now, we can only describe the nullability of functions.
const auto *Fn = dyn_cast<clang::FunctionDecl>(&D);
if (!Fn) return;
if (auto C = constraintFromDeclaredType(Fn->getReturnType())) {
Slot S;
S.set_return_type(true);
Emit(*Fn, std::move(S), std::move(*C));
}
for (unsigned I = 0; I < Fn->param_size(); ++I) {
if (auto C = constraintFromDeclaredType(Fn->getParamDecl(I)->getType())) {
Slot S;
S.set_parameter(I);
Emit(*Fn, std::move(S), std::move(*C));
}
}
}
bool isInferenceTarget(const FunctionDecl &FD) {
// Inferring properties of template instantiations isn't useful in itself.
// We can't record them anywhere unless they apply to the template in general.
// TODO: work out in what circumstances that would be safe.
return !FD.getTemplateInstantiationPattern();
}
EvidenceSites EvidenceSites::discover(ASTContext &Ctx) {
struct Walker : public RecursiveASTVisitor<Walker> {
EvidenceSites Out;
// We do want to see concrete code, including function instantiations.
bool shouldVisitTemplateInstantiations() const { return true; }
bool VisitFunctionDecl(const FunctionDecl *FD) {
if (isInferenceTarget(*FD)) Out.Declarations.push_back(FD);
// Visiting template instantiations is fine, these are valid functions!
// But we'll be limited in what we can infer.
bool IsUsefulImplementation =
FD->doesThisDeclarationHaveABody() &&
// We will not get anywhere with dependent code.
!FD->isDependentContext();
if (IsUsefulImplementation) Out.Implementations.push_back(FD);
return true;
}
};
Walker W;
W.TraverseAST(Ctx);
return std::move(W.Out);
}
} // namespace clang::tidy::nullability