| // 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/type_nullability.h" | |
| #include <cassert> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/base/nullability.h" | |
| #include "absl/log/check.h" | |
| #include "nullability/type_and_maybe_loc_visitor.h" | |
| #include "clang/AST/ASTContext.h" | |
| #include "clang/AST/ASTFwd.h" | |
| #include "clang/AST/Attr.h" | |
| #include "clang/AST/DeclCXX.h" | |
| #include "clang/AST/DeclTemplate.h" | |
| #include "clang/AST/Expr.h" | |
| #include "clang/AST/ExprCXX.h" | |
| #include "clang/AST/NestedNameSpecifier.h" | |
| #include "clang/AST/TemplateBase.h" | |
| #include "clang/AST/TemplateName.h" | |
| #include "clang/AST/Type.h" | |
| #include "clang/AST/TypeLoc.h" | |
| #include "clang/AST/TypeVisitor.h" | |
| #include "clang/Analysis/FlowSensitive/Arena.h" | |
| #include "clang/Analysis/FlowSensitive/Formula.h" | |
| #include "clang/Basic/LLVM.h" | |
| #include "clang/Basic/SourceLocation.h" | |
| #include "clang/Basic/SourceManager.h" | |
| #include "clang/Basic/Specifiers.h" | |
| #include "llvm/ADT/STLExtras.h" | |
| #include "llvm/ADT/STLFunctionalExtras.h" | |
| #include "llvm/ADT/SmallPtrSet.h" | |
| #include "llvm/Support/Casting.h" | |
| #include "llvm/Support/SaveAndRestore.h" | |
| #include "llvm/Support/ScopedPrinter.h" | |
| namespace clang::tidy::nullability { | |
| bool isSupportedPointerType(QualType T) { | |
| return isSupportedRawPointerType(T) || isSupportedSmartPointerType(T); | |
| } | |
| bool isSupportedRawPointerType(QualType T) { return T->isPointerType(); } | |
| bool isSupportedSmartPointerType(QualType T) { | |
| return !underlyingRawPointerType(T).isNull(); | |
| } | |
| static bool isStandardSmartPointerDecl(const CXXRecordDecl *RD) { | |
| if (!RD->getDeclContext()->isStdNamespace()) return false; | |
| const IdentifierInfo *ID = RD->getIdentifier(); | |
| if (ID == nullptr) return false; | |
| StringRef Name = ID->getName(); | |
| return Name == "unique_ptr" || Name == "shared_ptr"; | |
| } | |
| static const CXXRecordDecl *absl_nullable getSmartPointerBaseClass( | |
| const CXXRecordDecl *absl_nullable RD, | |
| llvm::SmallPtrSet<const CXXRecordDecl *, 2> &Seen, | |
| AccessSpecifier BaseAccess) { | |
| if (RD == nullptr) return nullptr; | |
| if (isStandardSmartPointerDecl(RD) || RD->hasAttr<TypeNullableAttr>()) | |
| return RD; | |
| if (RD->hasDefinition()) | |
| for (const CXXBaseSpecifier &Base : RD->bases()) | |
| if (Base.getAccessSpecifier() <= BaseAccess) { | |
| const CXXRecordDecl *BaseClass = Base.getType()->getAsCXXRecordDecl(); | |
| // If we didn't get a `CXXRecordDecl` above, this could be something | |
| // like `unique_ptr<T>` (where `T` is a dependent type). In this case, | |
| // return the `CXXRecordDecl` of the underlying template -- it's the | |
| // best we can do. | |
| if (BaseClass == nullptr) { | |
| if (const auto* TST = | |
| Base.getType()->getAs<TemplateSpecializationType>()) { | |
| // If the base class template name is a dependent name, the template | |
| // decl will be null. | |
| if (const auto* TD = TST->getTemplateName().getAsTemplateDecl()) | |
| // And, if the base class is a template template parameter, we can | |
| // retrieve the template decl, but not the templated decl, so | |
| // don't assert presence during the cast. | |
| BaseClass = | |
| dyn_cast_if_present<CXXRecordDecl>(TD->getTemplatedDecl()); | |
| // We need to be careful here: Once we start looking at underlying | |
| // templates, we may walk into cycles, as a template may derive from | |
| // itself (either directly or indirectly), though with different | |
| // template arguments. | |
| // To protect against infinite recursion, make sure we haven't seen | |
| // this particular base class before. (We only need to do this in | |
| // this case where we're looking at the template itself rather than | |
| // a specialization.) | |
| if (BaseClass != nullptr) { | |
| if (!Seen.insert(BaseClass).second) return nullptr; | |
| } | |
| } | |
| } | |
| if (const CXXRecordDecl *Result = | |
| getSmartPointerBaseClass(BaseClass, Seen, BaseAccess)) | |
| return Result; | |
| } | |
| return nullptr; | |
| } | |
| /// If `RD` or one of its bases (with access at most as restrictive as | |
| /// `BaseAccess`) is a smart pointer class, returns that smart | |
| /// pointer class; otherwise, returns null. | |
| static const CXXRecordDecl *absl_nullable getSmartPointerBaseClass( | |
| const CXXRecordDecl *absl_nullable RD, AccessSpecifier BaseAccess) { | |
| llvm::SmallPtrSet<const CXXRecordDecl *, 2> Seen; | |
| return getSmartPointerBaseClass(RD, Seen, BaseAccess); | |
| } | |
| static QualType underlyingPointerTypeFromTemplateArg( | |
| const ClassTemplateSpecializationDecl &CTSD, const ASTContext &ASTCtx) { | |
| if (CTSD.getTemplateArgs().size() == 0) return QualType(); | |
| if (CTSD.getTemplateArgs()[0].getKind() != TemplateArgument::Type) | |
| return QualType(); | |
| QualType TemplateArg = CTSD.getTemplateArgs()[0].getAsType(); | |
| return ASTCtx.getPointerType(ASTCtx.getBaseElementType(TemplateArg)); | |
| } | |
| QualType underlyingRawPointerType(QualType T, AccessSpecifier BaseAccess) { | |
| const CXXRecordDecl *RD = T.getCanonicalType()->getAsCXXRecordDecl(); | |
| if (RD == nullptr) return QualType(); | |
| const ASTContext &ASTCtx = RD->getASTContext(); | |
| // There's a special case we need to handle here: | |
| // If `RD` is a `ClassTemplateSpecializationDecl` for an uninstantiated | |
| // specialization of a smart pointer (or a class derived from it), it's just | |
| // an empty shell -- it doesn't contain any base specifiers or any of the type | |
| // aliases we need (`pointer`, `element_type`). | |
| // We deal with this as follows: | |
| // * We check the primary template for base classes. | |
| // * We extract the underlying pointer type from the template argument (as | |
| // that's the best we can do). | |
| auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD); | |
| if (CTSD && !CTSD->hasDefinition()) { | |
| if (getSmartPointerBaseClass( | |
| CTSD->getSpecializedTemplate()->getTemplatedDecl(), BaseAccess) == | |
| nullptr) | |
| return QualType(); | |
| return underlyingPointerTypeFromTemplateArg(*CTSD, ASTCtx); | |
| } | |
| const CXXRecordDecl *SmartPtrDecl = getSmartPointerBaseClass(RD, BaseAccess); | |
| if (SmartPtrDecl == nullptr) return QualType(); | |
| const auto &Idents = ASTCtx.Idents; | |
| if (auto PointerIt = Idents.find("pointer"); PointerIt != Idents.end()) { | |
| if (auto *TND = SmartPtrDecl->lookup(PointerIt->getValue()) | |
| .find_first<TypedefNameDecl>()) { | |
| // It's possible for a `unique_ptr` to have an underlying `pointer` type | |
| // that is not a raw pointer if there is a custom deleter that specifies | |
| // such a type. (The only requirement is the the underlying pointer type | |
| // is a NullablePointer.) This case is rare, so we simply ignore such | |
| // pointers. | |
| if (isSupportedRawPointerType(TND->getUnderlyingType())) | |
| return TND->getUnderlyingType(); | |
| return QualType(); | |
| } | |
| } | |
| if (auto PointerIt = Idents.find("element_type"); PointerIt != Idents.end()) { | |
| if (auto *TND = SmartPtrDecl->lookup(PointerIt->getValue()) | |
| .find_first<TypedefNameDecl>()) | |
| return ASTCtx.getPointerType(TND->getUnderlyingType()); | |
| } | |
| // If we don't have a `pointer` or `element_type` type alias, we deduce the | |
| // underlying pointer type from the template argument if possible. | |
| if (auto *SmartPointerCTSD = | |
| dyn_cast<ClassTemplateSpecializationDecl>(SmartPtrDecl)) | |
| return underlyingPointerTypeFromTemplateArg(*SmartPointerCTSD, ASTCtx); | |
| if (SmartPtrDecl->hasDefinition()) { | |
| for (const CXXBaseSpecifier& Base : SmartPtrDecl->bases()) { | |
| if (Base.getAccessSpecifier() <= BaseAccess) { | |
| QualType BaseType = Base.getType(); | |
| if (QualType UnderlyingBase = | |
| underlyingRawPointerType(BaseType, BaseAccess); | |
| !UnderlyingBase.isNull()) { | |
| return UnderlyingBase; | |
| } | |
| } | |
| } | |
| } | |
| return QualType(); | |
| } | |
| PointerTypeNullability PointerTypeNullability::createSymbolic( | |
| dataflow::Arena &A) { | |
| PointerTypeNullability Symbolic; | |
| Symbolic.Symbolic = true; | |
| Symbolic.Nonnull = A.makeAtom(); | |
| Symbolic.Nullable = A.makeAtom(); | |
| return Symbolic; | |
| } | |
| PointerTypeNullability PointerTypeNullability::createSymbolic( | |
| dataflow::Atom Nonnull, dataflow::Atom Nullable) { | |
| PointerTypeNullability Symbolic; | |
| Symbolic.Symbolic = true; | |
| Symbolic.Nonnull = Nonnull; | |
| Symbolic.Nullable = Nullable; | |
| return Symbolic; | |
| } | |
| llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, | |
| const PointerTypeNullability &PN) { | |
| // TODO: should symbolic nullabilities have names? | |
| if (PN.isSymbolic()) | |
| return OS << "Symbolic(Nonnull=" << PN.Nonnull << ", " | |
| << "Nullable=" << PN.Nullable << ")"; | |
| return OS << PN.concrete(); | |
| } | |
| std::string nullabilityToString(const TypeNullability &Nullability) { | |
| std::string Result = "["; | |
| llvm::interleave( | |
| Nullability, | |
| [&](const PointerTypeNullability &PN) { Result += llvm::to_string(PN); }, | |
| [&] { Result += ", "; }); | |
| Result += "]"; | |
| return Result; | |
| } | |
| FileID getGoverningFile(const Decl *absl_nullable D) { | |
| if (!D) return FileID(); | |
| return D->getASTContext() | |
| .getSourceManager() | |
| .getDecomposedExpansionLoc(D->getLocation()) | |
| .first; | |
| } | |
| namespace { | |
| // Recognize aliases annotated with [[clang::annotate("Nullable")]] etc. as | |
| // equivalent to alias that apply _Nullable, etc. | |
| // | |
| // Ideally such aliases would apply the _Nullable attribute themselves, but we | |
| // support this alternative as well. | |
| std::optional<NullabilityKind> getAliasNullability(const TemplateName &TN) { | |
| if (const auto *TD = TN.getAsTemplateDecl()) { | |
| if (!TD->getTemplatedDecl()) return std::nullopt; // BuiltinTemplateDecl | |
| if (const auto *A = TD->getTemplatedDecl()->getAttr<AnnotateAttr>()) { | |
| if (A->getAnnotation() == "Nullable") return NullabilityKind::Nullable; | |
| if (A->getAnnotation() == "Nonnull") return NullabilityKind::NonNull; | |
| if (A->getAnnotation() == "Nullability_Unspecified") | |
| return NullabilityKind::Unspecified; | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| QualType ignoreTrivialSugar(QualType T) { | |
| while (!T.hasLocalQualifiers() && isa<ParenType>(T)) | |
| T = T->getLocallyUnqualifiedSingleStepDesugaredType(); | |
| return T; | |
| } | |
| // True if T is Foo<args...> which is an alias for exactly Bar<args...>. | |
| // We treat such aliases as "transparent" (equivalent to a using decl). | |
| // The governing pragma is where Foo is used, not where it is defined. | |
| bool isTransparentAlias(QualType T) { | |
| // Unpack T, and check it's a template alias pointing to another template. | |
| if (T.hasLocalQualifiers()) return false; | |
| // Foo<arg0, arg1> | |
| const auto *FooUse = dyn_cast<TemplateSpecializationType>(T); | |
| if (!FooUse || !FooUse->isTypeAlias()) return false; | |
| // template <param0, param1> using Foo = ...; | |
| auto *FooDecl = FooUse->getTemplateName().getAsTemplateDecl(); | |
| if (!FooDecl || !FooDecl->getTemplatedDecl()) return false; | |
| // Bar<param0, param1> | |
| auto *BarUse = dyn_cast<TemplateSpecializationType>(ignoreTrivialSugar( | |
| cast<TypeAliasDecl>(FooDecl->getTemplatedDecl())->getUnderlyingType())); | |
| if (!BarUse) return false; | |
| // No funny business where the forwarded-to template is a template param. | |
| if (!isa_and_present<ClassTemplateDecl, TypeAliasTemplateDecl>( | |
| BarUse->getTemplateName().getAsTemplateDecl())) | |
| return false; | |
| // Now verify Foo is exactly forwarding its params to Bar. | |
| for (int I = 0; I < BarUse->template_arguments().size(); ++I) { | |
| auto &Arg = BarUse->template_arguments()[I]; | |
| switch (Arg.getKind()) { | |
| case TemplateArgument::Type: | |
| if (auto *Parm = dyn_cast<TemplateTypeParmType>(Arg.getAsType()); | |
| Parm && Parm->getDepth() == FooDecl->getTemplateDepth() && | |
| Parm->getIndex() == I) | |
| continue; | |
| return false; | |
| case TemplateArgument::Expression: | |
| if (auto *DRE = dyn_cast<DeclRefExpr>(Arg.getAsExpr())) { | |
| if (auto *Parm = dyn_cast<NonTypeTemplateParmDecl>(DRE->getDecl()); | |
| Parm && Parm->getDepth() == Parm->getTemplateDepth() && | |
| Parm->getIndex() == I) | |
| continue; | |
| } | |
| return false; | |
| // TODO: we could recognize pack forwarding. | |
| default: | |
| return false; | |
| } | |
| } | |
| // Foo may have extra params, Bar may have extra (defaulted) params. | |
| return true; | |
| } | |
| // If T is Foo<U> which expands to U, return U. | |
| std::optional<QualType> unwrapAlias(QualType T) { | |
| // Validate that T is exactly Alias<args...> | |
| if (T.hasLocalQualifiers()) return std::nullopt; | |
| const auto *TST = dyn_cast<TemplateSpecializationType>(T); | |
| if (!TST || !TST->isTypeAlias() || TST->template_arguments().empty() || | |
| TST->template_arguments().front().getKind() != TemplateArgument::Type) | |
| return std::nullopt; | |
| auto *TD = TST->getTemplateName().getAsTemplateDecl(); | |
| if (!TD) return std::nullopt; | |
| // Now desugar T to check if it expands to arg0 of the original alias. | |
| while (true) { | |
| QualType Next = T->getLocallyUnqualifiedSingleStepDesugaredType(); | |
| if (Next.hasLocalQualifiers()) return std::nullopt; | |
| if (Next.getTypePtr() == T.getTypePtr()) return std::nullopt; // not sugar | |
| if (auto *Subst = dyn_cast<SubstTemplateTypeParmType>(T); | |
| Subst && Subst->getAssociatedDecl() == TD && Subst->getIndex() == 0) { | |
| // Use the sugared form of the argument. | |
| return TST->template_arguments().front().getAsType(); | |
| } | |
| T = Next; | |
| } | |
| } | |
| // Traverses a Type to find the points where it might be nullable. | |
| // This will visit the contained PointerType in the correct order to produce | |
| // the TypeNullability vector. | |
| // | |
| // Subclasses must provide | |
| // `void report(const Type*, FileID, optional<NullabilityKind>, | |
| // std::optional<TypeLoc>)` | |
| // (the FileID is the one whose #pragma governs the type) | |
| // They may override TypeAndMaybeLocVisitor visit*Type methods to customize the | |
| // traversal. | |
| // | |
| // Canonically-equivalent Types produce equivalent sequences of report() calls: | |
| // - corresponding pointer Types are canonically-equivalent | |
| // - the NullabilityKind may be different, as it derives from type sugar | |
| template <class Impl> | |
| class NullabilityWalker : public TypeAndMaybeLocVisitor<Impl> { | |
| using Base = TypeAndMaybeLocVisitor<Impl>; | |
| Impl &derived() { return *static_cast<Impl *>(this); } | |
| protected: | |
| // A nullability attribute we've seen, waiting to attach to a pointer type. | |
| // There may be sugar in between: Attributed -> Typedef -> Typedef -> Pointer. | |
| // All non-sugar types must consume nullability, most will ignore it. | |
| std::optional<NullabilityKind> PendingNullability; | |
| private: | |
| // The file whose #pragma governs the type currently being walked. | |
| FileID File; | |
| // The most complete and direct TypeLoc seen so far for the type currently | |
| // being visited. | |
| std::optional<TypeLoc> BestLocSoFar; | |
| // Update `BestLocSoFar` for a new TypeLoc seen. | |
| // | |
| // If not `OverrideQualifiedType`, an existing TypeLoc with qualifiers in | |
| // `BestLocSoFar` will be kept instead of replacing it with `Loc`. This | |
| // supports reporting of the most complete TypeLoc for a type, e.g. | |
| // `std::unique_ptr<int>` instead of just `unique_ptr<int>`. | |
| void recordLoc(TypeLoc Loc, bool OverrideQualifiedType = false) { | |
| if (BestLocSoFar && BestLocSoFar->getType().getCanonicalType() != | |
| Loc.getType().getCanonicalType()) { | |
| // We've moved on to visiting a new type, so clear the Loc. | |
| BestLocSoFar = std::nullopt; | |
| } | |
| // In most cases we want to keep the most qualified Loc for the type, but | |
| // template arguments supersede that preference. And don't keep any bare | |
| // `auto` TypeLocs, because bare `auto` cannot be annotated. | |
| if ((!BestLocSoFar || OverrideQualifiedType || | |
| !BestLocSoFar->getPrefix()) && | |
| Loc.getTypeLocClass() != TypeLoc::Auto) { | |
| BestLocSoFar = Loc; | |
| } | |
| } | |
| void sawNullability(NullabilityKind NK) { | |
| // If we see nullability applied twice, the outer one wins. | |
| assert((NK == NullabilityKind::Unspecified || | |
| PendingNullability != NullabilityKind::Unspecified) && | |
| "Unknown around nullability sugar should have been ignored!"); | |
| if (!PendingNullability.has_value()) PendingNullability = NK; | |
| } | |
| void ignoreUnexpectedNullability() { | |
| // TODO: Can we upgrade this to an assert? | |
| // clang is pretty thorough about ensuring we can't put _Nullable on | |
| // non-pointers, even failing template instantiation on this basis. | |
| PendingNullability.reset(); | |
| } | |
| // While walking types instantiated from templates, e.g.: | |
| // - the underlying type of alias TemplateSpecializationTypes | |
| // - type aliases inside class template instantiations | |
| // we see SubstTemplateTypeParmTypes where type parameters were referenced. | |
| // The directly-available underlying types lack sugar, but we can retrieve the | |
| // sugar from the arguments of the original e.g. TemplateSpecializationType. | |
| // | |
| // The "template context" associates template params with the | |
| // corresponding args, to allow this retrieval. | |
| // In general, not just the directly enclosing template params but also those | |
| // of outer classes are accessible. | |
| // So conceptually this maps (depth, index, pack_index) => TemplateArgument. | |
| // To avoid copying these maps, inner contexts *extend* from outer ones. | |
| // | |
| // When we start to walk a TemplateArgument (in place of a SubstTTPType), we | |
| // must do so in the template instantiation context where the argument was | |
| // written. Then when we're done, we must restore the old context. | |
| struct TemplateContext { | |
| // A decl that owns an arg list, per SubstTTPType::getAssociatedDecl. | |
| // For aliases: TypeAliasTemplateDecl. | |
| // For classes: ClassTemplateSpecializationDecl. | |
| const Decl *AssociatedDecl = nullptr; | |
| // The sugared template arguments to AssociatedDecl, as written in the code. | |
| // If absent, the arguments could not be reconstructed. | |
| std::optional<ArrayRef<TemplateArgument>> Args; | |
| // The file whose #pragma governs types written in Args. | |
| FileID ArgsFile; | |
| // In general, multiple template params are in scope (nested templates). | |
| // These are a linked list: *this describes one, *Extends describes the | |
| // next. In practice, this is the enclosing class template. | |
| const TemplateContext *Extends = nullptr; | |
| // The template context in which the args were written. | |
| // The args may reference params visible in this context. | |
| const TemplateContext *ArgContext = nullptr; | |
| // `Args` plus location information, if available. | |
| std::optional<std::vector<TemplateArgumentLoc>> ArgLocs; | |
| // Example showing a TemplateContext graph: | |
| // | |
| // // (some sugar and nested templates for the example) | |
| // using INT = int; using FLOAT = float; | |
| // template <class T> struct Outer { | |
| // template <class U> struct Inner { | |
| // using Pair = std::pair<T, U>; | |
| // } | |
| // } | |
| // | |
| // template <class X> | |
| // struct S { | |
| // using Type = typename Outer<INT>::Inner<X>::Pair; | |
| // } | |
| // | |
| // using Target = S<FLOAT>::Type; | |
| // | |
| // Per clang's AST, instantiated Type is std::pair<int, float> with only | |
| // SubstTemplateTypeParmTypes for sugar, we're trying to recover INT, FLOAT. | |
| // | |
| // When walking a type with a qualifier, e.g., for the S<FLOAT>:: we set up: | |
| // | |
| // Current -> {Associated=S<float>, Args=<FLOAT>, Extends=null, ArgCtx=null} | |
| // | |
| // This means that when resolving ::Type: | |
| // - we can resugar occurrences of X (float -> FLOAT) | |
| // - ArgContext=null: the arg FLOAT may not refer to template params | |
| // (or at least we can't resugar them) | |
| // - Extends=null: there are no other template params we can resugar | |
| // | |
| // Skipping up to ::Pair inside S<FLOAT>'s instantiation, we have the graph: | |
| // | |
| // Current -> {Associated=Outer<int>::Inner<float>, Args=<X>} | |
| // | Extends | | |
| // A{Associated=Outer<int>, Args=<INT>, Extends=null} | ArgContext | |
| // | ArgContext | | |
| // B{Associated=S<float>, Args=<FLOAT>, Extends=null, ArgContext=null} | |
| // | |
| // (Note that B here is the original TemplateContext we set up above). | |
| // | |
| // This means that when resolving ::Pair: | |
| // - we can resugar instances of U (float -> X) | |
| // - ArgContext=B: when resugaring U, we can resugar X (float -> FLOAT) | |
| // - Extends=A: we can also resugar T (int -> INT) | |
| // - A.ArgContext=B: when resugaring T, we can resugar X. | |
| // (we never do, because INT doesn't mention X) | |
| // - A.Extends=null: there are no other template params te resugar | |
| // - B.ArgContext=null: FLOAT may not refer to any template params | |
| // - B.Extends=null: there are no other template params to resugar | |
| // (e.g. Type's definition cannot refer to T) | |
| }; | |
| // The context that provides sugared args for the template params that are | |
| // accessible to the type we're currently walking. | |
| const TemplateContext *CurrentTemplateContext = nullptr; | |
| // Adjusts args list from those of primary template => template pattern. | |
| // | |
| // A template arg list corresponds 1:1 to primary template params. | |
| // In partial specializations, the correspondence may differ: | |
| // template <int, class> struct S; | |
| // template <class T> struct S<0, T> { | |
| // using Alias = T; // T refers to param #0 | |
| // }; | |
| // S<0, int*>::Alias X; // T is bound to arg #1 | |
| // or | |
| // template <class> struct S; | |
| // template <class T> struct S<T*> { using Alias = T; } | |
| // S<int*>::Alias X; // arg #0 is int*, param #0 is bound to int | |
| void translateTemplateArgsForSpecialization(TemplateContext &Ctx) { | |
| // Only relevant where partial specialization is used. | |
| // - Full specializations may not refer to template params at all. | |
| // - For primary templates, the input is already correct. | |
| const TemplateArgumentList *PartialArgs = nullptr; | |
| if (const ClassTemplateSpecializationDecl *CTSD = | |
| llvm::dyn_cast<ClassTemplateSpecializationDecl>( | |
| Ctx.AssociatedDecl)) { | |
| if (isa_and_nonnull<ClassTemplatePartialSpecializationDecl>( | |
| CTSD->getTemplateInstantiationPattern())) | |
| PartialArgs = &CTSD->getTemplateInstantiationArgs(); | |
| } else if (const VarTemplateSpecializationDecl *VTSD = | |
| llvm::dyn_cast<VarTemplateSpecializationDecl>( | |
| Ctx.AssociatedDecl)) { | |
| if (isa_and_nonnull<VarTemplatePartialSpecializationDecl>( | |
| VTSD->getTemplateInstantiationPattern())) | |
| PartialArgs = &VTSD->getTemplateInstantiationArgs(); | |
| } | |
| if (!PartialArgs) return; | |
| // To get from the template arg list to the partial-specialization arg list | |
| // means running much of the template argument deduction algorithm. | |
| // This is complex in general. [temp.deduct] For now, bail out. | |
| // In future, hopefully we can handle at least simple cases. | |
| Ctx.Args.reset(); | |
| Ctx.ArgLocs.reset(); | |
| } | |
| void report(const Type *T) { | |
| if (BestLocSoFar && | |
| // We only report unqualified types, but the best Loc for such a type | |
| // is the qualified Loc (if present). So, when checking that | |
| // `BestLocSoFar` is a valid TypeLoc for `T`, compare the canonical | |
| // types of `BestLocSoFar`'s *unqualified* type and `T`. | |
| BestLocSoFar->getType()->getCanonicalTypeUnqualified() != | |
| T->getCanonicalTypeInternal()) { | |
| BestLocSoFar = std::nullopt; | |
| } | |
| derived().report(T, File, PendingNullability, BestLocSoFar); | |
| PendingNullability.reset(); | |
| BestLocSoFar = std::nullopt; | |
| } | |
| // If we see foo<args>::ty then we may need sugar from args to resugar ty. | |
| // Record the information in a TemplateContext graph. | |
| std::vector<TemplateContext> getBoundTemplateArgsFromQualifiedType( | |
| const Type* absl_nonnull T, | |
| std::optional<NestedNameSpecifierLoc> NNSLoc) { | |
| std::vector<TemplateContext> BoundTemplateArgs; | |
| // Iterate over qualifiers right-to-left, looking for template args. | |
| for (NestedNameSpecifier NNS = T->getPrefix(); NNS;) { | |
| // TODO: there are other ways a NNS could bind template args: | |
| // template <typename T> foo { struct bar { using baz = T; }; }; | |
| // using T = foo<int * _Nullable>::bar; | |
| // using U = T::baz; | |
| // Here T:: is not a TemplateSpecializationType (directly or indirectly). | |
| // Nevertheless it provides sugar that is referenced from baz. | |
| // Probably we need another type visitor to collect bindings in general. | |
| if (NNS.getKind() == NestedNameSpecifier::Kind::Type) { | |
| if (const auto* TST = | |
| dyn_cast_or_null<TemplateSpecializationType>(NNS.getAsType())) { | |
| TemplateContext Ctx; | |
| Ctx.Args = TST->template_arguments(); | |
| Ctx.ArgsFile = File; | |
| Ctx.ArgContext = CurrentTemplateContext; | |
| // `Extends` is initialized below: we chain BoundTemplateArgs | |
| // together. | |
| Ctx.AssociatedDecl = | |
| TST->isTypeAlias() | |
| ? TST->getTemplateName().getAsTemplateDecl() | |
| : static_cast<Decl*>(TST->getAsCXXRecordDecl()); | |
| if (NNSLoc) { | |
| if (auto TSTLoc = NNSLoc->getAsTypeLoc() | |
| .getAs<TemplateSpecializationTypeLoc>()) { | |
| Ctx.ArgLocs = std::vector<TemplateArgumentLoc>(); | |
| Ctx.ArgLocs->reserve(TSTLoc.getNumArgs()); | |
| for (unsigned I = 0, N = TSTLoc.getNumArgs(); I < N; ++I) { | |
| Ctx.ArgLocs->push_back(TSTLoc.getArgLoc(I)); | |
| } | |
| } | |
| } | |
| translateTemplateArgsForSpecialization(Ctx); | |
| BoundTemplateArgs.push_back(Ctx); | |
| } | |
| } | |
| // Get next prefix from the NNS and NNSLoc. | |
| switch (NNS.getKind()) { | |
| case NestedNameSpecifier::Kind::Null: | |
| case NestedNameSpecifier::Kind::Global: | |
| case NestedNameSpecifier::Kind::MicrosoftSuper: | |
| NNS = std::nullopt; | |
| if (NNSLoc) NNSLoc = clang::NestedNameSpecifierLoc(); | |
| break; | |
| case NestedNameSpecifier::Kind::Namespace: | |
| NNS = NNS.getAsNamespaceAndPrefix().Prefix; | |
| if (NNSLoc) NNSLoc = NNSLoc->getAsNamespaceAndPrefix().Prefix; | |
| break; | |
| case NestedNameSpecifier::Kind::Type: | |
| NNS = NNS.getAsType()->getPrefix(); | |
| if (NNSLoc) NNSLoc = NNSLoc->getAsTypeLoc().getPrefix(); | |
| break; | |
| default: | |
| NNS = std::nullopt; | |
| if (NNSLoc) NNSLoc = clang::NestedNameSpecifierLoc(); | |
| llvm_unreachable("unexpected NestedNameSpecifier kind"); | |
| } | |
| } | |
| if (!BoundTemplateArgs.empty()) { | |
| // Wire up the inheritance chain so all the contexts are visible. | |
| BoundTemplateArgs.back().Extends = CurrentTemplateContext; | |
| for (int I = 0; I < BoundTemplateArgs.size() - 1; ++I) | |
| BoundTemplateArgs[I].Extends = &BoundTemplateArgs[I + 1]; | |
| } | |
| return BoundTemplateArgs; | |
| } | |
| template <typename TypeLocT> | |
| std::vector<TemplateContext> getBoundTemplateArgsFromQualifiedType( | |
| const Type* absl_nonnull T, std::optional<TypeLoc> L) { | |
| return getBoundTemplateArgsFromQualifiedType( | |
| T, L ? std::optional<NestedNameSpecifierLoc>( | |
| L->getAs<TypeLocT>().getQualifierLoc()) | |
| : std::nullopt); | |
| } | |
| public: | |
| NullabilityWalker(FileID File) : File(File) {} | |
| void visit(TypeLoc Loc) { visit(Loc.getType(), Loc); } | |
| void visit(QualType T, std::optional<TypeLoc> L = std::nullopt) { | |
| visit(T.getTypePtr(), L ? L->getUnqualifiedLoc() : L); | |
| } | |
| void visit(const TemplateArgument &TA, | |
| std::optional<TemplateArgumentLoc> TAL) { | |
| switch (TA.getKind()) { | |
| case TemplateArgument::Type: { | |
| const auto *ArgTypeSourceInfo = | |
| TAL ? TAL->getTypeSourceInfo() : nullptr; | |
| auto ArgLoc = | |
| ArgTypeSourceInfo != nullptr | |
| ? std::optional<TypeLoc>(ArgTypeSourceInfo->getTypeLoc()) | |
| : std::nullopt; | |
| // Always prefer a template argument Loc over a broader Loc for a type | |
| // defined as equal to a template argument, e.g. for the type | |
| // `std::vector<int *>::value_type`, prefer to report the Loc for the | |
| // `int *` template argument rather than the entire type, since the | |
| // value_type alias is equal to the template parameter. | |
| if (ArgLoc) recordLoc(*ArgLoc, /*OverrideQualifiedType=*/true); | |
| visit(TA.getAsType(), ArgLoc); | |
| break; | |
| } | |
| case TemplateArgument::Pack: { | |
| for (const auto &PackElt : TA.getPackAsArray()) | |
| visit(PackElt, std::nullopt); | |
| break; | |
| } | |
| default: | |
| // Don't handle non-type template arguments. | |
| break; | |
| } | |
| } | |
| void visit(const DeclContext *absl_nonnull DC) { | |
| // For now, only consider enclosing classes. | |
| // TODO: The nullability of template functions can affect local classes too, | |
| // this can be relevant e.g. when instantiating templates with such types. | |
| if (auto *CRD = dyn_cast<CXXRecordDecl>(DC)) | |
| visit(DC->getParentASTContext().getCanonicalTagType(CRD), std::nullopt); | |
| } | |
| void visit(const Type *absl_nonnull T, std::optional<TypeLoc> L) { | |
| if (L) recordLoc(*L); | |
| Base::visit(T, L); | |
| } | |
| // Check if `T` might represent a qualified type like foo<args>::ty where we | |
| // may need sugar from args to resugar ty. The majority should be covered by | |
| // visitTemplateSpecializationType, visitTypedefType, and visitRecordType, | |
| // but technically these other cases also support getQualifierLoc(). | |
| std::vector<TemplateContext> getBoundTemplateArgsFromOtherQualifiedTypes( | |
| const Type* absl_nonnull T, std::optional<TypeLoc> L) { | |
| switch (T->getTypeClass()) { | |
| case Type::DeducedTemplateSpecialization: | |
| return getBoundTemplateArgsFromQualifiedType< | |
| DeducedTemplateSpecializationTypeLoc>(T, L); | |
| case Type::DependentName: | |
| return getBoundTemplateArgsFromQualifiedType<DependentNameTypeLoc>(T, | |
| L); | |
| case Type::Enum: | |
| case Type::InjectedClassName: | |
| return getBoundTemplateArgsFromQualifiedType<TagTypeLoc>(T, L); | |
| case Type::Using: | |
| return getBoundTemplateArgsFromQualifiedType<UsingTypeLoc>(T, L); | |
| default: | |
| break; | |
| } | |
| return {}; | |
| } | |
| void visitType(const Type *absl_nonnull T, std::optional<TypeLoc> L) { | |
| std::vector<TemplateContext> BoundTemplateArgs = | |
| getBoundTemplateArgsFromOtherQualifiedTypes(T, L); | |
| std::optional<llvm::SaveAndRestore<const TemplateContext*>> Restore; | |
| if (!BoundTemplateArgs.empty()) | |
| Restore.emplace(CurrentTemplateContext, &BoundTemplateArgs.front()); | |
| // For sugar not explicitly handled below, desugar and continue. | |
| // (We need to walk the full structure of the canonical type.) | |
| if (auto *Desugar = | |
| T->getLocallyUnqualifiedSingleStepDesugaredType().getTypePtr(); | |
| Desugar != T) { | |
| // We can't arbitrarily desugar TypeLocs the way we can for types, so we | |
| // don't collect more TypeLocs from this point in. | |
| return visit(Desugar, std::nullopt); | |
| } | |
| // We don't expect to see any nullable non-sugar types except PointerType | |
| // and `RecordType`s that correspond to smart pointers. | |
| ignoreUnexpectedNullability(); | |
| Base::visitType(T, L); | |
| } | |
| void visitFunctionProtoType(const FunctionProtoType *absl_nonnull FPT, | |
| std::optional<FunctionProtoTypeLoc> L) { | |
| ignoreUnexpectedNullability(); | |
| if (FPT->getNoReturnAttr() && L && L->getNumParams() > 0 && | |
| L->getParam(0) == nullptr) { | |
| // This FunctionProtoType was unwrapped and rewrapped to add a noreturn | |
| // attribute, in a way that lost source information. We should not walk | |
| // the TypeLoc. | |
| L = std::nullopt; | |
| } | |
| visit(FPT->getReturnType(), | |
| L ? std::optional<TypeLoc>(L->getReturnLoc()) : std::nullopt); | |
| if (L) { | |
| CHECK(FPT->getParamTypes().size() == L->getNumParams()); | |
| } | |
| for (unsigned I = 0, N = FPT->getParamTypes().size(); I < N; ++I) { | |
| std::optional<TypeLoc> ParamLoc; | |
| if (L) { | |
| const auto *ParamDecl = L->getParam(I); | |
| // The only known case of null ParamDecls is when a function type is | |
| // seen as a template argument in a type of a lambda capture's implicit | |
| // FieldDecl. We avoid using NullabilityWalker to walk the TypeLocs of | |
| // such Decls. If other cases arise, this CHECK serves to make sure we | |
| // find out about them and handle them appropriately. | |
| CHECK(ParamDecl); | |
| if (auto *TSI = ParamDecl->getTypeSourceInfo()) { | |
| ParamLoc = TSI->getTypeLoc(); | |
| } | |
| } | |
| visit(FPT->getParamType(I), ParamLoc); | |
| } | |
| } | |
| void visitTemplateSpecializationType( | |
| const TemplateSpecializationType *absl_nonnull TST, | |
| std::optional<TemplateSpecializationTypeLoc> L) { | |
| std::vector<TemplateContext> BoundTemplateArgs = | |
| getBoundTemplateArgsFromQualifiedType( | |
| TST, L ? std::optional<NestedNameSpecifierLoc>(L->getQualifierLoc()) | |
| : std::nullopt); | |
| std::optional<llvm::SaveAndRestore<const TemplateContext*>> Restore; | |
| if (!BoundTemplateArgs.empty()) | |
| Restore.emplace(CurrentTemplateContext, &BoundTemplateArgs.front()); | |
| if (TST->isTypeAlias()) { | |
| auto NK = getAliasNullability(TST->getTemplateName()); | |
| if (NK == NullabilityKind::Unspecified) { | |
| auto Inner = unwrapAlias(QualType(TST, 0)); | |
| if (!Inner || !isUnknownValidOn(*Inner)) NK = std::nullopt; | |
| } | |
| if (NK) sawNullability(*NK); | |
| // Aliases are sugar, visit the underlying type. | |
| // Record template args so we can resugar substituted params. | |
| // | |
| // TODO(b/281474380): `TemplateSpecializationType::template_arguments()` | |
| // doesn't contain defaulted arguments. Can we fetch or compute these in | |
| // sugared form? | |
| TemplateContext Ctx{ | |
| /*AssociatedDecl=*/TST->getTemplateName().getAsTemplateDecl(), | |
| /*Args=*/TST->template_arguments(), | |
| /*ArgsFile=*/File, | |
| /*Extends=*/CurrentTemplateContext, | |
| /*ArgContext=*/CurrentTemplateContext, | |
| }; | |
| if (L) { | |
| Ctx.ArgLocs = std::vector<TemplateArgumentLoc>(); | |
| Ctx.ArgLocs->reserve(L->getNumArgs()); | |
| for (unsigned I = 0, N = L->getNumArgs(); I < N; ++I) { | |
| Ctx.ArgLocs->push_back(L->getArgLoc(I)); | |
| } | |
| } | |
| TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl(); | |
| llvm::SaveAndRestore<const TemplateContext *> UseAlias( | |
| CurrentTemplateContext, &Ctx); | |
| llvm::SaveAndRestore SwitchFile(File, isTransparentAlias(QualType(TST, 0)) | |
| ? File | |
| : getGoverningFile(TD)); | |
| visitType(TST, L); | |
| return; | |
| } | |
| auto *CRD = TST->getAsCXXRecordDecl(); | |
| CHECK(CRD) << "Expected an alias or class specialization in concrete code"; | |
| if (isSupportedSmartPointerType(QualType(TST, 0))) { | |
| report(TST); | |
| } else { | |
| ignoreUnexpectedNullability(); | |
| } | |
| visit(CRD->getDeclContext()); | |
| ArrayRef<TemplateArgument> TSTArgs = TST->template_arguments(); | |
| CHECK(!L || TSTArgs.size() == L->getNumArgs()); | |
| for (unsigned I = 0; I < TSTArgs.size(); ++I) { | |
| visit(TSTArgs[I], L ? std::optional<TemplateArgumentLoc>(L->getArgLoc(I)) | |
| : std::nullopt); | |
| } | |
| // `TSTArgs` doesn't contain any default arguments. | |
| // Retrieve these (though in unsugared form) from the | |
| // `ClassTemplateSpecializationDecl`. | |
| // TODO(b/281474380): Can we fetch or compute default arguments in sugared | |
| // form? | |
| if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(CRD)) { | |
| for (unsigned I = TSTArgs.size(); I < CTSD->getTemplateArgs().size(); | |
| ++I) { | |
| visit(CTSD->getTemplateArgs()[I], std::nullopt); | |
| } | |
| } | |
| } | |
| void visitSubstTemplateTypeParmType( | |
| const SubstTemplateTypeParmType *absl_nonnull T, | |
| std::optional<SubstTemplateTypeParmTypeLoc> L) { | |
| // The underlying type of T in the AST has no sugar, as the template has | |
| // only one body instantiated per canonical args. | |
| // Instead, try to find the (sugared) template argument that T is bound to. | |
| for (const auto *Ctx = CurrentTemplateContext; Ctx; Ctx = Ctx->Extends) { | |
| if (T->getAssociatedDecl() != Ctx->AssociatedDecl) continue; | |
| // If args are not available, fall back to un-sugared arg. | |
| if (!Ctx->Args.has_value()) break; | |
| unsigned Index = T->getIndex(); | |
| // Valid because pack must be the last param in non-function templates. | |
| // TODO: if we support function templates, we need to be smarter here. | |
| if (auto PackIndex = T->getPackIndex()) | |
| Index = Ctx->Args->size() - 1 - *PackIndex; | |
| // TODO(b/281474380): `Args` may be too short if `Index` refers to an | |
| // arg that was defaulted. We eventually want to populate | |
| // `CurrentAliasTemplate->Args` with the default arguments in this case, | |
| // but for now, we just walk the underlying type without sugar. | |
| if (Index < Ctx->Args->size()) { | |
| const TemplateArgument &Arg = (*Ctx->Args)[Index]; | |
| std::optional<TemplateArgumentLoc> ArgLoc; | |
| if (Ctx->ArgLocs) { | |
| ArgLoc = (*Ctx->ArgLocs)[Index]; | |
| } | |
| // When we start to walk a sugared TemplateArgument (in place of T), | |
| // we must do so in the template instantiation context where the | |
| // argument was written. | |
| llvm::SaveAndRestore OriginalContext( | |
| CurrentTemplateContext, CurrentTemplateContext->ArgContext); | |
| llvm::SaveAndRestore SwitchFile(File, Ctx->ArgsFile); | |
| return visit(Arg, ArgLoc); | |
| } | |
| } | |
| // Our top-level type references an unbound type param. | |
| // Our original input was the underlying type of an instantiation, we | |
| // lack the context needed to resugar it. | |
| // TODO: maybe this could be an assert in some cases (alias params)? | |
| // We would need to trust all callers are obtaining types appropriately, | |
| // and that clang never partially-desugars in a problematic way. | |
| visitType(T, L); | |
| } | |
| void visitTypedefType(const TypedefType* T, std::optional<TypedefTypeLoc> L) { | |
| std::vector<TemplateContext> BoundTemplateArgs = | |
| getBoundTemplateArgsFromQualifiedType( | |
| T, L ? std::optional<NestedNameSpecifierLoc>(L->getQualifierLoc()) | |
| : std::nullopt); | |
| std::optional<llvm::SaveAndRestore<const TemplateContext*>> Restore; | |
| if (!BoundTemplateArgs.empty()) | |
| Restore.emplace(CurrentTemplateContext, &BoundTemplateArgs.front()); | |
| llvm::SaveAndRestore SwitchFile(File, getGoverningFile(T->getDecl())); | |
| // Don't look for new Locs inside an alias. | |
| visitType(T, std::nullopt); | |
| } | |
| void visitRecordType(const RecordType *absl_nonnull RT, | |
| std::optional<RecordTypeLoc> L) { | |
| std::vector<TemplateContext> BoundTemplateArgs = | |
| getBoundTemplateArgsFromQualifiedType( | |
| RT, L ? std::optional<NestedNameSpecifierLoc>(L->getQualifierLoc()) | |
| : std::nullopt); | |
| std::optional<llvm::SaveAndRestore<const TemplateContext*>> Restore; | |
| if (!BoundTemplateArgs.empty()) | |
| Restore.emplace(CurrentTemplateContext, &BoundTemplateArgs.front()); | |
| if (isSupportedSmartPointerType(QualType(RT, 0))) { | |
| report(RT); | |
| } else { | |
| ignoreUnexpectedNullability(); | |
| } | |
| visit(RT->getOriginalDecl()->getDeclContext()); | |
| // Visit template arguments of this record type. | |
| if (auto* CTSD = | |
| dyn_cast<ClassTemplateSpecializationDecl>(RT->getOriginalDecl())) { | |
| unsigned I = 0; | |
| // If we have a sugared template context, use the sugar. | |
| for (auto Ctx = CurrentTemplateContext; Ctx; Ctx = Ctx->Extends) { | |
| if (Ctx->AssociatedDecl != CTSD) continue; | |
| llvm::SaveAndRestore SwitchFile(File, Ctx->ArgsFile); | |
| llvm::SaveAndRestore OriginalContext(CurrentTemplateContext, | |
| Ctx->ArgContext); | |
| if (!Ctx->Args) break; | |
| for (unsigned N = Ctx->Args->size(); I < N; ++I) { | |
| std::optional<TemplateArgumentLoc> ArgLoc; | |
| if (Ctx->ArgLocs) { | |
| ArgLoc = (*Ctx->ArgLocs)[I]; | |
| } | |
| auto Arg = (*Ctx->Args)[I]; | |
| visit(Arg, ArgLoc); | |
| } | |
| break; | |
| } | |
| // If we didn't see all the declarations's arguments in the template | |
| // context, either there wasn't a matching context available or there are | |
| // defaulted arguments. Visit (remaining) arguments from the declaration, | |
| // without sugar or location. | |
| auto DeclArgs = CTSD->getTemplateArgs().asArray(); | |
| for (unsigned N = DeclArgs.size(); I < N; ++I) { | |
| visit(DeclArgs[I], std::nullopt); | |
| } | |
| } | |
| } | |
| void visitAttributedType(const AttributedType *absl_nonnull AT, | |
| std::optional<AttributedTypeLoc> L) { | |
| auto NK = AT->getImmediateNullability(); | |
| if (NK == NullabilityKind::Unspecified) { | |
| if (!isUnknownValidOn(AT->getModifiedType())) NK = std::nullopt; | |
| } | |
| if (NK) sawNullability(*NK); | |
| visit(AT->getModifiedType(), | |
| L ? std::optional<TypeLoc>(L->getModifiedLoc()) : std::nullopt); | |
| CHECK(!PendingNullability.has_value()) | |
| << "Should have been consumed by modified type! " | |
| << AT->getModifiedType().getAsString(); | |
| } | |
| void visitPointerType(const PointerType *absl_nonnull PT, | |
| std::optional<PointerTypeLoc> L) { | |
| report(PT); | |
| visit(PT->getPointeeType(), | |
| L ? std::optional<TypeLoc>(L->getPointeeLoc()) : std::nullopt); | |
| } | |
| void visitReferenceType(const ReferenceType *absl_nonnull RT, | |
| std::optional<ReferenceTypeLoc> L) { | |
| ignoreUnexpectedNullability(); | |
| visit(RT->getPointeeTypeAsWritten(), | |
| L ? std::optional<TypeLoc>(L->getPointeeLoc()) : std::nullopt); | |
| } | |
| void visitArrayType(const ArrayType *absl_nonnull AT, | |
| std::optional<ArrayTypeLoc> L) { | |
| ignoreUnexpectedNullability(); | |
| visit(AT->getElementType(), | |
| L ? std::optional<TypeLoc>(L->getElementLoc()) : std::nullopt); | |
| } | |
| void visitParenType(const ParenType *absl_nonnull PT, | |
| std::optional<ParenTypeLoc> L) { | |
| visit(PT->getInnerType(), | |
| L ? std::optional<TypeLoc>(L->getInnerLoc()) : std::nullopt); | |
| } | |
| void visitAutoType(const AutoType* absl_nonnull AT, | |
| std::optional<AutoTypeLoc> L) { | |
| // Intentionally lose track of location info inside a bare `auto` type. | |
| // There's no location inside of `auto` (explicit or implicit, as in a | |
| // lambda capture with init) that's eligible for an annotation. | |
| visitType(AT, std::nullopt); | |
| } | |
| }; | |
| struct CountWalker : public NullabilityWalker<CountWalker> { | |
| CountWalker() : NullabilityWalker(FileID()) {} | |
| unsigned Count = 0; | |
| void report(const Type *absl_nonnull, FileID, std::optional<NullabilityKind>, | |
| std::optional<TypeLoc>) { | |
| ++Count; | |
| } | |
| }; | |
| // T is *lexically* a pointer type if its sugar chain contains no aliases. | |
| // For the purposes of Unknown, we also don't want nullability attributes. | |
| static bool isLexicalPointerTypeWithoutNullability(QualType T) { | |
| if (!isSupportedPointerType(T)) return false; | |
| while (true) { | |
| if (T->isTypedefNameType()) return false; | |
| if (const auto *AT = dyn_cast<AttributedType>(T)) | |
| if (AT->getImmediateNullability().has_value()) return false; | |
| auto Next = T->getLocallyUnqualifiedSingleStepDesugaredType(); | |
| if (Next.getTypePtr() == T.getTypePtr()) return true; | |
| T = Next; | |
| } | |
| } | |
| } // namespace | |
| bool isUnknownValidOn(QualType T) { | |
| // Unwrap transparent aliases and trivial sugar to get the "real" type. | |
| T = ignoreTrivialSugar(T); | |
| while (isTransparentAlias(T)) | |
| T = ignoreTrivialSugar(T->getLocallyUnqualifiedSingleStepDesugaredType()); | |
| if (!isSupportedPointerType(T)) return false; | |
| return isLexicalPointerTypeWithoutNullability(T); | |
| } | |
| unsigned countPointersInType(QualType T) { | |
| // Certain expressions like pseudo-destructors have no type, treat as void. | |
| // (exprType() cannot fold them to void, as it doesn't have the ASTContext). | |
| if (T.isNull()) return 0; | |
| CountWalker PointerCountWalker; | |
| PointerCountWalker.visit(T); | |
| return PointerCountWalker.Count; | |
| } | |
| unsigned countPointersInType(const DeclContext *absl_nonnull DC) { | |
| CountWalker PointerCountWalker; | |
| PointerCountWalker.visit(DC); | |
| return PointerCountWalker.Count; | |
| } | |
| unsigned countPointersInType(const TemplateArgument &TA) { | |
| CountWalker PointerCountWalker; | |
| PointerCountWalker.visit(TA, std::nullopt); | |
| return PointerCountWalker.Count; | |
| } | |
| QualType exprType(const Expr *absl_nonnull E) { | |
| if (E->hasPlaceholderType(BuiltinType::BoundMember)) | |
| return Expr::findBoundMemberType(E); | |
| return E->getType(); | |
| } | |
| unsigned countPointersInType(const Expr *absl_nonnull E) { | |
| return countPointersInType(exprType(E)); | |
| } | |
| NullabilityKind TypeNullabilityDefaults::get(FileID File) const { | |
| if (FileNullability && File.isValid()) { | |
| if (auto It = FileNullability->find(File); It != FileNullability->end()) | |
| return It->second; | |
| } | |
| return DefaultNullability; | |
| } | |
| TypeNullability getTypeNullability( | |
| QualType T, FileID File, const TypeNullabilityDefaults &Defaults, | |
| llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam) { | |
| CHECK(!T->isDependentType()) << T.getAsString(); | |
| struct Walker : NullabilityWalker<Walker> { | |
| std::vector<PointerTypeNullability> Annotations; | |
| llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam; | |
| const TypeNullabilityDefaults &Defaults; | |
| Walker(FileID File, const TypeNullabilityDefaults &Defaults) | |
| : NullabilityWalker(File), Defaults(Defaults) {} | |
| void report(const Type *absl_nonnull, FileID File, | |
| std::optional<NullabilityKind> NK, std::optional<TypeLoc>) { | |
| if (!NK) NK = Defaults.get(File); | |
| Annotations.push_back(*NK); | |
| } | |
| void visitSubstTemplateTypeParmType( | |
| const SubstTemplateTypeParmType *absl_nonnull ST, | |
| std::optional<SubstTemplateTypeParmTypeLoc> L) { | |
| if (SubstituteTypeParam) { | |
| if (auto Subst = SubstituteTypeParam(ST)) { | |
| DCHECK_EQ(Subst->size(), | |
| countPointersInType(ST->getCanonicalTypeInternal())) | |
| << "Substituted nullability has the wrong structure: " | |
| << QualType(ST, 0).getAsString(); | |
| // Check if the PendingNullability is more precise than the | |
| // substituted nullability. | |
| if (!Subst->empty() && PendingNullability.has_value()) { | |
| PointerTypeNullability &SubstNullability = Subst->front(); | |
| if (SubstNullability.concrete() == NullabilityKind::Unspecified) { | |
| SubstNullability = PointerTypeNullability(*PendingNullability); | |
| } | |
| } | |
| // Normally, PendingNullability is consumed when we visit further and | |
| // hit a non-sugared type like PointerType. However, here we are not | |
| // visiting further, so we need to consume the PendingNullability | |
| // ourselves. | |
| PendingNullability.reset(); | |
| llvm::append_range(Annotations, *Subst); | |
| return; | |
| } | |
| } | |
| NullabilityWalker::visitSubstTemplateTypeParmType(ST, std::nullopt); | |
| } | |
| } AnnotationVisitor(File, Defaults); | |
| AnnotationVisitor.SubstituteTypeParam = SubstituteTypeParam; | |
| AnnotationVisitor.visit(T, std::nullopt); | |
| return std::move(AnnotationVisitor.Annotations); | |
| } | |
| TypeNullability getTypeNullability( | |
| TypeLoc TL, const TypeNullabilityDefaults &Defaults, | |
| llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam) { | |
| return getTypeNullability(TL.getType(), | |
| Defaults.Ctx | |
| ? Defaults.Ctx->getSourceManager().getFileID( | |
| TL.getLocalSourceRange().getBegin()) | |
| : FileID(), | |
| Defaults, SubstituteTypeParam); | |
| } | |
| TypeNullability getTypeNullability( | |
| const ValueDecl &D, const TypeNullabilityDefaults &Defaults, | |
| llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam) { | |
| return getTypeNullability(D.getType(), getGoverningFile(&D), Defaults, | |
| SubstituteTypeParam); | |
| } | |
| TypeNullability getTypeNullability( | |
| const TypeDecl &D, const TypeNullabilityDefaults &Defaults, | |
| llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam) { | |
| return getTypeNullability(D.getASTContext().getTypeDeclType(&D), | |
| getGoverningFile(&D), Defaults, | |
| SubstituteTypeParam); | |
| } | |
| TypeNullability unspecifiedNullability(const Expr *absl_nonnull E) { | |
| return TypeNullability(countPointersInType(E), NullabilityKind::Unspecified); | |
| } | |
| namespace { | |
| // Visitor to rebuild a QualType with explicit nullability. | |
| // Extra AttributedType nodes are added wrapping interior PointerTypes, and | |
| // other sugar is added as needed to allow this (e.g. TypeSpecializationType). | |
| // | |
| // We only have to handle types that have nontrivial nullability vectors, i.e. | |
| // those handled by NullabilityWalker. | |
| // Additionally, we only operate on canonical types (otherwise the sugar we're | |
| // adding could conflict with existing sugar). | |
| // | |
| // This needs to stay in sync with the other algorithms that manipulate | |
| // nullability data structures for particular types: the non-flow-sensitive | |
| // transfer and NullabilityWalker. | |
| struct Rebuilder : public TypeVisitor<Rebuilder, QualType> { | |
| Rebuilder(const TypeNullability &Nullability, ASTContext &Ctx) | |
| : Nullability(Nullability), Ctx(Ctx) {} | |
| bool done() const { return Nullability.empty(); } | |
| using Base = TypeVisitor<Rebuilder, QualType>; | |
| using Base::Visit; | |
| QualType Visit(QualType T) { | |
| if (T.isNull()) return T; | |
| return Ctx.getQualifiedType(Visit(T.getTypePtr()), T.getLocalQualifiers()); | |
| } | |
| TemplateArgument Visit(TemplateArgument TA) { | |
| if (TA.getKind() == TemplateArgument::Type) | |
| return TemplateArgument(Visit(TA.getAsType())); | |
| return TA; | |
| } | |
| // Default behavior for unhandled types: do not transform. | |
| QualType VisitType(const Type *absl_nonnull T) { return QualType(T, 0); } | |
| QualType VisitPointerType(const PointerType *absl_nonnull PT) { | |
| CHECK(!Nullability.empty()) | |
| << "Nullability vector too short at " << QualType(PT, 0).getAsString(); | |
| NullabilityKind NK = Nullability.front().concrete(); | |
| Nullability = Nullability.drop_front(); | |
| QualType Rebuilt = Ctx.getPointerType(Visit(PT->getPointeeType())); | |
| if (NK == NullabilityKind::Unspecified) return Rebuilt; | |
| return Ctx.getAttributedType(NK, Rebuilt, Rebuilt); | |
| } | |
| QualType VisitRecordType(const RecordType *absl_nonnull RT) { | |
| if (const auto* CTSD = | |
| dyn_cast<ClassTemplateSpecializationDecl>(RT->getOriginalDecl())) { | |
| std::vector<TemplateArgument> TransformedArgs; | |
| for (const auto &Arg : CTSD->getTemplateArgs().asArray()) | |
| TransformedArgs.push_back(Visit(Arg)); | |
| return Ctx.getTemplateSpecializationType( | |
| clang::ElaboratedTypeKeyword::None, | |
| TemplateName(CTSD->getSpecializedTemplate()), TransformedArgs, | |
| TransformedArgs, QualType(RT, 0)); | |
| } | |
| return QualType(RT, 0); | |
| } | |
| QualType VisitFunctionProtoType(const FunctionProtoType *absl_nonnull T) { | |
| QualType Ret = Visit(T->getReturnType()); | |
| std::vector<QualType> Params; | |
| for (const auto &Param : T->getParamTypes()) Params.push_back(Visit(Param)); | |
| return Ctx.getFunctionType(Ret, Params, T->getExtProtoInfo()); | |
| } | |
| QualType VisitLValueReferenceType(const LValueReferenceType *absl_nonnull T) { | |
| return Ctx.getLValueReferenceType(Visit(T->getPointeeType())); | |
| } | |
| QualType VisitRValueReferenceType(const RValueReferenceType *absl_nonnull T) { | |
| return Ctx.getRValueReferenceType(Visit(T->getPointeeType())); | |
| } | |
| QualType VisitConstantArrayType(const ConstantArrayType *absl_nonnull AT) { | |
| return Ctx.getConstantArrayType(Visit(AT->getElementType()), AT->getSize(), | |
| AT->getSizeExpr(), AT->getSizeModifier(), | |
| AT->getIndexTypeCVRQualifiers()); | |
| } | |
| QualType VisitIncompleteArrayType( | |
| const IncompleteArrayType *absl_nonnull AT) { | |
| return Ctx.getIncompleteArrayType(Visit(AT->getElementType()), | |
| AT->getSizeModifier(), | |
| AT->getIndexTypeCVRQualifiers()); | |
| } | |
| QualType VisitVariableArrayType(const VariableArrayType *absl_nonnull AT) { | |
| return Ctx.getVariableArrayType(Visit(AT->getElementType()), | |
| AT->getSizeExpr(), AT->getSizeModifier(), | |
| AT->getIndexTypeCVRQualifiers()); | |
| } | |
| private: | |
| ArrayRef<PointerTypeNullability> Nullability; | |
| ASTContext &Ctx; | |
| }; | |
| } // namespace | |
| QualType rebuildWithNullability(QualType T, const TypeNullability &Nullability, | |
| ASTContext &Ctx) { | |
| Rebuilder V(Nullability, Ctx); | |
| QualType Result = V.Visit(T.getCanonicalType()); | |
| CHECK(V.done()) << "Nullability vector[" << Nullability.size() | |
| << "] too long for " << T.getAsString(); | |
| return Result; | |
| } | |
| std::string printWithNullability(QualType T, const TypeNullability &Nullability, | |
| ASTContext &Ctx) { | |
| return rebuildWithNullability(T, Nullability, Ctx) | |
| .getAsString(Ctx.getPrintingPolicy()); | |
| } | |
| std::vector<TypeNullabilityLoc> getTypeNullabilityLocs( | |
| TypeLoc Loc, const TypeNullabilityDefaults &Defaults) { | |
| CHECK(!Loc.getType()->isDependentType()) << Loc.getType().getAsString(); | |
| struct Walker : NullabilityWalker<Walker> { | |
| std::vector<TypeNullabilityLoc> TypeNullabilityLocs; | |
| unsigned Slot = 0; | |
| const TypeNullabilityDefaults &Defaults; | |
| // Ignores any default nullability pragmas. | |
| Walker(FileID File, const TypeNullabilityDefaults &Defaults) | |
| : NullabilityWalker(File), Defaults(Defaults) {} | |
| void report(const Type *absl_nonnull T, FileID File, | |
| std::optional<NullabilityKind> NK, std::optional<TypeLoc> Loc) { | |
| if (!NK) { | |
| // If the file has a specified default nullability, report that as an | |
| // existing annotation. Don't use Defaults.get(File) in order to avoid | |
| // treating a lack of pragma as an annotation of | |
| // Defaults.DefaultNullability. | |
| if (Defaults.FileNullability && File.isValid()) { | |
| if (auto It = Defaults.FileNullability->find(File); | |
| It != Defaults.FileNullability->end()) | |
| NK = It->second; | |
| } | |
| } | |
| TypeNullabilityLocs.push_back({Slot, T, Loc, NK}); | |
| ++Slot; | |
| } | |
| } LocsWalker(Defaults.Ctx ? Defaults.Ctx->getSourceManager().getFileID( | |
| Loc.getLocalSourceRange().getBegin()) | |
| : FileID(), | |
| Defaults); | |
| LocsWalker.visit(Loc); | |
| return std::move(LocsWalker.TypeNullabilityLocs); | |
| } | |
| } // namespace clang::tidy::nullability |