| // 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/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 { |
| |
| static bool SmartPointersEnabled = false; |
| |
| namespace test { |
| |
| EnableSmartPointers::EnableSmartPointers() { enableSmartPointers(true); } |
| |
| } // namespace test |
| |
| void enableSmartPointers(bool Enabled) { SmartPointersEnabled = Enabled; } |
| |
| bool smartPointersEnabled() { return SmartPointersEnabled; } |
| |
| 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 bool isAnnotatedNullabilityCompatible(const CXXRecordDecl *RD) { |
| // If the specialization hasn't been instantiated -- for example because it |
| // is only used as a function parameter type -- then the specialization won't |
| // contain the `absl_nullability_compatible` tag. Therefore, we look at the |
| // template rather than the specialization. |
| if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) |
| RD = CTSD->getSpecializedTemplate()->getTemplatedDecl(); |
| |
| if (RD->hasAttr<TypeNullableAttr>()) return true; |
| |
| const auto &Idents = RD->getASTContext().Idents; |
| auto It = Idents.find("absl_nullability_compatible"); |
| if (It == Idents.end()) return false; |
| return RD->lookup(It->getValue()).find_first<TypedefNameDecl>() != nullptr; |
| } |
| |
| static absl::Nullable<const CXXRecordDecl *> getSmartPointerBaseClass( |
| absl::Nullable<const CXXRecordDecl *> RD, |
| llvm::SmallPtrSet<const CXXRecordDecl *, 2> &Seen, |
| AccessSpecifier BaseAccess) { |
| if (RD == nullptr) return nullptr; |
| |
| if (isStandardSmartPointerDecl(RD) || isAnnotatedNullabilityCompatible(RD)) |
| 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 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>( |
| TST->getTemplateName().getAsTemplateDecl()->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 absl::Nullable<const CXXRecordDecl *> getSmartPointerBaseClass( |
| absl::Nullable<const CXXRecordDecl *> RD, AccessSpecifier BaseAccess) { |
| llvm::SmallPtrSet<const CXXRecordDecl *, 2> Seen; |
| return getSmartPointerBaseClass(RD, Seen, BaseAccess); |
| } |
| |
| QualType underlyingRawPointerType(QualType T, AccessSpecifier BaseAccess) { |
| if (!SmartPointersEnabled) return QualType(); |
| |
| 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 (`absl_nullability_compatible`, `pointer`, `element_type`). |
| // We deal with this as follows: |
| // * We check the primary template for base classes and the |
| // `absl_nullability_compatible` type alias. |
| // * We extract the underlying pointer type from the template argument (as |
| // that's the best we can do). |
| if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD); |
| CTSD && !CTSD->hasDefinition()) { |
| if (getSmartPointerBaseClass( |
| CTSD->getSpecializedTemplate()->getTemplatedDecl(), BaseAccess) == |
| nullptr) |
| return QualType(); |
| |
| 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)); |
| } |
| |
| 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()); |
| } |
| return QualType(); |
| } |
| |
| PointerTypeNullability PointerTypeNullability::createSymbolic( |
| dataflow::Arena &A) { |
| PointerTypeNullability Symbolic; |
| Symbolic.Symbolic = true; |
| Symbolic.Nonnull = A.makeAtom(); |
| Symbolic.Nullable = A.makeAtom(); |
| 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(absl::Nullable<const Decl *> D) { |
| if (!D) return FileID(); |
| return D->getASTContext() |
| .getSourceManager() |
| .getDecomposedExpansionLoc(D->getLocation()) |
| .first; |
| } |
| |
| namespace { |
| // Recognize aliases e.g. Nonnull<T> as equivalent to T _Nonnull, etc. |
| // These aliases should be annotated with [[clang::annotate("Nullable")]] etc. |
| // |
| // TODO: Ideally such aliases could apply the _Nonnull attribute themselves. |
| // This requires resolving compatibility issues with clang, such as use with |
| // user-defined pointer-like types. |
| 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, ElaboratedType>(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); } |
| |
| // 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; |
| |
| // 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 `OverrideElaborated`, an existing ElaboratedTypeLoc 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 OverrideElaborated = 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 Elaborated Loc for the type, but |
| // template arguments supersede that preference. |
| if (!BestLocSoFar || OverrideElaborated || |
| BestLocSoFar->getTypeLocClass() != TypeLoc::Elaborated) { |
| BestLocSoFar = Loc; |
| } |
| } |
| |
| void sawNullability(NullabilityKind NK) { |
| // If we see nullability applied twice, the outer one wins. |
| assert(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 the ElaboratedType for the S<FLOAT>:: qualifier 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; |
| } |
| |
| 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); |
| } |
| 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, /*OverrideElaborated=*/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(absl::Nonnull<const DeclContext *> 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().getRecordType(CRD), std::nullopt); |
| } |
| |
| void visit(absl::Nonnull<const Type *> T, std::optional<TypeLoc> L) { |
| if (L) recordLoc(*L); |
| Base::visit(T, L); |
| } |
| |
| void visitType(absl::Nonnull<const Type *> T, std::optional<TypeLoc> L) { |
| // 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(absl::Nonnull<const FunctionProtoType *> 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( |
| absl::Nonnull<const TemplateSpecializationType *> TST, |
| std::optional<TemplateSpecializationTypeLoc> L) { |
| 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( |
| absl::Nonnull<const SubstTemplateTypeParmType *> 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); |
| } |
| |
| // If we see foo<args>::ty then we may need sugar from args to resugar ty. |
| void visitElaboratedType(absl::Nonnull<const ElaboratedType *> ET, |
| std::optional<ElaboratedTypeLoc> L) { |
| std::vector<TemplateContext> BoundTemplateArgs; |
| std::optional<NestedNameSpecifierLoc> NNSLoc; |
| if (L) { |
| NNSLoc = L->getQualifierLoc(); |
| } |
| // Iterate over qualifiers right-to-left, looking for template args. |
| for (auto *NNS = ET->getQualifier(); 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 (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->getTypeLoc().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); |
| } |
| |
| NNS = NNS->getPrefix(); |
| if (NNSLoc) { |
| NNSLoc = NNSLoc->getPrefix(); |
| } |
| } |
| std::optional<llvm::SaveAndRestore<const TemplateContext *>> Restore; |
| 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]; |
| Restore.emplace(CurrentTemplateContext, &BoundTemplateArgs.front()); |
| } |
| visit(ET->getNamedType(), |
| L ? std::optional<TypeLoc>(L->getNamedTypeLoc()) : std::nullopt); |
| } |
| |
| void visitTypedefType(const TypedefType *T, std::optional<TypedefTypeLoc> L) { |
| llvm::SaveAndRestore SwitchFile(File, getGoverningFile(T->getDecl())); |
| // Don't look for new Locs inside an alias. |
| visitType(T, std::nullopt); |
| } |
| |
| void visitRecordType(absl::Nonnull<const RecordType *> RT, |
| std::optional<RecordTypeLoc> L) { |
| if (isSupportedSmartPointerType(QualType(RT, 0))) { |
| report(RT); |
| } else { |
| ignoreUnexpectedNullability(); |
| } |
| visit(RT->getDecl()->getDeclContext()); |
| |
| // Visit template arguments of this record type. |
| if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl())) { |
| 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(absl::Nonnull<const AttributedType *> 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(absl::Nonnull<const PointerType *> PT, |
| std::optional<PointerTypeLoc> L) { |
| report(PT); |
| visit(PT->getPointeeType(), |
| L ? std::optional<TypeLoc>(L->getPointeeLoc()) : std::nullopt); |
| } |
| |
| void visitReferenceType(absl::Nonnull<const ReferenceType *> RT, |
| std::optional<ReferenceTypeLoc> L) { |
| ignoreUnexpectedNullability(); |
| visit(RT->getPointeeTypeAsWritten(), |
| L ? std::optional<TypeLoc>(L->getPointeeLoc()) : std::nullopt); |
| } |
| |
| void visitArrayType(absl::Nonnull<const ArrayType *> AT, |
| std::optional<ArrayTypeLoc> L) { |
| ignoreUnexpectedNullability(); |
| visit(AT->getElementType(), |
| L ? std::optional<TypeLoc>(L->getElementLoc()) : std::nullopt); |
| } |
| |
| void visitParenType(absl::Nonnull<const ParenType *> PT, |
| std::optional<ParenTypeLoc> L) { |
| visit(PT->getInnerType(), |
| L ? std::optional<TypeLoc>(L->getInnerLoc()) : std::nullopt); |
| } |
| }; |
| |
| struct CountWalker : public NullabilityWalker<CountWalker> { |
| CountWalker() : NullabilityWalker(FileID()) {} |
| unsigned Count = 0; |
| void report(absl::Nonnull<const Type *>, 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(absl::Nonnull<const DeclContext *> 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(absl::Nonnull<const Expr *> E) { |
| if (E->hasPlaceholderType(BuiltinType::BoundMember)) |
| return Expr::findBoundMemberType(E); |
| return E->getType(); |
| } |
| |
| unsigned countPointersInType(absl::Nonnull<const Expr *> 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(absl::Nonnull<const Type *>, FileID File, |
| std::optional<NullabilityKind> NK, std::optional<TypeLoc>) { |
| if (!NK) NK = Defaults.get(File); |
| Annotations.push_back(*NK); |
| } |
| |
| void visitSubstTemplateTypeParmType( |
| absl::Nonnull<const SubstTemplateTypeParmType *> 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(); |
| 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(absl::Nonnull<const Expr *> 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(absl::Nonnull<const Type *> T) { return QualType(T, 0); } |
| |
| QualType VisitPointerType(absl::Nonnull<const PointerType *> 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(AttributedType::getNullabilityAttrKind(NK), |
| Rebuilt, Rebuilt); |
| } |
| |
| QualType VisitRecordType(absl::Nonnull<const RecordType *> RT) { |
| if (const auto *CTSD = |
| dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl())) { |
| std::vector<TemplateArgument> TransformedArgs; |
| for (const auto &Arg : CTSD->getTemplateArgs().asArray()) |
| TransformedArgs.push_back(Visit(Arg)); |
| return Ctx.getTemplateSpecializationType( |
| TemplateName(CTSD->getSpecializedTemplate()), TransformedArgs, |
| QualType(RT, 0)); |
| } |
| return QualType(RT, 0); |
| } |
| |
| QualType VisitFunctionProtoType(absl::Nonnull<const FunctionProtoType *> 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( |
| absl::Nonnull<const LValueReferenceType *> T) { |
| return Ctx.getLValueReferenceType(Visit(T->getPointeeType())); |
| } |
| QualType VisitRValueReferenceType( |
| absl::Nonnull<const RValueReferenceType *> T) { |
| return Ctx.getRValueReferenceType(Visit(T->getPointeeType())); |
| } |
| |
| QualType VisitConstantArrayType(absl::Nonnull<const ConstantArrayType *> AT) { |
| return Ctx.getConstantArrayType(Visit(AT->getElementType()), AT->getSize(), |
| AT->getSizeExpr(), AT->getSizeModifier(), |
| AT->getIndexTypeCVRQualifiers()); |
| } |
| QualType VisitIncompleteArrayType( |
| absl::Nonnull<const IncompleteArrayType *> AT) { |
| return Ctx.getIncompleteArrayType(Visit(AT->getElementType()), |
| AT->getSizeModifier(), |
| AT->getIndexTypeCVRQualifiers()); |
| } |
| QualType VisitVariableArrayType(absl::Nonnull<const VariableArrayType *> AT) { |
| return Ctx.getVariableArrayType( |
| Visit(AT->getElementType()), AT->getSizeExpr(), AT->getSizeModifier(), |
| AT->getIndexTypeCVRQualifiers(), AT->getBracketsRange()); |
| } |
| |
| 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) { |
| 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(absl::Nonnull<const Type *> 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 |