blob: e93e032f86896785c333563d50b3e6835378ff12 [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
#include "nullability/pointer_nullability_diagnosis.h"
#include <cassert>
#include <cstdint>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include "absl/base/nullability.h"
#include "absl/log/check.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/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/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "clang/Tooling/Transformer/SourceCode.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/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 {{PointerNullabilityDiagnostic::ErrorCode::Untracked, DiagCtx,
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));
}
// Diagnoses whether `E` violates the expectation that it is nonnull.
static SmallVector<PointerNullabilityDiagnostic> diagnoseNonnullExpected(
const Expr* absl_nonnull E, const Environment& Env, 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, Env))
IsNullable = isNullable(*ActualVal, Env);
else if (E->getType()->isNullPtrType())
IsNullable = isReachableNullptrLiteral(Env);
if (IsNullable.has_value()) {
if (*IsNullable) {
if (Range.isInvalid())
Range = CharSourceRange::getTokenRange(E->getSourceRange());
if (const Expr* NullCheck =
matchesNonConstCallNullCheck(*E, Ctx, Env.getCurrentFunc());
NullCheck != nullptr) {
CharSourceRange NoteRange = getRangeModuloMacros(
CharSourceRange::getTokenRange(NullCheck->getSourceRange()), Ctx);
return {{PointerNullabilityDiagnostic::ErrorCode::
ExpectedNonnullWithCheckOnNonConstCall,
DiagCtx, getRangeModuloMacros(Range, Ctx), Callee, ParamName,
NoteRange}};
}
return {{PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
DiagCtx, getRangeModuloMacros(Range, Ctx), Callee, ParamName}};
}
return {};
}
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);
}
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.Env, 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(
{PointerNullabilityDiagnostic::ErrorCode::ExpectedEqualNullability,
DiagCtx, getRangeModuloMacros(LHSRange, Ctx), Callee, ParamName});
}
return Diagnostics;
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseDereference(
const UnaryOperator* absl_nonnull UnaryOp,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
return diagnoseNonnullExpected(
UnaryOp->getSubExpr(), State.Env, *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.Env, *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.Env, *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.Env, *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 {{PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
PointerNullabilityDiagnostic::Context::Assignment,
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()) {
CHECK_GE(Args.size(), ParamTypes.size());
Args = Args.take_front(ParamTypes.size());
}
CHECK_EQ(ParamTypes.size(), Args.size());
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 {{PointerNullabilityDiagnostic::ErrorCode::AssertFailed,
PointerNullabilityDiagnostic::Context::Other,
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.Env,
*Result.Context,
PointerNullabilityDiagnostic::Context::Other);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseAddSubtract(
Expr* PtrExpr, Expr* IntExpr, const Environment& Env, 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, Env, 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.Env,
*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.Env, *Result.Context);
}
return diagnoseAddSubtract(BinaryOp->getLHS(), BinaryOp->getRHS(), State.Env,
*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.Env, *Result.Context,
PointerNullabilityDiagnostic::Context::Other);
Diagnostics.append(
diagnoseNonnullExpected(BinaryOp->getRHS(), State.Env, *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 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());
return diagnoseConstructorCall(ConstructorArgs, CtorDecl, Result, State);
}
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) {
CHECK(CEInMakeUnique->getArg(I)->isDefaultArgument());
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.Env, *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();
CHECK(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);
}
static SmallVector<PointerNullabilityDiagnostic> diagnoseMemberInitializer(
const CXXCtorInitializer* absl_nonnull CI,
const MatchFinder::MatchResult& Result, const DiagTransferState& State) {
CHECK(CI->isAnyMemberInitializer());
auto* Member = CI->getAnyMember();
const auto* InitExpr = CI->getInit();
if (!CI->isWritten()) {
// Don't warn if a nonnull pointer field is merely default-initialized to
// null. (If the constructor doesn't initialize the field to nonnull, we'll
// catch this in diagnoseNonnullSmartPointerFieldMovedFromAtExit.)
if (!isa<CXXDefaultInitExpr>(InitExpr)) return {};
// Do warn if a nonnull pointer field has an in-class initializer that
// explicitly initializes it to null.
return diagnoseAssignmentLike(
Member->getType(),
getTypeNullability(*Member, State.Lattice.defaults()), InitExpr, State,
*Result.Context, PointerNullabilityDiagnostic::Context::Initializer,
nullptr, nullptr,
CharSourceRange::getTokenRange(Member->getSourceRange()));
}
return diagnoseAssignmentLike(
Member->getType(), getTypeNullability(*Member, State.Lattice.defaults()),
InitExpr, 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 {{PointerNullabilityDiagnostic::ErrorCode::
AccessingMovedFromNonnullPointer,
PointerNullabilityDiagnostic::Context::Other,
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(
{PointerNullabilityDiagnostic::ErrorCode::ExpectedNonnull,
PointerNullabilityDiagnostic::Context::Initializer,
getRangeModuloMacros(
CharSourceRange::getTokenRange(DefaultVal->getSourceRange()), Ctx),
Callee, 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({ErrorCode, PointerNullabilityDiagnostic::Context::Other,
CharSourceRange::getTokenRange(Range), nullptr, nullptr,
CharSourceRange::getTokenRange(CanonicalRange)});
return;
}
// If a function parameter has a nullability annotation in the canonical
// declaration but no annotation in its corresponding definition, the
// annotation in the declaration is infused into the definition at the AST
// level. Consequently, T and CanonicalT will have the same nullabilities
// even though they are inconsistent in code. To catch this specific case,
// we attempt to read the raw attribute. If the nullability is set, but
// there's either no nullability attribute (or no attribute altogether), it
// means that there's an inconsistency in annotations.
if (const auto* AT = dyn_cast<AttributedType>(T)) {
if (AT->getImmediateNullability().has_value() &&
(!AT->getAttr() || (AT->getAttrKind() != attr::TypeNonNull &&
AT->getAttrKind() != attr::TypeNullable &&
AT->getAttrKind() != attr::TypeNullUnspecified)) &&
// Only issue the diagnostic if the infused nullability is different
// from the default.
Defaults.get(File) != *AT->getImmediateNullability()) {
Diags.push_back({ErrorCode, PointerNullabilityDiagnostic::Context::Other,
CharSourceRange::getTokenRange(Range), nullptr, nullptr,
CharSourceRange::getTokenRange(CanonicalRange)});
}
}
}
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();
CHECK(NumParams <= FuncCanonical->getNumParams());
for (unsigned int I = 0; I < NumParams; ++I) {
const auto* Parm = Func->getParamDecl(I);
const auto* ParmCanonical = FuncCanonical->getParamDecl(I);
checkAnnotationsConsistentHelper(
Parm->getType(), ParmCanonical->getType(), 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 CharSourceRange getMethodClosingBraceRange(const CXXMethodDecl& Method) {
if (!Method.hasBody()) {
// If the method doesn't have a body, fall back to using the entire method
// source range (which should be the source range for the declaration).
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());
}
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;
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()) {
// If the field isn't modeled, we can't access it below -- but it also can't
// be moved from because the method obviously doesn't refer to it.
if (!ModeledFields.contains(Field)) continue;
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;
}
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(
{PointerNullabilityDiagnostic::ErrorCode::Untracked,
PointerNullabilityDiagnostic::Context::Other,
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)) {
Diags.push_back(
{PointerNullabilityDiagnostic::ErrorCode::
NonnullPointerFieldNullableAtExit,
PointerNullabilityDiagnostic::Context::Other,
getMethodClosingBraceRange(*Method), nullptr, nullptr,
CharSourceRange::getTokenRange(Field->getSourceRange())});
}
}
}
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();
}
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);
}
llvm::Expected<llvm::SmallVector<PointerNullabilityDiagnostic>>
diagnosePointerNullability(const ValueDecl* VD,
const NullabilityPragmas& Pragmas,
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);
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()) 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);
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));
};
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