| // 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 |