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