blob: 001fe4b4483b2961aad85307cf37b0d381f21e1c [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/pointer_nullability_diagnosis.h"
#include <cassert>
#include <cstdint>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/base/nullability.h"
#include "nullability/forwarding_functions.h"
#include "nullability/pointer_nullability.h"
#include "nullability/pointer_nullability_analysis.h"
#include "nullability/pointer_nullability_lattice.h"
#include "nullability/pointer_nullability_matchers.h"
#include "nullability/pragma.h"
#include "nullability/type_nullability.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTTypeTraits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/LambdaCapture.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/TemplateBase.h"
#include "clang/AST/TypeBase.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/ASTOps.h"
#include "clang/Analysis/FlowSensitive/AdornedCFG.h"
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/Solver.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
#include "clang/Basic/AttrKinds.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Lambda.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Tooling/Transformer/SourceCode.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
#define DEBUG_TYPE "nullability-diagnostic"
namespace clang::tidy::nullability {
using ast_matchers::anyOf;
using ast_matchers::binaryOperator;
using ast_matchers::BoundNodes;
using ast_matchers::callExpr;
using ast_matchers::cxxConstructExpr;
using ast_matchers::cxxMemberCallExpr;
using ast_matchers::cxxOperatorCallExpr;
using ast_matchers::declStmt;
using ast_matchers::expr;
using ast_matchers::findAll;
using ast_matchers::hasArgument;
using ast_matchers::hasLHS;
using ast_matchers::hasOperands;
using ast_matchers::hasOperatorName;
using ast_matchers::hasType;
using ast_matchers::initListExpr;
using ast_matchers::isInteger;
using ast_matchers::match;
using ast_matchers::MatchFinder;
using ast_matchers::onImplicitObjectArgument;
using ast_matchers::unaryOperator;
using ast_matchers::unless;
using dataflow::CFGMatchSwitchBuilder;
using dataflow::DataflowAnalysisContext;
using dataflow::Environment;
using dataflow::FieldSet;
using dataflow::PointerValue;
using dataflow::RecordInitListHelper;
using dataflow::RecordStorageLocation;
using dataflow::StorageLocation;
using ::llvm::SmallVector;
namespace {
using DiagTransferState =
dataflow::TransferStateForDiagnostics<PointerNullabilityLattice>;
using DiagTransferFunc =
dataflow::CFGMatchSwitch<const DiagTransferState,
SmallVector<PointerNullabilityDiagnostic>>;
} // namespace
static CharSourceRange getRangeModuloMacros(CharSourceRange Range,
const ASTContext& Ctx) {
if (auto RangeOpt =
tooling::getFileRange(Range, Ctx, /*IncludeMacroExpansion=*/true))
return RangeOpt.value();
return Range;
}
static SmallVector<PointerNullabilityDiagnostic> untrackedError(
const Expr* E, const ASTContext& Ctx,
PointerNullabilityDiagnostic::Context DiagCtx =
PointerNullabilityDiagnostic::Context::Other) {
return {{
.Code = PointerNullabilityDiagnostic::ErrorCode::Untracked,
.Ctx = DiagCtx,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(E->getSourceRange()), Ctx),
}};
}
constexpr llvm::StringLiteral kNonConstMethodCallID("non-const-method-call");
// Matches a call (member call or operator call) to a non-const method.
AST_MATCHER_FUNCTION(clang::ast_matchers::StatementMatcher,
nonConstMethodCall) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names.
return callExpr(
callee(cxxMethodDecl(unless(isConst())).bind(kNonConstMethodCallID)));
}
// If `Pointer` is a pointer-producing expression that violates the expectation
// that it is nonnull, check if `Pointer` contains:
// - a call to a non-const method or operator.
// - if so, is it in `ParentFunction` that contains another call to the same
// non-const method or operator in a null check?
// Returns the null check expression if found or nullptr otherwise.
static const Expr* absl_nullable matchesNonConstCallNullCheck(
const Expr& Pointer, ASTContext& Ctx,
const FunctionDecl* absl_nullable ParentFunction) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names.
if (ParentFunction == nullptr) return nullptr;
// Check if the pointer is produced by a non-const method call.
const CXXMethodDecl* NonConstMethodAsPointerSrc =
ast_matchers::selectFirst<const CXXMethodDecl>(
kNonConstMethodCallID,
match(nonConstMethodCall(),
DynTypedNode::create(*Pointer.IgnoreParenImpCasts()), Ctx));
if (NonConstMethodAsPointerSrc == nullptr) return nullptr;
// Next, check if there is a null check on a call to the same method.
// NOTE: we don't check that this is executed before the Pointer expression,
// or sufficient to show non-nullness, but the approximation may still be
// useful.
auto CallsMatchedMethod =
callExpr(callee(equalsNode(NonConstMethodAsPointerSrc)));
auto NullCheckOnNonConstCall = functionDecl(hasBody(hasDescendant(
expr(anyOf(binaryOperator(
anyOf(hasOperatorName("=="), hasOperatorName("!=")),
hasOperands(CallsMatchedMethod,
implicitCastExpr(hasSourceExpression(
cxxNullPtrLiteralExpr())))),
implicitCastExpr(hasCastKind(CK_PointerToBoolean),
hasSourceExpression(CallsMatchedMethod))))
.bind("non-const-null-check"))));
return ast_matchers::selectFirst<const Expr>(
"non-const-null-check",
ast_matchers::match(NullCheckOnNonConstCall,
DynTypedNode::create(*ParentFunction), Ctx));
}
// Determines if the pointer expression appears inside a lambda via a lambda
// capture. Handles cases like:
// - `*p` and `p->x`, where `p` is a captured pointer variable
// - `*o.p`, where `o` is a captured object with a pointer member `p`
// Currently does not handle the cases:
// - `*this->p` and `*p`, where `p` is a pointer member of the enclosing class
// and `this` is captured in the lambda.
static bool isCapturedVariableOrMemberAccess(const Expr* absl_nonnull E) {
E = E->IgnoreParenImpCasts();
// Cases like `*p` and `p->x`, where `p` is captured in the lambda.
if (const DeclRefExpr* Variable = dyn_cast<DeclRefExpr>(E))
return Variable->refersToEnclosingVariableOrCapture();
// Cases like `*o.p`, where `o` is an object captured in the lambda.
if (const MemberExpr* MemberAccess = dyn_cast<MemberExpr>(E)) {
const Expr* Base = MemberAccess->getBase();
if (const DeclRefExpr* BaseVariable = dyn_cast<DeclRefExpr>(Base))
return BaseVariable->refersToEnclosingVariableOrCapture();
}
return false;
}
// Diagnoses whether `E` violates the expectation that it is nonnull.
static SmallVector<PointerNullabilityDiagnostic> diagnoseNonnullExpected(
const Expr* absl_nonnull E, const DiagTransferState& State, ASTContext& Ctx,
PointerNullabilityDiagnostic::Context DiagCtx,
const clang::NamedDecl* absl_nullable Callee = nullptr,
const clang::IdentifierInfo* absl_nullable ParamName = nullptr,
CharSourceRange Range = {}) {
std::optional<bool> IsNullable;
if (PointerValue* ActualVal = getPointerValue(E, State.Env))
IsNullable = isNullable(*ActualVal, State.Env);
else if (E->getType()->isNullPtrType())
IsNullable = isReachableNullptrLiteral(State.Env);
if (!IsNullable.has_value()) {
LLVM_DEBUG({
llvm::dbgs()
<< "The dataflow analysis framework does not model a PointerValue "
"for the following Expr, and thus its dereference is marked as "
"unsafe:\n";
E->dump();
});
return untrackedError(E, Ctx, DiagCtx);
}
if (!*IsNullable) return {};
if (Range.isInvalid())
Range = CharSourceRange::getTokenRange(E->getSourceRange());
if (const Expr* NullCheck =
matchesNonConstCallNullCheck(*E, Ctx, State.Env.getCurrentFunc()))
return {{
.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = DiagCtx,
.Range = getRangeModuloMacros(Range, Ctx),
.Callee = Callee,
.ParamName = ParamName,
.NoteRange = getRangeModuloMacros(
CharSourceRange::getTokenRange(NullCheck->getSourceRange()), Ctx),
.NoteMessage =
"the nullable pointer comes from a non-const method call, and "
"there is a null check on a similar call here. To show stability, "
"capture the value to a local variable, then null-check and use "
"that. Or, mark the method as const (if possible, and if it has "
"zero params).",
}};
Range = getRangeModuloMacros(Range, Ctx);
if (isCapturedVariableOrMemberAccess(E))
return {
{.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = DiagCtx,
.Range = Range,
.Callee = Callee,
.ParamName = ParamName,
.NoteRange = Range,
.NoteMessage =
"This pointer is captured and dereferenced in a lambda. If it is "
"null-checked outside the lambda, consider capturing it by "
"value, or capturing the pointee instead (possibly with an "
"init-capture). Otherwise, do a null check inside the lambda "
"body to ensure null safety."}};
PointerNullabilityDiagnostic Diag{
.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = DiagCtx,
.Range = Range,
.Callee = Callee,
.ParamName = ParamName,
};
// If a nonnull pointer field is dereferenced in a destructor where it is
// modeled as nullable (because the enclosing object may have been moved from
// before destruction), explain why and how to fix it.
if (const FieldDecl* Field =
fieldTreatedAsNullableAtDestructorEntry(*E, State.Lattice)) {
Diag.NoteRange = CharSourceRange::getTokenRange(Field->getSourceRange());
Diag.NoteMessage =
"a Nonnull pointer field may be null at destructor entry (possibly "
"because it was moved from); null-check it before dereferencing, or "
"make the type non-movable if it should never be moved from";
}
return {Diag};
}
static bool invariantMatch(ArrayRef<PointerTypeNullability> A,
ArrayRef<PointerTypeNullability> B) {
if (A.size() != B.size()) {
llvm::dbgs() << "Nullability vectors should be the same size "
"when expected to be invariant, but they were not: "
<< nullabilityToString(A) << " vs. " << nullabilityToString(B)
<< "\n";
// Do not produce a diagnostic for these cases.
return true;
}
for (int I = 0; I < A.size(); ++I) {
NullabilityKind AN = A[I].concrete();
NullabilityKind BN = B[I].concrete();
if (AN != BN && AN != NullabilityKind::Unspecified &&
BN != NullabilityKind::Unspecified) {
return false;
}
}
return true;
}
// Diagnoses a conceptual assignment of LHS = RHS.
// LHS can be a variable, the return value of a function, a param etc.
static SmallVector<PointerNullabilityDiagnostic> diagnoseAssignmentLike(
QualType LHSType, ArrayRef<PointerTypeNullability> LHSNullability,
const Expr* absl_nonnull RHS, const DiagTransferState& State,
ASTContext& Ctx, PointerNullabilityDiagnostic::Context DiagCtx,
const clang::NamedDecl* absl_nullable Callee = nullptr,
const clang::IdentifierInfo* absl_nullable ParamName = nullptr,
CharSourceRange LHSRange = {}) {
// Nullability vectors start with any raw or smart pointer types that are
// not in template arguments and then continue further into (potentially
// nested) template arguments. Pointers outside template arguments have
// well-defined variance relationships that we can check for each slot,
// accumulating diagnostics along the way. Once we reach the first template
// argument slot, we require invariant nullability from that point on. The
// true relationship needed for safety may be different for different
// templates, but it is currently infeasible for us to determine the true
// relationship.
SmallVector<PointerNullabilityDiagnostic> Diagnostics;
// If LHS is a supported pointer type or a reference to such, then the
// outermost pointer slot has a covariant requirement for the value's
// nullability, similar to subtype relationships, i.e. a Nullable LHS type can
// accept Nullable or Nonnull values, but a Nonnull LHS type can accept only
// Nonnull values.
if (isSupportedPointerType(LHSType.getNonReferenceType())) {
QualType RHSType = RHS->getType().getNonReferenceType();
if (!RHSType->isNullPtrType() && !isSupportedPointerType(RHSType)) {
LLVM_DEBUG({
llvm::dbgs() << "LHS is a pointer, but RHS is not.\n LHS type: ";
LHSType->dump(llvm::dbgs(), Ctx);
llvm::dbgs() << "RHSType: ";
RHSType->dump(llvm::dbgs(), Ctx);
});
return untrackedError(RHS, Ctx, DiagCtx);
}
if (LHSNullability.front().concrete() == NullabilityKind::NonNull) {
Diagnostics = diagnoseNonnullExpected(RHS, State, Ctx, DiagCtx, Callee,
ParamName, LHSRange);
}
// Continue unwrapping pointer layers outside of template arguments. Each of
// these pointer layers is invariant if mutable, and covariant if const.
// Again, we are comparing the nullability layers of the LHS type with the
// nullability layers of the RHS value.
// TODO: b/343960612 - implement this unwrapping and checking, including for
// smart pointers, which are not as trivially unwrappable. For now, return
// early, except for mutable l-value reference types, which fall through to
// the type-nullability check below. Once additional pointer layers are
// unwrapped, we can fall through to checking function pointer types and
// then invariant nullability for any template argument pointers, but will
// need more complex control flow for mutable l-value reference types.
if (!LHSType->isLValueReferenceType() ||
LHSType.getNonReferenceType().isConstQualified())
return Diagnostics;
// If the last pointer layer is a function pointer, we need to recurse into
// the function pointer with different relationship requirements.
// e.g. given the following
// ```cc
// int* _Nonnull (*_Nullable p)(bool* _Nullable b);
// int* _Nullable (*_Nullable q)(bool* _Nonnull b);
// ```
// it would safe to assign `q = p;` but not safe to assign `p = q;`.
//
// The return types have the same covariant requirement for const pointers
// as outside of function pointer types, but the parameter types have a
// contravariant requirement for const pointers. Mutable pointers remain
// invariant. Within the function pointer type, we are comparing the
// nullabilities of the types on the LHS and RHS, since there are no values
// on the RHS in this context.
// TODO: b/343960612 - implement recursion into function pointer return and
// parameter types.
}
// Now we have reached layers that require invariant type nullability, either
// references to mutable pointers or pointers contained in template arguments.
const TypeNullability* RHSNullability = State.Lattice.getTypeNullability(RHS);
if (!RHSNullability) {
// The LHS might not have pointer layers, in which case it is expected
// for the RHS to be missing nullability in some cases.
if (LHSNullability.empty()) return {};
return untrackedError(RHS, Ctx, DiagCtx);
}
if (LHSRange.isInvalid())
LHSRange = CharSourceRange::getTokenRange(RHS->getSourceRange());
if (!invariantMatch(LHSNullability, *RHSNullability)) {
Diagnostics.push_back({
.Code =
PointerNullabilityDiagnostic::ErrorCode::ExpectedEqualNullability,
.Ctx = DiagCtx,
.Range = getRangeModuloMacros(LHSRange, Ctx),
.Callee = Callee,
.ParamName = ParamName,
});
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseDereference(
const UnaryOperator* absl_nonnull UnaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseNonnullExpected(
UnaryOp->getSubExpr(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::NullableDereference);
}
static SmallVector<PointerNullabilityDiagnostic>
diagnoseSmartPointerDereference(const CXXOperatorCallExpr* absl_nonnull Op,
const MatchFinder::MatchResult& Result,
const DiagTransferState& State) {
return diagnoseNonnullExpected(
Op->getArg(0), State, *Result.Context,
PointerNullabilityDiagnostic::Context::NullableDereference);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseSubscript(
const ArraySubscriptExpr* absl_nonnull Subscript,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseNonnullExpected(
Subscript->getBase(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::NullableDereference);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseArrow(
const MemberExpr* absl_nonnull MemberExpr,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseNonnullExpected(
MemberExpr->getBase(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::NullableDereference,
/*Callee=*/nullptr, /*ParamName=*/nullptr);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseAssignment(
const BinaryOperator* absl_nonnull Op,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
const TypeNullability* LHSNullability =
State.Lattice.getTypeNullability(Op->getLHS());
if (!LHSNullability) return {};
return diagnoseAssignmentLike(
Op->getLHS()->getType(), *LHSNullability, Op->getRHS(), State,
*Result.Context, PointerNullabilityDiagnostic::Context::Assignment);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseSmartPointerAssignment(
const CXXOperatorCallExpr* absl_nonnull Op,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
const TypeNullability* LHSNullability =
State.Lattice.getTypeNullability(Op->getArg(0));
if (!LHSNullability) return {};
return diagnoseAssignmentLike(
Op->getArg(0)->getType(), *LHSNullability, Op->getArg(1), State,
*Result.Context, PointerNullabilityDiagnostic::Context::Assignment);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseSmartPointerReset(
const CXXMemberCallExpr* absl_nonnull MCE,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
const TypeNullability* ObjArgNullability =
State.Lattice.getTypeNullability(MCE->getImplicitObjectArgument());
if (!ObjArgNullability) return {};
ArrayRef<PointerTypeNullability> ReceiverNullability = *ObjArgNullability;
if (MCE->getImplicitObjectArgument()->getType()->isPointerType())
ReceiverNullability = ReceiverNullability.drop_front();
if (MCE->getNumArgs() == 0 ||
(MCE->getNumArgs() == 1 && MCE->getArg(0)->getType()->isNullPtrType()) ||
(MCE->getNumArgs() == 1 && MCE->getArg(0)->isDefaultArgument())) {
if (ReceiverNullability.front().concrete() == NullabilityKind::NonNull)
return {{
.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = PointerNullabilityDiagnostic::Context::Assignment,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(MCE->getSourceRange()),
*Result.Context),
}};
return {};
}
return diagnoseAssignmentLike(
MCE->getObjectType(), ReceiverNullability, MCE->getArg(0), State,
*Result.Context, PointerNullabilityDiagnostic::Context::Assignment);
}
// Diagnoses whether any of the arguments are incompatible with the
// corresponding type in the function prototype.
// ParmDecls is best-effort and used only for param names in diagnostics.
static SmallVector<PointerNullabilityDiagnostic> diagnoseArgumentCompatibility(
const FunctionProtoType& CalleeFPT,
ArrayRef<PointerTypeNullability> ParamsNullability,
ArrayRef<const ParmVarDecl*> ParmDecls, ArrayRef<const Expr*> Args,
const clang::NamedDecl* absl_nullable Callee,
const DiagTransferState& State, ASTContext& Ctx) {
auto ParamTypes = CalleeFPT.getParamTypes();
// C-style varargs cannot be annotated and therefore are unchecked.
if (CalleeFPT.isVariadic()) {
if (Args.size() < ParamTypes.size())
llvm::reportFatalInternalError(
"Callee prototype is variadic, but we see fewer args than params.");
Args = Args.take_front(ParamTypes.size());
}
if (ParamTypes.size() != Args.size())
llvm::reportFatalInternalError(
"Mismatch between number of params and args");
SmallVector<PointerNullabilityDiagnostic> Diagnostics;
for (unsigned int I = 0; I < Args.size(); ++I) {
unsigned Len = countPointersInType(ParamTypes[I]);
auto ParamNullability = ParamsNullability.take_front(Len);
ParamsNullability = ParamsNullability.drop_front(Len);
const clang::IdentifierInfo* ParamName =
(I < ParmDecls.size()) ? ParmDecls[I]->getIdentifier() : nullptr;
Diagnostics.append(diagnoseAssignmentLike(
ParamTypes[I], ParamNullability, Args[I], State, Ctx,
PointerNullabilityDiagnostic::Context::FunctionArgument, Callee,
ParamName));
}
return Diagnostics;
}
static NullabilityKind parseNullabilityKind(StringRef EnumName) {
return llvm::StringSwitch<NullabilityKind>(EnumName)
.Case("NK_nonnull", NullabilityKind::NonNull)
.Case("NK_nullable", NullabilityKind::Nullable)
.Case("NK_unspecified", NullabilityKind::Unspecified)
.Default(NullabilityKind::Unspecified);
}
/// Evaluates the `__assert_nullability` call by comparing the expected
/// nullability to the nullability computed by the dataflow analysis.
///
/// If the function being diagnosed is called `__assert_nullability`, we assume
/// it is a call of the shape __assert_nullability<a, b, c, ...>(p), where `p`
/// is an expression that contains pointers and a, b, c ... represent each of
/// the NullabilityKinds in `p`'s expected nullability. An expression's
/// nullability can be expressed as a vector of NullabilityKinds, where each
/// vector element corresponds to one of the pointers contained in the
/// expression.
///
/// For example:
/// \code
/// enum NullabilityKind {
/// NK_nonnull,
/// NK_nullable,
/// NK_unspecified,
/// };
///
/// template<NullabilityKind ...NK, typename T>
/// void __assert_nullability(T&);
///
/// template<typename T0, typename T1>
/// struct Struct2Arg {
/// T0 arg0;
/// T1 arg1;
/// };
///
/// void target(Struct2Arg<int *, int * _Nullable> p) {
/// __assert_nullability<NK_unspecified, NK_nullable>(p);
/// }
/// \endcode
static SmallVector<PointerNullabilityDiagnostic> diagnoseAssertNullabilityCall(
const CallExpr* absl_nonnull CE, const DiagTransferState& State,
ASTContext& Ctx) {
auto* DRE = cast<DeclRefExpr>(CE->getCallee()->IgnoreImpCasts());
// Extract the expected nullability from the template parameter pack.
TypeNullability Expected;
for (auto P : DRE->template_arguments()) {
if (P.getArgument().getKind() == TemplateArgument::Expression) {
if (auto* EnumDRE = dyn_cast<DeclRefExpr>(P.getSourceExpression())) {
Expected.push_back(parseNullabilityKind(EnumDRE->getDecl()->getName()));
}
}
}
// Compare the nullability computed by nullability analysis with the
// expected one.
const Expr* GivenExpr = CE->getArg(0);
const TypeNullability* MaybeComputed =
State.Lattice.getTypeNullability(GivenExpr);
if (MaybeComputed == nullptr) return untrackedError(CE, Ctx);
if (*MaybeComputed == Expected) return {};
LLVM_DEBUG({
// The computed and expected nullabilities differ. Print both to aid
// debugging.
llvm::dbgs() << "__assert_nullability failed at location: ";
CE->getExprLoc().print(llvm::dbgs(), Ctx.getSourceManager());
llvm::dbgs() << "\nExpression:\n";
GivenExpr->dump();
llvm::dbgs() << "Expected nullability: ";
llvm::dbgs() << nullabilityToString(Expected) << "\n";
llvm::dbgs() << "Computed nullability: ";
llvm::dbgs() << nullabilityToString(*MaybeComputed) << "\n";
});
return {{
.Code = PointerNullabilityDiagnostic::ErrorCode::AssertFailed,
.Ctx = PointerNullabilityDiagnostic::Context::Other,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(CE->getSourceRange()), Ctx),
}};
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseIncrementDecrement(
const UnaryOperator* absl_nonnull UnaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseNonnullExpected(UnaryOp->getSubExpr(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::Other);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseAddSubtract(
Expr* PtrExpr, Expr* IntExpr, const DiagTransferState& State,
ASTContext& Ctx) {
// Adding or subtracting zero is allowed even if the pointer is null.
if (auto* Lit = dyn_cast<IntegerLiteral>(IntExpr->IgnoreParenImpCasts())) {
if (Lit->getValue().isZero()) return {};
}
return diagnoseNonnullExpected(PtrExpr, State, Ctx,
PointerNullabilityDiagnostic::Context::Other);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseAddSubtractAssign(
const BinaryOperator* absl_nonnull BinaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseAddSubtract(BinaryOp->getLHS(), BinaryOp->getRHS(), State,
*Result.Context);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseAddSubtractInteger(
const BinaryOperator* absl_nonnull BinaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
if (BinaryOp->getLHS()->getType()->isIntegerType()) {
return diagnoseAddSubtract(BinaryOp->getRHS(), BinaryOp->getLHS(), State,
*Result.Context);
}
return diagnoseAddSubtract(BinaryOp->getLHS(), BinaryOp->getRHS(), State,
*Result.Context);
}
static SmallVector<PointerNullabilityDiagnostic> diagnosePointerDifference(
const BinaryOperator* absl_nonnull BinaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
SmallVector<PointerNullabilityDiagnostic> Diagnostics =
diagnoseNonnullExpected(BinaryOp->getLHS(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::Other);
Diagnostics.append(
diagnoseNonnullExpected(BinaryOp->getRHS(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::Other));
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseConstructorCall(
ArrayRef<const Expr*> ConstructorArgs, const CXXConstructorDecl* CtorDecl,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
auto* CalleeFPT = CtorDecl->getType()->getAs<FunctionProtoType>();
if (!CalleeFPT) return {};
// ctor's type is void(Args), so its nullability == arg nullability.
auto CtorNullability =
getTypeNullability(*CtorDecl, State.Lattice.defaults());
return diagnoseArgumentCompatibility(
*CalleeFPT, CtorNullability, CtorDecl->getAsFunction()->parameters(),
ConstructorArgs, CtorDecl, State, *Result.Context);
}
static void checkRecordFieldsForUninitializedNonnull(
const CXXRecordDecl* absl_nonnull RD,
const TypeNullabilityDefaults& Defaults, ASTContext& Ctx,
PointerNullabilityDiagnostic::ErrorCode Code,
PointerNullabilityDiagnostic::Context DiagCtx, CharSourceRange Range,
SmallVectorImpl<PointerNullabilityDiagnostic>& Diags) {
for (const FieldDecl* Field : RD->fields()) {
if (!isSupportedPointerType(Field->getType())) {
continue;
}
TypeNullability FieldNullability = getTypeNullability(*Field, Defaults);
if (FieldNullability.empty() ||
FieldNullability.front().concrete() != NullabilityKind::NonNull) {
continue;
}
// Check if the field has a non-nullable in-class initializer.
if (const Expr* Init = Field->getInClassInitializer()) {
if (!Init->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) {
// Statically check if the initializer expression is explicitly
// annotated as nullable. This is needed for defaulted constructors
// where we don't run dataflow analysis.
FileID FID = Ctx.getSourceManager().getFileID(Init->getExprLoc());
TypeNullability InitNullability =
getTypeNullability(Init->getType(), FID, Defaults);
if (InitNullability.empty() ||
InitNullability.front().concrete() != NullabilityKind::Nullable) {
continue;
}
}
}
Diags.push_back({
.Code = Code,
.Ctx = DiagCtx,
.Range = Range,
.NoteRange = CharSourceRange::getTokenRange(Field->getSourceRange()),
.NoteMessage = "pointer field is declared here",
});
}
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseConstructExpr(
const CXXConstructExpr* absl_nonnull CE,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
const CXXConstructorDecl* CtorDecl = CE->getConstructor();
ArrayRef<const Expr*> ConstructorArgs(CE->getArgs(), CE->getNumArgs());
SmallVector<PointerNullabilityDiagnostic> Diagnostics =
diagnoseConstructorCall(ConstructorArgs, CtorDecl, Result, State);
// Diagnose uninitialized or null-initialized nonnull pointer members during
// default construction of aggregate classes.
if (CtorDecl->isDefaultConstructor() && !CE->isListInitialization()) {
const CXXRecordDecl* RD = CtorDecl->getParent();
if (!RD->hasUserProvidedDefaultConstructor()) {
checkRecordFieldsForUninitializedNonnull(
RD, State.Lattice.defaults(), *Result.Context,
PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
PointerNullabilityDiagnostic::Context::Initializer,
CharSourceRange::getTokenRange(CE->getSourceRange()), Diagnostics);
}
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic>
diagnoseMakeUniqueConstructExpr(
const CallExpr* absl_nonnull MakeUniqueCall,
const CXXConstructExpr* absl_nonnull CEInMakeUnique,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
CXXConstructorDecl* CtorDecl = CEInMakeUnique->getConstructor();
// Use the arguments from the `MakeUniqueCall`, which has the
// call-site nullability information from the `State`, instead of the
// arguments from `CEInMakeUnique`.
ArrayRef<const Expr*> ConstructorArgs(MakeUniqueCall->getArgs(),
MakeUniqueCall->getNumArgs());
SmallVector<const Expr*> CopyOfArgs;
if (CEInMakeUnique->getNumArgs() > MakeUniqueCall->getNumArgs()) {
// Perhaps there are default arguments. Append them to the end of the
// argument list, so that we get the same number of arguments as a
// real constructor call.
CopyOfArgs.insert(CopyOfArgs.end(), ConstructorArgs.begin(),
ConstructorArgs.end());
for (unsigned I = MakeUniqueCall->getNumArgs();
I < CEInMakeUnique->getNumArgs(); ++I) {
if (!CEInMakeUnique->getArg(I)->isDefaultArgument())
llvm::reportFatalInternalError(
"ConstructExpr's extra arguments (vs make_unique) are not default "
"arguments");
CopyOfArgs.push_back(CEInMakeUnique->getArg(I));
}
ConstructorArgs = CopyOfArgs;
}
return diagnoseConstructorCall(ConstructorArgs, CtorDecl, Result, State);
}
static SmallVector<PointerNullabilityDiagnostic>
diagnoseMakeUniqueParenInitListExpr(
const CallExpr* absl_nonnull MakeUniqueCall,
const CXXParenListInitExpr* absl_nonnull InitListInMakeUnique,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
if (!InitListInMakeUnique->getType()->isRecordType()) return {};
RecordInitListHelper InitListHelper(InitListInMakeUnique);
SmallVector<PointerNullabilityDiagnostic> Diagnostics;
// Skip through the base inits to get to the field inits. Any initialization
// of base classes/fields will be collected from the InitListExpr for the
// base initialization.
int I = InitListHelper.base_inits().size();
// Use the arguments from the `MakeUniqueCall`, which has the
// call-site nullability information from the `State`, instead of the
// `field_inits` from `InitListInMakeUnique`.
int NumArgs = MakeUniqueCall->getNumArgs();
for (auto [Field, Init] : InitListHelper.field_inits()) {
// The make_unique call can have fewer arguments than fields in the struct.
// The rest should be various kinds of default initializers.
const Expr* Arg;
if (I < NumArgs) {
Arg = MakeUniqueCall->getArg(I);
} else {
assert(isa<ImplicitValueInitExpr>(Init) || isa<CXXConstructExpr>(Init) ||
isa<CXXDefaultInitExpr>(Init));
Arg = Init;
}
Diagnostics.append(diagnoseAssignmentLike(
Field->getType(), getTypeNullability(*Field, State.Lattice.defaults()),
Arg, State, *Result.Context,
PointerNullabilityDiagnostic::Context::Initializer));
++I;
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseCallExpr(
const CallExpr* absl_nonnull CE, const MatchFinder::MatchResult& Result,
const DiagTransferState& State) {
// Handle some special cases first.
if (auto* FD = CE->getDirectCallee()) {
// __assert_nullability is a special-case (for testing)
if (FD->getDeclName().isIdentifier() &&
FD->getName() == "__assert_nullability") {
return diagnoseAssertNullabilityCall(CE, State, *Result.Context);
}
// std::make_unique is a special-case: we want to verify the underlying
// constructor call or initializer list.
if (const Expr* Initializer = getUnderlyingInitExprInStdMakeUnique(*FD)) {
if (const auto* ConstructExpr = dyn_cast<CXXConstructExpr>(Initializer)) {
return diagnoseMakeUniqueConstructExpr(CE, ConstructExpr, Result,
State);
}
if (const auto* ParenInitList =
dyn_cast<CXXParenListInitExpr>(Initializer)) {
return diagnoseMakeUniqueParenInitListExpr(CE, ParenInitList, Result,
State);
}
if (const auto* ImpCast = dyn_cast<ImplicitCastExpr>(Initializer);
ImpCast && ImpCast->getCastKind() == CK_UserDefinedConversion) {
// Just a user-defined-conversion operator with no arguments being
// forwarded. No need to log a warning of the unhandled case. Fall
// through to the generic diagnosis of the call expression.
} else {
llvm::errs()
<< "Nullability: Unexpected initializer expression in make_unique: "
<< Initializer->getStmtClassName() << " in "
<< CE->getBeginLoc().printToString(
Result.Context->getSourceManager())
<< "\n";
assert(false);
}
}
}
const Expr* Callee = CE->getCallee();
auto* CalleeNullabilityPtr =
State.Lattice.getTypeNullability(CE->getCallee());
if (!CalleeNullabilityPtr) return {};
const FunctionProtoType* CalleeType;
ArrayRef CalleeNullability = *CalleeNullabilityPtr; // Matches CalleeType.
// Callee is typically a function pointer (not for members or builtins).
// Check it for null, and unwrap the pointer for the next step.
if (Callee->getType()->isPointerType()) {
auto D =
diagnoseNonnullExpected(Callee, State, *Result.Context,
PointerNullabilityDiagnostic::Context::Other);
// TODO: should we continue to diagnose arguments?
if (!D.empty()) return D;
CalleeNullability = CalleeNullability.drop_front();
CalleeType =
Callee->getType()->getPointeeType()->getAs<FunctionProtoType>();
} else {
QualType ET = exprType(Callee);
// pseudo-destructor exprs are callees with null types :-(
CalleeType = ET.isNull() ? nullptr : ET->getAs<FunctionProtoType>();
}
if (!CalleeType) return {};
// We should rely entirely on the callee's nullability vector, and not at all
// on the FunctionProtoType's sugar. Throw it away to be sure!
CalleeType = cast<FunctionProtoType>(
CalleeType->getCanonicalTypeInternal().getTypePtr());
// Now check the args against the parameter types.
ArrayRef<const Expr*> Args(CE->getArgs(), CE->getNumArgs());
// The first argument of an member operator call expression is the implicit
// object argument, which does not appear in the list of parameter types.
// Note that operator calls always have a direct callee.
if (isa<CXXOperatorCallExpr>(CE) &&
isa<CXXMethodDecl>(CE->getDirectCallee())) {
Args = Args.drop_front();
}
auto ParamNullability = CalleeNullability.drop_front(
countPointersInType(CalleeType->getReturnType()));
ArrayRef<ParmVarDecl*> Params;
if (auto* DC = CE->getDirectCallee()) Params = DC->parameters();
return diagnoseArgumentCompatibility(
*CalleeType, ParamNullability, Params, Args,
dyn_cast_or_null<FunctionDecl>(CE->getCalleeDecl()), State,
*Result.Context);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseReturn(
const ReturnStmt* absl_nonnull RS, const MatchFinder::MatchResult& Result,
const DiagTransferState& State) {
if (!RS->getRetValue()) return {};
auto* Function = State.Env.getCurrentFunc();
assert(Function);
auto FunctionNullability =
getTypeNullability(*Function, State.Lattice.defaults());
auto ReturnTypeNullability =
ArrayRef(FunctionNullability)
.take_front(countPointersInType(Function->getReturnType()));
return diagnoseAssignmentLike(
Function->getReturnType(), ReturnTypeNullability, RS->getRetValue(),
State, *Result.Context,
PointerNullabilityDiagnostic::Context::ReturnValue);
}
// Warn when class members are explicitly initialized to a nullable value in the
// member initializer list. Initialization to null via a default member
// initializer or default initialization (i.e. no initializer expression) is
// warned on elsewhere (`checkNonnullPointerMemberDefaultInitializer()` and
// `diagnoseNonnullPointerFieldNullableAtExit()`).
static SmallVector<PointerNullabilityDiagnostic> diagnoseMemberInitializer(
const CXXCtorInitializer* absl_nonnull CI,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
const FieldDecl* Member = CI->getAnyMember();
assert(Member != nullptr);
// Ignore default initialization and use of a default member initializer.
if (!CI->isWritten()) {
return {};
}
return diagnoseAssignmentLike(
Member->getType(), getTypeNullability(*Member, State.Lattice.defaults()),
CI->getInit(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::Initializer);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseInitListExpr(
const InitListExpr* absl_nonnull ILE,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
if (!ILE->getType()->isRecordType()) return {};
if (ILE->isSemanticForm() && ILE->isTransparent()) return {};
RecordInitListHelper InitListHelper(ILE);
SmallVector<PointerNullabilityDiagnostic> Diagnostics;
for (auto [Field, Init] : InitListHelper.field_inits()) {
auto Range = Init->getSourceRange();
// ImplicitValueInitExprs do not have a source range, so we use the end
// of the initializer list.
if (isa<ImplicitValueInitExpr>(Init)) {
assert(Init->getSourceRange().isInvalid());
Range = ILE->getRBraceLoc();
}
Diagnostics.append(diagnoseAssignmentLike(
Field->getType(), getTypeNullability(*Field, State.Lattice.defaults()),
Init, State, *Result.Context,
PointerNullabilityDiagnostic::Context::Initializer, nullptr, nullptr,
CharSourceRange::getTokenRange(Range)));
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseDeclStmt(
const DeclStmt* absl_nonnull DS, const MatchFinder::MatchResult& Result,
const DiagTransferState& State) {
SmallVector<PointerNullabilityDiagnostic> Diagnostics;
for (const Decl* D : DS->decls()) {
if (auto* VD = dyn_cast<VarDecl>(D); VD && VD->hasInit()) {
Diagnostics.append(diagnoseAssignmentLike(
VD->getType(), getTypeNullability(*VD, State.Lattice.defaults()),
VD->getInit(), State, *Result.Context,
PointerNullabilityDiagnostic::Context::Initializer));
}
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic>
diagnoseMovedFromNonnullSmartPointer(const Expr* absl_nonnull E,
const MatchFinder::MatchResult& Result,
const DiagTransferState& State) {
const TypeNullability* Nullability = State.Lattice.getTypeNullability(E);
const auto& Ctx = *Result.Context;
if (Nullability == nullptr) return untrackedError(E, Ctx);
if (Nullability->front().concrete() != NullabilityKind::NonNull) return {};
PointerValue* Val = getPointerValueFromSmartPointer(
State.Env.get<RecordStorageLocation>(*E), State.Env);
if (Val == nullptr) return untrackedError(E, Ctx);
if (isNullable(*Val, State.Env))
return {{
.Code = PointerNullabilityDiagnostic::ErrorCode::
AccessingMovedFromNonnullPointer,
.Ctx = PointerNullabilityDiagnostic::Context::Other,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(E->getSourceRange()), Ctx),
}};
return {};
}
namespace {
/// Expressions of smart pointer type that are allowed to be in a moved-from
/// state even if the smart pointer is annotated nonnull.
class AllowedMovedFromNonnullSmartPointerExprs {
public:
explicit AllowedMovedFromNonnullSmartPointerExprs(const FunctionDecl* Func) {
for (const BoundNodes& Node :
match(findAll(expr(anyOf(
cxxMemberCallExpr(
isSmartPointerMethodCall("reset", "Reset"),
unless(hasArgument(0, hasType(isNullPtrType()))),
onImplicitObjectArgument(expr().bind("e"))),
cxxOperatorCallExpr(isSmartPointerOperatorCall("=", 2),
hasArgument(0, expr().bind("e")))))),
*Func->getBody(), Func->getASTContext())) {
AllowedExprs.insert(normalize(Node.getNodeAs<Expr>("e")));
}
}
/// Returns whether `E` is allowed to be in a moved-from state even if the
/// smart pointer is annotated nonnull.
bool allowed(const Expr* E) const {
return AllowedExprs.contains(normalize(E));
}
private:
/// Normalizes `E` to ignore parentheses and casts.
/// We wrap this in a function so that, if we need to change the
/// normalization, all callers use consistent behavior.
static const Expr* normalize(const Expr* E) {
return E->IgnoreParenBaseCasts();
}
llvm::DenseSet<const Expr*> AllowedExprs;
};
} // namespace
static bool shouldDiagnoseExpectedNonnullDefaultArgValue(
clang::ASTContext& Ctx, const ParmVarDecl& Param,
const TypeNullabilityDefaults& Defaults) {
const Expr* Init = Param.getInit();
if (!Init) return false;
if (Init->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull))
return true;
QualType InitTy = Init->getType();
if (InitTy->isDependentType() || !isSupportedPointerType(InitTy))
return false;
if (TypeNullability DefaultValueAnnotation = getTypeNullability(
exprType(Init), Ctx.getSourceManager().getFileID(Param.getLocation()),
Defaults);
!DefaultValueAnnotation.empty() &&
DefaultValueAnnotation.front().concrete() == NullabilityKind::Nullable) {
return true;
}
return false;
}
// Checks for simple cases of default arguments that conflict with annotations
// on the parameter declaration.
//
// Default argument values are missing from the CFG at callsites, so they can't
// be analyzed in the same way as other function arguments. And the
// PointerNullabilityDiagnoser is only run over the CFG (not the entire AST),
// which doesn't really include elements of function declarations, only their
// bodies. Therefore, these initializations must be checked separately to ensure
// diagnostics are produced exactly once per invalid default argument
// declaration, regardless of how many times the function is called (including
// not called at all).
static void checkParmVarDeclWithPointerDefaultArg(
clang::ASTContext& Ctx, const clang::ParmVarDecl& Parm,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags,
const TypeNullabilityDefaults& Defaults,
const clang::NamedDecl* absl_nullable Callee = nullptr) {
if (Parm.getType()->isDependentType()) return;
TypeNullability DeclAnnotation = getTypeNullability(Parm, Defaults);
if (DeclAnnotation.empty() ||
DeclAnnotation.front().concrete() != NullabilityKind::NonNull) {
return;
}
const Expr* DefaultVal = Parm.getInit();
if (!DefaultVal ||
!shouldDiagnoseExpectedNonnullDefaultArgValue(Ctx, Parm, Defaults))
return;
Diags.push_back({
.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = PointerNullabilityDiagnostic::Context::Initializer,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(DefaultVal->getSourceRange()), Ctx),
.Callee = Callee,
.ParamName = Parm.getIdentifier(),
});
}
static void checkAnnotationsConsistentHelper(
QualType T, QualType CanonicalT, const FileID& File,
const FileID& CanonicalFile, SourceRange Range, SourceRange CanonicalRange,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags,
const TypeNullabilityDefaults& Defaults,
PointerNullabilityDiagnostic::ErrorCode ErrorCode) {
TypeNullability Nullability = getTypeNullability(T, File, Defaults);
TypeNullability CanonicalNullability =
getTypeNullability(CanonicalT, CanonicalFile, Defaults);
if (Nullability != CanonicalNullability) {
Diags.push_back({
.Code = ErrorCode,
.Ctx = PointerNullabilityDiagnostic::Context::Other,
.Range = CharSourceRange::getTokenRange(Range),
.NoteRange = CharSourceRange::getTokenRange(CanonicalRange),
.NoteMessage = "previous declaration is here",
});
return;
}
}
static void checkAnnotationsConsistent(
const ValueDecl* absl_nonnull VD,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags,
const TypeNullabilityDefaults& Defaults) {
auto* CanonicalDecl = cast<ValueDecl>(VD->getCanonicalDecl());
// We check against the annotation on the canonical decl, so if this is the
// canonical decl, there is nothing to do.
if (VD == CanonicalDecl) return;
auto FileID =
VD->getASTContext().getSourceManager().getFileID(VD->getLocation());
auto CanonicalFileID =
CanonicalDecl->getASTContext().getSourceManager().getFileID(
CanonicalDecl->getLocation());
const auto* Func = dyn_cast<FunctionDecl>(VD);
if (Func != nullptr) {
const auto* FuncCanonical = cast<FunctionDecl>(CanonicalDecl);
unsigned int NumParams = Func->getNumParams();
if (NumParams > FuncCanonical->getNumParams())
llvm::reportFatalInternalError(
"Function decl has more parameters than its canonical declaration.");
// If a function parameter has a nullability annotation in the
// canonical declaration but no annotation in its corresponding definition,
// the Attribute (from the annotation) in the canonical declaration is
// infused into the definition's Type. To compare what is written/annotated,
// we consult the Type through the TypeSourceInfo, if available. The
// infusing seems to only happen for Function parameters for now, but
// we could use TSI for return types and global variable types as well.
const FunctionProtoType* FuncTSIPT = nullptr;
if (const auto* TSI = Func->getTypeSourceInfo()) {
if (const auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
FuncTSIPT = FPT;
}
}
const FunctionProtoType* FuncCanonicalTSIPT = nullptr;
if (const auto* TSI = FuncCanonical->getTypeSourceInfo()) {
if (const auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
FuncCanonicalTSIPT = FPT;
}
}
for (unsigned int I = 0; I < NumParams; ++I) {
const auto* Parm = Func->getParamDecl(I);
const auto* ParmCanonical = FuncCanonical->getParamDecl(I);
QualType ParmType =
FuncTSIPT ? FuncTSIPT->getParamType(I) : Parm->getType();
QualType ParmCanonicalType = FuncCanonicalTSIPT
? FuncCanonicalTSIPT->getParamType(I)
: ParmCanonical->getType();
checkAnnotationsConsistentHelper(ParmType, ParmCanonicalType, FileID,
CanonicalFileID, Parm->getSourceRange(),
ParmCanonical->getSourceRange(), Diags,
Defaults,
PointerNullabilityDiagnostic::ErrorCode::
InconsistentAnnotationsForParameter);
}
checkAnnotationsConsistentHelper(
Func->getReturnType(), FuncCanonical->getReturnType(), FileID,
CanonicalFileID, Func->getReturnTypeSourceRange(),
FuncCanonical->getReturnTypeSourceRange(), Diags, Defaults,
PointerNullabilityDiagnostic::ErrorCode::
InconsistentAnnotationsForReturn);
} else {
checkAnnotationsConsistentHelper(
VD->getType(), CanonicalDecl->getType(), FileID, CanonicalFileID,
VD->getSourceRange(), CanonicalDecl->getSourceRange(), Diags, Defaults,
PointerNullabilityDiagnostic::ErrorCode::InconsistentAnnotations);
}
}
static void checkNonnullPointerMemberDefaultInitializer(
clang::ASTContext& Ctx, const TypeNullabilityDefaults& Defaults,
const FieldDecl& Member,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags) {
// Ignore if the member is not a pointer annotated as nonnull.
TypeNullability MemberTypeNullability = getTypeNullability(Member, Defaults);
if (MemberTypeNullability.empty() ||
MemberTypeNullability.front().concrete() != NullabilityKind::NonNull) {
return;
}
// Ignore if there is no default member initializer.
if (!Member.hasInClassInitializer()) {
return;
}
const Expr* Initializer = Member.getInClassInitializer();
if (Initializer == nullptr) {
return;
}
// Ignore if this is not a C++ class or struct.
const CXXRecordDecl* RD =
dyn_cast_if_present<CXXRecordDecl>(Member.getParent());
if (RD == nullptr) {
return;
}
const TagDecl::TagKind DeclKind = RD->getTagKind();
if (DeclKind != TagDecl::TagKind::Class &&
DeclKind != TagDecl::TagKind::Struct) {
return;
}
// Ignore if the initializer expression is not a null pointer constant.
// The constant may be wrapped in casts and, in the case of smart pointers,
// converting constructors. `isNullPointerConstant` handles most wrapper nodes
// but does not handle constructors (`CXXConstructExpr`), so we use
// `getSubExprAsWritten()` for that case.
// TODO: b/475944638 - Warn on nullable expressions other than null pointer
// constants in default member initializers.
if (const CastExpr* SubExpr = dyn_cast<CastExpr>(Initializer)) {
Initializer = SubExpr->getSubExprAsWritten();
}
if (!Initializer->isNullPointerConstant(Ctx,
Expr::NPC_ValueDependentIsNotNull)) {
return;
}
PointerNullabilityDiagnostic Diagnostic = {
.Code = PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
.Ctx = PointerNullabilityDiagnostic::Context::Initializer,
.Range = getRangeModuloMacros(
CharSourceRange::getTokenRange(Initializer->getSourceRange()), Ctx),
};
// Suggest using `ABSL_REQUIRE_EXPLICIT_INIT` for aggregate classes.
if (RD->isAggregate()) {
Diagnostic.NoteRange = getRangeModuloMacros(
CharSourceRange::getTokenRange(Member.getSourceRange()), Ctx),
Diagnostic.NoteMessage =
"This nonnull-annotated class member should have a nonnull initializer."
" If there is no appropriate default, consider using "
"ABSL_REQUIRE_EXPLICIT_INIT and setting each instance to a nonnull "
"value (instead of using a null default).";
}
Diags.push_back(std::move(Diagnostic));
}
static CharSourceRange getMethodClosingBraceRange(const CXXMethodDecl& Method) {
if (Method.isDefaulted() || !Method.hasBody()) {
// If the method doesn't have a body (or is defaulted), fall back to using
// the entire method source range (which should be the source range for the
// declaration).
return CharSourceRange::getTokenRange(Method.getSourceRange());
}
auto* Body = dyn_cast<CompoundStmt>(Method.getBody());
if (Body == nullptr) {
return CharSourceRange::getTokenRange(Method.getBody()->getSourceRange());
}
return CharSourceRange::getTokenRange(Body->getRBracLoc(),
Body->getRBracLoc());
}
// Returns true if `Method` is `&&`-qualified and non-const. Running such a
// method moves from the object, after which its member invariants (including
// member nullability) may no longer hold.
static bool isNonConstRValueRefQualified(const CXXMethodDecl& Method) {
return Method.getRefQualifier() == clang::RQ_RValue && !Method.isConst();
}
// Returns true if an object of this class may be moved from before it is
// destroyed.
static bool classMayBeMovedFrom(const CXXRecordDecl& RD) {
// Implicitly-declared move operations may not appear in `methods()` yet, so
// detect the "needs an implicit move operation" case explicitly. Once
// declared, such operations take an `RD&&` parameter and are found by the
// loop below.
if (RD.needsImplicitMoveConstructor() || RD.needsImplicitMoveAssignment())
return true;
for (const CXXMethodDecl* Method : RD.methods()) {
if (Method->isDeleted()) continue;
// A non-const `&&`-qualified method consumes `*this`.
if (isNonConstRValueRefQualified(*Method)) return true;
// A method with a parameter that is an rvalue reference to the same class
// (a move constructor, move assignment operator, or a consuming method such
// as `void Cord::Append(Cord&& src)`) may move from that argument, nulling
// out its nonnull pointer members.
for (const ParmVarDecl* Param : Method->parameters()) {
if (const auto* RRT = Param->getType()->getAs<RValueReferenceType>()) {
// A `const RD&&` parameter cannot be moved from.
if (RRT->getPointeeType().isConstQualified()) continue;
if (const CXXRecordDecl* ParamRD =
RRT->getPointeeType()->getAsCXXRecordDecl();
ParamRD != nullptr &&
ParamRD->getCanonicalDecl() == RD.getCanonicalDecl())
return true;
}
}
}
return false;
}
static void diagnoseNonnullPointerFieldNullableAtExit(
const FunctionDecl& Func,
const dataflow::DataflowAnalysisState<PointerNullabilityLattice>&
StateAtExit,
DataflowAnalysisContext& AnalysisContext,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags) {
const auto* Method = dyn_cast<CXXMethodDecl>(&Func);
if (Method == nullptr) return;
// It isn't possible to access fields after the destructor exits, so don't
// analyze destructors.
if (isa<CXXDestructorDecl>(Method)) return;
// Delegating constructors delegate initialization to another constructor,
// which will be checked separately.
if (const auto* Ctor = dyn_cast<CXXConstructorDecl>(Method)) {
if (Ctor->isDelegatingConstructor()) return;
}
// Running a &&-qualified method requires moving from the object, after which
// its invariants (including member nullability) may no longer be valid.
if (isNonConstRValueRefQualified(*Method)) return;
RecordStorageLocation* RecordLoc =
StateAtExit.Env.getThisPointeeStorageLocation();
if (RecordLoc == nullptr) return;
const CXXRecordDecl* RD = Method->getParent();
FieldSet ModeledFields =
AnalysisContext.getModeledFields(RecordLoc->getType());
for (const FieldDecl* Field : RD->fields()) {
bool SmartPointer;
if (isSupportedRawPointerType(Field->getType())) {
SmartPointer = false;
} else if (isSupportedSmartPointerType(Field->getType())) {
SmartPointer = true;
} else {
continue;
}
TypeNullability FieldNullability =
getTypeNullability(*Field, StateAtExit.Lattice.defaults());
if (FieldNullability.empty() ||
FieldNullability.front().concrete() != NullabilityKind::NonNull) {
continue;
}
if (ModeledFields.contains(Field)) {
StorageLocation* FieldLoc = RecordLoc->getChild(*Field);
if (FieldLoc == nullptr) continue;
PointerValue* Val;
if (SmartPointer) {
Val = getPointerValueFromSmartPointer(
cast<RecordStorageLocation>(FieldLoc), StateAtExit.Env);
} else {
Val = StateAtExit.Env.get<PointerValue>(*FieldLoc);
}
if (Val == nullptr) {
Diags.push_back({
.Code = PointerNullabilityDiagnostic::ErrorCode::Untracked,
.Ctx = PointerNullabilityDiagnostic::Context::Other,
.Range = CharSourceRange::getTokenRange(Field->getSourceRange()),
});
continue;
}
// We may see a value without nullability properties here, so guard
// against that. This can happen, for example, if the field was
// initialized by the framework on function entry but not accessed on some
// paths, hence not giving us an opportunity to initialize the nullability
// properties on those paths.
if (!hasPointerNullState(*Val)) return;
if (!isNullable(*Val, StateAtExit.Env)) {
continue;
}
} else if (!isa<CXXConstructorDecl>(Method)) {
// The field is not modeled. For constructors, this means it was left
// uninitialized, which we report below. For other methods, it means the
// method does not access it, so it has not been moved from.
continue;
}
Diags.push_back({
.Code = PointerNullabilityDiagnostic::ErrorCode::
NonnullPointerFieldNullableAtExit,
.Ctx = PointerNullabilityDiagnostic::Context::Other,
.Range = getMethodClosingBraceRange(*Method),
.NoteRange = CharSourceRange::getTokenRange(Field->getSourceRange()),
.NoteMessage = "pointer field is declared here",
});
}
}
static DiagTransferFunc pointerNullabilityDiagnoserBefore() {
// Almost all diagnosis callbacks should be run before the transfer function
// has been applied because we want to check preconditions for the operation
// performed by the `CFGElement`.
return CFGMatchSwitchBuilder<const DiagTransferState,
SmallVector<PointerNullabilityDiagnostic>>()
// `*`
.CaseOfCFGStmt<UnaryOperator>(isPointerDereference(), diagnoseDereference)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("*", 1),
diagnoseSmartPointerDereference)
// `[]`
.CaseOfCFGStmt<ArraySubscriptExpr>(isPointerSubscript(),
diagnoseSubscript)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("[]", 2),
diagnoseSmartPointerDereference)
// `->`. Covers raw and smart pointers, because smart-pointer
// `operator->` doesn't dereference. It just returns a pointer from which
// a MemberExpr is built (with `->`), which does the actual dereference.
.CaseOfCFGStmt<MemberExpr>(isPointerArrow(), diagnoseArrow)
// `=` / `reset()` / `Reset()`
.CaseOfCFGStmt<BinaryOperator>(
binaryOperator(hasOperatorName("="), hasLHS(isPointerExpr())),
diagnoseAssignment)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isSmartPointerOperatorCall("=", 2),
diagnoseSmartPointerAssignment)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerMethodCall("reset", "Reset"), diagnoseSmartPointerReset)
// `--` / `++`
.CaseOfCFGStmt<UnaryOperator>(
unaryOperator(hasType(isSupportedRawPointer()),
anyOf(hasOperatorName("++"), hasOperatorName("--"))),
diagnoseIncrementDecrement)
// `+=` / `-=`
.CaseOfCFGStmt<BinaryOperator>(
binaryOperator(anyOf(hasOperatorName("+="), hasOperatorName("-=")),
hasOperands(isPointerExpr(), hasType(isInteger()))),
diagnoseAddSubtractAssign)
// `+` / `-`
.CaseOfCFGStmt<BinaryOperator>(
binaryOperator(
anyOf(hasOperatorName("+"), hasOperatorName("-")),
anyOf(hasOperands(isPointerExpr(), hasType(isInteger())),
hasOperands(hasType(isInteger()), isPointerExpr()))),
diagnoseAddSubtractInteger)
.CaseOfCFGStmt<BinaryOperator>(
binaryOperator(hasOperatorName("-"),
hasOperands(isPointerExpr(), isPointerExpr())),
diagnosePointerDifference)
// Check compatibility of parameter assignments and return values.
.CaseOfCFGStmt<CallExpr>(callExpr(), diagnoseCallExpr)
.CaseOfCFGStmt<CXXConstructExpr>(cxxConstructExpr(),
diagnoseConstructExpr)
.CaseOfCFGStmt<ReturnStmt>(isPointerReturn(), diagnoseReturn)
// Check compatibility of member initializers.
.CaseOfCFGInit<CXXCtorInitializer>(isCtorMemberInitializer(),
diagnoseMemberInitializer)
// Check compatibility of initializer lists.
.CaseOfCFGStmt<InitListExpr>(initListExpr(), diagnoseInitListExpr)
.CaseOfCFGStmt<DeclStmt>(declStmt(), diagnoseDeclStmt)
.Build();
}
static DiagTransferFunc pointerNullabilityDiagnoserAfter(
const AllowedMovedFromNonnullSmartPointerExprs& AllowedMovedFromNonnull) {
return CFGMatchSwitchBuilder<const DiagTransferState,
SmallVector<PointerNullabilityDiagnostic>>()
// `diagnoseMovedFromNonnullSmartPointer` needs to be run after the
// transfer function has been applied so that the pointer and its
// nullability properties are guaranteed be initialized (through
// `ensureSmartPointerInitialized()`).
.CaseOfCFGStmt<Expr>(
expr(hasType(isSupportedSmartPointer()), isGLValue()),
[&AllowedMovedFromNonnull](const Expr* absl_nonnull E,
const MatchFinder::MatchResult& Result,
const DiagTransferState& State)
-> SmallVector<PointerNullabilityDiagnostic> {
if (AllowedMovedFromNonnull.allowed(E)) return {};
return diagnoseMovedFromNonnullSmartPointer(E, Result, State);
})
.Build();
}
// Diagnose explicitly defaulted constructors to check for uninitialized,
// nonnull pointers. Although most constructors are checked by
// `diagnoseNonnullPointerFieldNullableAtExit`, the defaulted constructor is a
// special case: if it's not ODR-used in the same translation unit, Clang won't
// generate its body, so `diagnoseNonnullPointerFieldNullableAtExit` can't run.
static void checkExplicitlyDefaultedConstructor(
clang::ASTContext& Ctx, const TypeNullabilityDefaults& Defaults,
const CXXConstructorDecl& Ctor,
llvm::SmallVector<PointerNullabilityDiagnostic>& Diags) {
if (!Ctor.isDefaultConstructor() || !Ctor.isDefaulted() ||
Ctor.isImplicit() || Ctor.isDeleted()) {
return;
}
const CXXRecordDecl* RD = Ctor.getParent();
if (RD->isTemplated() ||
Ctx.getSourceManager().isInSystemHeader(RD->getLocation())) {
return;
}
if (!RD->isClass() && !RD->isStruct()) {
return;
}
// Check all fields of the record for uninitialized nonnull pointers.
checkRecordFieldsForUninitializedNonnull(
RD, Defaults, Ctx,
PointerNullabilityDiagnostic::ErrorCode::
NonnullPointerFieldNullableAtExit,
PointerNullabilityDiagnostic::Context::Other,
CharSourceRange::getTokenRange(Ctor.getSourceRange()), Diags);
}
std::unique_ptr<dataflow::Solver> makeDefaultSolverForDiagnosis() {
// This limit is set based on empirical observations. Mostly, it is a rough
// proxy for a line between "finite" and "effectively infinite", rather than a
// strict limit on resource use.
constexpr std::int64_t MaxSATIterations = 2'000'000;
return std::make_unique<dataflow::WatchedLiteralsSolver>(MaxSATIterations);
}
// Returns the nonnull pointer fields (raw and smart) of the destructor's class
// that must be modeled as nullable at destructor entry, because a move
// operation may have nulled them out before destruction. Returns empty if the
// class cannot be moved from.
static llvm::DenseSet<const FieldDecl*>
computeFieldsToTreatAsNullableAtDestructorEntry(
const CXXDestructorDecl& Dtor, const TypeNullabilityDefaults& Defaults) {
llvm::DenseSet<const FieldDecl*> Result;
const CXXRecordDecl* RD = Dtor.getParent();
if (RD == nullptr || !classMayBeMovedFrom(*RD)) return Result;
for (const FieldDecl* Field : RD->fields()) {
if (!isSupportedPointerType(Field->getType())) continue;
// A const field is copied (not moved) by a defaulted move operation and
// cannot be nulled by a &&-qualified method, so it is never moved-from and
// remains nonnull at destructor entry.
if (Field->getType().isConstQualified()) continue;
// Only downgrade fields annotated `_Nonnull`; nullable and unannotated
// fields already tolerate a null value at entry.
TypeNullability FieldNullability = getTypeNullability(*Field, Defaults);
if (FieldNullability.empty() ||
FieldNullability.front().concrete() != NullabilityKind::NonNull) {
continue;
}
Result.insert(Field);
}
return Result;
}
// Records the flow-sensitive nullability of each by-value pointer capture of
// the lambda constructed at this program point, keyed by (closure record,
// capture field).
static void recordLambdaCaptureNullability(
const CFGElement& Elt, const dataflow::Environment& Env,
LambdaCaptureNullabilityMap& CaptureMap) {
std::optional<CFGStmt> S = Elt.getAs<CFGStmt>();
if (!S) return;
const auto* LE = dyn_cast<LambdaExpr>(S->getStmt());
if (LE == nullptr) return;
const CXXRecordDecl* Closure = LE->getLambdaClass();
auto Field = Closure->field_begin();
const auto* Capture = LE->capture_begin();
const auto* Init = LE->capture_init_begin();
for (; Capture != LE->capture_end() && Field != Closure->field_end() &&
Init != LE->capture_init_end();
++Capture, ++Field, ++Init) {
// Only by-value pointer captures of variables are modeled. By-reference
// and this/*this captures alias mutable outer state and retain declared
// nullability state.
if (const Expr* InitExpr = *Init;
Capture->getCaptureKind() == LCK_ByCopy &&
Capture->capturesVariable() && InitExpr != nullptr &&
isSupportedPointerType(InitExpr->getType())) {
NullabilityKind Kind = getNullability(InitExpr, Env);
if (Kind == NullabilityKind::NonNull || Kind == NullabilityKind::Nullable)
CaptureMap.insert({{Closure, *Field}, Kind});
}
}
}
// Translates the (closure, field) entries recorded for `Closure` into the
// captured variables that its in-lambda `DeclRefExpr`s resolve to.
static llvm::DenseMap<const ValueDecl*, NullabilityKind>
computeCapturedVarNullability(const CXXRecordDecl& Closure,
const LambdaCaptureNullabilityMap& CaptureMap) {
llvm::DenseMap<const ValueDecl*, NullabilityKind> Result;
auto Field = Closure.field_begin();
const auto* Capture = Closure.captures_begin();
for (; Capture != Closure.captures_end() && Field != Closure.field_end();
++Capture, ++Field) {
if (!Capture->capturesVariable()) continue;
if (auto It = CaptureMap.find({&Closure, *Field}); It != CaptureMap.end())
Result.insert({Capture->getCapturedVar(), It->second});
}
return Result;
}
llvm::Expected<llvm::SmallVector<PointerNullabilityDiagnostic>>
diagnosePointerNullability(const ValueDecl* absl_nonnull VD,
const NullabilityPragmas& Pragmas,
LambdaCaptureNullabilityMap& CaptureMap,
const SolverFactory& MakeSolver) {
// This limit is set based on empirical observations. Mostly, it is a rough
// proxy for a line between "finite" and "effectively infinite", rather than a
// strict limit on resource use.
constexpr std::int32_t MaxBlockVisits = 20'000;
llvm::SmallVector<PointerNullabilityDiagnostic> Diags;
// Skip templated functions where we don't have full type information, but
// allow template instantiations (not isTemplated()).
if (VD->isTemplated()) return Diags;
ASTContext& Ctx = VD->getASTContext();
TypeNullabilityDefaults Defaults{Ctx, Pragmas};
checkAnnotationsConsistent(VD, Diags, Defaults);
if (const auto* Ctor = dyn_cast<CXXConstructorDecl>(VD)) {
checkExplicitlyDefaultedConstructor(Ctx, Defaults, *Ctor, Diags);
}
if (const FieldDecl* Member = dyn_cast<FieldDecl>(VD)) {
checkNonnullPointerMemberDefaultInitializer(Ctx, Defaults, *Member, Diags);
}
const auto* Func = dyn_cast<FunctionDecl>(VD);
if (Func == nullptr) return Diags;
for (const ParmVarDecl* Parm : Func->parameters())
checkParmVarDeclWithPointerDefaultArg(Ctx, *Parm, Diags, Defaults, Func);
// Use `doesThisDeclarationHaveABody()` rather than `hasBody()` to ensure we
// analyze forward-declared functions only once.
if (!Func->doesThisDeclarationHaveABody() || Func->isDefaulted())
return Diags;
AllowedMovedFromNonnullSmartPointerExprs AllowedMovedFromNonnull(Func);
// TODO(b/332565018): it would be nice to have some common pieces (limits,
// adorning, error-handling) reused. diagnoseFunction() is too restrictive.
auto CFG = dataflow::AdornedCFG::build(*Func);
if (!CFG) return CFG.takeError();
std::unique_ptr<dataflow::Solver> Solver = MakeSolver();
DataflowAnalysisContext AnalysisContext(*Solver);
Environment Env(AnalysisContext, *Func);
PointerNullabilityAnalysis Analysis(Ctx, Env, Pragmas);
if (const auto* Dtor = dyn_cast<CXXDestructorDecl>(Func)) {
Analysis.setFieldsToTreatAsNullableAtDestructorEntry(
computeFieldsToTreatAsNullableAtDestructorEntry(*Dtor, Defaults));
}
// If analyzing a lambda call operator, install the capture-time
// nullabilities recorded while analyzing the enclosing function.
if (const auto* Method = dyn_cast<CXXMethodDecl>(Func);
Method != nullptr && Method->getParent()->isLambda()) {
Analysis.setCapturedVarNullability(
computeCapturedVarNullability(*Method->getParent(), CaptureMap));
}
dataflow::CFGEltCallbacks<PointerNullabilityAnalysis> PostAnalysisCallbacks;
PostAnalysisCallbacks.Before =
[&, Diagnoser = pointerNullabilityDiagnoserBefore()](
const CFGElement& Elt,
const dataflow::DataflowAnalysisState<PointerNullabilityLattice>&
State) mutable {
auto EltDiagnostics = Diagnoser(Elt, Ctx, {State.Lattice, State.Env});
llvm::move(EltDiagnostics, std::back_inserter(Diags));
};
PostAnalysisCallbacks.After =
[&,
Diagnoser = pointerNullabilityDiagnoserAfter(AllowedMovedFromNonnull)](
const CFGElement& Elt,
const dataflow::DataflowAnalysisState<PointerNullabilityLattice>&
State) mutable {
auto EltDiagnostics = Diagnoser(Elt, Ctx, {State.Lattice, State.Env});
llvm::move(EltDiagnostics, std::back_inserter(Diags));
// Record the capture-time nullability of any lambda constructed here,
// for use when its call operator is later analyzed.
recordLambdaCaptureNullability(Elt, State.Env, CaptureMap);
};
auto Result = dataflow::runDataflowAnalysis(
*CFG, Analysis, Env, PostAnalysisCallbacks, MaxBlockVisits);
if (!Result) return Result.takeError();
if (Solver->reachedLimit())
return llvm::createStringError(llvm::errc::interrupted,
"SAT solver timed out");
const std::optional<
dataflow::DataflowAnalysisState<PointerNullabilityLattice>>&
ExitBlockState = (*Result)[CFG->getCFG().getExit().getBlockID()];
if (ExitBlockState.has_value()) {
diagnoseNonnullPointerFieldNullableAtExit(*Func, *ExitBlockState,
AnalysisContext, Diags);
}
return Diags;
}
} // namespace clang::tidy::nullability