| // 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_analysis.h" |
| |
| #include <cassert> |
| #include <functional> |
| #include <optional> |
| #include <vector> |
| |
| #include "absl/log/check.h" |
| #include "nullability/pointer_nullability.h" |
| #include "nullability/pointer_nullability_lattice.h" |
| #include "nullability/pointer_nullability_matchers.h" |
| #include "nullability/type_nullability.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/DeclTemplate.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/AST/ExprCXX.h" |
| #include "clang/AST/OperationKinds.h" |
| #include "clang/AST/Stmt.h" |
| #include "clang/AST/Type.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/ASTMatchers/ASTMatchers.h" |
| #include "clang/Analysis/CFG.h" |
| #include "clang/Analysis/FlowSensitive/Arena.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/StorageLocation.h" |
| #include "clang/Analysis/FlowSensitive/Value.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/Specifiers.h" |
| #include "llvm/Support/Debug.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| namespace clang::tidy::nullability { |
| |
| using ast_matchers::MatchFinder; |
| using dataflow::Arena; |
| using dataflow::BoolValue; |
| using dataflow::CFGMatchSwitchBuilder; |
| using dataflow::ComparisonResult; |
| using dataflow::DataflowAnalysisContext; |
| using dataflow::Environment; |
| using dataflow::Formula; |
| using dataflow::PointerValue; |
| using dataflow::StorageLocation; |
| using dataflow::TransferState; |
| using dataflow::Value; |
| |
| #define DEBUG_TYPE "pointer_nullability_analysis.cc" |
| |
| namespace { |
| |
| TypeNullability prepend(NullabilityKind Head, const TypeNullability &Tail) { |
| TypeNullability Result = {Head}; |
| Result.insert(Result.end(), Tail.begin(), Tail.end()); |
| return Result; |
| } |
| |
| void computeNullability(const Expr *E, |
| TransferState<PointerNullabilityLattice> &State, |
| std::function<TypeNullability()> Compute) { |
| (void)State.Lattice.insertExprNullabilityIfAbsent(E, [&] { |
| auto Nullability = Compute(); |
| if (unsigned ExpectedSize = countPointersInType(E); |
| ExpectedSize != Nullability.size()) { |
| // A nullability vector must have one entry per pointer in the type. |
| // If this is violated, we probably failed to handle some AST node. |
| LLVM_DEBUG({ |
| llvm::dbgs() |
| << "=== Nullability vector has wrong number of entries: ===\n"; |
| llvm::dbgs() << "Expression: \n"; |
| dump(E, llvm::dbgs()); |
| llvm::dbgs() << "\nNullability (" << Nullability.size() |
| << " pointers): " << nullabilityToString(Nullability) |
| << "\n"; |
| llvm::dbgs() << "\nType (" << ExpectedSize << " pointers): \n"; |
| dump(exprType(E), llvm::dbgs()); |
| llvm::dbgs() << "=================================\n"; |
| }); |
| |
| // We can't meaningfully interpret the vector, so discard it. |
| // TODO: fix all broken cases and upgrade to CHECK or DCHECK or so. |
| Nullability.assign(ExpectedSize, NullabilityKind::Unspecified); |
| } |
| return Nullability; |
| }); |
| } |
| |
| // Returns the computed nullability for a subexpr of the current expression. |
| // This is always available as we compute bottom-up. |
| const TypeNullability &getNullabilityForChild( |
| const Expr *E, TransferState<PointerNullabilityLattice> &State) { |
| return State.Lattice.insertExprNullabilityIfAbsent(E, [&] { |
| // Since we process child nodes before parents, we should already have |
| // computed the child nullability. However, this is not true in all test |
| // cases. So, we return unspecified nullability annotations. |
| // TODO: fix this issue, and CHECK() instead. |
| LLVM_DEBUG({ |
| llvm::dbgs() << "=== Missing child nullability: ===\n"; |
| dump(E, llvm::dbgs()); |
| llvm::dbgs() << "==================================\n"; |
| }); |
| |
| return unspecifiedNullability(E); |
| }); |
| } |
| |
| /// Compute the nullability annotation of type `T`, which contains types |
| /// originally written as a class template type parameter. |
| /// |
| /// Example: |
| /// |
| /// \code |
| /// template <typename F, typename S> |
| /// struct pair { |
| /// S *_Nullable getNullablePtrToSecond(); |
| /// }; |
| /// \endcode |
| /// |
| /// Consider the following member call: |
| /// |
| /// \code |
| /// pair<int *, int *_Nonnull> x; |
| /// x.getNullablePtrToSecond(); |
| /// \endcode |
| /// |
| /// The class template specialization `x` has the following substitutions: |
| /// |
| /// F=int *, whose nullability is [_Unspecified] |
| /// S=int * _Nonnull, whose nullability is [_Nonnull] |
| /// |
| /// The return type of the member call `x.getNullablePtrToSecond()` is |
| /// S * _Nullable. |
| /// |
| /// When we call `substituteNullabilityAnnotationsInClassTemplate` with the type |
| /// `S * _Nullable` and the `base` node of the member call (in this case, a |
| /// `DeclRefExpr`), it returns the nullability of the given type after applying |
| /// substitutions, which in this case is [_Nullable, _Nonnull]. |
| TypeNullability substituteNullabilityAnnotationsInClassTemplate( |
| QualType T, const TypeNullability &BaseNullabilityAnnotations, |
| QualType BaseType) { |
| return getNullabilityAnnotationsFromType( |
| T, |
| [&](const SubstTemplateTypeParmType *ST) |
| -> std::optional<TypeNullability> { |
| // The class specialization that is BaseType and owns ST. |
| const ClassTemplateSpecializationDecl *Specialization = nullptr; |
| if (const auto *RT = BaseType->getAs<RecordType>()) |
| Specialization = |
| dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl()); |
| // TODO: handle nested templates, where associated decl != base type |
| // (e.g. PointerNullabilityTest.MemberFunctionTemplateOfTemplateStruct) |
| if (!Specialization || Specialization != ST->getAssociatedDecl()) |
| return std::nullopt; |
| // TODO: The code below does not deal correctly with partial |
| // specializations. We should eventually handle these, but for now, just |
| // bail out. |
| if (isa<ClassTemplatePartialSpecializationDecl>( |
| ST->getReplacedParameter()->getDeclContext())) |
| return std::nullopt; |
| |
| unsigned ArgIndex = ST->getIndex(); |
| auto TemplateArgs = Specialization->getTemplateArgs().asArray(); |
| |
| // TODO: If the type was substituted from a pack template argument, |
| // we must find the slice that pertains to this particular type. |
| // For now, just give up on resugaring this type. |
| if (ST->getPackIndex().has_value()) return std::nullopt; |
| |
| unsigned PointerCount = |
| countPointersInType(Specialization->getDeclContext()); |
| for (auto TA : TemplateArgs.take_front(ArgIndex)) { |
| PointerCount += countPointersInType(TA); |
| } |
| |
| unsigned SliceSize = countPointersInType(TemplateArgs[ArgIndex]); |
| return ArrayRef(BaseNullabilityAnnotations) |
| .slice(PointerCount, SliceSize) |
| .vec(); |
| }); |
| } |
| |
| /// Compute nullability annotations of `T`, which might contain template type |
| /// variable substitutions bound by the call `CE`. |
| /// |
| /// Example: |
| /// |
| /// \code |
| /// template<typename F, typename S> |
| /// std::pair<S, F> flip(std::pair<F, S> p); |
| /// \endcode |
| /// |
| /// Consider the following CallExpr: |
| /// |
| /// \code |
| /// flip<int * _Nonnull, int * _Nullable>(std::make_pair(&x, &y)); |
| /// \endcode |
| /// |
| /// This CallExpr has the following substitutions: |
| /// F=int * _Nonnull, whose nullability is [_Nonnull] |
| /// S=int * _Nullable, whose nullability is [_Nullable] |
| /// |
| /// The return type of this CallExpr is `std::pair<S, F>`. |
| /// |
| /// When we call `substituteNullabilityAnnotationsInFunctionTemplate` with the |
| /// type `std::pair<S, F>` and the above CallExpr, it returns the nullability |
| /// the given type after applying substitutions, which in this case is |
| /// [_Nullable, _Nonnull]. |
| TypeNullability substituteNullabilityAnnotationsInFunctionTemplate( |
| QualType T, const CallExpr *CE) { |
| return getNullabilityAnnotationsFromType( |
| T, |
| [&](const SubstTemplateTypeParmType *ST) |
| -> std::optional<TypeNullability> { |
| auto *DRE = dyn_cast<DeclRefExpr>(CE->getCallee()->IgnoreImpCasts()); |
| if (DRE == nullptr) return std::nullopt; |
| |
| // TODO: Handle calls that use template argument deduction. |
| |
| // Does this refer to a parameter of the function template? |
| // If not (e.g. nested templates, template specialization types in the |
| // return value), we handle the desugaring elsewhere. |
| auto *ReferencedFunction = dyn_cast<FunctionDecl>(DRE->getDecl()); |
| if (!ReferencedFunction) return std::nullopt; |
| if (ReferencedFunction->getPrimaryTemplate() != ST->getAssociatedDecl()) |
| return std::nullopt; |
| |
| // Some or all of the template arguments may be deduced, and we won't |
| // see those on the `DeclRefExpr`. If the template argument was deduced, |
| // we don't have any sugar for it. |
| // TODO(b/268348533): Can we somehow obtain it from the function param |
| // it was deduced from? |
| // TODO(b/268345783): This check, as well as the index into |
| // `template_arguments` below, may be incorrect in the presence of |
| // parameters packs. In function templates, parameter packs may appear |
| // anywhere in the parameter list. The index may therefore refer to one |
| // of the pack arguments, but we might incorrectly interpret it as |
| // referring to an argument that follows the pack. |
| if (ST->getIndex() >= DRE->template_arguments().size()) |
| return std::nullopt; |
| |
| TypeSourceInfo *TSI = |
| DRE->template_arguments()[ST->getIndex()].getTypeSourceInfo(); |
| if (TSI == nullptr) return std::nullopt; |
| return getNullabilityAnnotationsFromType(TSI->getType()); |
| }); |
| } |
| |
| PointerTypeNullability getPointerTypeNullability( |
| const Expr *E, PointerNullabilityAnalysis::Lattice &L) { |
| // TODO: handle this in non-flow-sensitive transfer instead |
| if (auto FromClang = E->getType()->getNullability(); |
| FromClang && *FromClang != NullabilityKind::Unspecified) |
| return *FromClang; |
| |
| if (const auto *NonFlowSensitive = L.getExprNullability(E)) { |
| if (!NonFlowSensitive->empty()) |
| // Return the nullability of the topmost pointer in the type. |
| return NonFlowSensitive->front(); |
| } |
| |
| return NullabilityKind::Unspecified; |
| } |
| |
| void initPointerFromTypeNullability( |
| PointerValue &PointerVal, const Expr *E, |
| TransferState<PointerNullabilityLattice> &State) { |
| initPointerNullState(PointerVal, State.Env.getDataflowAnalysisContext(), |
| getPointerTypeNullability(E, State.Lattice)); |
| } |
| |
| /// Returns a new pointer value referencing the same location as `PointerVal` |
| /// but with any "top" nullability properties unpacked into fresh atoms. |
| /// This is analogous to the unpacking done on `TopBoolValue`s in the framework. |
| /// TODO(mboehme): When we add support for smart pointers, this function will |
| /// also need to be called when accessing the `PointerValue` that underlies the |
| /// smart pointer. |
| PointerValue *unpackPointerValue(PointerValue &PointerVal, Environment &Env) { |
| auto [FromNullable, Null] = getPointerNullState(PointerVal); |
| if (FromNullable && Null) return nullptr; |
| |
| auto &A = Env.getDataflowAnalysisContext().arena(); |
| |
| auto &NewPointerVal = Env.create<PointerValue>(PointerVal.getPointeeLoc()); |
| initPointerNullState(NewPointerVal, Env.getDataflowAnalysisContext()); |
| auto NewNullability = getPointerNullState(NewPointerVal); |
| assert(NewNullability.FromNullable != nullptr); |
| assert(NewNullability.IsNull != nullptr); |
| |
| if (FromNullable != nullptr) |
| Env.assume(A.makeEquals(*NewNullability.FromNullable, *FromNullable)); |
| if (Null != nullptr) Env.assume(A.makeEquals(*NewNullability.IsNull, *Null)); |
| |
| return &NewPointerVal; |
| } |
| |
| void transferValue_NullPointer( |
| const Expr *NullPointer, const MatchFinder::MatchResult &, |
| TransferState<PointerNullabilityLattice> &State) { |
| if (auto *PointerVal = getPointerValueFromExpr(NullPointer, State.Env)) { |
| initNullPointer(*PointerVal, State.Env.getDataflowAnalysisContext()); |
| } |
| } |
| |
| void transferValue_NotNullPointer( |
| const Expr *NotNullPointer, const MatchFinder::MatchResult &, |
| TransferState<PointerNullabilityLattice> &State) { |
| if (auto *PointerVal = getPointerValueFromExpr(NotNullPointer, State.Env)) { |
| initPointerNullState(*PointerVal, State.Env.getDataflowAnalysisContext(), |
| NullabilityKind::NonNull); |
| } |
| } |
| |
| void transferValue_Pointer(const Expr *PointerExpr, |
| const MatchFinder::MatchResult &Result, |
| TransferState<PointerNullabilityLattice> &State) { |
| auto *PointerVal = getPointerValueFromExpr(PointerExpr, State.Env); |
| if (!PointerVal) return; |
| |
| initPointerFromTypeNullability(*PointerVal, PointerExpr, State); |
| |
| if (const auto *Cast = dyn_cast<CastExpr>(PointerExpr); |
| Cast && Cast->getCastKind() == CK_LValueToRValue) { |
| PointerValue *NewPointerVal = unpackPointerValue(*PointerVal, State.Env); |
| if (!NewPointerVal) return; |
| if (StorageLocation *Loc = |
| State.Env.getStorageLocation(*Cast->getSubExpr())) |
| State.Env.setValue(*Loc, *NewPointerVal); |
| State.Env.setValue(*PointerExpr, *NewPointerVal); |
| } |
| } |
| |
| // TODO(b/233582219): Implement promotion of nullability for initially |
| // unknown pointers when there is evidence that it is nullable, for example |
| // when the pointer is compared to nullptr, or casted to boolean. |
| void transferValue_NullCheckComparison( |
| const BinaryOperator *BinaryOp, const MatchFinder::MatchResult &result, |
| TransferState<PointerNullabilityLattice> &State) { |
| auto &A = State.Env.arena(); |
| |
| auto *LHS = getPointerValueFromExpr(BinaryOp->getLHS(), State.Env); |
| auto *RHS = getPointerValueFromExpr(BinaryOp->getRHS(), State.Env); |
| |
| if (!LHS || !RHS) return; |
| if (!hasPointerNullState(*LHS) || !hasPointerNullState(*RHS)) return; |
| |
| auto *LHSNull = getPointerNullState(*LHS).IsNull; |
| auto *RHSNull = getPointerNullState(*RHS).IsNull; |
| |
| // If the null state of either pointer is "top", the result of the comparison |
| // is a top bool, and we don't have any knowledge we can add to the flow |
| // condition. |
| if (LHSNull == nullptr || RHSNull == nullptr) { |
| State.Env.setValue(*BinaryOp, A.makeTopValue()); |
| return; |
| } |
| |
| // Special case: Are we comparing against `nullptr`? |
| // We can avoid modifying the flow condition in this case and simply propagate |
| // the nullability of the other operand (potentially with a negation). |
| if (LHSNull == &A.makeLiteral(true)) { |
| if (BinaryOp->getOpcode() == BO_EQ) |
| State.Env.setValue(*BinaryOp, A.makeBoolValue(*RHSNull)); |
| else |
| State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(*RHSNull))); |
| return; |
| } |
| if (RHSNull == &A.makeLiteral(true)) { |
| if (BinaryOp->getOpcode() == BO_EQ) |
| State.Env.setValue(*BinaryOp, A.makeBoolValue(*LHSNull)); |
| else |
| State.Env.setValue(*BinaryOp, A.makeBoolValue(A.makeNot(*LHSNull))); |
| return; |
| } |
| |
| // Boolean representing the comparison between the two pointer values, |
| // automatically created by the dataflow framework. |
| auto &PointerComparison = |
| cast<BoolValue>(State.Env.getValue(*BinaryOp))->formula(); |
| |
| CHECK(BinaryOp->getOpcode() == BO_EQ || BinaryOp->getOpcode() == BO_NE); |
| auto &PointerEQ = BinaryOp->getOpcode() == BO_EQ |
| ? PointerComparison |
| : A.makeNot(PointerComparison); |
| auto &PointerNE = BinaryOp->getOpcode() == BO_EQ |
| ? A.makeNot(PointerComparison) |
| : PointerComparison; |
| |
| // nullptr == nullptr |
| State.Env.assume(A.makeImplies(A.makeAnd(*LHSNull, *RHSNull), PointerEQ)); |
| // nullptr != notnull |
| State.Env.assume( |
| A.makeImplies(A.makeAnd(*LHSNull, A.makeNot(*RHSNull)), PointerNE)); |
| // notnull != nullptr |
| State.Env.assume( |
| A.makeImplies(A.makeAnd(A.makeNot(*LHSNull), *RHSNull), PointerNE)); |
| } |
| |
| void transferValue_NullCheckImplicitCastPtrToBool( |
| const Expr *CastExpr, const MatchFinder::MatchResult &, |
| TransferState<PointerNullabilityLattice> &State) { |
| auto &A = State.Env.arena(); |
| auto *PointerVal = |
| getPointerValueFromExpr(CastExpr->IgnoreImplicit(), State.Env); |
| if (!PointerVal) return; |
| |
| auto Nullability = getPointerNullState(*PointerVal); |
| if (Nullability.IsNull != nullptr) |
| State.Env.setValue(*CastExpr, |
| A.makeBoolValue(A.makeNot(*Nullability.IsNull))); |
| else |
| State.Env.setValue(*CastExpr, A.makeTopValue()); |
| } |
| |
| void initializeOutputParameter(const Expr *Arg, dataflow::Environment &Env, |
| QualType ParamTy) { |
| // When a function has an "output parameter" - a non-const pointer or |
| // reference to a pointer of unknown nullability - assume that the function |
| // may set the pointer to non-null. |
| // |
| // For example, in the following code sequence we assume that the function may |
| // modify the pointer in a way that makes a subsequent dereference safe: |
| // |
| // void maybeModify(int ** _Nonnull); |
| // |
| // int *p = nullptr; |
| // initializePointer(&p); |
| // *p; // safe |
| |
| if (ParamTy.isNull()) return; |
| if (ParamTy->getPointeeType().isNull()) return; |
| if (!isSupportedRawPointerType(ParamTy->getPointeeType())) return; |
| if (ParamTy->getPointeeType().isConstQualified()) return; |
| |
| // TODO(b/298200521): This should extend support to annotations that suggest |
| // different in/out state |
| TypeNullability InnerNullability = |
| getNullabilityAnnotationsFromType(ParamTy->getPointeeType()); |
| if (InnerNullability.front().concrete() != NullabilityKind::Unspecified) |
| return; |
| |
| StorageLocation *Loc = nullptr; |
| if (ParamTy->isPointerType()) { |
| if (PointerValue *OuterPointer = getPointerValueFromExpr(Arg, Env)) |
| Loc = &OuterPointer->getPointeeLoc(); |
| } else if (ParamTy->isReferenceType()) { |
| Loc = Env.getStorageLocation(*Arg); |
| } |
| if (Loc == nullptr) return; |
| |
| auto *InnerPointer = |
| cast<PointerValue>(Env.createValue(ParamTy->getPointeeType())); |
| initPointerNullState(*InnerPointer, Env.getDataflowAnalysisContext(), |
| NullabilityKind::Unspecified); |
| |
| Env.setValue(*Loc, *InnerPointer); |
| } |
| |
| void transferValue_CallExpr(const CallExpr *CallExpr, |
| const MatchFinder::MatchResult &Result, |
| TransferState<PointerNullabilityLattice> &State) { |
| // The dataflow framework itself does not create values for `CallExpr`s. |
| // However, we need these in some cases, so we produce them ourselves. |
| |
| StorageLocation *Loc = nullptr; |
| if (CallExpr->isGLValue()) { |
| // The function returned a reference. Create a storage location for the |
| // expression so that if code creates a pointer from the reference, we will |
| // produce a `PointerValue`. |
| Loc = State.Env.getStorageLocation(*CallExpr); |
| if (!Loc) { |
| // This is subtle: We call `createStorageLocation(QualType)`, not |
| // `createStorageLocation(const Expr &)`, so that we create a new |
| // storage location every time. |
| Loc = &State.Env.createStorageLocation(CallExpr->getType()); |
| State.Env.setStorageLocation(*CallExpr, *Loc); |
| } |
| } |
| |
| if (isSupportedRawPointerType(CallExpr->getType())) { |
| // Create a pointer so that we can attach nullability to it and have the |
| // nullability propagate with the pointer. |
| auto *PointerVal = getPointerValueFromExpr(CallExpr, State.Env); |
| if (!PointerVal) { |
| PointerVal = |
| cast<PointerValue>(State.Env.createValue(CallExpr->getType())); |
| } |
| initPointerFromTypeNullability(*PointerVal, CallExpr, State); |
| |
| if (Loc != nullptr) |
| State.Env.setValue(*Loc, *PointerVal); |
| else |
| // `Loc` is set iff `CallExpr` is a glvalue, so we know here that it must |
| // be a prvalue. |
| State.Env.setValue(*CallExpr, *PointerVal); |
| } |
| |
| // Make output parameters (with unknown nullability) initialized to unknown. |
| const auto *FuncDecl = CallExpr->getDirectCallee(); |
| if (!FuncDecl) return; |
| if (FuncDecl->getNumParams() != CallExpr->getNumArgs()) return; |
| if (auto *II = FuncDecl->getDeclName().getAsIdentifierInfo(); |
| II && II->isStr("__assert_nullability")) { |
| return; |
| } |
| for (unsigned i = 0; i < CallExpr->getNumArgs(); ++i) { |
| const auto *Arg = CallExpr->getArg(i); |
| initializeOutputParameter(Arg, State.Env, |
| FuncDecl->getParamDecl(i)->getType()); |
| } |
| } |
| |
| void transferValue_AccessorCall( |
| const CXXMemberCallExpr *MCE, const MatchFinder::MatchResult &Result, |
| TransferState<PointerNullabilityLattice> &State) { |
| auto *member = Result.Nodes.getNodeAs<clang::ValueDecl>("member-decl"); |
| PointerValue *PointerVal = nullptr; |
| if (dataflow::RecordStorageLocation *RecordLoc = |
| dataflow::getImplicitObjectLocation(*MCE, State.Env)) { |
| StorageLocation *Loc = RecordLoc->getChild(*member); |
| PointerVal = dyn_cast_or_null<PointerValue>(State.Env.getValue(*Loc)); |
| } |
| if (!PointerVal) { |
| // Use value that may have been set by the builtin transfer function or by |
| // `ensurePointerHasValue()`. |
| PointerVal = getPointerValueFromExpr(MCE, State.Env); |
| } |
| if (PointerVal) { |
| State.Env.setValue(*MCE, *PointerVal); |
| initPointerFromTypeNullability(*PointerVal, MCE, State); |
| } |
| } |
| |
| void transferValue_ConstMemberCall( |
| const CXXMemberCallExpr *MCE, const MatchFinder::MatchResult &Result, |
| TransferState<PointerNullabilityLattice> &State) { |
| if (!isSupportedRawPointerType(MCE->getType()) || !MCE->isPRValue()) { |
| // We can't handle it as a special case, but still need to handle it. |
| transferValue_CallExpr(MCE, Result, State); |
| return; |
| } |
| dataflow::RecordStorageLocation *RecordLoc = |
| dataflow::getImplicitObjectLocation(*MCE, State.Env); |
| if (RecordLoc == nullptr) { |
| // We can't handle it as a special case, but still need to handle it. |
| transferValue_CallExpr(MCE, Result, State); |
| return; |
| } |
| PointerValue *PointerVal = |
| State.Lattice.getConstMethodReturnValue(*RecordLoc, MCE, State.Env); |
| if (PointerVal) { |
| State.Env.setValue(*MCE, *PointerVal); |
| initPointerFromTypeNullability(*PointerVal, MCE, State); |
| } |
| } |
| |
| void transferValue_NonConstMemberCall( |
| const CXXMemberCallExpr *MCE, const MatchFinder::MatchResult &Result, |
| TransferState<PointerNullabilityLattice> &State) { |
| // When a non-const member function is called, reset all pointer-type fields |
| // of the implicit object. |
| if (dataflow::RecordStorageLocation *RecordLoc = |
| dataflow::getImplicitObjectLocation(*MCE, State.Env)) { |
| for (const auto [Field, FieldLoc] : RecordLoc->children()) { |
| if (!isSupportedRawPointerType(Field->getType())) continue; |
| Value *V = State.Env.createValue(Field->getType()); |
| State.Env.setValue(*FieldLoc, *V); |
| } |
| State.Lattice.clearConstMethodReturnValues(*RecordLoc); |
| } |
| // The nullability of the Expr itself still needs to be handled. |
| transferValue_CallExpr(MCE, Result, State); |
| } |
| |
| void transferType_DeclRefExpr(const DeclRefExpr *DRE, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(DRE, State, [&] { |
| auto Nullability = getNullabilityAnnotationsFromType(DRE->getType()); |
| State.Lattice.overrideNullabilityFromDecl(DRE->getDecl(), Nullability); |
| return Nullability; |
| }); |
| } |
| |
| void transferType_MemberExpr(const MemberExpr *ME, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(ME, State, [&]() { |
| auto BaseNullability = getNullabilityForChild(ME->getBase(), State); |
| QualType MemberType = ME->getType(); |
| // When a MemberExpr is a part of a member function call |
| // (a child of CXXMemberCallExpr), the MemberExpr models a |
| // partially-applied member function, which isn't a real C++ construct. |
| // The AST does not provide rich type information for such MemberExprs. |
| // Instead, the AST specifies a placeholder type, specifically |
| // BuiltinType::BoundMember. So we have to look at the type of the member |
| // function declaration. |
| if (ME->hasPlaceholderType(BuiltinType::BoundMember)) { |
| MemberType = ME->getMemberDecl()->getType(); |
| } |
| auto Nullability = substituteNullabilityAnnotationsInClassTemplate( |
| MemberType, BaseNullability, ME->getBase()->getType()); |
| State.Lattice.overrideNullabilityFromDecl(ME->getMemberDecl(), Nullability); |
| return Nullability; |
| }); |
| } |
| |
| void transferType_MemberCallExpr( |
| const CXXMemberCallExpr *MCE, const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(MCE, State, [&]() { |
| return ArrayRef(getNullabilityForChild(MCE->getCallee(), State)) |
| .take_front(countPointersInType(MCE)) |
| .vec(); |
| }); |
| } |
| |
| void transferType_CastExpr(const CastExpr *CE, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(CE, State, [&]() -> TypeNullability { |
| // Most casts that can convert ~unrelated types drop nullability in general. |
| // As a special case, preserve nullability of outer pointer types. |
| // For example, int* p; (void*)p; is a BitCast, but preserves nullability. |
| auto PreserveTopLevelPointers = [&](TypeNullability V) { |
| auto ArgNullability = getNullabilityForChild(CE->getSubExpr(), State); |
| const PointerType *ArgType = dyn_cast<PointerType>( |
| CE->getSubExpr()->getType().getCanonicalType().getTypePtr()); |
| const PointerType *CastType = |
| dyn_cast<PointerType>(CE->getType().getCanonicalType().getTypePtr()); |
| for (int I = 0; ArgType && CastType; ++I) { |
| V[I] = ArgNullability[I]; |
| ArgType = dyn_cast<PointerType>(ArgType->getPointeeType().getTypePtr()); |
| CastType = |
| dyn_cast<PointerType>(CastType->getPointeeType().getTypePtr()); |
| } |
| return V; |
| }; |
| |
| switch (CE->getCastKind()) { |
| // Casts between unrelated types: we can't say anything about nullability. |
| case CK_LValueBitCast: |
| case CK_BitCast: |
| case CK_LValueToRValueBitCast: |
| return PreserveTopLevelPointers(unspecifiedNullability(CE)); |
| |
| // Casts between equivalent types. |
| case CK_LValueToRValue: |
| case CK_NoOp: |
| case CK_AtomicToNonAtomic: |
| case CK_NonAtomicToAtomic: |
| case CK_AddressSpaceConversion: |
| return getNullabilityForChild(CE->getSubExpr(), State); |
| |
| // Controlled conversions between types |
| // TODO: these should be doable somehow |
| case CK_BaseToDerived: |
| case CK_DerivedToBase: |
| case CK_UncheckedDerivedToBase: |
| return PreserveTopLevelPointers(unspecifiedNullability(CE)); |
| case CK_UserDefinedConversion: |
| case CK_ConstructorConversion: |
| return unspecifiedNullability(CE); |
| |
| case CK_Dynamic: { |
| auto Result = unspecifiedNullability(CE); |
| // A dynamic_cast to pointer is null if the runtime check fails. |
| if (isa<PointerType>(CE->getType().getCanonicalType())) |
| Result.front() = NullabilityKind::Nullable; |
| return Result; |
| } |
| |
| // Primitive values have no nullability. |
| case CK_ToVoid: |
| case CK_MemberPointerToBoolean: |
| case CK_PointerToBoolean: |
| case CK_PointerToIntegral: |
| case CK_IntegralCast: |
| case CK_IntegralToBoolean: |
| case CK_IntegralToFloating: |
| case CK_FloatingToFixedPoint: |
| case CK_FixedPointToFloating: |
| case CK_FixedPointCast: |
| case CK_FixedPointToIntegral: |
| case CK_IntegralToFixedPoint: |
| case CK_FixedPointToBoolean: |
| case CK_FloatingToIntegral: |
| case CK_FloatingToBoolean: |
| case CK_BooleanToSignedIntegral: |
| case CK_FloatingCast: |
| case CK_FloatingRealToComplex: |
| case CK_FloatingComplexToReal: |
| case CK_FloatingComplexToBoolean: |
| case CK_FloatingComplexCast: |
| case CK_FloatingComplexToIntegralComplex: |
| case CK_IntegralRealToComplex: |
| case CK_IntegralComplexToReal: |
| case CK_IntegralComplexToBoolean: |
| case CK_IntegralComplexCast: |
| case CK_IntegralComplexToFloatingComplex: |
| return {}; |
| |
| // This can definitely be null! |
| case CK_NullToPointer: { |
| auto Nullability = getNullabilityAnnotationsFromType(CE->getType()); |
| // Despite the name `NullToPointer`, the destination type of the cast |
| // may be `nullptr_t` (which is, itself, not a pointer type). |
| if (!CE->getType()->isNullPtrType()) |
| Nullability.front() = NullabilityKind::Nullable; |
| return Nullability; |
| } |
| |
| // Pointers out of thin air, who knows? |
| case CK_IntegralToPointer: |
| return unspecifiedNullability(CE); |
| |
| // Decayed objects are never null. |
| case CK_ArrayToPointerDecay: |
| case CK_FunctionToPointerDecay: |
| return prepend(NullabilityKind::NonNull, |
| getNullabilityForChild(CE->getSubExpr(), State)); |
| |
| // Despite its name, the result type of `BuiltinFnToFnPtr` is a function, |
| // not a function pointer, so nullability doesn't change. |
| case CK_BuiltinFnToFnPtr: |
| return getNullabilityForChild(CE->getSubExpr(), State); |
| |
| // TODO: what is our model of member pointers? |
| case CK_BaseToDerivedMemberPointer: |
| case CK_DerivedToBaseMemberPointer: |
| case CK_NullToMemberPointer: |
| case CK_ReinterpretMemberPointer: |
| case CK_ToUnion: // and unions? |
| return unspecifiedNullability(CE); |
| |
| // TODO: Non-C/C++ constructs, do we care about these? |
| case CK_CPointerToObjCPointerCast: |
| case CK_ObjCObjectLValueCast: |
| case CK_MatrixCast: |
| case CK_VectorSplat: |
| case CK_BlockPointerToObjCPointerCast: |
| case CK_AnyPointerToBlockPointerCast: |
| case CK_ARCProduceObject: |
| case CK_ARCConsumeObject: |
| case CK_ARCReclaimReturnedObject: |
| case CK_ARCExtendBlockObject: |
| case CK_CopyAndAutoreleaseBlockObject: |
| case CK_ZeroToOCLOpaqueType: |
| case CK_IntToOCLSampler: |
| return unspecifiedNullability(CE); |
| |
| case CK_Dependent: |
| CHECK(false) << "Shouldn't see dependent casts here?"; |
| } |
| }); |
| } |
| |
| void transferType_MaterializeTemporaryExpr( |
| const MaterializeTemporaryExpr *MTE, const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(MTE, State, [&]() { |
| return getNullabilityForChild(MTE->getSubExpr(), State); |
| }); |
| } |
| |
| void transferType_CallExpr(const CallExpr *CE, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| // TODO: Check CallExpr arguments in the diagnoser against the nullability of |
| // parameters. |
| computeNullability(CE, State, [&]() { |
| // TODO(mboehme): Instead of relying on Clang to propagate nullability sugar |
| // to the `CallExpr`'s type, we should extract nullability directly from the |
| // callee `Expr . |
| auto Nullability = |
| substituteNullabilityAnnotationsInFunctionTemplate(CE->getType(), CE); |
| if (!Nullability.empty()) { |
| State.Lattice.overrideNullabilityFromDecl(CE->getCalleeDecl(), |
| Nullability); |
| } |
| return Nullability; |
| }); |
| } |
| |
| void transferType_UnaryOperator( |
| const UnaryOperator *UO, const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(UO, State, [&]() -> TypeNullability { |
| switch (UO->getOpcode()) { |
| case UO_AddrOf: |
| return prepend(NullabilityKind::NonNull, |
| getNullabilityForChild(UO->getSubExpr(), State)); |
| case UO_Deref: |
| return ArrayRef(getNullabilityForChild(UO->getSubExpr(), State)) |
| .drop_front() |
| .vec(); |
| |
| case UO_PostInc: |
| case UO_PostDec: |
| case UO_PreInc: |
| case UO_PreDec: |
| case UO_Plus: |
| case UO_Minus: |
| case UO_Not: |
| case UO_LNot: |
| case UO_Real: |
| case UO_Imag: |
| case UO_Extension: |
| return getNullabilityForChild(UO->getSubExpr(), State); |
| |
| case UO_Coawait: |
| // TODO: work out what to do here! |
| return unspecifiedNullability(UO); |
| } |
| }); |
| } |
| |
| void transferType_NewExpr(const CXXNewExpr *NE, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(NE, State, [&]() { |
| TypeNullability result = getNullabilityAnnotationsFromType(NE->getType()); |
| result.front() = NE->shouldNullCheckAllocation() ? NullabilityKind::Nullable |
| : NullabilityKind::NonNull; |
| return result; |
| }); |
| } |
| |
| void transferType_ArraySubscriptExpr( |
| const ArraySubscriptExpr *ASE, const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(ASE, State, [&]() { |
| auto &BaseNullability = getNullabilityForChild(ASE->getBase(), State); |
| QualType BaseType = ASE->getBase()->getType(); |
| CHECK(isSupportedRawPointerType(BaseType) || BaseType->isVectorType()); |
| return isSupportedRawPointerType(BaseType) |
| ? ArrayRef(BaseNullability).slice(1).vec() |
| : BaseNullability; |
| }); |
| } |
| |
| void transferType_ThisExpr(const CXXThisExpr *TE, |
| const MatchFinder::MatchResult &MR, |
| TransferState<PointerNullabilityLattice> &State) { |
| computeNullability(TE, State, [&]() { |
| TypeNullability result = getNullabilityAnnotationsFromType(TE->getType()); |
| result.front() = NullabilityKind::NonNull; |
| return result; |
| }); |
| } |
| |
| auto buildTypeTransferer() { |
| return CFGMatchSwitchBuilder<TransferState<PointerNullabilityLattice>>() |
| .CaseOfCFGStmt<DeclRefExpr>(ast_matchers::declRefExpr(), |
| transferType_DeclRefExpr) |
| .CaseOfCFGStmt<MemberExpr>(ast_matchers::memberExpr(), |
| transferType_MemberExpr) |
| .CaseOfCFGStmt<CXXMemberCallExpr>(ast_matchers::cxxMemberCallExpr(), |
| transferType_MemberCallExpr) |
| .CaseOfCFGStmt<CastExpr>(ast_matchers::castExpr(), transferType_CastExpr) |
| .CaseOfCFGStmt<MaterializeTemporaryExpr>( |
| ast_matchers::materializeTemporaryExpr(), |
| transferType_MaterializeTemporaryExpr) |
| .CaseOfCFGStmt<CallExpr>(ast_matchers::callExpr(), transferType_CallExpr) |
| .CaseOfCFGStmt<UnaryOperator>(ast_matchers::unaryOperator(), |
| transferType_UnaryOperator) |
| .CaseOfCFGStmt<CXXNewExpr>(ast_matchers::cxxNewExpr(), |
| transferType_NewExpr) |
| .CaseOfCFGStmt<ArraySubscriptExpr>(ast_matchers::arraySubscriptExpr(), |
| transferType_ArraySubscriptExpr) |
| .CaseOfCFGStmt<CXXThisExpr>(ast_matchers::cxxThisExpr(), |
| transferType_ThisExpr) |
| .Build(); |
| } |
| |
| auto buildValueTransferer() { |
| // The value transfer functions must establish: |
| // - if we're transferring over an Expr |
| // - and the Expr has a supported pointer type |
| // - and the Expr's value is modeled by the framework (or this analysis) |
| // - then the PointerValue has nullability properties (is_null/from_nullable) |
| return CFGMatchSwitchBuilder<TransferState<PointerNullabilityLattice>>() |
| // Handles initialization of the null states of pointers. |
| .CaseOfCFGStmt<Expr>(isAddrOf(), transferValue_NotNullPointer) |
| // TODO(mboehme): I believe we should be able to move handling of null |
| // pointers to the non-flow-sensitive part of the analysis. |
| .CaseOfCFGStmt<Expr>(isNullPointerLiteral(), transferValue_NullPointer) |
| .CaseOfCFGStmt<CXXMemberCallExpr>(isSupportedPointerAccessorCall(), |
| transferValue_AccessorCall) |
| .CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(), |
| transferValue_ConstMemberCall) |
| .CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(), |
| transferValue_NonConstMemberCall) |
| .CaseOfCFGStmt<CallExpr>(isCallExpr(), transferValue_CallExpr) |
| .CaseOfCFGStmt<Expr>(isPointerExpr(), transferValue_Pointer) |
| // Handles comparison between 2 pointers. |
| .CaseOfCFGStmt<BinaryOperator>(isPointerCheckBinOp(), |
| transferValue_NullCheckComparison) |
| // Handles checking of pointer as boolean. |
| .CaseOfCFGStmt<Expr>(isImplicitCastPointerToBool(), |
| transferValue_NullCheckImplicitCastPtrToBool) |
| .Build(); |
| } |
| |
| // Ensure all prvalue expressions of pointer type have a `PointerValue` |
| // associated with them so we can track nullability through them. |
| void ensurePointerHasValue(const CFGElement &Elt, Environment &Env) { |
| auto S = Elt.getAs<CFGStmt>(); |
| if (!S) return; |
| |
| auto *E = dyn_cast<Expr>(S->getStmt()); |
| if (E == nullptr || !E->isPRValue() || |
| !isSupportedRawPointerType(E->getType())) |
| return; |
| |
| if (Env.getValue(*E) == nullptr) |
| // `createValue()` always produces a value for pointer types. |
| Env.setValue(*E, *Env.createValue(E->getType())); |
| } |
| |
| } // namespace |
| |
| PointerNullabilityAnalysis::PointerNullabilityAnalysis(ASTContext &Context) |
| : DataflowAnalysis<PointerNullabilityAnalysis, PointerNullabilityLattice>( |
| Context), |
| TypeTransferer(buildTypeTransferer()), |
| ValueTransferer(buildValueTransferer()) {} |
| |
| PointerTypeNullability PointerNullabilityAnalysis::assignNullabilityVariable( |
| const ValueDecl *D, dataflow::Arena &A) { |
| auto [It, Inserted] = NFS.DeclTopLevelNullability.try_emplace(D); |
| if (Inserted) It->second = PointerTypeNullability::createSymbolic(A); |
| return It->second; |
| } |
| |
| void PointerNullabilityAnalysis::transfer(const CFGElement &Elt, |
| PointerNullabilityLattice &Lattice, |
| Environment &Env) { |
| TransferState<PointerNullabilityLattice> State(Lattice, Env); |
| |
| ensurePointerHasValue(Elt, Env); |
| TypeTransferer(Elt, getASTContext(), State); |
| ValueTransferer(Elt, getASTContext(), State); |
| } |
| |
| static const Formula *mergeFormulas(const Formula *Bool1, |
| const Environment &Env1, |
| const Formula *Bool2, |
| const Environment &Env2, |
| Environment &MergedEnv) { |
| if (Bool1 == Bool2) { |
| return Bool1; |
| } |
| |
| if (Bool1 == nullptr || Bool2 == nullptr) return nullptr; |
| |
| auto &A = MergedEnv.arena(); |
| |
| // If `Bool1` and `Bool2` is constrained to the same true / false value, that |
| // can serve as the return value - this simplifies the flow condition tracked |
| // in `MergedEnv`. Otherwise, information about which path was taken is used |
| // to associate the return value with `Bool1` and `Bool2`. |
| if (Env1.proves(*Bool1)) { |
| if (Env2.proves(*Bool2)) { |
| return &A.makeLiteral(true); |
| } |
| } else if (Env1.proves(A.makeNot(*Bool1)) && Env2.proves(A.makeNot(*Bool2))) { |
| return &A.makeLiteral(false); |
| } |
| |
| auto &MergedBool = A.makeAtomRef(A.makeAtom()); |
| // TODO(b/233582219): Flow conditions are not necessarily mutually |
| // exclusive, a fix is in order: https://reviews.llvm.org/D130270. Update |
| // this section when the patch is commited. |
| auto FC1 = Env1.getFlowConditionToken(); |
| auto FC2 = Env2.getFlowConditionToken(); |
| MergedEnv.assume(A.makeOr( |
| A.makeAnd(A.makeAtomRef(FC1), A.makeEquals(MergedBool, *Bool1)), |
| A.makeAnd(A.makeAtomRef(FC2), A.makeEquals(MergedBool, *Bool2)))); |
| return &MergedBool; |
| } |
| |
| bool PointerNullabilityAnalysis::merge(QualType Type, const Value &Val1, |
| const Environment &Env1, |
| const Value &Val2, |
| const Environment &Env2, |
| Value &MergedVal, |
| Environment &MergedEnv) { |
| if (!isSupportedRawPointerType(Type)) { |
| return false; |
| } |
| |
| if (!hasPointerNullState(cast<PointerValue>(Val1)) || |
| !hasPointerNullState(cast<PointerValue>(Val2))) { |
| return false; |
| } |
| |
| auto &MergedPointerVal = cast<PointerValue>(MergedVal); |
| DataflowAnalysisContext &Ctx = MergedEnv.getDataflowAnalysisContext(); |
| auto &A = MergedEnv.arena(); |
| |
| auto Nullability1 = getPointerNullState(cast<PointerValue>(Val1)); |
| auto Nullability2 = getPointerNullState(cast<PointerValue>(Val2)); |
| |
| // Initialize `MergedPointerVal`'s nullability properties with atoms. These |
| // are potentially replaced with "top" below. |
| assert(!hasPointerNullState(MergedPointerVal)); |
| initPointerNullState(MergedPointerVal, Ctx); |
| auto MergedNullability = getPointerNullState(MergedPointerVal); |
| assert(MergedNullability.FromNullable != nullptr); |
| assert(MergedNullability.IsNull != nullptr); |
| |
| if (auto *FromNullable = |
| mergeFormulas(Nullability1.FromNullable, Env1, |
| Nullability2.FromNullable, Env2, MergedEnv)) |
| MergedEnv.assume( |
| A.makeEquals(*MergedNullability.FromNullable, *FromNullable)); |
| else |
| forgetFromNullable(MergedPointerVal, Ctx); |
| |
| if (auto *Null = mergeFormulas(Nullability1.IsNull, Env1, Nullability2.IsNull, |
| Env2, MergedEnv)) |
| MergedEnv.assume(A.makeEquals(*MergedNullability.IsNull, *Null)); |
| else |
| forgetIsNull(MergedPointerVal, Ctx); |
| |
| return true; |
| } |
| |
| ComparisonResult PointerNullabilityAnalysis::compare(QualType Type, |
| const Value &Val1, |
| const Environment &Env1, |
| const Value &Val2, |
| const Environment &Env2) { |
| if (const auto *PointerVal1 = dyn_cast<PointerValue>(&Val1)) { |
| const auto &PointerVal2 = cast<PointerValue>(Val2); |
| |
| if (&PointerVal1->getPointeeLoc() != &PointerVal2.getPointeeLoc()) |
| return ComparisonResult::Different; |
| |
| if (hasPointerNullState(*PointerVal1) != hasPointerNullState(PointerVal2)) |
| return ComparisonResult::Different; |
| |
| if (!hasPointerNullState(*PointerVal1)) return ComparisonResult::Same; |
| |
| auto Nullability1 = getPointerNullState(*PointerVal1); |
| auto Nullability2 = getPointerNullState(PointerVal2); |
| |
| // Ideally, we would be checking for equivalence of formulas, but that's |
| // expensive, so we simply check for identity instead. |
| return Nullability1.FromNullable == Nullability2.FromNullable && |
| Nullability1.IsNull == Nullability2.IsNull |
| ? ComparisonResult::Same |
| : ComparisonResult::Different; |
| } |
| |
| return ComparisonResult::Unknown; |
| } |
| |
| // Returns the result of widening a nullability property. |
| // `Prev` is the formula in the previous iteration, `Cur` is the formula in the |
| // current iteration. |
| // If the two formulas are equivalent (though not necessarily identical), |
| // returns `Cur`, as this is the formula that is appropriate to use in the |
| // current environment (where we will produce the widened pointer). Otherwise, |
| // returns null, to indicate that the property should be widened to "top". |
| static const Formula *widenNullabilityProperty(const Formula *Prev, |
| const Environment &PrevEnv, |
| const Formula *Cur, |
| Environment &CurEnv) { |
| if (Prev == Cur) return Cur; |
| if (Prev == nullptr || Cur == nullptr) return nullptr; |
| |
| Arena &A = CurEnv.arena(); |
| |
| if (PrevEnv.proves(*Prev)) { |
| if (CurEnv.proves(*Cur)) return Cur; |
| } else if (PrevEnv.proves(A.makeNot(*Prev)) && |
| CurEnv.proves(A.makeNot(*Cur))) { |
| return Cur; |
| } |
| |
| return nullptr; |
| } |
| |
| Value *PointerNullabilityAnalysis::widen(QualType Type, Value &Prev, |
| const Environment &PrevEnv, |
| Value &Current, |
| Environment &CurrentEnv) { |
| // Widen pointers to a pointer with a "top" storage location. |
| if (auto *PrevPtr = dyn_cast<PointerValue>(&Prev)) { |
| auto &CurPtr = cast<PointerValue>(Current); |
| |
| DataflowAnalysisContext &DACtx = CurrentEnv.getDataflowAnalysisContext(); |
| assert(&PrevEnv.getDataflowAnalysisContext() == &DACtx); |
| |
| if (!hasPointerNullState(*PrevPtr) || !hasPointerNullState(CurPtr)) |
| return nullptr; |
| |
| auto [FromNullablePrev, NullPrev] = getPointerNullState(*PrevPtr); |
| auto [FromNullableCur, NullCur] = getPointerNullState(CurPtr); |
| |
| const Formula *FromNullableWidened = widenNullabilityProperty( |
| FromNullablePrev, PrevEnv, FromNullableCur, CurrentEnv); |
| const Formula *NullWidened = |
| widenNullabilityProperty(NullPrev, PrevEnv, NullCur, CurrentEnv); |
| |
| // Is `PrevPtr` already equivalent to the widened pointer we are about to |
| // produce? If so, return `PrevPtr` to signal this. |
| if (&PrevPtr->getPointeeLoc() == |
| &getTopStorageLocation(DACtx, PrevPtr->getPointeeLoc().getType()) && |
| // Check whether |
| // - the previous nullability property is equivalent to the current |
| // property (in which case the widened property is non-null), or |
| // - the previous nullability property is already "top" (i.e. null) |
| (FromNullableWidened != nullptr || FromNullablePrev == nullptr) && |
| (NullWidened != nullptr || NullPrev == nullptr)) { |
| return PrevPtr; |
| } |
| |
| // Widen the nullability properties. |
| auto &WidenedPtr = CurrentEnv.create<PointerValue>( |
| getTopStorageLocation(DACtx, CurPtr.getPointeeLoc().getType())); |
| initPointerNullState(WidenedPtr, DACtx); |
| auto WidenedNullability = getPointerNullState(WidenedPtr); |
| assert(WidenedNullability.FromNullable != nullptr); |
| assert(WidenedNullability.IsNull != nullptr); |
| |
| auto &A = CurrentEnv.arena(); |
| if (FromNullableWidened != nullptr) |
| CurrentEnv.assume( |
| A.makeEquals(*WidenedNullability.FromNullable, *FromNullableWidened)); |
| else |
| forgetFromNullable(WidenedPtr, DACtx); |
| if (NullWidened != nullptr) |
| CurrentEnv.assume(A.makeEquals(*WidenedNullability.IsNull, *NullWidened)); |
| else |
| forgetIsNull(WidenedPtr, DACtx); |
| |
| return &WidenedPtr; |
| } |
| |
| return nullptr; |
| } |
| |
| StorageLocation &PointerNullabilityAnalysis::getTopStorageLocation( |
| DataflowAnalysisContext &DACtx, QualType Ty) { |
| auto [It, Inserted] = TopStorageLocations.try_emplace(Ty, nullptr); |
| if (Inserted) It->second = &DACtx.createStorageLocation(Ty); |
| return *It->second; |
| } |
| |
| } // namespace clang::tidy::nullability |