| // 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/inference/collect_evidence.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/base/nullability.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/log/check.h" |
| #include "nullability/ast_helpers.h" |
| #include "nullability/inference/inferable.h" |
| #include "nullability/inference/inference.proto.h" |
| #include "nullability/inference/slot_fingerprint.h" |
| #include "nullability/macro_arg_capture.h" |
| #include "nullability/pointer_nullability.h" |
| #include "nullability/pointer_nullability_analysis.h" |
| #include "nullability/pointer_nullability_lattice.h" |
| #include "nullability/pragma.h" |
| #include "nullability/type_nullability.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/Attr.h" |
| #include "clang/AST/Attrs.inc" |
| #include "clang/AST/Decl.h" |
| #include "clang/AST/DeclBase.h" |
| #include "clang/AST/DeclCXX.h" |
| #include "clang/AST/DeclGroup.h" |
| #include "clang/AST/Expr.h" |
| #include "clang/AST/ExprCXX.h" |
| #include "clang/AST/OperationKinds.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/AST/Stmt.h" |
| #include "clang/AST/Type.h" |
| #include "clang/AST/TypeLoc.h" |
| #include "clang/Analysis/CFG.h" |
| #include "clang/Analysis/FlowSensitive/ASTOps.h" |
| #include "clang/Analysis/FlowSensitive/AdornedCFG.h" |
| #include "clang/Analysis/FlowSensitive/Arena.h" |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h" |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" |
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" |
| #include "clang/Analysis/FlowSensitive/Formula.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/Builtins.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/OperatorKinds.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Basic/Specifiers.h" |
| #include "clang/Index/USRGeneration.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/FunctionExtras.h" |
| #include "llvm/ADT/STLFunctionalExtras.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/raw_ostream.h" |
| |
| namespace clang::tidy::nullability { |
| using ::clang::dataflow::DataflowAnalysisContext; |
| using ::clang::dataflow::Environment; |
| using ::clang::dataflow::Formula; |
| using ::clang::dataflow::RecordInitListHelper; |
| using ::clang::dataflow::WatchedLiteralsSolver; |
| |
| using ConcreteNullabilityCache = |
| absl::flat_hash_map<const Decl *, |
| std::optional<const PointerTypeNullability>>; |
| |
| std::string_view getOrGenerateUSR(USRCache &Cache, const Decl &Decl) { |
| auto [It, Inserted] = Cache.try_emplace(&Decl); |
| if (Inserted) { |
| llvm::SmallString<128> USR; |
| if (!index::generateUSRForDecl(&Decl, USR)) It->second = USR.str(); |
| } |
| return It->second; |
| } |
| |
| static llvm::DenseSet<const CXXMethodDecl *> getOverridden( |
| const CXXMethodDecl *Derived) { |
| llvm::DenseSet<const CXXMethodDecl *> Overridden; |
| for (const CXXMethodDecl *Base : Derived->overridden_methods()) { |
| Overridden.insert(Base); |
| for (const CXXMethodDecl *BaseOverridden : getOverridden(Base)) { |
| Overridden.insert(BaseOverridden); |
| } |
| } |
| return Overridden; |
| } |
| |
| /// Shared base class for visitors that walk the AST for evidence collection |
| /// purposes, to ensure they see the same nodes. |
| template <typename Derived> |
| struct EvidenceLocationsWalker : public RecursiveASTVisitor<Derived> { |
| // We do want to see concrete code, including function instantiations. |
| bool shouldVisitTemplateInstantiations() const { return true; } |
| |
| // In order to collect from more default member initializers, we do want to |
| // see defaulted default constructors, which are implicitly-defined |
| // functions whether the declaration is implicit or explicit. We also want |
| // to see lambda bodies in the form of operator() definitions that are not |
| // themselves implicit but show up in an implicit context. |
| bool shouldVisitImplicitCode() const { return true; } |
| }; |
| |
| using VirtualMethodOverridesMap = |
| absl::flat_hash_map<const CXXMethodDecl *, |
| llvm::DenseSet<const CXXMethodDecl *>>; |
| |
| /// Collect a map from virtual methods to a set of their overrides. |
| static VirtualMethodOverridesMap getVirtualMethodOverrides(ASTContext &Ctx) { |
| struct Walker : public EvidenceLocationsWalker<Walker> { |
| VirtualMethodOverridesMap Out; |
| |
| bool VisitCXXMethodDecl(const CXXMethodDecl *MD) { |
| if (MD->isVirtual()) { |
| for (const auto *O : getOverridden(MD)) { |
| Out[O].insert(MD); |
| } |
| } |
| return true; |
| } |
| }; |
| |
| Walker W; |
| W.TraverseAST(Ctx); |
| return std::move(W.Out); |
| } |
| |
| namespace { |
| enum VirtualMethodEvidenceFlowDirection { |
| kFromBaseToDerived, |
| kFromDerivedToBase, |
| kBoth, |
| }; |
| } // namespace |
| |
| static VirtualMethodEvidenceFlowDirection getFlowDirection(Evidence::Kind Kind, |
| bool ForReturnSlot) { |
| switch (Kind) { |
| case Evidence::ANNOTATED_NONNULL: |
| case Evidence::UNCHECKED_DEREFERENCE: |
| case Evidence::NONNULL_ARGUMENT: |
| case Evidence::NONNULL_RETURN: |
| case Evidence::ASSIGNED_TO_NONNULL: |
| case Evidence::ABORT_IF_NULL: |
| case Evidence::ARITHMETIC: |
| case Evidence::GCC_NONNULL_ATTRIBUTE: |
| // Evidence pointing toward Unknown is only used to prevent Nonnull |
| // inferences; it cannot override Nullable. So propagate it in the same |
| // direction we do for Nonnull-pointing evidence. |
| case Evidence::ANNOTATED_UNKNOWN: |
| case Evidence::UNKNOWN_ARGUMENT: |
| case Evidence::UNKNOWN_RETURN: |
| return ForReturnSlot ? kFromBaseToDerived : kFromDerivedToBase; |
| case Evidence::ANNOTATED_NULLABLE: |
| case Evidence::NULLABLE_ARGUMENT: |
| case Evidence::NULLABLE_RETURN: |
| case Evidence::ASSIGNED_TO_MUTABLE_NULLABLE: |
| case Evidence::ASSIGNED_FROM_NULLABLE: |
| case Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER: |
| return ForReturnSlot ? kFromDerivedToBase : kFromBaseToDerived; |
| case Evidence::NULLABLE_REFERENCE_RETURN: |
| case Evidence::NONNULL_REFERENCE_RETURN: |
| case Evidence::UNKNOWN_REFERENCE_RETURN: |
| case Evidence::NULLABLE_REFERENCE_ARGUMENT: |
| case Evidence::NONNULL_REFERENCE_ARGUMENT: |
| case Evidence::UNKNOWN_REFERENCE_ARGUMENT: |
| return kBoth; |
| } |
| } |
| |
| static llvm::DenseSet<const CXXMethodDecl *> |
| getAdditionalTargetsForVirtualMethod( |
| const CXXMethodDecl *MD, Evidence::Kind Kind, bool ForReturnSlot, |
| const VirtualMethodOverridesMap &OverridesMap) { |
| VirtualMethodEvidenceFlowDirection FlowDirection = |
| getFlowDirection(Kind, ForReturnSlot); |
| switch (FlowDirection) { |
| case kFromBaseToDerived: |
| if (auto It = OverridesMap.find(MD); It != OverridesMap.end()) |
| return It->second; |
| return {}; |
| case kFromDerivedToBase: |
| return getOverridden(MD); |
| case kBoth: |
| llvm::DenseSet<const CXXMethodDecl *> Results = getOverridden(MD); |
| if (auto It = OverridesMap.find(MD); It != OverridesMap.end()) |
| Results.insert(It->second.begin(), It->second.end()); |
| return Results; |
| } |
| } |
| |
| llvm::unique_function<EvidenceEmitter> evidenceEmitter( |
| llvm::unique_function<void(const Evidence &) const> Emit, |
| USRCache &USRCache, ASTContext &Ctx) { |
| class EvidenceEmitterImpl { |
| public: |
| EvidenceEmitterImpl( |
| llvm::unique_function<void(const Evidence &) const> Emit, |
| nullability::USRCache &USRCache, ASTContext &Ctx) |
| : Emit(std::move(Emit)), |
| USRCache(USRCache), |
| OverridesMap(getVirtualMethodOverrides(Ctx)) {} |
| |
| void operator()(const Decl &Target, Slot S, Evidence::Kind Kind, |
| SourceLocation Loc) const { |
| CHECK(isInferenceTarget(Target)) |
| << "Evidence emitted for a Target which is not an inference target: " |
| << (dyn_cast<NamedDecl>(&Target) |
| ? dyn_cast<NamedDecl>(&Target)->getQualifiedNameAsString() |
| : "not a named decl"); |
| |
| Evidence E; |
| E.set_slot(S); |
| E.set_kind(Kind); |
| |
| std::string_view USR = getOrGenerateUSR(USRCache, Target); |
| if (USR.empty()) return; // Can't emit without a USR |
| E.mutable_symbol()->set_usr(USR); |
| |
| // TODO: make collecting and propagating location information optional? |
| auto &SM = |
| Target.getDeclContext()->getParentASTContext().getSourceManager(); |
| // TODO: are macro locations actually useful enough for debugging? |
| // we could leave them out, and make room for non-macro samples. |
| if (Loc = SM.getFileLoc(Loc); Loc.isValid()) |
| E.set_location(Loc.printToString(SM)); |
| |
| Emit(E); |
| |
| // Virtual methods and their overrides constrain each other's |
| // nullabilities, so propagate evidence in the appropriate direction based |
| // on the evidence kind and whether the evidence is for the return type or |
| // a parameter type. |
| if (auto *MD = dyn_cast<CXXMethodDecl>(&Target); MD && MD->isVirtual()) { |
| for (const auto *O : getAdditionalTargetsForVirtualMethod( |
| MD, Kind, S == SLOT_RETURN_TYPE, OverridesMap)) { |
| USR = getOrGenerateUSR(USRCache, *O); |
| if (USR.empty()) return; // Can't emit without a USR |
| E.mutable_symbol()->set_usr(USR); |
| Emit(E); |
| } |
| } |
| } |
| |
| private: |
| llvm::unique_function<void(const Evidence &) const> Emit; |
| nullability::USRCache &USRCache; |
| const VirtualMethodOverridesMap OverridesMap; |
| }; |
| return EvidenceEmitterImpl(std::move(Emit), USRCache, Ctx); |
| } |
| |
| namespace { |
| class InferableSlot { |
| public: |
| InferableSlot(PointerTypeNullability Nullability, Slot Slot, const Decl &Decl) |
| : SymbolicNullability(Nullability), |
| TargetSlot(Slot), |
| InferenceTarget(Decl) {} |
| |
| const PointerTypeNullability &getSymbolicNullability() const { |
| return SymbolicNullability; |
| } |
| Slot getTargetSlot() const { return TargetSlot; } |
| const Decl &getInferenceTarget() const { return InferenceTarget; } |
| |
| private: |
| const PointerTypeNullability SymbolicNullability; |
| const Slot TargetSlot; |
| const Decl &InferenceTarget; |
| }; |
| } // namespace |
| |
| /// If Stmt is a dereference, returns its target and location. |
| static std::pair<const Expr *, SourceLocation> describeDereference( |
| const Stmt &Stmt) { |
| if (auto *Op = dyn_cast<UnaryOperator>(&Stmt); |
| Op && Op->getOpcode() == UO_Deref) { |
| return {Op->getSubExpr(), Op->getOperatorLoc()}; |
| } |
| if (auto *ME = dyn_cast<MemberExpr>(&Stmt); ME && ME->isArrow()) { |
| return {ME->getBase(), ME->getOperatorLoc()}; |
| } |
| // pointers to members; at the time of writing, they aren't a supported |
| // pointer type, so this is a no-op. |
| if (const auto *BO = dyn_cast<BinaryOperator>(&Stmt); |
| BO && (BO->getOpcode() == clang::BinaryOperatorKind::BO_PtrMemD || |
| BO->getOpcode() == clang::BinaryOperatorKind::BO_PtrMemI)) { |
| return {BO->getRHS(), BO->getOperatorLoc()}; |
| } |
| if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(&Stmt); |
| OCE && OCE->getOperator() == clang::OO_Star && |
| isSupportedSmartPointerType(OCE->getArg(0)->getType())) { |
| return {OCE->getArg(0), OCE->getOperatorLoc()}; |
| } |
| return {nullptr, SourceLocation()}; |
| } |
| |
| /// Inferable slots are nullability slots not explicitly annotated in source |
| /// code that we are currently capable of handling. This returns a boolean |
| /// constraint representing these slots having a) the nullability inferred from |
| /// the previous round for this slot or b) Unknown nullability if no inference |
| /// was made in the previous round or there was no previous round. |
| static const Formula &getInferableSlotsAsInferredOrUnknownConstraint( |
| const std::vector<InferableSlot> &InferableSlots, USRCache &USRCache, |
| const PreviousInferences &PreviousInferences, dataflow::Arena &A) { |
| const Formula *Constraint = &A.makeLiteral(true); |
| for (auto &IS : InferableSlots) { |
| std::string_view USR = getOrGenerateUSR(USRCache, IS.getInferenceTarget()); |
| SlotFingerprint Fingerprint = fingerprint(USR, IS.getTargetSlot()); |
| auto Nullability = IS.getSymbolicNullability(); |
| const Formula &Nullable = PreviousInferences.Nullable.contains(Fingerprint) |
| ? Nullability.isNullable(A) |
| : A.makeNot(Nullability.isNullable(A)); |
| const Formula &Nonnull = PreviousInferences.Nonnull.contains(Fingerprint) |
| ? Nullability.isNonnull(A) |
| : A.makeNot(Nullability.isNonnull(A)); |
| Constraint = &A.makeAnd(*Constraint, A.makeAnd(Nullable, Nonnull)); |
| } |
| return *Constraint; |
| } |
| |
| static void overrideNullability(const ValueDecl &D, |
| const PointerNullabilityLattice &Lattice, |
| TypeNullability &N) { |
| if (N.empty()) { |
| // We expect this not to be the case, but not to a crash-worthy level, so |
| // just log if it is. |
| llvm::errs() << "Nullability for type " << D.getType().getAsString(); |
| if (auto *ND = dyn_cast<clang::NamedDecl>(&D)) { |
| llvm::errs() << " for Decl named " << ND->getQualifiedNameAsString(); |
| } |
| llvm::errs() << " requested with overrides, but is an empty vector.\n"; |
| } else { |
| Lattice.overrideNullabilityFromDecl(&D, N); |
| } |
| } |
| |
| static TypeNullability getNullabilityAnnotationsFromDeclAndOverrides( |
| const ValueDecl &D, const PointerNullabilityLattice &Lattice) { |
| TypeNullability N = getTypeNullability(D, Lattice.defaults()); |
| overrideNullability(D, Lattice, N); |
| return N; |
| } |
| |
| static TypeNullability getReturnTypeNullabilityAnnotations( |
| const FunctionDecl &D, const TypeNullabilityDefaults &Defaults) { |
| // Use the QualType, FileID overload of getTypeNullability for return types, |
| // because of complexity around the following cases: |
| // |
| // The return TypeLoc for `auto`-returning functions contains an undeduced |
| // `auto` type, even if the `auto` has been deduced. See |
| // https://github.com/llvm/llvm-project/issues/42259 for more. |
| // |
| // FunctionDecls with top-level TypeLocs that are not simple |
| // FunctionTypeLoc, such as those with attributes, would need excavation of |
| // the function's FunctionTypeLoc before being able to retrieve the return |
| // TypeLoc. |
| return getTypeNullability(D.getReturnType(), getGoverningFile(&D), Defaults); |
| } |
| |
| static TypeNullability getReturnTypeNullabilityAnnotationsWithOverrides( |
| const FunctionDecl &D, const PointerNullabilityLattice &Lattice) { |
| TypeNullability N = |
| getReturnTypeNullabilityAnnotations(D, Lattice.defaults()); |
| |
| // The FunctionDecl is the key used for overrides for the return |
| // type. To look up overrides for parameters, we would pass a |
| // ParmVarDecl to `overrideNullability`. |
| overrideNullability(D, Lattice, N); |
| return N; |
| } |
| |
| static Evidence::Kind getArgEvidenceKindFromNullability( |
| NullabilityKind Nullability, bool IsReference) { |
| switch (Nullability) { |
| case NullabilityKind::Nullable: |
| return IsReference ? Evidence::NULLABLE_REFERENCE_ARGUMENT |
| : Evidence::NULLABLE_ARGUMENT; |
| case NullabilityKind::NonNull: |
| return IsReference ? Evidence::NONNULL_REFERENCE_ARGUMENT |
| : Evidence::NONNULL_ARGUMENT; |
| default: |
| return IsReference ? Evidence::UNKNOWN_REFERENCE_ARGUMENT |
| : Evidence::UNKNOWN_ARGUMENT; |
| } |
| } |
| |
| static std::optional<Evidence::Kind> evidenceKindFromDeclaredNullability( |
| const TypeNullability &Nullability) { |
| switch (Nullability.front().concrete()) { |
| default: |
| return std::nullopt; |
| case NullabilityKind::NonNull: |
| return Evidence::ANNOTATED_NONNULL; |
| case NullabilityKind::Nullable: |
| return Evidence::ANNOTATED_NULLABLE; |
| } |
| } |
| |
| static std::optional<Evidence::Kind> evidenceKindFromDeclaredTypeLoc( |
| TypeLoc Loc, const TypeNullabilityDefaults &Defaults) { |
| if (!isSupportedPointerType(Loc.getType().getNonReferenceType())) |
| return std::nullopt; |
| auto Nullability = getTypeNullability(Loc, Defaults); |
| return evidenceKindFromDeclaredNullability(Nullability); |
| } |
| |
| static std::optional<Evidence::Kind> evidenceKindFromDeclaredReturnType( |
| const FunctionDecl &D, const TypeNullabilityDefaults &Defaults) { |
| if (!isSupportedPointerType(D.getReturnType().getNonReferenceType())) |
| return std::nullopt; |
| return evidenceKindFromDeclaredNullability( |
| getReturnTypeNullabilityAnnotations(D, Defaults)); |
| } |
| |
| static bool isOrIsConstructedFromNullPointerConstant( |
| absl::Nonnull<const Expr *> E, ASTContext &Ctx) { |
| if (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull) != |
| Expr::NPCK_NotNull) { |
| return true; |
| } |
| if (auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(E)) { |
| E = DefaultInit->getExpr(); |
| } |
| const Expr *SubExpr = &dataflow::ignoreCFGOmittedNodes(*E); |
| if (auto *MaterializeTempExpr = dyn_cast<MaterializeTemporaryExpr>(SubExpr)) { |
| SubExpr = MaterializeTempExpr->getSubExpr(); |
| } |
| if (auto *BindTemp = dyn_cast<CXXBindTemporaryExpr>(SubExpr)) { |
| SubExpr = BindTemp->getSubExpr(); |
| } |
| auto *CE = dyn_cast<CXXConstructExpr>(SubExpr->IgnoreImpCasts()); |
| if (!CE) return false; |
| return CE != nullptr && CE->getNumArgs() == 1 && |
| CE->getArg(0)->isNullPointerConstant( |
| Ctx, Expr::NPC_ValueDependentIsNotNull) != Expr::NPCK_NotNull; |
| } |
| |
| namespace { |
| class DefinitionEvidenceCollector { |
| public: |
| // Instantiate the class only in this static function, to restrict the |
| // lifetime of the object, which holds reference parameters. |
| static void collect(std::vector<InferableSlot> &InferableSlots, |
| const Formula &InferableSlotsConstraint, |
| llvm::function_ref<EvidenceEmitter> Emit, |
| const CFGElement &CFGElem, |
| const PointerNullabilityLattice &Lattice, |
| const Environment &Env) { |
| DefinitionEvidenceCollector Collector( |
| InferableSlots, InferableSlotsConstraint, Emit, Lattice, Env); |
| if (auto CFGStmt = CFGElem.getAs<clang::CFGStmt>()) { |
| const Stmt *S = CFGStmt->getStmt(); |
| if (!S) return; |
| Collector.fromDereference(*S); |
| Collector.fromCallExpr(*S); |
| Collector.fromConstructExpr(*S); |
| Collector.fromReturn(*S); |
| Collector.fromAssignment(*S); |
| Collector.fromArithmetic(*S); |
| Collector.fromAggregateInitialization(*S); |
| } else if (auto CFGInit = CFGElem.getAs<clang::CFGInitializer>()) { |
| Collector.fromCFGInitializer(*CFGInit); |
| } |
| } |
| |
| private: |
| DefinitionEvidenceCollector(std::vector<InferableSlot> &InferableSlots, |
| const Formula &InferableSlotsConstraint, |
| llvm::function_ref<EvidenceEmitter> Emit, |
| const PointerNullabilityLattice &Lattice, |
| const Environment &Env) |
| : InferableSlots(InferableSlots), |
| InferableSlotsConstraint(InferableSlotsConstraint), |
| Emit(Emit), |
| Lattice(Lattice), |
| Env(Env) {} |
| |
| /// Records evidence derived from the necessity that `Value` is nonnull. |
| /// It may be dereferenced, passed as a nonnull param, etc, per |
| /// `EvidenceKind`. |
| void mustBeNonnull(const dataflow::PointerValue &Value, SourceLocation Loc, |
| Evidence::Kind EvidenceKind) { |
| CHECK(hasPointerNullState(Value)) |
| << "Value should be the value of an expression. Cannot collect " |
| "evidence for nonnull-ness if there is no null state."; |
| auto *IsNull = getPointerNullState(Value).IsNull; |
| // If `IsNull` is top, we can't infer anything about it. |
| if (IsNull == nullptr) return; |
| auto &A = Env.arena(); |
| mustBeTrue(A.makeNot(*IsNull), Loc, EvidenceKind); |
| } |
| |
| /// Records evidence for Nonnull-ness derived from the necessity that |
| /// `MustBeTrue` must be true. |
| /// |
| /// Does not consider the possibility that the formula can only be proven true |
| /// by marking a slot Nullable, as this is is not a pattern we have yet seen |
| /// in practice. This function could easily be extended to do so, though. |
| void mustBeTrue(const Formula &MustBeTrue, SourceLocation Loc, |
| Evidence::Kind EvidenceKind) { |
| auto &A = Env.arena(); |
| // If `Value` is already proven true or false (or both, which indicates |
| // unsatisfiable flow conditions), collect no evidence. |
| if (Env.proves(MustBeTrue) || Env.proves(A.makeNot(MustBeTrue))) return; |
| |
| for (auto &IS : InferableSlots) { |
| auto &SlotNonnull = IS.getSymbolicNullability().isNonnull(A); |
| auto &SlotNonnullImpliesFormulaTrue = |
| A.makeImplies(SlotNonnull, MustBeTrue); |
| // Don't collect evidence if the implication is true by virtue of |
| // `SlotNonnull` being false. |
| // |
| // In practice, `SlotNonnull` can be made false by a flow condition, and |
| // marking the slot Nonnull would make that conditioned block dead code. |
| // Technically, this does make a dereference, etc. "safe", but we'd prefer |
| // to mark a different slot Nonnull that has a more direct relationship |
| // with `MustBeTrue`. |
| // |
| // e.g. We'd prefer to mark `q` Nonnull rather than `p` in the following: |
| // ``` |
| // void target(int* p, int* q) { |
| // if (!p) { |
| // *q; |
| // } |
| // } |
| // ``` |
| if (Env.allows(SlotNonnull) && |
| Env.proves(SlotNonnullImpliesFormulaTrue)) { |
| Emit(IS.getInferenceTarget(), IS.getTargetSlot(), EvidenceKind, Loc); |
| return; |
| } |
| } |
| } |
| |
| void fromDereference(const Stmt &S) { |
| auto [Target, Loc] = describeDereference(S); |
| if (!Target || !isSupportedPointerType(Target->getType())) return; |
| |
| // It is a dereference of a pointer. Now gather evidence from it. |
| dataflow::PointerValue *DereferencedValue = getPointerValue(Target, Env); |
| if (!DereferencedValue) return; |
| mustBeNonnull(*DereferencedValue, Loc, Evidence::UNCHECKED_DEREFERENCE); |
| } |
| |
| /// Collect evidence for each of `InferableSlots` if that slot being marked |
| /// Nullable would imply `Value`'s FromNullable property. |
| /// |
| /// This function is called when we have reason to believe that `Value` must |
| /// be Nullable. As we can't directly retrieve the combination of Decl and |
| /// Slot that corresponds to `Value`'s nullability, we consider each inferable |
| /// slot and emit evidence for all inferable slots that, if marked Nullable, |
| /// cause `Value` to be considered explicitly Nullable. |
| void mustBeMarkedNullable(const dataflow::PointerValue &Value, |
| SourceLocation Loc, Evidence::Kind EvidenceKind) { |
| CHECK(hasPointerNullState(Value)) |
| << "Value should be the value of an expression. Cannot collect " |
| "evidence for nonnull-ness if there is no null state."; |
| auto *FromNullable = getPointerNullState(Value).FromNullable; |
| // If `FromNullable` is top, we can't infer anything about it. |
| if (FromNullable == nullptr) return; |
| // If the flow conditions already imply that `Value` is from a Nullable, |
| // then we don't have any new evidence of a necessary annotation. |
| if (Env.proves(*FromNullable)) return; |
| |
| auto &A = Env.arena(); |
| // Otherwise, if an inferable slot being annotated Nullable would imply that |
| // `Value` is from a Nullable, then we have evidence suggesting that slot |
| // should be annotated. We collect this evidence for every slot that |
| // connects in this way to `Value`. |
| // |
| // e.g. We should mark both `p` and `q` Nullable in the following: |
| // ``` |
| // void target(int* p, int* q, bool b) { |
| // Nullable<int*>& x = b ? p : q; |
| // ... |
| // } |
| // ``` |
| // because at runtime, either `p` or `q` could be taken as a mutable |
| // reference and later set to nullptr. |
| for (auto &IS : InferableSlots) { |
| auto &SlotNullableImpliesValueFromNullable = A.makeImplies( |
| IS.getSymbolicNullability().isNullable(A), *FromNullable); |
| if (Env.proves(SlotNullableImpliesValueFromNullable)) |
| Emit(IS.getInferenceTarget(), IS.getTargetSlot(), EvidenceKind, Loc); |
| } |
| } |
| |
| void fromAssignmentToType(QualType Type, |
| const TypeNullability &TypeNullability, |
| const dataflow::PointerValue &PointerValue, |
| SourceLocation ValueLoc) { |
| // TODO: Account for variance and each layer of nullability when we handle |
| // more than top-level pointers. |
| if (TypeNullability.empty()) return; |
| const PointerTypeNullability &TopLevel = TypeNullability[0]; |
| dataflow::Arena &A = Env.arena(); |
| if (TopLevel.concrete() == NullabilityKind::NonNull || |
| (TopLevel.isSymbolic() && |
| Env.proves( |
| A.makeImplies(InferableSlotsConstraint, TopLevel.isNonnull(A))))) { |
| mustBeNonnull(PointerValue, ValueLoc, Evidence::ASSIGNED_TO_NONNULL); |
| } else if (!Type.isConstQualified() && Type->isReferenceType() && |
| (TopLevel.concrete() == NullabilityKind::Nullable || |
| (TopLevel.isSymbolic() && |
| Env.proves(A.makeImplies(InferableSlotsConstraint, |
| TopLevel.isNullable(A)))))) { |
| mustBeMarkedNullable(PointerValue, ValueLoc, |
| Evidence::ASSIGNED_TO_MUTABLE_NULLABLE); |
| } |
| } |
| |
| template <typename CallOrConstructExpr> |
| void fromArgsAndParams(const FunctionDecl &CalleeDecl, |
| const CallOrConstructExpr &Expr) { |
| bool CollectEvidenceForCallee = isInferenceTarget(CalleeDecl); |
| bool CollectEvidenceForCaller = !InferableSlots.empty(); |
| |
| for (ParamAndArgIterator<CallOrConstructExpr> Iter(CalleeDecl, Expr); Iter; |
| ++Iter) { |
| const auto ParamType = Iter.param().getType().getNonReferenceType(); |
| if (!isSupportedPointerType(ParamType)) continue; |
| if (!isSupportedPointerType(Iter.arg().getType())) { |
| // These builtins are declared with pointer type parameters even when |
| // given a valid argument of type uintptr_t. In this case, there's |
| // nothing to infer, but also nothing unexpected to crash over. |
| auto BuiltinID = CalleeDecl.getBuiltinID(); |
| if (BuiltinID == Builtin::BI__builtin_is_aligned || |
| BuiltinID == Builtin::BI__builtin_align_up || |
| BuiltinID == Builtin::BI__builtin_align_down) { |
| continue; |
| } |
| } |
| // the corresponding argument should also be a pointer. |
| CHECK(isSupportedPointerType(Iter.arg().getType())) |
| << "Unsupported argument " << Iter.argIdx() |
| << " type: " << Iter.arg().getType().getAsString(); |
| |
| if (isa<clang::CXXDefaultArgExpr>(Iter.arg())) { |
| // Evidence collection for the callee from default argument values is |
| // handled when collecting from declarations, and there's no useful |
| // evidence available to collect for the caller. |
| return; |
| } |
| |
| dataflow::PointerValue *PV = getPointerValue(&Iter.arg(), Env); |
| if (!PV) continue; |
| |
| SourceLocation ArgLoc = Iter.arg().getExprLoc(); |
| |
| if (CollectEvidenceForCaller) { |
| auto ParamNullability = getNullabilityAnnotationsFromDeclAndOverrides( |
| Iter.param(), Lattice); |
| |
| // Collect evidence from constraints that the parameter's nullability |
| // places on the argument's nullability. |
| fromAssignmentToType(Iter.param().getType(), ParamNullability, *PV, |
| ArgLoc); |
| } |
| |
| if (CollectEvidenceForCallee) { |
| // Emit evidence of the parameter's nullability. First, calculate that |
| // nullability based on InferableSlots for the caller being assigned to |
| // Unknown or their previously-inferred value, to reflect the current |
| // annotations and not all possible annotations for them. |
| NullabilityKind ArgNullability = |
| getNullability(*PV, Env, &InferableSlotsConstraint); |
| Emit(CalleeDecl, paramSlot(Iter.paramIdx()), |
| getArgEvidenceKindFromNullability( |
| ArgNullability, Iter.param().getType()->isReferenceType()), |
| ArgLoc); |
| } |
| } |
| } |
| |
| /// Collects evidence from the assignment of function arguments to the types |
| /// of the corresponding parameter, used when we have a FunctionProtoType but |
| /// no FunctionDecl. |
| /// TODO: When we collect evidence for more complex slots than just top-level |
| /// pointers, emit evidence of the function parameter's nullability as a slot |
| /// in the appropriate declaration. |
| void fromFunctionProtoTypeCall(const FunctionProtoType &CalleeType, |
| const CallExpr &Expr) { |
| // For each pointer parameter of the function, ... |
| for (unsigned I = 0; I < CalleeType.getNumParams(); ++I) { |
| const auto ParamType = CalleeType.getParamType(I); |
| if (!isSupportedPointerType(ParamType.getNonReferenceType())) continue; |
| // the corresponding argument should also be a pointer. |
| CHECK(isSupportedPointerType(Expr.getArg(I)->getType())) |
| << "Unsupported argument " << I |
| << " type: " << Expr.getArg(I)->getType().getAsString(); |
| |
| dataflow::PointerValue *PV = getPointerValue(Expr.getArg(I), Env); |
| if (!PV) continue; |
| |
| // TODO: when we infer function pointer/reference parameters' |
| // nullabilities, check for overrides from previous inference iterations. |
| auto ParamNullability = getNullabilityAnnotationsFromType(ParamType); |
| |
| // Collect evidence from constraints that the parameter's nullability |
| // places on the argument's nullability. |
| fromAssignmentToType(ParamType, ParamNullability, *PV, |
| Expr.getArg(I)->getExprLoc()); |
| } |
| } |
| |
| /// Collect evidence that the function pointer was dereferenced and from the |
| /// matching up of parameter/argument nullabilities. |
| void fromFunctionPointerCallExpr(const Type &CalleeFunctionType, |
| const CallExpr &Expr) { |
| if (InferableSlots.empty()) return; |
| if (const auto *Callee = Expr.getCallee()) { |
| // Function pointers are only ever raw pointers. |
| if (const auto *PV = getRawPointerValue(Callee, Env)) { |
| mustBeNonnull(*PV, Expr.getExprLoc(), Evidence::UNCHECKED_DEREFERENCE); |
| } |
| } |
| |
| auto *CalleeFunctionProtoType = |
| CalleeFunctionType.getAs<FunctionProtoType>(); |
| CHECK(CalleeFunctionProtoType); |
| fromFunctionProtoTypeCall(*CalleeFunctionProtoType, Expr); |
| } |
| |
| /// Handles the case of a call to a function without a FunctionDecl, e.g. that |
| /// is provided as a parameter or another decl, e.g. a field or local |
| /// variable. |
| /// |
| /// Example: We can collect evidence for the nullability of `p` and (when we |
| /// handle more than top-level pointer slots) `j` in the following, based on |
| /// the call to `callee`: |
| /// ``` |
| /// void target(int* p, void (*callee)(Nonnull<int*> i, int* j)) { |
| /// callee(p, nullptr); |
| /// } |
| /// ``` |
| /// |
| /// With `CalleeDecl` in this case not being a FunctionDecl as in most |
| /// CallExpr cases, distinct handling is needed. |
| void fromCallExprWithoutFunctionCalleeDecl(const Decl &CalleeDecl, |
| const CallExpr &Expr) { |
| if (CalleeDecl.isFunctionPointerType()) { |
| if (auto *FuncType = CalleeDecl.getFunctionType()) { |
| fromFunctionPointerCallExpr(*FuncType, Expr); |
| } else if (const auto *BD = dyn_cast<BindingDecl>(&CalleeDecl)) { |
| // TODO(b/352043668): This is a workaround for the unexplained returning |
| // of nullptr from `getFunctionType()` for BindingDecls. |
| fromFunctionPointerCallExpr(*BD->getType()->getPointeeType(), Expr); |
| } else { |
| llvm::errs() << "Unsupported case of a function pointer type, for " |
| "which we aren't retrieving a valid FunctionType. \n"; |
| CalleeDecl.dump(); |
| } |
| return; |
| } |
| |
| // Ignore calls of pointers to members. The dereferencing of the pointer is |
| // handled as a dereference at the BinaryOperator node, which additionally |
| // captures pointers to fields. |
| // TODO(b/309625642) Consider collecting evidence for the arguments being |
| // passed as parameters to the pointed-to member. |
| if (const auto *BinaryOpCallee = dyn_cast_or_null<BinaryOperator>( |
| Expr.getCallee()->IgnoreParenImpCasts()); |
| BinaryOpCallee && |
| (BinaryOpCallee->getOpcode() == clang::BinaryOperatorKind::BO_PtrMemD || |
| BinaryOpCallee->getOpcode() == |
| clang::BinaryOperatorKind::BO_PtrMemI)) { |
| return; |
| } |
| |
| // Function references are a rare case, but similar to function pointers, we |
| // can collect evidence from arguments assigned to parameter types. |
| if (auto *FuncType = CalleeDecl.getFunctionType()) { |
| if (auto *FuncProtoType = FuncType->getAs<FunctionProtoType>()) { |
| fromFunctionProtoTypeCall(*FuncProtoType, Expr); |
| return; |
| } |
| } |
| |
| // A reference to a function pointer is another rare case, but we can |
| // collect the same evidence we would for a function pointer. |
| if (const auto *CalleeAsValueDecl = |
| dyn_cast<clang::ValueDecl>(&CalleeDecl)) { |
| if (QualType CalleeType = CalleeAsValueDecl->getType(); |
| CalleeType.getNonReferenceType()->isFunctionPointerType()) { |
| fromFunctionPointerCallExpr( |
| *(CalleeType.getNonReferenceType()->getPointeeType()), Expr); |
| return; |
| } |
| } |
| |
| // If we run into other cases meeting this criterion, skip them, but log |
| // first so we can potentially add support later. |
| llvm::errs() << "Unsupported case of a CallExpr without a FunctionDecl. " |
| "Not collecting any evidence from this CallExpr:\n"; |
| Expr.getBeginLoc().dump(CalleeDecl.getASTContext().getSourceManager()); |
| Expr.dump(); |
| llvm::errs() << "Which is a call to:\n"; |
| CalleeDecl.dump(); |
| } |
| |
| /// Given a `CallExpr` for a call to our special macro single-argument capture |
| /// function, collect evidence for a slot that can prevent the abort condition |
| /// from being true if it is annotated Nonnull. |
| /// |
| /// e.g. From `CHECK(x)`, we collect evidence for a slot that can cause `x` to |
| /// not be null. |
| void fromAbortIfFalseMacroCall(const CallExpr &CallExpr) { |
| CHECK_EQ(CallExpr.getNumArgs(), 1); |
| const Expr *Arg = CallExpr.getArg(0); |
| if (!Arg) return; |
| QualType ArgType = Arg->getType(); |
| if (isSupportedPointerType(ArgType)) { |
| const dataflow::PointerValue *PV = getPointerValue(Arg, Env); |
| if (!PV) return; |
| mustBeNonnull(*PV, Arg->getExprLoc(), Evidence::ABORT_IF_NULL); |
| } else if (ArgType->isBooleanType()) { |
| const dataflow::BoolValue *BV = Env.get<dataflow::BoolValue>(*Arg); |
| if (!BV || BV->getKind() == dataflow::BoolValue::Kind::TopBool) return; |
| mustBeTrue(BV->formula(), Arg->getExprLoc(), Evidence::ABORT_IF_NULL); |
| } |
| } |
| |
| /// Given a `CallExpr` for a call to our special macro two-argument capture |
| /// function for not-equal checks, if one of the arguments is a nullptr |
| /// constant or provably null, collect evidence for a slot that can prevent |
| /// the other argument from being null. |
| /// |
| /// e.g. From `CHECK_NE(x, nullptr)`, we collect evidence for a slot that can |
| /// cause `x` to not be null. |
| void fromAbortIfEqualMacroCall(const CallExpr &CallExpr) { |
| CHECK_EQ(CallExpr.getNumArgs(), 2); |
| const Expr *First = CallExpr.getArg(0); |
| const Expr *Second = CallExpr.getArg(1); |
| bool FirstSupported = isSupportedPointerType(First->getType()); |
| bool SecondSupported = isSupportedPointerType(Second->getType()); |
| if (!FirstSupported && !SecondSupported) return; |
| |
| ASTContext &Context = CallExpr.getCalleeDecl()->getASTContext(); |
| const dataflow::PointerValue *ValueComparedToNull = nullptr; |
| SourceLocation EvidenceLoc; |
| if (First->isNullPointerConstant(Context, |
| Expr::NPC_ValueDependentIsNotNull)) { |
| if (!isSupportedPointerType(Second->getType())) return; |
| ValueComparedToNull = getPointerValue(Second, Env); |
| if (!ValueComparedToNull) return; |
| EvidenceLoc = Second->getExprLoc(); |
| } else if (Second->isNullPointerConstant( |
| Context, Expr::NPC_ValueDependentIsNotNull)) { |
| if (!isSupportedPointerType(First->getType())) return; |
| ValueComparedToNull = getPointerValue(First, Env); |
| if (!ValueComparedToNull) return; |
| EvidenceLoc = First->getExprLoc(); |
| } else { |
| if (!FirstSupported || !SecondSupported) { |
| // If this happens outside of the nullptr literal case, we'd like to |
| // know about it. |
| llvm::errs() << "Value of a supported pointer type compared to a value " |
| "of a type that is not a supported pointer type.: \n"; |
| CallExpr.dump(); |
| CallExpr.getExprLoc().dump( |
| CallExpr.getCalleeDecl()->getASTContext().getSourceManager()); |
| return; |
| } |
| |
| const dataflow::PointerValue *FirstPV = getPointerValue(First, Env); |
| if (!FirstPV) return; |
| const dataflow::PointerValue *SecondPV = getPointerValue(Second, Env); |
| if (!SecondPV) return; |
| PointerNullState FirstNullState = getPointerNullState(*FirstPV); |
| if (!FirstNullState.IsNull) return; |
| PointerNullState SecondNullState = getPointerNullState(*SecondPV); |
| if (!SecondNullState.IsNull) return; |
| |
| if (Env.proves(*FirstNullState.IsNull)) { |
| ValueComparedToNull = SecondPV; |
| EvidenceLoc = Second->getExprLoc(); |
| } else if (Env.proves(*SecondNullState.IsNull)) { |
| ValueComparedToNull = FirstPV; |
| EvidenceLoc = First->getExprLoc(); |
| } else { |
| return; |
| } |
| } |
| |
| mustBeNonnull(*ValueComparedToNull, EvidenceLoc, Evidence::ABORT_IF_NULL); |
| } |
| |
| void fromCallExpr(const Stmt &S) { |
| auto *CallExpr = dyn_cast<clang::CallExpr>(&S); |
| if (!CallExpr) return; |
| auto *CalleeDecl = CallExpr->getCalleeDecl(); |
| if (!CalleeDecl) return; |
| if (auto *CalleeFunctionDecl = dyn_cast<clang::FunctionDecl>(CalleeDecl)) { |
| if (CalleeFunctionDecl->getDeclName().isIdentifier()) { |
| llvm::StringRef Name = CalleeFunctionDecl->getName(); |
| if (Name == ArgCaptureAbortIfFalse) { |
| fromAbortIfFalseMacroCall(*CallExpr); |
| return; |
| } |
| if (Name == ArgCaptureAbortIfEqual) { |
| fromAbortIfEqualMacroCall(*CallExpr); |
| return; |
| } |
| } |
| fromArgsAndParams(*CalleeFunctionDecl, *CallExpr); |
| } else { |
| fromCallExprWithoutFunctionCalleeDecl(*CalleeDecl, *CallExpr); |
| } |
| } |
| |
| void fromConstructExpr(const Stmt &S) { |
| auto *ConstructExpr = dyn_cast<clang::CXXConstructExpr>(&S); |
| if (!ConstructExpr) return; |
| auto *ConstructorDecl = dyn_cast_or_null<clang::CXXConstructorDecl>( |
| ConstructExpr->getConstructor()); |
| if (!ConstructorDecl) return; |
| |
| fromArgsAndParams(*ConstructorDecl, *ConstructExpr); |
| } |
| |
| void fromReturn(const Stmt &S) { |
| // Is this CFGElement a return statement? |
| auto *ReturnStmt = dyn_cast<clang::ReturnStmt>(&S); |
| if (!ReturnStmt) return; |
| auto *ReturnExpr = ReturnStmt->getRetValue(); |
| if (!ReturnExpr || !isSupportedPointerType(ReturnExpr->getType())) return; |
| const FunctionDecl *CurrentFunc = Env.getCurrentFunc(); |
| CHECK(CurrentFunc) << "A return statement outside of a function?"; |
| |
| // Skip gathering evidence about the current function's return type if |
| // the current function is not an inference target or the return type |
| // already includes an annotation. |
| if (isInferenceTarget(*CurrentFunc) && |
| !evidenceKindFromDeclaredReturnType(*CurrentFunc, Lattice.defaults())) { |
| NullabilityKind ReturnNullability = |
| getNullability(ReturnExpr, Env, &InferableSlotsConstraint); |
| bool ReturnTypeIsReference = |
| CurrentFunc->getReturnType()->isReferenceType(); |
| Evidence::Kind ReturnEvidenceKind; |
| switch (ReturnNullability) { |
| case NullabilityKind::Nullable: |
| ReturnEvidenceKind = ReturnTypeIsReference |
| ? Evidence::NULLABLE_REFERENCE_RETURN |
| : Evidence::NULLABLE_RETURN; |
| break; |
| case NullabilityKind::NonNull: |
| ReturnEvidenceKind = ReturnTypeIsReference |
| ? Evidence::NONNULL_REFERENCE_RETURN |
| : Evidence::NONNULL_RETURN; |
| break; |
| default: |
| ReturnEvidenceKind = ReturnTypeIsReference |
| ? Evidence::UNKNOWN_REFERENCE_RETURN |
| : Evidence::UNKNOWN_RETURN; |
| } |
| Emit(*CurrentFunc, SLOT_RETURN_TYPE, ReturnEvidenceKind, |
| ReturnExpr->getExprLoc()); |
| } |
| |
| const dataflow::PointerValue *PV = getPointerValue(ReturnExpr, Env); |
| if (!PV) return; |
| TypeNullability ReturnTypeNullability = |
| getReturnTypeNullabilityAnnotationsWithOverrides(*CurrentFunc, Lattice); |
| fromAssignmentToType(Env.getCurrentFunc()->getReturnType(), |
| ReturnTypeNullability, *PV, ReturnExpr->getExprLoc()); |
| } |
| |
| /// Checks whether PointerValue is null or nullable and if so, collects |
| /// evidence for a slot that would, if marked Nullable, cause |
| /// TypeNullability's first-layer nullability to be Nullable. |
| /// |
| /// e.g. This is used for example to collect from the following: |
| /// ``` |
| /// void target(int* p, int* q, NullabilityUnknown<int*> r) { |
| /// p = nullptr; |
| /// if (!r) { |
| /// q = r; |
| /// } |
| /// } |
| /// ``` |
| /// evidence for each of the assignments of `p` and `q` that they were |
| /// ASSIGNED_FROM_NULLABLE. |
| void fromAssignmentFromNullable(const TypeNullability &TypeNullability, |
| const dataflow::PointerValue &PointerValue, |
| SourceLocation ValueLoc, |
| Evidence::Kind EvidenceKind) { |
| if (TypeNullability.empty() || !hasPointerNullState(PointerValue)) return; |
| dataflow::Arena &A = Env.arena(); |
| if (getNullability(PointerValue, Env, &InferableSlotsConstraint) == |
| NullabilityKind::Nullable) { |
| const Formula &TypeIsNullable = TypeNullability[0].isNullable(A); |
| // If the flow conditions already imply that the type is nullable, or |
| // that the type is not nullable, we can skip collecting evidence. |
| if (Env.proves(TypeIsNullable) || !Env.allows(TypeIsNullable)) return; |
| |
| for (auto &IS : InferableSlots) { |
| auto &Implication = A.makeImplies( |
| IS.getSymbolicNullability().isNullable(A), TypeIsNullable); |
| // It's not expected that a slot's isNullable formula could be proven |
| // false by the environment alone (without the |
| // InferableSlotsConstraint), but SAT calls are relatively expensive, so |
| // only DCHECK. |
| DCHECK(Env.allows(IS.getSymbolicNullability().isNullable(A))); |
| if (Env.proves(Implication)) { |
| Emit(IS.getInferenceTarget(), IS.getTargetSlot(), EvidenceKind, |
| ValueLoc); |
| return; |
| } |
| } |
| } |
| } |
| |
| /// Collects evidence based on an assignment of RHS to LHSDecl, through a |
| /// direct assignment statement, aggregate initialization, etc. |
| void fromAssignmentLike(const ValueDecl &LHSDecl, const Expr &RHS, |
| SourceLocation Loc, |
| Evidence::Kind EvidenceKindForAssignmentFromNullable = |
| Evidence::ASSIGNED_FROM_NULLABLE) { |
| fromAssignmentLike( |
| LHSDecl.getType(), |
| getNullabilityAnnotationsFromDeclAndOverrides(LHSDecl, Lattice), RHS, |
| Loc, EvidenceKindForAssignmentFromNullable); |
| } |
| |
| /// Collects evidence based on an assignment of RHS to an expression with type |
| /// LHSType and nullability LHSNullability, through a direct assignment |
| /// statement, aggregate initialization, etc. |
| void fromAssignmentLike(QualType LHSType, |
| const TypeNullability &LHSNullability, |
| const Expr &RHS, SourceLocation Loc, |
| Evidence::Kind EvidenceKindForAssignmentFromNullable = |
| Evidence::ASSIGNED_FROM_NULLABLE) { |
| const dataflow::PointerValue *PV = getPointerValue(&RHS, Env); |
| if (!PV) return; |
| fromAssignmentToType(LHSType, LHSNullability, *PV, Loc); |
| fromAssignmentFromNullable(LHSNullability, *PV, Loc, |
| EvidenceKindForAssignmentFromNullable); |
| } |
| |
| /// Collects evidence from direct assignment statements, e.g. `p = nullptr`, |
| /// whether initializing a new declaration or re-assigning to an existing |
| /// declaration. |
| void fromAssignment(const Stmt &S) { |
| if (InferableSlots.empty()) return; |
| |
| // Initialization of new decl. |
| if (auto *DeclStmt = dyn_cast<clang::DeclStmt>(&S)) { |
| for (auto *Decl : DeclStmt->decls()) { |
| if (auto *VarDecl = dyn_cast<clang::VarDecl>(Decl); |
| VarDecl && VarDecl->hasInit()) { |
| bool DeclTypeSupported = |
| isSupportedPointerType(VarDecl->getType().getNonReferenceType()); |
| bool InitTypeSupported = isSupportedPointerType( |
| VarDecl->getInit()->getType().getNonReferenceType()); |
| if (!DeclTypeSupported) return; |
| if (!InitTypeSupported) { |
| llvm::errs() << "Unsupported init type for pointer decl: " |
| << VarDecl->getInit()->getType() << "\n"; |
| return; |
| } |
| fromAssignmentLike(*VarDecl, *VarDecl->getInit(), |
| VarDecl->getInit()->getExprLoc()); |
| } |
| } |
| return; |
| } |
| |
| // Assignment to existing decl. |
| const Expr *LHS = nullptr; |
| const Expr *RHS = nullptr; |
| std::optional<SourceLocation> Loc = std::nullopt; |
| // Raw pointers. |
| if (auto *BinaryOp = dyn_cast<clang::BinaryOperator>(&S); |
| BinaryOp && |
| BinaryOp->getOpcode() == clang::BinaryOperatorKind::BO_Assign) { |
| LHS = BinaryOp->getLHS(); |
| RHS = BinaryOp->getRHS(); |
| Loc = BinaryOp->getOperatorLoc(); |
| } else if ( |
| // Smart pointers. |
| auto *CXXOpCall = dyn_cast<clang::CXXOperatorCallExpr>(&S); |
| CXXOpCall && CXXOpCall->getOperator() == clang::OO_Equal) { |
| LHS = CXXOpCall->getArg(0); |
| RHS = CXXOpCall->getArg(1); |
| Loc = CXXOpCall->getOperatorLoc(); |
| } else { |
| return; |
| } |
| const QualType LHSType = LHS->getType(); |
| if (!isSupportedPointerType(LHSType)) return; |
| if (!isSupportedPointerType(RHS->getType())) return; |
| |
| const TypeNullability *TypeNullability = Lattice.getTypeNullability(LHS); |
| CHECK(TypeNullability); |
| fromAssignmentLike(LHSType, *TypeNullability, *RHS, *Loc); |
| } |
| |
| void fromArithmeticArg(const Expr *Arg, SourceLocation Loc) { |
| // No support needed for smart pointers, which do not support arithmetic |
| // operations. |
| if (!Arg || !isSupportedRawPointerType(Arg->getType())) return; |
| if (auto *PV = getPointerValue(Arg, Env)) |
| mustBeNonnull(*PV, Loc, Evidence::ARITHMETIC); |
| } |
| |
| void fromArithmetic(const Stmt &S) { |
| // A nullptr can be added to 0 and nullptr can be subtracted from nullptr |
| // without hitting UB. But for now, we skip handling these special cases and |
| // assume all pointers involved in these operations must be nonnull. |
| switch (S.getStmtClass()) { |
| default: |
| return; |
| case Stmt::CompoundAssignOperatorClass: { |
| auto *Op = cast<clang::CompoundAssignOperator>(&S); |
| switch (Op->getOpcode()) { |
| default: |
| return; |
| case BO_AddAssign: |
| case BO_SubAssign: |
| fromArithmeticArg(Op->getLHS(), Op->getExprLoc()); |
| } |
| break; |
| } |
| case Stmt::BinaryOperatorClass: { |
| auto *Op = cast<clang::BinaryOperator>(&S); |
| switch (Op->getOpcode()) { |
| default: |
| return; |
| case BO_Add: |
| case BO_Sub: |
| fromArithmeticArg(Op->getLHS(), Op->getExprLoc()); |
| fromArithmeticArg(Op->getRHS(), Op->getExprLoc()); |
| } |
| break; |
| } |
| case Stmt::UnaryOperatorClass: { |
| auto *Op = cast<clang::UnaryOperator>(&S); |
| switch (Op->getOpcode()) { |
| default: |
| return; |
| case UO_PostInc: |
| case UO_PreInc: |
| case UO_PostDec: |
| case UO_PreDec: |
| fromArithmeticArg(Op->getSubExpr(), Op->getExprLoc()); |
| } |
| break; |
| } |
| } |
| } |
| |
| void fromCFGInitializer(const CFGInitializer &CFGInit) { |
| const CXXCtorInitializer *Initializer = CFGInit.getInitializer(); |
| if (!Initializer) { |
| // We expect this not to be the case, but not to a production-crash-worthy |
| // level, so assert instead of CHECK. |
| llvm::errs() << "CFGInitializer with null CXXCtorInitializer.\n"; |
| CFGInit.dump(); |
| assert(Initializer); |
| } |
| |
| // Base and delegating initializers are collected from when we see the |
| // underlying CXXConstructExpr, so we don't need to handle those, only the |
| // member initializers. |
| const FieldDecl *Field = Initializer->getAnyMember(); |
| if (Field == nullptr || InferableSlots.empty() || |
| !isSupportedPointerType(Field->getType())) |
| return; |
| |
| bool IsDefaultInitializer = Initializer->isInClassMemberInitializer(); |
| if (isSupportedSmartPointerType(Field->getType()) && |
| !IsDefaultInitializer && !Initializer->isWritten()) { |
| // We skip unwritten non-default member initializers for smart pointer |
| // fields because we check the end block of the constructor for the |
| // fields' nullability later. This allows us to avoid inferring Nullable |
| // for smart pointers without default initializers that are only ever (and |
| // always) assigned to a Nonnull value in constructor bodies. |
| return; |
| } |
| |
| const Expr *InitExpr = Initializer->getInit(); |
| bool NullptrDefaultInit = |
| IsDefaultInitializer && isOrIsConstructedFromNullPointerConstant( |
| InitExpr, Field->getASTContext()); |
| |
| fromAssignmentLike(*Field, *InitExpr, InitExpr->getExprLoc(), |
| NullptrDefaultInit |
| ? Evidence::NULLPTR_DEFAULT_MEMBER_INITIALIZER |
| : Evidence::ASSIGNED_FROM_NULLABLE); |
| } |
| |
| void fromFieldInits(const RecordInitListHelper &Helper) { |
| // Any initialization of base classes/fields will be collected from the |
| // InitListExpr for the base initialization, so we only need to collect here |
| // from the field inits. |
| for (auto [Field, InitExpr] : Helper.field_inits()) { |
| if (!isSupportedPointerType(Field->getType())) return; |
| |
| fromAssignmentLike(*Field, *InitExpr, InitExpr->getExprLoc()); |
| } |
| } |
| |
| void fromAggregateInitialization(const Stmt &S) { |
| if (auto *InitList = dyn_cast<clang::InitListExpr>(&S); |
| InitList && InitList->getType()->isRecordType() && |
| !(InitList->isSemanticForm() && InitList->isTransparent())) { |
| fromFieldInits(RecordInitListHelper(InitList)); |
| return; |
| } |
| if (auto *ParenListInit = dyn_cast<clang::CXXParenListInitExpr>(&S); |
| ParenListInit && ParenListInit->getType()->isRecordType()) { |
| fromFieldInits(RecordInitListHelper(ParenListInit)); |
| } |
| } |
| |
| const std::vector<InferableSlot> &InferableSlots; |
| const Formula &InferableSlotsConstraint; |
| llvm::function_ref<EvidenceEmitter> Emit; |
| const PointerNullabilityLattice &Lattice; |
| const Environment &Env; |
| }; |
| } // namespace |
| |
| /// Returns a function that the analysis can use to override Decl nullability |
| /// values from the source code being analyzed with previously inferred |
| /// nullabilities. |
| /// |
| /// In practice, this should only override the default nullability for Decls |
| /// that do not spell out a nullability in source code, because we only pass in |
| /// inferences from the previous round which are non-trivial and annotations |
| /// "inferred" by reading an annotation from source code in the previous round |
| /// were marked trivial. |
| static auto getConcreteNullabilityOverrideFromPreviousInferences( |
| ConcreteNullabilityCache &Cache, USRCache &USRCache, |
| const PreviousInferences &PreviousInferences) { |
| return [&](const Decl &D) -> std::optional<const PointerTypeNullability *> { |
| auto [It, Inserted] = Cache.try_emplace(&D); |
| if (Inserted) { |
| std::optional<const Decl *> FingerprintedDecl; |
| Slot Slot; |
| if (auto *FD = dyn_cast<FunctionDecl>(&D)) { |
| FingerprintedDecl = FD; |
| Slot = SLOT_RETURN_TYPE; |
| } else if (auto *PD = dyn_cast<ParmVarDecl>(&D)) { |
| if (auto *Parent = dyn_cast_or_null<FunctionDecl>( |
| PD->getParentFunctionOrMethod())) { |
| FingerprintedDecl = Parent; |
| Slot = paramSlot(PD->getFunctionScopeIndex()); |
| } |
| } |
| if (!FingerprintedDecl) return std::nullopt; |
| auto Fingerprint = |
| fingerprint(getOrGenerateUSR(USRCache, **FingerprintedDecl), Slot); |
| if (PreviousInferences.Nullable.contains(Fingerprint)) { |
| It->second.emplace(NullabilityKind::Nullable); |
| } else if (PreviousInferences.Nonnull.contains(Fingerprint)) { |
| It->second.emplace(NullabilityKind::NonNull); |
| } else { |
| It->second = std::nullopt; |
| } |
| } |
| if (!It->second) return std::nullopt; |
| return &*It->second; |
| }; |
| } |
| |
| template <typename ContainerT> |
| static bool hasAnyInferenceTargets(const ContainerT &Decls) { |
| return std::any_of(Decls.begin(), Decls.end(), |
| [](const Decl *D) { return D && isInferenceTarget(*D); }); |
| } |
| |
| static bool hasAnyInferenceTargets(dataflow::ReferencedDecls &RD) { |
| return hasAnyInferenceTargets(RD.Fields) || |
| hasAnyInferenceTargets(RD.Globals) || |
| hasAnyInferenceTargets(RD.Functions); |
| } |
| |
| std::unique_ptr<dataflow::Solver> makeDefaultSolverForInference() { |
| constexpr std::int64_t MaxSATIterations = 200'000; |
| return std::make_unique<dataflow::WatchedLiteralsSolver>(MaxSATIterations); |
| } |
| |
| // If D is a constructor definition, collect ASSIGNED_FROM_NULLABLE evidence for |
| // smart pointer fields implicitly default-initialized and left nullable in the |
| // exit block of the constructor body. |
| static void collectEvidenceFromConstructorExitBlock( |
| const clang::Decl &MaybeConstructor, const Environment &ExitEnv, |
| llvm::function_ref<EvidenceEmitter> Emit) { |
| auto *Ctor = dyn_cast<CXXConstructorDecl>(&MaybeConstructor); |
| if (!Ctor) return; |
| for (auto *Initializer : Ctor->inits()) { |
| if (Initializer->isWritten() || Initializer->isInClassMemberInitializer()) { |
| // We collect evidence from explicitly-written member initializers and |
| // default member initializers elsewhere, when analyzing the |
| // constructor's CFGInitializers. |
| continue; |
| } |
| const FieldDecl *Field = Initializer->getAnyMember(); |
| if (Field == nullptr || !isSupportedSmartPointerType(Field->getType()) || |
| !isInferenceTarget(*Field)) |
| continue; |
| // `Field` is a smart pointer field that was not explicitly |
| // initialized in the constructor member initializer list and does not |
| // have a default member initializer, so it was default constructed |
| // (and null) at the beginning of the constructor body. |
| |
| // If it is still nullable in the constructor's exit block |
| // environment, we collect evidence that it was assigned from a |
| // nullable value. |
| const dataflow::PointerValue *PV = getPointerValueFromSmartPointer( |
| cast<dataflow::RecordStorageLocation>( |
| ExitEnv.getThisPointeeStorageLocation()->getChild(*Field)), |
| ExitEnv); |
| if (PV == nullptr) continue; |
| // We have seen copy/move constructors that leave smart pointer fields |
| // without null state, because the field of the copied/moved-from value is |
| // not modeled or referenced and so has no null state. |
| // We don't get useful evidence from these cases anyway, because whether the |
| // field has been initialized with a non-null value is determined by some |
| // other form of construction for the same type, and we'll collect the |
| // relevant evidence there. |
| // In these cases, return early without emitting evidence. |
| if (!hasPointerNullState(*PV) && Ctor->isCopyOrMoveConstructor()) return; |
| |
| // Otherwise, we should always have null state for smart pointer fields. If |
| // not, we should fail loudly so we find out about it and can better handle |
| // those specific cases. |
| CHECK(hasPointerNullState(*PV)); |
| if (isNullable(*PV, ExitEnv)) { |
| Emit(*Field, Slot(0), Evidence::ASSIGNED_FROM_NULLABLE, |
| Ctor->isImplicit() ? Field->getBeginLoc() : Ctor->getBeginLoc()); |
| } |
| } |
| } |
| |
| llvm::Error collectEvidenceFromDefinition( |
| const Decl &Definition, llvm::function_ref<EvidenceEmitter> Emit, |
| USRCache &USRCache, const NullabilityPragmas &Pragmas, |
| const PreviousInferences PreviousInferences, |
| const SolverFactory &MakeSolver) { |
| ASTContext &Ctx = Definition.getASTContext(); |
| dataflow::ReferencedDecls ReferencedDecls; |
| Stmt *TargetStmt = nullptr; |
| std::optional<DeclStmt> DeclStmtForVarDecl; |
| const auto *TargetAsFunc = dyn_cast<FunctionDecl>(&Definition); |
| if (TargetAsFunc != nullptr) { |
| if (!TargetAsFunc->doesThisDeclarationHaveABody()) { |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), |
| "Function definitions must have a body."); |
| } |
| TargetStmt = TargetAsFunc->getBody(); |
| ReferencedDecls = dataflow::getReferencedDecls(*TargetAsFunc); |
| } else if (auto *Var = dyn_cast<VarDecl>(&Definition)) { |
| if (!Var->hasInit()) { |
| return llvm::createStringError( |
| llvm::inconvertibleErrorCode(), |
| "Variable definitions must have an initializer."); |
| } |
| // Synthesize a temporary DeclStmt for the assignment of the variable to |
| // its initializing expression. This is an unusual pattern that does not |
| // perfectly reflect the CFG or AST for declaration or assignment of a |
| // global variable, and it is possible that this may cause unexpected |
| // behavior in clang tools/utilities. |
| TargetStmt = |
| &DeclStmtForVarDecl.emplace(DeclGroupRef(const_cast<VarDecl *>(Var)), |
| Var->getBeginLoc(), Var->getEndLoc()); |
| ReferencedDecls = dataflow::getReferencedDecls(*TargetStmt); |
| if (!isInferenceTarget(*Var) && !hasAnyInferenceTargets(ReferencedDecls)) { |
| // If this variable is not an inference target and the initializer does |
| // not reference any inference targets, we won't be able to collect any |
| // useful evidence from the initializer. |
| return llvm::Error::success(); |
| } |
| } else { |
| std::string Msg = |
| "Unable to find a valid target definition from Definition:\n"; |
| llvm::raw_string_ostream Stream(Msg); |
| Definition.dump(Stream); |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), Msg); |
| } |
| |
| CHECK(TargetStmt) << "TargetStmt should have been assigned a non-null value."; |
| |
| llvm::Expected<dataflow::AdornedCFG> ACFG = |
| dataflow::AdornedCFG::build(Definition, *TargetStmt, Ctx); |
| if (!ACFG) return ACFG.takeError(); |
| |
| std::unique_ptr<dataflow::Solver> Solver = MakeSolver(); |
| DataflowAnalysisContext AnalysisContext(*Solver); |
| Environment Env = TargetAsFunc ? Environment(AnalysisContext, *TargetAsFunc) |
| : Environment(AnalysisContext, *TargetStmt); |
| PointerNullabilityAnalysis Analysis(Ctx, Env, Pragmas); |
| |
| TypeNullabilityDefaults Defaults = TypeNullabilityDefaults(Ctx, Pragmas); |
| |
| std::vector<InferableSlot> InferableSlots; |
| if (TargetAsFunc && isInferenceTarget(*TargetAsFunc)) { |
| auto Parameters = TargetAsFunc->parameters(); |
| for (auto I = 0; I < Parameters.size(); ++I) { |
| if (hasInferable(Parameters[I]->getType().getNonReferenceType()) && |
| !evidenceKindFromDeclaredTypeLoc( |
| Parameters[I]->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| InferableSlots.emplace_back(Analysis.assignNullabilityVariable( |
| Parameters[I], AnalysisContext.arena()), |
| paramSlot(I), *TargetAsFunc); |
| } |
| } |
| } |
| |
| for (const FieldDecl *Field : ReferencedDecls.Fields) { |
| if (isInferenceTarget(*Field) && |
| !evidenceKindFromDeclaredTypeLoc( |
| Field->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| InferableSlots.emplace_back( |
| Analysis.assignNullabilityVariable(Field, AnalysisContext.arena()), |
| Slot(0), *Field); |
| } |
| } |
| for (const VarDecl *Global : ReferencedDecls.Globals) { |
| if (isInferenceTarget(*Global) && |
| !evidenceKindFromDeclaredTypeLoc( |
| Global->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| InferableSlots.emplace_back( |
| Analysis.assignNullabilityVariable(Global, AnalysisContext.arena()), |
| Slot(0), *Global); |
| } |
| } |
| for (const FunctionDecl *Function : ReferencedDecls.Functions) { |
| if (isInferenceTarget(*Function) && |
| hasInferable(Function->getReturnType()) && |
| !evidenceKindFromDeclaredReturnType(*Function, Defaults)) { |
| InferableSlots.emplace_back( |
| Analysis.assignNullabilityVariable(Function, AnalysisContext.arena()), |
| SLOT_RETURN_TYPE, *Function); |
| } |
| } |
| |
| const auto &InferableSlotsConstraint = |
| getInferableSlotsAsInferredOrUnknownConstraint(InferableSlots, USRCache, |
| PreviousInferences, |
| AnalysisContext.arena()); |
| |
| ConcreteNullabilityCache ConcreteNullabilityCache; |
| Analysis.assignNullabilityOverride( |
| getConcreteNullabilityOverrideFromPreviousInferences( |
| ConcreteNullabilityCache, USRCache, PreviousInferences)); |
| |
| std::vector< |
| std::optional<dataflow::DataflowAnalysisState<PointerNullabilityLattice>>> |
| Results; |
| dataflow::CFGEltCallbacks<PointerNullabilityAnalysis> PostAnalysisCallbacks; |
| PostAnalysisCallbacks.Before = |
| [&](const CFGElement &Element, |
| const dataflow::DataflowAnalysisState<PointerNullabilityLattice> |
| &State) { |
| DefinitionEvidenceCollector::collect(InferableSlots, |
| InferableSlotsConstraint, Emit, |
| Element, State.Lattice, State.Env); |
| }; |
| if (llvm::Error Error = dataflow::runDataflowAnalysis(*ACFG, Analysis, Env, |
| PostAnalysisCallbacks) |
| .moveInto(Results)) |
| return Error; |
| |
| if (Solver->reachedLimit()) { |
| return llvm::createStringError(llvm::errc::interrupted, |
| "SAT solver reached iteration limit"); |
| } |
| |
| if (Results.empty()) return llvm::Error::success(); |
| if (std::optional<dataflow::DataflowAnalysisState<PointerNullabilityLattice>> |
| &ExitBlockResult = Results[ACFG->getCFG().getExit().getBlockID()]) { |
| collectEvidenceFromConstructorExitBlock(Definition, ExitBlockResult->Env, |
| Emit); |
| } |
| |
| return llvm::Error::success(); |
| } |
| |
| static void collectEvidenceFromDefaultArgument( |
| const clang::FunctionDecl &Fn, const clang::ParmVarDecl &ParamDecl, |
| Slot ParamSlot, llvm::function_ref<EvidenceEmitter> Emit) { |
| // We don't handle all cases of default arguments, because the expressions |
| // used for the argument are not available in any CFG, because the AST nodes |
| // are once-per-decl children of the ParmVarDecl, not once-per-call children |
| // of the CallExpr. Including them in the callsite CFG would be a |
| // significant undertaking, so for now, only handle nullptr literals (and 0) |
| // and expressions whose types already include an annotation, which we can |
| // handle just from declarations instead of call sites and should handle the |
| // majority of cases. |
| if (!isSupportedPointerType(ParamDecl.getType().getNonReferenceType())) |
| return; |
| if (!ParamDecl.hasDefaultArg()) return; |
| if (ParamDecl.hasUnparsedDefaultArg() || |
| ParamDecl.hasUninstantiatedDefaultArg()) { |
| Emit(Fn, ParamSlot, Evidence::UNKNOWN_ARGUMENT, ParamDecl.getEndLoc()); |
| return; |
| } |
| const Expr *DefaultArg = ParamDecl.getDefaultArg(); |
| CHECK(DefaultArg); |
| |
| if (isOrIsConstructedFromNullPointerConstant(DefaultArg, |
| Fn.getASTContext())) { |
| Emit(Fn, ParamSlot, Evidence::NULLABLE_ARGUMENT, DefaultArg->getExprLoc()); |
| } else { |
| auto Nullability = getNullabilityAnnotationsFromType(DefaultArg->getType()); |
| if (auto K = getArgEvidenceKindFromNullability( |
| Nullability.front().concrete(), |
| ParamDecl.getType()->isReferenceType())) { |
| Emit(Fn, ParamSlot, K, DefaultArg->getExprLoc()); |
| } else { |
| Emit(Fn, ParamSlot, Evidence::UNKNOWN_ARGUMENT, DefaultArg->getExprLoc()); |
| } |
| } |
| } |
| |
| static void collectNonnullAttributeEvidence( |
| const clang::FunctionDecl &Fn, unsigned ParamIndex, SourceLocation Loc, |
| llvm::function_ref<EvidenceEmitter> Emit) { |
| const ParmVarDecl *ParamDecl = Fn.getParamDecl(ParamIndex); |
| // The attribute does not apply to references-to-pointers or nested pointers |
| // or smart pointers. |
| if (isSupportedRawPointerType(ParamDecl->getType())) { |
| Emit(Fn, paramSlot(ParamIndex), Evidence::GCC_NONNULL_ATTRIBUTE, Loc); |
| } |
| } |
| |
| void collectEvidenceFromTargetDeclaration( |
| const clang::Decl &D, llvm::function_ref<EvidenceEmitter> Emit, |
| const NullabilityPragmas &Pragmas) { |
| TypeNullabilityDefaults Defaults(D.getASTContext(), Pragmas); |
| if (const auto *Fn = dyn_cast<clang::FunctionDecl>(&D)) { |
| if (auto K = evidenceKindFromDeclaredReturnType(*Fn, Defaults)) |
| Emit(*Fn, SLOT_RETURN_TYPE, *K, |
| Fn->getReturnTypeSourceRange().getBegin()); |
| if (const auto *RNNA = Fn->getAttr<ReturnsNonNullAttr>()) { |
| // The attribute does not apply to references-to-pointers or nested |
| // pointers or smart pointers. |
| if (isSupportedRawPointerType(Fn->getReturnType())) { |
| Emit(*Fn, SLOT_RETURN_TYPE, Evidence::GCC_NONNULL_ATTRIBUTE, |
| RNNA->getLocation()); |
| } |
| } |
| |
| for (unsigned I = 0; I < Fn->param_size(); ++I) { |
| const ParmVarDecl *ParamDecl = Fn->getParamDecl(I); |
| if (auto K = evidenceKindFromDeclaredTypeLoc( |
| ParamDecl->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| Emit(*Fn, paramSlot(I), *K, ParamDecl->getTypeSpecStartLoc()); |
| } |
| |
| collectEvidenceFromDefaultArgument(*Fn, *ParamDecl, paramSlot(I), Emit); |
| |
| if (const auto *NNA = ParamDecl->getAttr<NonNullAttr>()) |
| collectNonnullAttributeEvidence(*Fn, I, NNA->getLocation(), Emit); |
| } |
| |
| if (const auto *NNA = Fn->getAttr<NonNullAttr>()) { |
| // The attribute may have arguments indicating one or more parameters |
| // that are nonnull. If no arguments are present, all top-level, |
| // non-reference, raw pointer parameter types are nonnull. Return types |
| // are not affected. |
| if (NNA->args_size() > 0) { |
| for (const clang::ParamIdx &P : NNA->args()) { |
| // getASTIndex starts with 0 and does not count any implicit `this` |
| // parameter, matching FunctionDecl::getParamDecl indexing. |
| unsigned I = P.getASTIndex(); |
| collectNonnullAttributeEvidence(*Fn, I, NNA->getLocation(), Emit); |
| } |
| } else { |
| for (unsigned I = 0; I < Fn->param_size(); ++I) { |
| collectNonnullAttributeEvidence(*Fn, I, NNA->getLocation(), Emit); |
| } |
| } |
| } |
| } else if (const auto *Field = dyn_cast<clang::FieldDecl>(&D)) { |
| if (auto K = evidenceKindFromDeclaredTypeLoc( |
| Field->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| Emit(*Field, Slot(0), *K, Field->getTypeSpecStartLoc()); |
| } |
| } else if (const auto *Var = dyn_cast<clang::VarDecl>(&D)) { |
| if (auto K = evidenceKindFromDeclaredTypeLoc( |
| Var->getTypeSourceInfo()->getTypeLoc(), Defaults)) { |
| Emit(*Var, Slot(0), *K, Var->getTypeSpecStartLoc()); |
| } |
| } |
| } |
| |
| EvidenceSites EvidenceSites::discover(ASTContext &Ctx) { |
| struct Walker : public EvidenceLocationsWalker<Walker> { |
| EvidenceSites Out; |
| |
| bool VisitFunctionDecl(absl::Nonnull<const FunctionDecl *> FD) { |
| if (isInferenceTarget(*FD)) Out.Declarations.insert(FD); |
| |
| // Visiting template instantiations is fine, these are valid functions! |
| // But we'll be limited in what we can infer. |
| bool IsUsefulDefinition = |
| FD->doesThisDeclarationHaveABody() && |
| // We will not get anywhere with dependent code. |
| !FD->isDependentContext() && |
| // Defaulted (aka implicitly-defined) default constructors give us a |
| // chance to analyze default member initializers more thoroughly, but |
| // otherwise implicit functions are not generally useful. |
| (!FD->isImplicit() || |
| (isa<CXXConstructorDecl>(FD) && |
| cast<CXXConstructorDecl>(FD)->isDefaultConstructor())); |
| if (IsUsefulDefinition) Out.Definitions.insert(FD); |
| |
| return true; |
| } |
| |
| bool VisitFieldDecl(absl::Nonnull<const FieldDecl *> FD) { |
| if (isInferenceTarget(*FD)) Out.Declarations.insert(FD); |
| return true; |
| } |
| |
| bool VisitVarDecl(absl::Nonnull<const VarDecl *> VD) { |
| if (isInferenceTarget(*VD)) { |
| Out.Declarations.insert(VD); |
| } |
| // Variable initializers outside of function bodies may contain evidence |
| // we won't otherwise see, even if the variable is not an inference |
| // target. |
| if (VD->hasInit() && !VD->getDeclContext()->isFunctionOrMethod() && |
| !VD->isTemplated()) |
| Out.Definitions.insert(VD); |
| return true; |
| } |
| }; |
| |
| Walker W; |
| W.TraverseAST(Ctx); |
| return std::move(W.Out); |
| } |
| |
| } // namespace clang::tidy::nullability |