https://github.com/Holo-xy created https://github.com/llvm/llvm-project/pull/213305
None >From 0d9194315989b7088ca5de18dd9d85972494a103 Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Sat, 13 Jun 2026 06:46:32 +0300 Subject: [PATCH 1/6] Introduce LateParsedAttrType AST placeholder type --- clang/include/clang/AST/ASTContext.h | 6 +++ clang/include/clang/AST/RecursiveASTVisitor.h | 6 +++ clang/include/clang/AST/TypeBase.h | 41 +++++++++++++++++++ clang/include/clang/AST/TypeLoc.h | 31 ++++++++++++++ clang/include/clang/AST/TypeProperties.td | 6 +++ clang/include/clang/Basic/TypeNodes.td | 1 + clang/lib/AST/ASTContext.cpp | 18 ++++++++ clang/lib/AST/ASTImporter.cpp | 7 ++++ clang/lib/AST/ASTStructuralEquivalence.cpp | 7 ++++ clang/lib/AST/ItaniumMangle.cpp | 1 + clang/lib/AST/TypePrinter.cpp | 15 +++++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 1 + clang/lib/CodeGen/CGDebugInfo.cpp | 1 + clang/lib/CodeGen/CodeGenFunction.cpp | 1 + clang/lib/Sema/SemaExpr.cpp | 1 + clang/lib/Sema/TreeTransform.h | 20 +++++++++ clang/lib/Serialization/ASTReader.cpp | 4 ++ clang/lib/Serialization/ASTWriter.cpp | 7 ++++ clang/tools/libclang/CIndex.cpp | 4 ++ .../TypeSystem/Clang/TypeSystemClang.cpp | 9 ++++ 20 files changed, 187 insertions(+) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index a4ed852d36442..2095bc1a1df67 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -1630,6 +1630,12 @@ class ASTContext : public RefCountedBase<ASTContext> { bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const; + /// Return a placeholder type for a late-parsed type attribute. + /// This type wraps another type and holds the LateParsedAttribute + /// that will be parsed later. + QualType getLateParsedAttrType(QualType Wrapped, + LateParsedTypeAttribute *LateParsedAttr) const; + /// Return the uniqued reference to a type adjusted from the original /// type to a new type. QualType getAdjustedType(QualType Orig, QualType New) const; diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index 529d657fc01f5..5a72ef0f17785 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -1164,6 +1164,9 @@ DEF_TRAVERSE_TYPE(CountAttributedType, { TRY_TO(TraverseType(T->desugar())); }) +DEF_TRAVERSE_TYPE(LateParsedAttrType, + { TRY_TO(TraverseType(T->getWrappedType())); }) + DEF_TRAVERSE_TYPE(BTFTagAttributedType, { TRY_TO(TraverseType(T->getWrappedType())); }) @@ -1522,6 +1525,9 @@ DEF_TRAVERSE_TYPELOC(AttributedType, DEF_TRAVERSE_TYPELOC(CountAttributedType, { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); }) +DEF_TRAVERSE_TYPELOC(LateParsedAttrType, + { TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); }) + DEF_TRAVERSE_TYPELOC(BTFTagAttributedType, { TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); }) diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index e3844d0cefa78..dd46a58a6fa50 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -70,6 +70,7 @@ class TagDecl; class TemplateParameterList; class Type; class Attr; +struct LateParsedTypeAttribute; enum { TypeAlignmentInBits = 4, @@ -3545,6 +3546,46 @@ class CountAttributedType final StringRef getAttributeName(bool WithMacroPrefix) const; }; +/// Represents a placeholder type for late-parsed type attributes. +/// This type wraps another type and holds an opaque pointer to a +/// LateParsedAttribute that will be parsed later (e.g., in ActOnFields). +/// Once parsed, this type is replaced with the appropriate attributed type +/// (e.g., CountAttributedType for counted_by). +class LateParsedAttrType : public Type, public llvm::FoldingSetNode { + friend class ASTContext; // ASTContext creates these. + + QualType WrappedTy; + LateParsedTypeAttribute *LateParsedTypeAttr; + + LateParsedAttrType(QualType Wrapped, QualType Canon, + LateParsedTypeAttribute *Attr) + : Type(LateParsedAttr, Canon, Wrapped->getDependence()), + WrappedTy(Wrapped), LateParsedTypeAttr(Attr) {} + +public: + QualType getWrappedType() const { return WrappedTy; } + LateParsedTypeAttribute *getLateParsedAttribute() const { + return LateParsedTypeAttr; + } + + bool isSugared() const { return true; } + QualType desugar() const { return WrappedTy; } + + void Profile(llvm::FoldingSetNodeID &ID) { + Profile(ID, WrappedTy, LateParsedTypeAttr); + } + + static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped, + LateParsedTypeAttribute *Attr) { + ID.AddPointer(Wrapped.getAsOpaquePtr()); + ID.AddPointer(Attr); + } + + static bool classof(const Type *T) { + return T->getTypeClass() == LateParsedAttr; + } +}; + /// Represents a type which was implicitly adjusted by the semantic /// engine for arbitrary reasons. For example, array and function types can /// decay, and function types can have their calling conventions adjusted. diff --git a/clang/include/clang/AST/TypeLoc.h b/clang/include/clang/AST/TypeLoc.h index 24df18dbaace4..2648806ac687a 100644 --- a/clang/include/clang/AST/TypeLoc.h +++ b/clang/include/clang/AST/TypeLoc.h @@ -1358,6 +1358,37 @@ class CountAttributedTypeLoc final SourceRange getLocalSourceRange() const; }; +struct LateParsedAttrLocInfo { + SourceLocation AttrNameLoc; +}; + +class LateParsedAttrTypeLoc + : public ConcreteTypeLoc<UnqualTypeLoc, LateParsedAttrTypeLoc, + LateParsedAttrType, LateParsedAttrLocInfo> { +public: + TypeLoc getInnerLoc() const { return getInnerTypeLoc(); } + + SourceLocation getAttrNameLoc() const { return getLocalData()->AttrNameLoc; } + + void setAttrNameLoc(SourceLocation Loc) { getLocalData()->AttrNameLoc = Loc; } + + SourceRange getLocalSourceRange() const { + return SourceRange(getAttrNameLoc(), getAttrNameLoc()); + } + + void initializeLocal(ASTContext &Context, SourceLocation Loc) { + setAttrNameLoc(Loc); + } + + unsigned getLocalDataSize() const { return sizeof(LateParsedAttrLocInfo); } + + QualType getInnerType() const { return getTypePtr()->getWrappedType(); } + + LateParsedTypeAttribute *getLateParsedAttribute() const { + return getTypePtr()->getLateParsedAttribute(); + } +}; + struct MacroQualifiedLocInfo { SourceLocation ExpansionLoc; }; diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td index f16c10da430f9..c5402a1925d99 100644 --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -44,6 +44,12 @@ let Class = CountAttributedType in { def : Creator<[{ return ctx.getCountAttributedType(WrappedTy, CountExpr, CountInBytes, OrNull, CoupledDecls); }]>; } +let Class = LateParsedAttrType in { + // Note: LateParsedAttrType is a transient placeholder type that should + // normally be replaced before serialization. So this won't be serialized. + def : Creator<[{ (void)ctx; llvm_unreachable("unreachable for serialization"); }]>; +} + let Class = AdjustedType in { def : Property<"originalType", QualType> { let Read = [{ node->getOriginalType() }]; diff --git a/clang/include/clang/Basic/TypeNodes.td b/clang/include/clang/Basic/TypeNodes.td index a9965a4a89aa1..700a73f669690 100644 --- a/clang/include/clang/Basic/TypeNodes.td +++ b/clang/include/clang/Basic/TypeNodes.td @@ -105,6 +105,7 @@ def ObjCInterfaceType : TypeNode<ObjCObjectType>, AlwaysCanonical; def ObjCObjectPointerType : TypeNode<Type>; def BoundsAttributedType : TypeNode<Type, 1>; def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical; +def LateParsedAttrType : TypeNode<Type>, NeverCanonical; def PipeType : TypeNode<Type>; def AtomicType : TypeNode<Type>; def BitIntType : TypeNode<Type>; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index abf0cd5e18c2b..562694ffc2b8c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -2581,6 +2581,9 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const { case Type::CountAttributed: return getTypeInfo(cast<CountAttributedType>(T)->desugar().getTypePtr()); + case Type::LateParsedAttr: + return getTypeInfo(cast<LateParsedAttrType>(T)->desugar().getTypePtr()); + case Type::BTFTagAttributed: return getTypeInfo( cast<BTFTagAttributedType>(T)->getWrappedType().getTypePtr()); @@ -3751,6 +3754,17 @@ QualType ASTContext::getCountAttributedType( return QualType(CATy, 0); } +QualType ASTContext::getLateParsedAttrType( + QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const { + QualType CanonTy = getCanonicalType(WrappedTy); + + auto *LPATy = new (*this, alignof(LateParsedAttrType)) + LateParsedAttrType(WrappedTy, CanonTy, LateParsedAttr); + + Types.push_back(LPATy); + return QualType(LPATy, 0); +} + QualType ASTContext::adjustType(QualType Orig, llvm::function_ref<QualType(QualType)> Adjust) const { @@ -14920,6 +14934,10 @@ static QualType getCommonSugarTypeNode(const ASTContext &Ctx, const Type *X, DX->isCountInBytes(), DX->isOrNull(), CDX); } + + case Type::LateParsedAttr: + return QualType(); + case Type::PredefinedSugar: assert(cast<PredefinedSugarType>(X)->getKind() != cast<PredefinedSugarType>(Y)->getKind()); diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index f5848d154f49e..d91da096b2466 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -1842,6 +1842,13 @@ ASTNodeImporter::VisitCountAttributedType(const CountAttributedType *T) { ArrayRef(CoupledDecls)); } +ExpectedType +ASTNodeImporter::VisitLateParsedAttrType(const LateParsedAttrType *T) { + // LateParsedAttrType is a transient placeholder that should not normally + // appear during AST import. Import as the wrapped type. + return import(T->getWrappedType()); +} + ExpectedType ASTNodeImporter::VisitTemplateTypeParmType( const TemplateTypeParmType *T) { Expected<TemplateTypeParmDecl *> ToDeclOrErr = import(T->getDecl()); diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index e0b62591a6a73..d8bbfbe5dac72 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -1208,6 +1208,13 @@ bool ASTStructuralEquivalence::isEquivalent( return false; break; + case Type::LateParsedAttr: + if (!IsStructurallyEquivalent( + Context, cast<LateParsedAttrType>(T1)->getWrappedType(), + cast<LateParsedAttrType>(T2)->getWrappedType())) + return false; + break; + case Type::BTFTagAttributed: if (!IsStructurallyEquivalent( Context, cast<BTFTagAttributedType>(T1)->getWrappedType(), diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index e5cdd6f31c507..60e77b66d92cf 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -2472,6 +2472,7 @@ bool CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty, case Type::BitInt: case Type::DependentBitInt: case Type::CountAttributed: + case Type::LateParsedAttr: llvm_unreachable("type is illegal as a nested name specifier"); case Type::SubstBuiltinTemplatePack: diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 53b869e019074..ae9838413ab44 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -288,6 +288,7 @@ bool TypePrinter::canPrefixQualifiers(const Type *T, case Type::MacroQualified: case Type::OverflowBehavior: case Type::CountAttributed: + case Type::LateParsedAttr: CanPrefixQualifiers = false; break; @@ -1832,6 +1833,20 @@ void TypePrinter::printCountAttributedAfter(const CountAttributedType *T, printCountAttributedImpl(T, OS, Policy); } +void TypePrinter::printLateParsedAttrBefore(const LateParsedAttrType *T, + raw_ostream &OS) { + // LateParsedAttrType is a transient placeholder that should not appear + // in user-facing output. Just print the wrapped type. + printBefore(T->getWrappedType(), OS); +} + +void TypePrinter::printLateParsedAttrAfter(const LateParsedAttrType *T, + raw_ostream &OS) { + // LateParsedAttrType is a transient placeholder that should not appear + // in user-facing output. Just print the wrapped type. + printAfter(T->getWrappedType(), OS); +} + void TypePrinter::printAttributedBefore(const AttributedType *T, raw_ostream &OS) { // FIXME: Generate this with TableGen. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 4b020c96964a7..7489a315af98c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -1642,6 +1642,7 @@ void CIRGenFunction::emitVariablyModifiedType(QualType type) { case Type::HLSLAttributedResource: case Type::HLSLInlineSpirv: case Type::PredefinedSugar: + case Type::LateParsedAttr: cgm.errorNYI("CIRGenFunction::emitVariablyModifiedType"); break; diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 3d429d0d78e82..6305c3a115dd4 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -4332,6 +4332,7 @@ llvm::DIType *CGDebugInfo::CreateTypeNode(QualType Ty, llvm::DIFile *Unit) { case Type::PredefinedSugar: return getOrCreateType(cast<PredefinedSugarType>(Ty)->desugar(), Unit); case Type::CountAttributed: + case Type::LateParsedAttr: case Type::Auto: case Type::Attributed: case Type::BTFTagAttributed: diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index b920266b59808..a5f71a88535b4 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -2569,6 +2569,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType type) { case Type::BitInt: case Type::HLSLInlineSpirv: case Type::PredefinedSugar: + case Type::LateParsedAttr: llvm_unreachable("type class is never variably-modified!"); case Type::Adjusted: diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ad6e7183cb3a4..0c051dc7d755a 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4667,6 +4667,7 @@ static void captureVariablyModifiedType(ASTContext &Context, QualType T, case Type::SubstTemplateTypeParm: case Type::MacroQualified: case Type::CountAttributed: + case Type::LateParsedAttr: // Keep walking after single level desugaring. T = T.getSingleStepDesugaredType(Context); break; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 53107c827006d..50a9d4feb9b6d 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -7768,6 +7768,26 @@ QualType TreeTransform<Derived>::TransformCountAttributedType( return Result; } +template <typename Derived> +QualType +TreeTransform<Derived>::TransformLateParsedAttrType(TypeLocBuilder &TLB, + LateParsedAttrTypeLoc TL) { + const LateParsedAttrType *OldTy = TL.getTypePtr(); + QualType InnerTy = getDerived().TransformType(TLB, TL.getInnerLoc()); + if (InnerTy.isNull()) + return QualType(); + + QualType Result = TL.getType(); + if (getDerived().AlwaysRebuild() || InnerTy != OldTy->getWrappedType()) { + Result = SemaRef.Context.getLateParsedAttrType( + InnerTy, OldTy->getLateParsedAttribute()); + } + + LateParsedAttrTypeLoc newTL = TLB.push<LateParsedAttrTypeLoc>(Result); + newTL.setAttrNameLoc(TL.getAttrNameLoc()); + return Result; +} + template <typename Derived> QualType TreeTransform<Derived>::TransformBTFTagAttributedType( TypeLocBuilder &TLB, BTFTagAttributedTypeLoc TL) { diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index f8a6a38bb9b5c..5c8827ea32514 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -7701,6 +7701,10 @@ void TypeLocReader::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { // Nothing to do } +void TypeLocReader::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { + // Nothing to do +} + void TypeLocReader::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { // Nothing to do. } diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index 21dda6f3733e4..2c56a052a5fb4 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -156,6 +156,9 @@ static TypeCode getTypeCodeForTypeClass(Type::TypeClass id) { #define TYPE_BIT_CODE(CLASS_ID, CODE_ID, CODE_VALUE) \ case Type::CLASS_ID: return TYPE_##CODE_ID; #include "clang/Serialization/TypeBitCodes.def" + case Type::LateParsedAttr: + llvm_unreachable( + "should be replaced with a concrete type before serialization"); case Type::Builtin: llvm_unreachable("shouldn't be serializing a builtin type this way"); } @@ -587,6 +590,10 @@ void TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { // Nothing to do } +void TypeLocWriter::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { + // Nothing to do +} + void TypeLocWriter::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { // Nothing to do. } diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index ac2fad38a1348..42d4b5bef7fb8 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -1738,6 +1738,10 @@ bool CursorVisitor::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { return Visit(TL.getInnerLoc()); } +bool CursorVisitor::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { + return Visit(TL.getInnerLoc()); +} + bool CursorVisitor::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { return Visit(TL.getWrappedLoc()); } diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 82fd9844cf96a..8adfe096a7624 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -4099,6 +4099,9 @@ TypeSystemClang::GetTypeClass(lldb::opaque_compiler_type_t type) { case clang::Type::Using: case clang::Type::PredefinedSugar: llvm_unreachable("Handled in RemoveWrappingTypes!"); + case clang::Type::LateParsedAttr: + llvm_unreachable("LateParsedAttrType is a transient parsing placeholder " + "that is resolved before the AST is finalized."); case clang::Type::UnaryTransform: break; case clang::Type::FunctionNoProto: @@ -4803,6 +4806,9 @@ lldb::Encoding TypeSystemClang::GetEncoding(lldb::opaque_compiler_type_t type) { case clang::Type::Using: case clang::Type::PredefinedSugar: llvm_unreachable("Handled in RemoveWrappingTypes!"); + case clang::Type::LateParsedAttr: + llvm_unreachable("LateParsedAttrType is a transient parsing placeholder " + "that is resolved before the AST is finalized."); case clang::Type::UnaryTransform: break; @@ -5105,6 +5111,9 @@ lldb::Format TypeSystemClang::GetFormat(lldb::opaque_compiler_type_t type) { case clang::Type::Using: case clang::Type::PredefinedSugar: llvm_unreachable("Handled in RemoveWrappingTypes!"); + case clang::Type::LateParsedAttr: + llvm_unreachable("LateParsedAttrType is a transient parsing placeholder " + "that is resolved before the AST is finalized."); case clang::Type::UnaryTransform: break; >From 90367bbe3755e98d035101a9fb595bc669241801 Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Fri, 19 Jun 2026 22:03:24 +0300 Subject: [PATCH 2/6] fix code placement --- clang/lib/CodeGen/CodeGenFunction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index a5f71a88535b4..51426183211ec 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -2569,7 +2569,6 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType type) { case Type::BitInt: case Type::HLSLInlineSpirv: case Type::PredefinedSugar: - case Type::LateParsedAttr: llvm_unreachable("type class is never variably-modified!"); case Type::Adjusted: @@ -2663,6 +2662,7 @@ void CodeGenFunction::EmitVariablyModifiedType(QualType type) { case Type::SubstTemplateTypeParm: case Type::MacroQualified: case Type::CountAttributed: + case Type::LateParsedAttr: // Keep walking after single level desugaring. type = type.getSingleStepDesugaredType(getContext()); break; >From 45490e6b37694883c039c1a8680613ec02b9b8c3 Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Sun, 19 Jul 2026 20:12:04 +0300 Subject: [PATCH 3/6] make LateParsedAttrType unreachable in AST import and serialization --- clang/lib/AST/ASTImporter.cpp | 4 +--- clang/lib/Serialization/ASTReader.cpp | 3 ++- clang/lib/Serialization/ASTWriter.cpp | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index d91da096b2466..9c45fd3ffd185 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -1844,9 +1844,7 @@ ASTNodeImporter::VisitCountAttributedType(const CountAttributedType *T) { ExpectedType ASTNodeImporter::VisitLateParsedAttrType(const LateParsedAttrType *T) { - // LateParsedAttrType is a transient placeholder that should not normally - // appear during AST import. Import as the wrapped type. - return import(T->getWrappedType()); + llvm_unreachable("should be replaced with a concrete type before AST import"); } ExpectedType ASTNodeImporter::VisitTemplateTypeParmType( diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 5c8827ea32514..49a78431506c5 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -7702,7 +7702,8 @@ void TypeLocReader::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { } void TypeLocReader::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { - // Nothing to do + llvm_unreachable( + "should be replaced with a concrete type before serialization"); } void TypeLocReader::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index 2c56a052a5fb4..0da13cd518ecd 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -591,7 +591,8 @@ void TypeLocWriter::VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { } void TypeLocWriter::VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { - // Nothing to do + llvm_unreachable( + "should be replaced with a concrete type before serialization"); } void TypeLocWriter::VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { >From 7c3277ba7ebbbb24419e54dcee0cf1345b01edb4 Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Sun, 19 Jul 2026 20:30:01 +0300 Subject: [PATCH 4/6] document LateParsedAttrType canonical type and drop FoldingSetNode --- clang/include/clang/AST/TypeBase.h | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index dd46a58a6fa50..663ccb5a8a446 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -3548,10 +3548,14 @@ class CountAttributedType final /// Represents a placeholder type for late-parsed type attributes. /// This type wraps another type and holds an opaque pointer to a -/// LateParsedAttribute that will be parsed later (e.g., in ActOnFields). +/// LateParsedTypeAttribute that will be parsed later (e.g., in ActOnFields). /// Once parsed, this type is replaced with the appropriate attributed type -/// (e.g., CountAttributedType for counted_by). -class LateParsedAttrType : public Type, public llvm::FoldingSetNode { +/// (e.g., CountAttributedType for `__counted_by`). +/// +/// Its canonical type is that of the wrapped type, so a consumer walking the +/// AST during late parsing must treat this as "attribute unresolved", not "no +/// attribute here". +class LateParsedAttrType : public Type { friend class ASTContext; // ASTContext creates these. QualType WrappedTy; @@ -3571,16 +3575,6 @@ class LateParsedAttrType : public Type, public llvm::FoldingSetNode { bool isSugared() const { return true; } QualType desugar() const { return WrappedTy; } - void Profile(llvm::FoldingSetNodeID &ID) { - Profile(ID, WrappedTy, LateParsedTypeAttr); - } - - static void Profile(llvm::FoldingSetNodeID &ID, QualType Wrapped, - LateParsedTypeAttribute *Attr) { - ID.AddPointer(Wrapped.getAsOpaquePtr()); - ID.AddPointer(Attr); - } - static bool classof(const Type *T) { return T->getTypeClass() == LateParsedAttr; } >From 9006e13956171092b1b5003bedb423cc88cb7d3c Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Fri, 31 Jul 2026 19:06:00 +0300 Subject: [PATCH 5/6] support counted_by on function params make the counted_by family (counted_by, counted_by_or_null, sized_by, sized_by_or_null) work in type position on a function parameter under -fexperimental-late-parse-attributes --- .../clang/Basic/DiagnosticSemaKinds.td | 6 + clang/include/clang/Parse/Parser.h | 18 +- clang/include/clang/Sema/DeclSpec.h | 6 +- clang/include/clang/Sema/Sema.h | 65 +++ clang/lib/Parse/ParseDecl.cpp | 82 ++- clang/lib/Parse/Parser.cpp | 5 + clang/lib/Sema/SemaBoundsSafety.cpp | 529 +++++++++++++++--- clang/lib/Sema/SemaType.cpp | 39 ++ 8 files changed, 674 insertions(+), 76 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 8097800e6744a..8039ac7b8cc18 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7131,6 +7131,12 @@ def err_counted_by_attr_refer_to_itself : Error< "'counted_by' cannot refer to the flexible array member %0">; def err_count_attr_must_be_in_structure : Error< "field %0 in '%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' not inside structure">; +def err_count_attr_not_param_of_same_function : Error< + "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}1' argument %0 is not a parameter of the same function as the annotated pointer">; +def err_count_attr_on_nested_type : Error< + "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' is not supported on " + "%select{a nested pointer|an array element}1; it must apply to the " + "annotated declaration's own type">; def err_count_attr_argument_not_integer : Error< "'%select{counted_by|sized_by|counted_by_or_null|sized_by_or_null}0' requires a non-boolean integer type argument">; def err_count_attr_only_support_simple_decl_reference : Error< diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 5e7af97feeb6c..7c72b1c307ea0 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -2670,7 +2670,8 @@ class Parser : public CodeCompletionHandler { void ParseTypeQualifierListOpt( DeclSpec &DS, unsigned AttrReqs = AR_AllAttributesParsed, bool AtomicOrPtrauthAllowed = true, bool IdentifierRequired = false, - llvm::function_ref<void()> CodeCompletionHandler = {}); + llvm::function_ref<void()> CodeCompletionHandler = {}, + LateParsedAttrList *LateAttrs = nullptr); /// ParseDirectDeclarator /// \verbatim @@ -8090,6 +8091,21 @@ class Parser : public CodeCompletionHandler { static void LateTemplateParserCallback(void *P, LateParsedTemplate &LPT); + /// Parse the cached tokens of \p LTA into \p OutAttrs. Takes ownership of + /// \p LTA. + static void ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute *LTA, + ParsedAttributes *OutAttrs); + + /// Validate \p LA against \p type and, if it applies, wrap \p type in a + /// \c LateParsedAttrType placeholder. Returns false if the attribute is + /// invalid for \p type. + static bool ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA, + QualType &type); + + /// Return the source location of the attribute name stored in \p LTA. + static SourceLocation + GetLateParsedAttributeLocationCallback(const LateParsedTypeAttribute *LTA); + /// We've parsed something that could plausibly be intended to be a template /// name (\p LHS) followed by a '<' token, and the following code can't /// possibly be an expression. Determine if this is likely to be a template-id diff --git a/clang/include/clang/Sema/DeclSpec.h b/clang/include/clang/Sema/DeclSpec.h index 6e7f9cd6e3d38..dac4c9f498f63 100644 --- a/clang/include/clang/Sema/DeclSpec.h +++ b/clang/include/clang/Sema/DeclSpec.h @@ -1303,6 +1303,7 @@ struct DeclaratorChunk { } ParsedAttributesView AttrList; + LateParsedAttrList LateAttrList; struct PointerTypeInfo { /// The type qualifiers: const/volatile/restrict/unaligned/atomic. @@ -2403,13 +2404,16 @@ class Declarator { /// This function takes attrs by R-Value reference because it takes ownership /// of those attributes from the parameter. void AddTypeInfo(const DeclaratorChunk &TI, ParsedAttributes &&attrs, - SourceLocation EndLoc) { + SourceLocation EndLoc, + const LateParsedAttrList &LateAttrs = {}) { DeclTypeInfo.push_back(TI); DeclTypeInfo.back().getAttrs().prepend(attrs.begin(), attrs.end()); getAttributePool().takeAllFrom(attrs.getPool()); if (!EndLoc.isInvalid()) SetRangeEnd(EndLoc); + + DeclTypeInfo.back().LateAttrList.append(LateAttrs); } /// AddTypeInfo - Add a chunk to this declarator. Also extend the range to diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index b8d760e7e0975..ca0ccb9cac29d 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -143,6 +143,8 @@ class InitializationKind; class InitializationSequence; class InitializedEntity; enum class LangAS : unsigned int; +struct LateParsedAttribute; +struct LateParsedTypeAttribute; class LocalInstantiationScope; class LookupResult; class MangleNumberingContext; @@ -1356,6 +1358,69 @@ class Sema final : public SemaBase { OpaqueParser = P; } + /// Parse the cached tokens of \p LTA into \p OutAttrs. Takes ownership of + /// \p LTA. + typedef void ParseLateParsedTypeAttributeCB(LateParsedTypeAttribute *LTA, + ParsedAttributes *OutAttrs); + + /// Validate \p LA against \p type and, if it applies, wrap \p type in a + /// \c LateParsedAttrType placeholder. Returns false if the attribute is + /// invalid for \p type. + typedef bool ProcessLateParsedTypeAttrCB(LateParsedAttribute *LA, + QualType &type); + ProcessLateParsedTypeAttrCB *ProcessLateParsedTypeAttrCallback = nullptr; + + /// Return the source location of the attribute name stored in \p LTA. + typedef SourceLocation + GetLateParsedAttributeLocationCB(const LateParsedTypeAttribute *LTA); + GetLateParsedAttributeLocationCB *GetLateParsedAttributeLocationCallback = + nullptr; + + /// Wrap \p type in a LateParsedAttrType placeholder for \p LTA, after + /// checking that the attribute is applicable to \p type at all. Returns + /// false if the attribute should be dropped. + bool ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind, + SourceLocation AttrNameLoc, QualType &type, + LateParsedTypeAttribute *LTA); + + /// RAII object that pushes a function prototype scope holding \p Params and + /// restores the previous scope on destruction. + /// + /// A count expression written inside a *nested* function prototype, as in + /// + /// \code + /// void f(int *__counted_by(len) (*cb)(int len)); + /// \endcode + /// + /// names a parameter of that inner prototype, whose scope has already been + /// popped by the time the attribute's tokens are parsed. Re-creating the + /// scope lets ordinary name lookup resolve the count at any nesting depth. + class FunctionPrototypeScopeRAII { + Sema &S; + Scope ProtoScope; + + public: + FunctionPrototypeScopeRAII(Sema &S, ArrayRef<Decl *> Params); + ~FunctionPrototypeScopeRAII(); + + FunctionPrototypeScopeRAII(const FunctionPrototypeScopeRAII &) = delete; + FunctionPrototypeScopeRAII & + operator=(const FunctionPrototypeScopeRAII &) = delete; + }; + + /// Resolve late-parsed type attributes in the types of \p Params. + /// + /// Called once the whole parameter clause has been parsed, so that a count + /// expression may refer to any parameter of the function, including one + /// declared after the annotated parameter. + /// + /// This pushes its own prototype scope for \p Params, so it does not require + /// the caller's prototype scope to still be open. It does need *some* scope + /// to be current, since lookup has to be able to reach file scope to + /// diagnose a count that names a global. + void ProcessLateParsedTypeAttributesForParams( + ArrayRef<Decl *> Params, ParseLateParsedTypeAttributeCB *ParseCB); + /// Callback to the parser to parse a type expressed as a string. std::function<TypeResult(StringRef, StringRef, SourceLocation)> ParseTypeFromStringCallback; diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 405dddf7991b4..92f04a91d1e5a 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -117,6 +117,15 @@ static bool IsAttributeArgsParsedInFunctionScope(const IdentifierInfo &II) { #undef CLANG_ATTR_PARSE_ARGS_IN_FUNCTION_SCOPE_LIST } +/// Returns true iff the attribute kind corresponds to a TypeAttr or +/// DeclOrTypeAttr in Attr.td. +static bool IsAttributeTypeAttr(ParsedAttr::Kind Kind) { + const auto &Infos = ParsedAttrInfo::getAllBuiltin(); + if ((size_t)Kind < Infos.size()) + return Infos[Kind]->IsType; + return false; +} + /// Check if the a start and end source location expand to the same macro. static bool FindLocsWithCommonFileID(Preprocessor &PP, SourceLocation StartLoc, SourceLocation EndLoc) { @@ -173,8 +182,14 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs, // The caller requested that this attribute **only** be late // parsed for `LateAttrParseExperimentalExt` attributes. This will // only be late parsed if the experimental language option is enabled. + // A type-attr-only list takes type attributes only; a decl attribute like + // `guarded_by` captured there would be silently dropped. + ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind( + AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax()); LateParse = getLangOpts().ExperimentalLateParseAttributes && - IsAttributeLateParsedExperimentalExt(*AttrName); + IsAttributeLateParsedExperimentalExt(*AttrName) && + (IsAttributeTypeAttr(AttrKind) || + !LateAttrs->lateAttrParseTypeAttrOnly()); } else { // The caller did not restrict late parsing to only // `LateAttrParseExperimentalExt` attributes so late parse @@ -193,7 +208,9 @@ bool Parser::ParseSingleGNUAttribute(ParsedAttributes &Attrs, // Handle attributes with arguments that require late parsing. LateParsedAttribute *LA = - new LateParsedAttribute(this, *AttrName, AttrNameLoc); + LateAttrs->lateAttrParseTypeAttrOnly() + ? new LateParsedTypeAttribute(this, *AttrName, AttrNameLoc) + : new LateParsedAttribute(this, *AttrName, AttrNameLoc); LateAttrs->push_back(LA); // Attributes in a class are parsed at the end of the class, along @@ -4922,6 +4939,35 @@ void LateParsedTypeAttribute::ParseInto(ParsedAttributes &OutAttrs) { Self->ParseLexedTypeAttribute(*this, OutAttrs); } +void Parser::ParseLateParsedTypeAttributeCallback(LateParsedTypeAttribute *LTA, + ParsedAttributes *OutAttrs) { + LTA->ParseInto(*OutAttrs); + delete LTA; +} + +SourceLocation Parser::GetLateParsedAttributeLocationCallback( + const LateParsedTypeAttribute *LTA) { + assert(LTA); + return LTA->AttrNameLoc; +} + +bool Parser::ProcessLateParsedTypeAttrCallback(LateParsedAttribute *LA, + QualType &type) { + auto *LTA = dyn_cast_if_present<LateParsedTypeAttribute>(LA); + if (!LTA) + return true; + + ParsedAttr::Kind AttrKind = ParsedAttr::getParsedKind( + <A->AttrName, nullptr, ParsedAttr::Form::GNU().getSyntax()); + if (LTA->Self->Actions.ActOnLateParsedTypeAttr(AttrKind, LTA->AttrNameLoc, + type, LTA)) + return true; + + // No placeholder was built, so nothing else will ever own these tokens. + delete LTA; + return false; +} + void Parser::TakeTypeAttrsAppendingFrom(LateParsedAttrList &To, LateParsedAttrList &From) { LateParsedAttrList::iterator It = @@ -6243,7 +6289,8 @@ bool Parser::isConstructorDeclarator(bool IsUnqualified, bool DeductionGuide, void Parser::ParseTypeQualifierListOpt( DeclSpec &DS, unsigned AttrReqs, bool AtomicOrPtrauthAllowed, - bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler) { + bool IdentifierRequired, llvm::function_ref<void()> CodeCompletionHandler, + LateParsedAttrList *LateAttrs) { if ((AttrReqs & AR_CXX11AttributesParsed) && isAllowedCXX11AttributeSpecifier()) { ParsedAttributes Attrs(AttrFactory); @@ -6405,7 +6452,7 @@ void Parser::ParseTypeQualifierListOpt( // recovery is graceful. if (AttrReqs & AR_GNUAttributesParsed || AttrReqs & AR_GNUAttributesParsedAndRejected) { - ParseGNUAttributes(DS.getAttributes()); + ParseGNUAttributes(DS.getAttributes(), LateAttrs); continue; // do *not* consume the next token! } // otherwise, FALL THROUGH! @@ -6588,8 +6635,19 @@ void Parser::ParseDeclaratorInternal(Declarator &D, ((D.getContext() != DeclaratorContext::CXXNew) ? AR_GNUAttributesParsed : AR_GNUAttributesParsedAndRejected); + + LateParsedAttrList LateAttrs(/*PSoon=*/true, + /*LateAttrParseExperimentalExtOnly=*/true, + /*LateAttrParseTypeAttrOnly=*/true); + + LateParsedAttrList *LateAttrsPtr = + D.getContext() == DeclaratorContext::Prototype && + !getLangOpts().CPlusPlus + ? &LateAttrs + : nullptr; + ParseTypeQualifierListOpt(DS, Reqs, /*AtomicOrPtrauthAllowed=*/true, - !D.mayOmitIdentifier()); + !D.mayOmitIdentifier(), {}, LateAttrsPtr); D.ExtendWithDeclSpec(DS); // Recursively parse the declarator. @@ -6602,12 +6660,12 @@ void Parser::ParseDeclaratorInternal(Declarator &D, DS.getVolatileSpecLoc(), DS.getRestrictSpecLoc(), DS.getAtomicSpecLoc(), DS.getUnalignedSpecLoc(), DS.getOverflowBehaviorLoc(), DS.isWrapSpecified()), - std::move(DS.getAttributes()), SourceLocation()); + std::move(DS.getAttributes()), SourceLocation(), LateAttrs); else // Remember that we parsed a Block type, and remember the type-quals. D.AddTypeInfo( DeclaratorChunk::getBlockPointer(DS.getTypeQualifiers(), Loc), - std::move(DS.getAttributes()), SourceLocation()); + std::move(DS.getAttributes()), SourceLocation(), LateAttrs); } else { // Is a reference DeclSpec DS(AttrFactory); @@ -7358,6 +7416,16 @@ void Parser::ParseFunctionDeclarator(Declarator &D, else if (RequiresArg) Diag(Tok, diag::err_argument_required_after_attribute); + // Every parameter is now declared and the prototype scope is still active, + // so resolve late-parsed attributes here. + if (getLangOpts().ExperimentalLateParseAttributes && !ParamInfo.empty()) { + SmallVector<Decl *, 16> Params; + for (const DeclaratorChunk::ParamInfo &PI : ParamInfo) + Params.push_back(PI.Param); + Actions.ProcessLateParsedTypeAttributesForParams( + Params, &Parser::ParseLateParsedTypeAttributeCallback); + } + // OpenCL disallows functions without a prototype, but it doesn't enforce // strict prototypes as in C23 because it allows a function definition to // have an identifier list. See OpenCL 3.0 6.11/g for more details. diff --git a/clang/lib/Parse/Parser.cpp b/clang/lib/Parse/Parser.cpp index 5e1fd4df1a3f0..66d6e4a2c8342 100644 --- a/clang/lib/Parse/Parser.cpp +++ b/clang/lib/Parse/Parser.cpp @@ -83,6 +83,11 @@ Parser::Parser(Preprocessor &pp, Sema &actions, bool skipFunctionBodies) [this](StringRef TypeStr, StringRef Context, SourceLocation IncludeLoc) { return this->ParseTypeFromString(TypeStr, Context, IncludeLoc); }; + + Actions.ProcessLateParsedTypeAttrCallback = + &Parser::ProcessLateParsedTypeAttrCallback; + Actions.GetLateParsedAttributeLocationCallback = + &Parser::GetLateParsedAttributeLocationCallback; } DiagnosticBuilder Parser::Diag(SourceLocation Loc, unsigned DiagID) { diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index b01250ccd0ffc..21ff9a30def3b 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -11,9 +11,12 @@ /// (e.g. `counted_by`) /// //===----------------------------------------------------------------------===// +#include "TreeTransform.h" #include "clang/Lex/Lexer.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Sema.h" +#include "llvm/Support/SaveAndRestore.h" +#include <optional> namespace clang { @@ -26,6 +29,56 @@ getCountAttrKind(bool CountInBytes, bool OrNull) { : CountAttributedType::CountedBy; } +namespace { +struct BoundsAttrFlags { + bool CountInBytes = false; + bool OrNull = false; +}; +} // namespace + +static std::optional<BoundsAttrFlags> getBoundsAttrFlags(ParsedAttr::Kind K) { + BoundsAttrFlags Flags; + switch (K) { + case ParsedAttr::AT_CountedBy: + break; + case ParsedAttr::AT_CountedByOrNull: + Flags.OrNull = true; + break; + case ParsedAttr::AT_SizedBy: + Flags.CountInBytes = true; + break; + case ParsedAttr::AT_SizedByOrNull: + Flags.CountInBytes = true; + Flags.OrNull = true; + break; + default: + return std::nullopt; + } + return Flags; +} + +bool Sema::ActOnLateParsedTypeAttr(ParsedAttr::Kind AttrKind, + SourceLocation AttrNameLoc, QualType &type, + LateParsedTypeAttribute *LTA) { + // Not an attribute this mechanism handles; leave it alone. + std::optional<BoundsAttrFlags> Flags = getBoundsAttrFlags(AttrKind); + if (!Flags) + return false; + + // The argument-independent validation, run before the attribute's argument + // has been parsed. An array parameter has already decayed to a pointer, so + // unlike a flexible array member there is no array spelling to accept here. + if (!type->isPointerType()) { + unsigned Kind = getCountAttrKind(Flags->CountInBytes, Flags->OrNull); + Diag(AttrNameLoc, diag::err_count_attr_not_on_ptr_or_flexible_array_member) + << Kind << AttrNameLoc << /*do not suggest counted_by*/ 0; + return false; + } + + type = Context.getLateParsedAttrType(type, LTA); + return true; +} + static const RecordDecl *GetEnclosingNamedOrTopAnonRecord(const FieldDecl *FD) { const auto *RD = FD->getParent(); // An unnamed struct is treated as anonymous struct at this point. @@ -49,59 +102,19 @@ enum class CountedByInvalidPointeeTypeKind { VALID, }; -bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, - bool OrNull) { - // Check the context the attribute is used in - - unsigned Kind = getCountAttrKind(CountInBytes, OrNull); - - if (FD->getParent()->isUnion()) { - Diag(FD->getBeginLoc(), diag::err_count_attr_in_union) - << Kind << FD->getSourceRange(); - return true; - } - - const auto FieldTy = FD->getType(); - if (FieldTy->isArrayType() && (CountInBytes || OrNull)) { - Diag(FD->getBeginLoc(), - diag::err_count_attr_not_on_ptr_or_flexible_array_member) - << Kind << FD->getLocation() << /* suggest counted_by */ 1; - return true; - } - if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) { - Diag(FD->getBeginLoc(), - diag::err_count_attr_not_on_ptr_or_flexible_array_member) - << Kind << FD->getLocation() << /* do not suggest counted_by */ 0; - return true; - } - - LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel = - LangOptions::StrictFlexArraysLevelKind::IncompleteOnly; - if (FieldTy->isArrayType() && - !Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy, - StrictFlexArraysLevel, true)) { - Diag(FD->getBeginLoc(), - diag::err_counted_by_attr_on_array_not_flexible_array_member) - << Kind << FD->getLocation(); - return true; - } - +/// Diagnose a counted_by-family attribute whose pointee (or array element) +/// type \p PointeeTy cannot support bounds computation. Shared between the +/// struct-field and function-parameter paths so both accept and reject the +/// same shapes. \p DowngradeFAMPointeeErrToWarn is the Linux-kernel +/// workaround; see the caller in CheckCountedByAttrOnField. Returns true if +/// the attribute cannot be applied. +static bool CheckCountAttrPointeeType(Sema &S, QualType PointeeTy, + bool CountInBytes, unsigned Kind, + int SelectPtrOrArr, + bool DowngradeFAMPointeeErrToWarn, + SourceLocation Loc, SourceRange Range) { CountedByInvalidPointeeTypeKind InvalidTypeKind = CountedByInvalidPointeeTypeKind::VALID; - QualType PointeeTy; - int SelectPtrOrArr = 0; - if (FieldTy->isPointerType()) { - PointeeTy = FieldTy->getPointeeType(); - SelectPtrOrArr = 0; - } else { - assert(FieldTy->isArrayType()); - const ArrayType *AT = getASTContext().getAsArrayType(FieldTy); - PointeeTy = AT->getElementType(); - SelectPtrOrArr = 1; - } - // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means - // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable - // when `FieldTy->isArrayType()`. bool ShouldWarn = false; if (PointeeTy->isAlwaysIncompleteType() && !CountInBytes) { // In general using `counted_by` or `counted_by_or_null` on @@ -125,8 +138,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, // * When the pointee type might not always be an incomplete type (i.e. // a type that is currently incomplete but might be completed later // on in the translation unit) the attribute is allowed by this method - // but later uses of the FieldDecl are checked that the pointee type - // is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`, + // but later uses of the annotated declaration are checked that the pointee + // type is complete see `BoundsSafetyCheckAssignmentToCountAttrPtr`, // `BoundsSafetyCheckInitialization`, and // `BoundsSafetyCheckUseOfCountAttrPtr` // @@ -141,9 +154,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, bool IsVoidPtr = PointeeTy->isVoidType(); if (IsVoidPtr) { // Emit a warning that this is a GNU extension. - Diag(FD->getBeginLoc(), diag::ext_gnu_counted_by_void_ptr) << Kind; - Diag(FD->getBeginLoc(), diag::note_gnu_counted_by_void_ptr_use_sized_by) - << Kind; + S.Diag(Loc, diag::ext_gnu_counted_by_void_ptr) << Kind; + S.Diag(Loc, diag::note_gnu_counted_by_void_ptr_use_sized_by) << Kind; assert(InvalidTypeKind == CountedByInvalidPointeeTypeKind::VALID); } else { InvalidTypeKind = CountedByInvalidPointeeTypeKind::INCOMPLETE; @@ -153,15 +165,8 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, } else if (PointeeTy->isFunctionType()) { InvalidTypeKind = CountedByInvalidPointeeTypeKind::FUNCTION; } else if (PointeeTy->isStructureTypeWithFlexibleArrayMember()) { - if (FieldTy->isArrayType() && !getLangOpts().BoundsSafety) { - // This is a workaround for the Linux kernel that has already adopted - // `counted_by` on a FAM where the pointee is a struct with a FAM. This - // should be an error because computing the bounds of the array cannot be - // done correctly without manually traversing every struct object in the - // array at runtime. To allow the code to be built this error is - // downgraded to a warning. + if (DowngradeFAMPointeeErrToWarn) ShouldWarn = true; - } InvalidTypeKind = CountedByInvalidPointeeTypeKind::FLEXIBLE_ARRAY_MEMBER; } @@ -169,12 +174,78 @@ bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, unsigned DiagID = ShouldWarn ? diag::warn_counted_by_attr_elt_type_unknown_size : diag::err_counted_by_attr_pointee_unknown_size; - Diag(FD->getBeginLoc(), DiagID) - << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind - << (ShouldWarn ? 1 : 0) << Kind << FD->getSourceRange(); + S.Diag(Loc, DiagID) << SelectPtrOrArr << PointeeTy << (int)InvalidTypeKind + << (ShouldWarn ? 1 : 0) << Kind << Range; + return true; + } + return false; +} + +bool Sema::CheckCountedByAttrOnField(FieldDecl *FD, Expr *E, bool CountInBytes, + bool OrNull) { + // Check the context the attribute is used in + + unsigned Kind = getCountAttrKind(CountInBytes, OrNull); + + if (FD->getParent()->isUnion()) { + Diag(FD->getBeginLoc(), diag::err_count_attr_in_union) + << Kind << FD->getSourceRange(); + return true; + } + + const auto FieldTy = FD->getType(); + if (FieldTy->isArrayType() && (CountInBytes || OrNull)) { + Diag(FD->getBeginLoc(), + diag::err_count_attr_not_on_ptr_or_flexible_array_member) + << Kind << FD->getLocation() << /* suggest counted_by */ 1; + return true; + } + if (!FieldTy->isArrayType() && !FieldTy->isPointerType()) { + Diag(FD->getBeginLoc(), + diag::err_count_attr_not_on_ptr_or_flexible_array_member) + << Kind << FD->getLocation() << /* do not suggest counted_by */ 0; + return true; + } + + LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel = + LangOptions::StrictFlexArraysLevelKind::IncompleteOnly; + if (FieldTy->isArrayType() && + !Decl::isFlexibleArrayMemberLike(getASTContext(), FD, FieldTy, + StrictFlexArraysLevel, true)) { + Diag(FD->getBeginLoc(), + diag::err_counted_by_attr_on_array_not_flexible_array_member) + << Kind << FD->getLocation(); return true; } + QualType PointeeTy; + int SelectPtrOrArr = 0; + if (FieldTy->isPointerType()) { + PointeeTy = FieldTy->getPointeeType(); + SelectPtrOrArr = 0; + } else { + assert(FieldTy->isArrayType()); + const ArrayType *AT = getASTContext().getAsArrayType(FieldTy); + PointeeTy = AT->getElementType(); + SelectPtrOrArr = 1; + } + // Note: The `Decl::isFlexibleArrayMemberLike` check earlier on means + // only `PointeeTy->isStructureTypeWithFlexibleArrayMember()` is reachable + // when `FieldTy->isArrayType()`. + // + // Downgrading the FAM-pointee error to a warning on such arrays is a + // workaround for the Linux kernel that has already adopted `counted_by` on + // a FAM where the pointee is a struct with a FAM. This should be an error + // because computing the bounds of the array cannot be done correctly + // without manually traversing every struct object in the array at runtime. + // To allow the code to be built the error is downgraded to a warning. + bool DowngradeFAMPointeeErrToWarn = + FieldTy->isArrayType() && !getLangOpts().BoundsSafety; + if (CheckCountAttrPointeeType(*this, PointeeTy, CountInBytes, Kind, + SelectPtrOrArr, DowngradeFAMPointeeErrToWarn, + FD->getBeginLoc(), FD->getSourceRange())) + return true; + // Check the expression if (!E->getType()->isIntegerType() || E->getType()->isBooleanType()) { @@ -411,4 +482,328 @@ bool Sema::BoundsSafetyCheckUseOfCountAttrPtr(const Expr *E) { return false; } +/// Build the CountAttributedType for a counted_by-family attribute on a +/// parameter's type, whose argument has just been parsed. \p Params is the +/// prototype's parameter list; the count must name one of them. Null on error. +static QualType buildCountAttributedTypeForParam(Sema &S, QualType InnerTy, + ArrayRef<Decl *> Params, + ParsedAttr &AL) { + std::optional<BoundsAttrFlags> Flags = getBoundsAttrFlags(AL.getKind()); + assert(Flags && "placeholder for a non-counted_by-family attribute"); + auto [CountInBytes, OrNull] = *Flags; + unsigned Kind = getCountAttrKind(CountInBytes, OrNull); + + // The same pointee rules as the struct-field path, checked in the same + // order: the pointee's size before the count expression. A placeholder is + // only created for a pointer, so the pointee is always there to take. + if (CheckCountAttrPointeeType(S, InnerTy->getPointeeType(), CountInBytes, + Kind, /*SelectPtrOrArr=*/0, + /*DowngradeFAMPointeeErrToWarn=*/false, + AL.getLoc(), AL.getRange())) + return QualType(); + + Expr *CountExpr = AL.getArgAsExpr(0); + if (!CountExpr) + return QualType(); + + // The argument must name a single declaration, so that assignments to the + // count can be related back to the pointer. No paren-stripping: the + // expression handed to BuildCountAttributedArrayOrPointerType must itself be + // the DeclRefExpr (BuildTypeCoupledDecls casts it), and the field path in + // CheckCountedByAttrOnField rejects parens the same way. + auto *DRE = dyn_cast<DeclRefExpr>(CountExpr); + if (!DRE) { + S.Diag(CountExpr->getBeginLoc(), + diag::err_count_attr_only_support_simple_decl_reference) + << Kind << CountExpr->getSourceRange(); + return QualType(); + } + + // The count must name a parameter of *this* prototype. Check list membership + // rather than DeclContext: mid-parse every ParmVarDecl shares the enclosing + // context, so a DeclContext check would wrongly accept an enclosing + // prototype's parameter, as in + // void f(int n, void (*cb)(int *__counted_by(n) p)); + auto *CountDecl = dyn_cast<ParmVarDecl>(DRE->getDecl()); + if (!CountDecl || !llvm::is_contained(Params, CountDecl)) { + S.Diag(DRE->getBeginLoc(), diag::err_count_attr_not_param_of_same_function) + << DRE->getDecl() << Kind << DRE->getSourceRange(); + return QualType(); + } + + if (!CountDecl->getType()->isIntegerType() || + CountDecl->getType()->isBooleanType()) { + S.Diag(DRE->getBeginLoc(), diag::err_count_attr_argument_not_integer) + << Kind << DRE->getSourceRange(); + return QualType(); + } + + return S.BuildCountAttributedArrayOrPointerType(InnerTy, CountExpr, + CountInBytes, OrNull); +} + +namespace { + +/// Where a LateParsedAttrType placeholder sits inside a parameter's type. +enum class LateAttrPosition { + Parameter, + NestedPointer, + ArrayElement, +}; + +using LateAttrTypeAndPosition = + std::pair<const LateParsedAttrType *, LateAttrPosition>; + +} // namespace + +/// Collect every LateParsedAttrType written in \p T, together with where it +/// sits. A placeholder must never survive into the finalized AST, so this has +/// to reach all of them, not just the ones in a position we can resolve. +static void +findLateParsedAttrTypes(ASTContext &Ctx, QualType T, LateAttrPosition Pos, + SmallVectorImpl<LateAttrTypeAndPosition> &Out) { + // A position is only ever demoted: once a placeholder is out of the running + // for describing the parameter it cannot come back into it. + auto Demote = [](LateAttrPosition Pos, LateAttrPosition To) { + return Pos == LateAttrPosition::Parameter ? To : Pos; + }; + + // Each step below matches the node itself with isa/dyn_cast and desugars by + // a single step otherwise. Desugaring helpers that use getAs would step + // straight over a placeholder, which is the one thing this must not do; the + // getPointeeType() call is safe only because the isa<> in front of it + // guarantees the match is the node itself, not something behind sugar. + while (!T.isNull()) { + const Type *Ty = T.getTypePtr(); + + if (const auto *LPT = dyn_cast<LateParsedAttrType>(Ty)) { + Out.emplace_back(LPT, Pos); + T = LPT->desugar(); + continue; + } + + // The two pointer kinds a declarator chunk can produce in C. Late parsing + // is not enabled for C++, so references and member pointers cannot occur. + if (isa<PointerType, BlockPointerType>(Ty)) { + T = Ty->getPointeeType(); + Pos = Demote(Pos, LateAttrPosition::NestedPointer); + continue; + } + if (const auto *AT = dyn_cast<AtomicType>(Ty)) { + // Not sugar, so the desugar fallback would stop here. No demotion: a + // placeholder cannot sit directly below _Atomic, since an atomic pointer + // is rejected when the placeholder is created. + T = AT->getValueType(); + continue; + } + if (const auto *AT = dyn_cast<ArrayType>(Ty)) { + T = AT->getElementType(); + Pos = Demote(Pos, LateAttrPosition::ArrayElement); + continue; + } + if (const auto *FT = dyn_cast<FunctionType>(Ty)) { + // A count written inside a function prototype names that prototype's own + // parameters, so the position resets: the return type and each parameter + // type are "own types" again, just of a different declaration. + // RebuildTypeWithLateParsedAttr::TransformFunctionProtoType switches the + // parameter list to match. + // + // Only the return type reaches this in practice: the parser resolves at + // every prototype's closing paren, so a nested prototype's parameters + // were handled by their own trigger. The loop below keeps the walk + // exhaustive, since a placeholder it misses escapes into the AST. + findLateParsedAttrTypes(Ctx, FT->getReturnType(), + LateAttrPosition::Parameter, Out); + if (const auto *FPT = dyn_cast<FunctionProtoType>(FT)) + for (QualType ParamTy : FPT->getParamTypes()) + findLateParsedAttrTypes(Ctx, ParamTy, LateAttrPosition::Parameter, + Out); + return; + } + + // Anything else can only hide a placeholder behind sugar. + QualType Desugared = T.getSingleStepDesugaredType(Ctx); + if (Desugared == T) + return; + T = Desugared; + } +} + +namespace { + +/// Rebuilds a type, replacing each LateParsedAttrType placeholder with the +/// concrete type its attribute denotes, parsing the cached tokens on the way. +/// +/// Every placeholder is replaced, including the ones whose attribute turns out +/// to be unusable: a placeholder may not reach the finalized AST, and once its +/// tokens are parsed it no longer refers to a live attribute either. +struct RebuildTypeWithLateParsedAttr + : TreeTransform<RebuildTypeWithLateParsedAttr> { + ParmVarDecl *PVD; + ArrayRef<Decl *> Params; + Sema::ParseLateParsedTypeAttributeCB *ParseCallback; + ArrayRef<LateAttrTypeAndPosition> Positions; + + RebuildTypeWithLateParsedAttr(Sema &SemaRef, ParmVarDecl *PVD, + ArrayRef<Decl *> Params, + Sema::ParseLateParsedTypeAttributeCB *ParseCB, + ArrayRef<LateAttrTypeAndPosition> Positions) + : TreeTransform(SemaRef), PVD(PVD), Params(Params), + ParseCallback(ParseCB), Positions(Positions) {} + + /// findLateParsedAttrTypes walked the same type this transform is walking, so + /// every placeholder reached here was classified by it. + LateAttrPosition getPosition(const LateParsedAttrType *LPT) const { + for (LateAttrTypeAndPosition Entry : Positions) + if (Entry.first == LPT) + return Entry.second; + llvm_unreachable("placeholder reached by the transform was not classified"); + } + + // TransformFunctionProtoType is overloaded; overriding one would hide the + // rest. The base two-argument version dispatches to the five-argument one + // through getDerived(). + using TreeTransform::TransformFunctionProtoType; + + /// A count inside a nested prototype names *that* prototype's parameters, as + /// in `void f(int *__counted_by(len) (*cb)(int len), int len2);`. Two things + /// are needed and they are not alternatives: the re-entered scope makes `len` + /// findable at all (the inner prototype's scope was popped when its + /// declarator finished), and the parameter list decides whether what lookup + /// found is allowed (lookup falls through to the enclosing scopes). + QualType TransformFunctionProtoType(TypeLocBuilder &TLB, + FunctionProtoTypeLoc TL) { + SmallVector<Decl *, 4> InnerParams; + for (unsigned I = 0, E = TL.getNumParams(); I != E; ++I) + if (ParmVarDecl *PD = TL.getParam(I)) + InnerParams.push_back(PD); + + Sema::FunctionPrototypeScopeRAII ProtoScope(SemaRef, InnerParams); + SaveAndRestore<ArrayRef<Decl *>> SavedParams(Params, InnerParams); + return TreeTransform::TransformFunctionProtoType(TLB, TL); + } + + /// A no-prototype function declares no parameters, so nothing in its return + /// type can name one: `void f(int *__counted_by(len) (*cb)(), int len);`. + /// Clearing the list is what rejects that; otherwise the list is still the + /// enclosing prototype's and `len` is accepted. There is no scope worth + /// re-entering: an empty one would not change what lookup finds. + QualType TransformFunctionNoProtoType(TypeLocBuilder &TLB, + FunctionNoProtoTypeLoc TL) { + SaveAndRestore<ArrayRef<Decl *>> SavedParams(Params, ArrayRef<Decl *>()); + return TreeTransform::TransformFunctionNoProtoType(TLB, TL); + } + + QualType TransformLateParsedAttrType(TypeLocBuilder &TLB, + LateParsedAttrTypeLoc TL) { + const LateParsedAttrType *LPT = TL.getTypePtr(); + LateParsedTypeAttribute *LTA = LPT->getLateParsedAttribute(); + assert(LTA && "LateParsedAttrType without a LateParsedTypeAttribute"); + + AttributeFactory AF; + ParsedAttributes Attrs(AF); + + // Parse the cached tokens. The callback also destroys LTA, so from here on + // the placeholder refers to an attribute that no longer exists. + assert(ParseCallback); + ParseCallback(LTA, &Attrs); + + QualType InnerTy = TransformType(TLB, TL.getInnerLoc()); + if (InnerTy.isNull()) { + PVD->setInvalidDecl(); + return QualType(); + } + + // An empty list means the argument failed to parse, which is already + // diagnosed. + QualType T; + if (!Attrs.empty()) { + assert(Attrs.size() == 1); + LateAttrPosition Pos = getPosition(LPT); + if (Pos == LateAttrPosition::Parameter) { + T = buildCountAttributedTypeForParam(SemaRef, InnerTy, Params, + Attrs[0]); + } else { + std::optional<BoundsAttrFlags> Flags = + getBoundsAttrFlags(Attrs[0].getKind()); + assert(Flags && "placeholder for a non-counted_by-family attribute"); + SemaRef.Diag(TL.getAttrNameLoc(), diag::err_count_attr_on_nested_type) + << getCountAttrKind(Flags->CountInBytes, Flags->OrNull) + << (Pos == LateAttrPosition::NestedPointer ? /*nested pointer*/ 0 + : /*array element*/ 1); + } + } + + if (T.isNull()) { + // Drop the attribute and keep the type it wrapped. Returning nothing + // would leave the caller holding the original type, placeholder and all. + PVD->setInvalidDecl(); + return InnerTy; + } + + TLB.push<CountAttributedTypeLoc>(T); + return T; + } +}; + +} // namespace + +Sema::FunctionPrototypeScopeRAII::FunctionPrototypeScopeRAII( + Sema &S, ArrayRef<Decl *> Params) + : S(S), ProtoScope(S.getCurScope(), + Scope::FunctionPrototypeScope | Scope::DeclScope, + S.getDiagnostics()) { + S.CurScope = &ProtoScope; + for (Decl *D : Params) + S.ActOnReenterCXXMethodParameter(&ProtoScope, + dyn_cast_if_present<ParmVarDecl>(D)); +} + +Sema::FunctionPrototypeScopeRAII::~FunctionPrototypeScopeRAII() { + // ActOnPopScope is what takes the parameters back out of the IdResolver. It + // is a no-op otherwise here: a scope holding only ParmVarDecls produces no + // end-of-scope diagnostics. + S.ActOnPopScope(SourceLocation(), &ProtoScope); + S.CurScope = ProtoScope.getParent(); +} + +void Sema::ProcessLateParsedTypeAttributesForParams( + ArrayRef<Decl *> Params, ParseLateParsedTypeAttributeCB *ParseCB) { + // The parameters of a nested prototype are put back in scope by + // RebuildTypeWithLateParsedAttr::TransformFunctionProtoType. Do the same for + // the outermost prototype's own parameters rather than relying on the + // caller's scope, so that resolution works wherever this is called from. + // + // Created on the first parameter that needs it: the parser calls this for + // every prototype it parses under -fexperimental-late-parse-attributes, and + // almost none of them carry a late-parsed attribute. + std::optional<FunctionPrototypeScopeRAII> ProtoScope; + + for (Decl *D : Params) { + auto *PVD = dyn_cast_if_present<ParmVarDecl>(D); + if (!PVD || !PVD->getTypeSourceInfo()) + continue; + + TypeSourceInfo *OldTSI = PVD->getTypeSourceInfo(); + SmallVector<LateAttrTypeAndPosition, 2> Found; + findLateParsedAttrTypes(Context, OldTSI->getType(), + LateAttrPosition::Parameter, Found); + if (Found.empty()) + continue; + + if (!ProtoScope) + ProtoScope.emplace(*this, Params); + + RebuildTypeWithLateParsedAttr Rebuild(*this, PVD, Params, ParseCB, Found); + TypeSourceInfo *TSI = Rebuild.TransformType(OldTSI); + if (!TSI) { + PVD->setInvalidDecl(); + continue; + } + PVD->setTypeSourceInfo(TSI); + // A parameter's declared type is the adjusted form of its written type. + PVD->setType(Context.getAdjustedParameterType(TSI->getType())); + } +} + } // namespace clang diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index d2bb312feadc1..420735b95076f 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -401,11 +401,21 @@ enum TypeAttrLocation { TAL_DeclName }; +static void fillAttrNameLocForLateParsedAttrTypeLoc(Sema &S, + LateParsedAttrTypeLoc TL) { + if (auto *LateAttr = TL.getLateParsedAttribute()) + if (S.GetLateParsedAttributeLocationCallback) + TL.setAttrNameLoc(S.GetLateParsedAttributeLocationCallback(LateAttr)); +} + static void processTypeAttrs(TypeProcessingState &state, QualType &type, TypeAttrLocation TAL, const ParsedAttributesView &attrs, CUDAFunctionTarget CFT = CUDAFunctionTarget::HostDevice); +static void processLateTypeAttrs(TypeProcessingState &state, QualType &type, + const LateParsedAttrList &LateAttrs); + static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr, QualType &type, CUDAFunctionTarget CFT); @@ -5488,6 +5498,10 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, processTypeAttrs(state, T, TAL_DeclChunk, DeclType.getAttrs(), S.CUDA().IdentifyTarget(D.getAttributes())); + // Wrap the type in a LateParsedAttrType placeholder for any not-yet-parsed + // attribute on this chunk, to be resolved once its arguments are in scope. + processLateTypeAttrs(state, T, DeclType.LateAttrList); + if (DeclType.Kind != DeclaratorChunk::Paren) { if (ExpectNoDerefChunk && !IsNoDerefableChunk(DeclType)) S.Diag(DeclType.Loc, diag::warn_noderef_on_non_pointer_or_array); @@ -6258,6 +6272,9 @@ namespace { void VisitCountAttributedTypeLoc(CountAttributedTypeLoc TL) { // nothing } + void VisitLateParsedAttrTypeLoc(LateParsedAttrTypeLoc TL) { + fillAttrNameLocForLateParsedAttrTypeLoc(State.getSema(), TL); + } void VisitBTFTagAttributedTypeLoc(BTFTagAttributedTypeLoc TL) { // nothing } @@ -6425,6 +6442,13 @@ GetTypeSourceInfoForDeclarator(TypeProcessingState &State, break; } + case TypeLoc::LateParsedAttr: { + auto TL = CurrTL.castAs<LateParsedAttrTypeLoc>(); + fillAttrNameLocForLateParsedAttrTypeLoc(S, TL); + CurrTL = TL.getNextTypeLoc().getUnqualifiedLoc(); + break; + } + case TypeLoc::Adjusted: case TypeLoc::BTFTagAttributed: { CurrTL = CurrTL.getNextTypeLoc().getUnqualifiedLoc(); @@ -9384,6 +9408,21 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type, } } +static void processLateTypeAttrs(TypeProcessingState &state, QualType &type, + const LateParsedAttrList &LateAttrs) { + if (LateAttrs.empty()) + return; + + Sema &S = state.getSema(); + assert(S.ProcessLateParsedTypeAttrCallback && + "late-parsed type attribute without a parser callback"); + + // Every attribute has to be offered, even after one is rejected: the + // callback is what hands each one to a placeholder or destroys it. + for (auto *LA : LateAttrs) + S.ProcessLateParsedTypeAttrCallback(LA, type); +} + void Sema::completeExprArrayBound(Expr *E) { if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParens())) { if (VarDecl *Var = dyn_cast<VarDecl>(DRE->getDecl())) { >From 20b2670d11276bee74d2cdb3de80616d9f2976f7 Mon Sep 17 00:00:00 2001 From: Holo-xy <[email protected]> Date: Fri, 31 Jul 2026 19:21:32 +0300 Subject: [PATCH 6/6] [Clang] Add tests for counted_by on function parameters Cover both orderings of the annotated pointer and its count, counts written in nested function-pointer prototypes, the pointee-kind and referent rules shared with the struct-field path, and the deferred incomplete-pointee checks. The AST test pins that both orderings converge on the same CountAttributedType. Co-Authored-By: Claude Opus 5 <[email protected]> --- .../AST/attr-counted-by-function-params.c | 40 +++ .../Sema/attr-counted-by-function-params.c | 329 ++++++++++++++++++ clang/test/Sema/warn-thread-safety-analysis.c | 5 + 3 files changed, 374 insertions(+) create mode 100644 clang/test/AST/attr-counted-by-function-params.c create mode 100644 clang/test/Sema/attr-counted-by-function-params.c diff --git a/clang/test/AST/attr-counted-by-function-params.c b/clang/test/AST/attr-counted-by-function-params.c new file mode 100644 index 0000000000000..20fe3473f526c --- /dev/null +++ b/clang/test/AST/attr-counted-by-function-params.c @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fexperimental-late-parse-attributes %s -ast-dump | FileCheck %s + +#define __counted_by(f) __attribute__((counted_by(f))) +#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f))) +#define __sized_by(f) __attribute__((sized_by(f))) + +// The count parameter is declared first. The attribute is still late-parsed; +// that is decided by the attribute and the language option, not by whether the +// name is already in scope. This pins that both orderings end up with the same +// CountAttributedType. +void back_ref(int count, int *__counted_by(count) buf); +// CHECK-LABEL: FunctionDecl {{.*}} back_ref 'void (int, int * __counted_by(count))' +// CHECK-NEXT: | |-ParmVarDecl {{.*}} used count 'int' +// CHECK-NEXT: | `-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *' + +// The count parameter is declared after the annotated pointer, so the +// attribute argument can only be parsed once the whole prototype is known. +void fwd_ref(int *__counted_by(count) buf, int count); +// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref 'void (int * __counted_by(count), int)' +// CHECK-NEXT: | |-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *' +// CHECK-NEXT: | `-ParmVarDecl {{.*}} used count 'int' + +void fwd_ref_or_null(int *__counted_by_or_null(count) buf, int count); +// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref_or_null 'void (int * __counted_by_or_null(count), int)' + +void fwd_ref_sized(void *__sized_by(count) buf, int count); +// CHECK-LABEL: FunctionDecl {{.*}} fwd_ref_sized 'void (void * __sized_by(count), int)' + +// Two parameters referring to the same count. +void two_buffers(int *__counted_by(count) a, int *__counted_by(count) b, + int count); +// CHECK-LABEL: FunctionDecl {{.*}} two_buffers 'void (int * __counted_by(count), int * __counted_by(count), int)' + +// The count expression resolves to the ParmVarDecl of this function, not to +// the global of the same name. +int count; +void shadowed(int *__counted_by(count) buf, int count); +// CHECK-LABEL: FunctionDecl {{.*}} shadowed 'void (int * __counted_by(count), int)' +// CHECK-NEXT: |-ParmVarDecl {{.*}} buf 'int * __counted_by(count)':'int *' +// CHECK-NEXT: `-ParmVarDecl {{.*}} used count 'int' diff --git a/clang/test/Sema/attr-counted-by-function-params.c b/clang/test/Sema/attr-counted-by-function-params.c new file mode 100644 index 0000000000000..8e7d95fa4f9ae --- /dev/null +++ b/clang/test/Sema/attr-counted-by-function-params.c @@ -0,0 +1,329 @@ +// RUN: %clang_cc1 -fexperimental-late-parse-attributes -fblocks -Wpointer-arith -fsyntax-only -verify %s + +#define __counted_by(f) __attribute__((counted_by(f))) +#define __counted_by_or_null(f) __attribute__((counted_by_or_null(f))) +#define __sized_by(f) __attribute__((sized_by(f))) +#define __sized_by_or_null(f) __attribute__((sized_by_or_null(f))) + +struct size_unknown; +struct size_known { + int field; +}; + +//============================================================================== +// Valid: the count parameter is declared before the annotated pointer +//============================================================================== + +void back_ref(int count, int *__counted_by(count) buf); +void back_ref_or_null(int count, int *__counted_by_or_null(count) buf); +void back_ref_sized(int count, void *__sized_by(count) buf); +void back_ref_sized_or_null(int count, void *__sized_by_or_null(count) buf); + +//============================================================================== +// Valid: the count parameter is declared *after* the annotated pointer +//============================================================================== + +// This is the case that requires late parsing: at the point the attribute is +// written, `count` is not yet in scope. + +void fwd_ref(int *__counted_by(count) buf, int count); +void fwd_ref_or_null(int *__counted_by_or_null(count) buf, int count); +void fwd_ref_sized(void *__sized_by(count) buf, int count); + +// The annotated pointer and the count may be separated by other parameters. +void fwd_ref_interleaved(int *__counted_by(count) buf, int other, int count); + +// Several parameters may refer to the same count. +void two_buffers(int *__counted_by(count) a, int *__counted_by(count) b, + int count); + +// A pointer may be annotated with a count declared before it and another +// pointer in the same prototype with a count declared after it. +void mixed(int first, int *__counted_by(first) a, int *__counted_by(second) b, + int second); + +// Incomplete pointee types are allowed, as for struct fields. +void incomplete_pointee(struct size_unknown *__counted_by(count) buf, + int count); +void const_pointee(const struct size_known *__counted_by(count) buf, int count); + +// The attribute binds to the pointer formed by the `*` immediately preceding +// it, so here it applies to the parameter itself, which is a pointer. +void outer_of_two(int **__counted_by(count) buf, int count); + +// Qualifiers on the annotated pointer itself do not disturb the attribute. +void qualified_ptr(int *__counted_by(count) const buf, int count); + +// A definition, not just a prototype. +int sum(int *__counted_by(count) buf, int count) { + int total = 0; + for (int i = 0; i < count; ++i) + total += buf[i]; + return total; +} + +//============================================================================== +// Invalid: the argument does not name a parameter of this function +//============================================================================== + +int global_count; + +// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter of the same function as the annotated pointer}} +void not_a_param(int *__counted_by(global_count) buf); + +// expected-error@+1{{use of undeclared identifier 'nope'}} +void undeclared(int *__counted_by(nope) buf); + +// A nested function-pointer parameter has its own parameter list. Its count +// must come from that list, not from the enclosing prototype, even though the +// enclosing prototype's parameters are in scope here. +// expected-error@+1{{'counted_by' argument 'n' is not a parameter of the same function as the annotated pointer}} +void outer_param_leak(int n, void (*cb)(int *__counted_by(n) p)); + +// The same shape, but referring to the inner prototype's own parameter, is OK. +void inner_param_ok(void (*cb)(int *__counted_by(m) p, int m)); + +// A global declared *after* the prototype is not visible either: the tokens are +// parsed at the end of the parameter clause, not at the end of the translation +// unit, so this fails in lookup rather than at the parameter check above. +// expected-error@+1{{use of undeclared identifier 'later_global'}} +void global_declared_later(int *__counted_by(later_global) buf); +int later_global; + +//============================================================================== +// Invalid: the argument must be a simple declaration reference +//============================================================================== + +// expected-error@+1{{'counted_by' argument must be a simple declaration reference}} +void not_simple_ref(int *__counted_by(count + 1) buf, int count); + +// expected-error@+1{{'counted_by' argument must be a simple declaration reference}} +void not_simple_ref_deref(int *__counted_by(*count) buf, int *count); + +// A parenthesized reference is rejected too, matching the struct-field path: +// the count expression itself must be the declaration reference. +// expected-error@+1{{'counted_by' argument must be a simple declaration reference}} +void not_simple_ref_paren(int *__counted_by((count)) buf, int count); + +// A constant count is meaningful under -fbounds-safety but not supported +// upstream, where the argument must name a parameter. +// expected-error@+1{{'counted_by' argument must be a simple declaration reference}} +void count_is_literal(int *__counted_by(4) buf); + +//============================================================================== +// Invalid: the count parameter must have a non-boolean integer type +//============================================================================== + +// expected-error@+1{{'counted_by' requires a non-boolean integer type argument}} +void count_is_ptr(int *__counted_by(count) buf, int *count); + +// expected-error@+1{{'counted_by' requires a non-boolean integer type argument}} +void count_is_float(int *__counted_by(count) buf, float count); + +// expected-error@+1{{'counted_by' requires a non-boolean integer type argument}} +void count_is_bool(int *__counted_by(count) buf, _Bool count); + +//============================================================================== +// Attributes in the declaration-specifiers are not part of this mechanism +//============================================================================== + +// They are still parsed eagerly and handled as declaration attributes. Both +// diagnostics below are pre-existing behavior, recorded here so a future change +// to the decl-attribute path is noticed. + +// Parsed eagerly, so `count` is not yet in scope. +// expected-error@+1{{use of undeclared identifier 'count'}} +void on_int(int __counted_by(count) buf, int count); + +// With `count` already in scope, it reaches the declaration-attribute path, +// whose subject list only permits struct fields. +// expected-error@+1{{'counted_by' attribute only applies to non-static data members}} +void on_int_count_first(int count, int __counted_by(count) buf); + +//============================================================================== +// Invalid: nested pointers are not supported for parameters +//============================================================================== + +// Bounds on a pointer that is not the parameter itself are neither checked nor +// updated by anything, so reject rather than silently accept. + +// expected-error@+1{{'counted_by' is not supported on a nested pointer; it must apply to the annotated declaration's own type}} +void nested(int *__counted_by(count) *buf, int count); + +// expected-error@+1{{'sized_by' is not supported on a nested pointer; it must apply to the annotated declaration's own type}} +void nested_sized(void *__sized_by(count) *buf, int count); + +// expected-error@+1{{'counted_by_or_null' is not supported on a nested pointer; it must apply to the annotated declaration's own type}} +void nested_or_null(int *__counted_by_or_null(count) *buf, int count); + +// An _Atomic qualifier on the outer pointer becomes a real AtomicType node +// above the placeholder, not sugar; the placeholder walk must step through it +// or the attribute would escape resolution entirely. +// expected-error@+1{{'counted_by' is not supported on a nested pointer; it must apply to the annotated declaration's own type}} +void nested_atomic(int *__counted_by(count) * _Atomic buf, int count); + +//============================================================================== +// Valid: the count names a nested function-pointer prototype's own parameters +//============================================================================== + +// A count written inside a function-pointer parameter's own prototype names +// that prototype's parameters rather than this function's. Both the parameters +// and the return type of the inner prototype may be annotated this way. + +void inner_return(int *__counted_by(len) (*cb)(int len), int other); +void inner_return_sized(void *__sized_by(len) (*cb)(int len)); +void inner_param_and_return(int *__counted_by(len) (*cb)(int *__counted_by(len) p, + int len)); + +// A parameter spelled as a function rather than a function pointer behaves the +// same, since it adjusts to a function pointer. +void inner_return_fn(int *__counted_by(len) cb(int len)); + +// The inner prototype's parameter shadows an outer name of the same name, and +// the inner one wins. +void inner_shadows_outer(int len, int *__counted_by(len) (*cb)(int len)); + +// A parameter shadowing a *global* of the same name binds to the parameter. The +// parameter list alone could not produce this: it can reject what lookup found, +// but only a scope can make lookup prefer the parameter over the file-scope +// declaration. The second one needs that scope to be *re-entered*, since the +// inner prototype's own scope is long gone by the time its count is parsed. +// Accepting these is what proves the count bound to the parameter; had it +// bound to `global_count`, the parameter check would have rejected it, exactly +// as it does for `not_a_param` above. +void shadows_global(int *__counted_by(global_count) buf, int global_count); +void inner_shadows_global(void (*cb)(int *__counted_by(global_count) p, + int global_count)); + +// ... and without a parameter to shadow it, the same spelling in a nested +// prototype still reaches the global and is still rejected. +// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter of the same function as the annotated pointer}} +void inner_reaches_global(void (*cb)(int *__counted_by(global_count) p)); + +//============================================================================== +// Invalid: a count in the return type of a *no-prototype* inner function +//============================================================================== + +// `()` is a FunctionNoProtoType in C, not a zero-parameter FunctionProtoType, +// so it declares no parameters at all and no count written in its return type +// can name one. +// +// The enclosing prototype's parameters are in scope here, so lookup does find +// them; what makes these ill-formed is that they do not belong to the inner +// function, whose callers cannot see them. + +// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the same function as the annotated pointer}} +void noproto_reaches_outer_param(int *__counted_by(len) (*cb)(), int len); + +// expected-error@+1{{'sized_by' argument 'len' is not a parameter of the same function as the annotated pointer}} +void noproto_reaches_outer_param_sized(void *__sized_by(len) (*cb)(), int len); + +// The same shape spelled as a function rather than a function pointer. +// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the same function as the annotated pointer}} +void noproto_fn_reaches_outer_param(int *__counted_by(len) cb(), int len); + +// A no-prototype inner function reaching a global is rejected too, though that +// falls out of the count not naming a parameter at all rather than of the +// no-prototype handling. +// expected-error@+1{{'counted_by' argument 'global_count' is not a parameter of the same function as the annotated pointer}} +void noproto_reaches_global(int *__counted_by(global_count) (*cb)()); + +// `(void)` *is* a zero-parameter FunctionProtoType, so it takes the prototype +// path. Kept alongside the cases above so a regression that conflates the two +// spellings shows up as a diagnostic appearing on only one of them. +// expected-error@+1{{'counted_by' argument 'len' is not a parameter of the same function as the annotated pointer}} +void protovoid_reaches_outer_param(int *__counted_by(len) (*cb)(void), int len); + +//============================================================================== +// Invalid: the attribute is not on any declaration's own type +//============================================================================== + +// These positions have to be rejected rather than ignored: the placeholder the +// parser leaves behind must never reach the finalized AST, which asserts on it +// when the AST is serialized. + +// The attribute applies to the array's element type, not to the parameter. +// expected-error@+1{{'counted_by' is not supported on an array element; it must apply to the annotated declaration's own type}} +void on_array_element(int *__counted_by(count) buf[10], int count); + +// A function-pointer parameter's return type may only name that prototype's own +// parameters, and this prototype has none. +// expected-error@+1{{'counted_by' argument 'count' is not a parameter of the same function as the annotated pointer}} +void on_fn_ptr_return(int *__counted_by(count) (*cb)(void), int count); + +// The same, spelled as a function parameter rather than a function pointer. +// expected-error@+1{{'counted_by' argument 'count' is not a parameter of the same function as the annotated pointer}} +void on_fn_return(int *__counted_by(count) cb(void), int count); + +// A prototype that is already invalid must not leave a placeholder behind for +// the enclosing prototype to trip over. +// expected-error@+1{{'counted_by' argument 'n' is not a parameter of the same function as the annotated pointer}} +void outer_then_nested(int n, void (*cb)(int *__counted_by(n) p)); +// expected-error@+1{{'counted_by' is not supported on a nested pointer; it must apply to the annotated declaration's own type}} +void after_invalid_nested(int *__counted_by(count) *buf, int count); + +//============================================================================== +// Invalid: the annotated type is not a pointer at all. +//============================================================================== + +// A block pointer is written with the same declarator syntax as a pointer, and +// so collects late-parsed attributes the same way, but it is not a pointer +// type. Rejecting it has to happen on the late-parsed path too: if the +// attribute were simply dropped there, turning the flag on would make clang +// accept what it otherwise diagnoses. +// expected-error@+1{{'counted_by' only applies to pointers or C99 flexible array members}} +void on_block_pointer(int (^__counted_by(count) blk)(void), int count); + +// An _Atomic pointer is an AtomicType, not a pointer type, so no placeholder +// is created for it in the first place. +// expected-error@+1{{'counted_by' only applies to pointers or C99 flexible array members}} +void on_atomic_pointer(int *__counted_by(count) _Atomic p, int count); + +//============================================================================== +// Invalid: the pointee type cannot support bounds computation +//============================================================================== + +// Parameters share the struct-field path's pointee rules, so both accept and +// reject the same shapes. + +struct fam { int count; int elems[]; }; + +// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'void (int)' is a function type}} +void fn_pointee(void (*__counted_by(count) fp)(int), int count); + +// expected-error@+1{{'sized_by' cannot be applied to a pointer with pointee of unknown size because 'void (int)' is a function type}} +void fn_pointee_sized(void (*__sized_by(count) fp)(int), int count); + +// expected-error@+1{{'counted_by' cannot be applied to a pointer with pointee of unknown size because 'struct fam' is a struct type with a flexible array member}} +void fam_pointee(struct fam *__counted_by(count) p, int count); + +// counted_by on a pointer to void counts bytes, like sized_by; a GNU +// extension, warned on exactly as for a struct field. +// expected-warning@+2{{'counted_by' on a pointer to void is a GNU extension, treated as 'sized_by'}} +// expected-note@+1{{use '__sized_by' to suppress this warning}} +void void_pointee(void *__counted_by(count) p, int count); + +//============================================================================== +// The deferred incomplete-pointee model applies to parameters as to fields +//============================================================================== + +// The attribute is accepted while the pointee may still be completed later, and +// each use or assignment then requires the complete type. + +// expected-note@+1 2{{consider providing a complete definition for 'struct deferred_pointee'}} +struct deferred_pointee; + +void take_deferred(struct deferred_pointee *q); + +// expected-note@+1{{consider using '__sized_by' instead of '__counted_by'}} +void deferred_use(struct deferred_pointee *__counted_by(count) p, int count) { + // expected-error@+1{{cannot use 'p' with '__counted_by' attributed type 'struct deferred_pointee * __counted_by(count)' (aka 'struct deferred_pointee *') because the pointee type 'struct deferred_pointee' is incomplete}} + take_deferred(p); +} + +// expected-note@+1{{consider using '__sized_by' instead of '__counted_by'}} +void deferred_assign(struct deferred_pointee *__counted_by(count) p, int count, + struct deferred_pointee *q) { + // expected-error@+1{{cannot assign to 'p' with '__counted_by' attributed type 'struct deferred_pointee * __counted_by(count)' (aka 'struct deferred_pointee *') because the pointee type 'struct deferred_pointee' is incomplete}} + p = q; +} diff --git a/clang/test/Sema/warn-thread-safety-analysis.c b/clang/test/Sema/warn-thread-safety-analysis.c index a0e9e7ce724cc..e980c3049abbd 100644 --- a/clang/test/Sema/warn-thread-safety-analysis.c +++ b/clang/test/Sema/warn-thread-safety-analysis.c @@ -343,6 +343,11 @@ void test_bdev_ops_fail(struct BDevOps *ops, struct BDev *bdev) { // attribute in a single __attribute__. void run(void) __attribute__((guarded_by(mu1), guarded_by(mu1))); // expected-warning 2{{only applies to non-static data members and global variables}} +// Under late parsing, a guarded_by written in the type-qualifier position of +// a function parameter used to be captured by the type-attr-only late list +// and silently dropped, losing the warning below. +void misplaced_in_param(int *__attribute__((guarded_by(mu1))) p); // expected-warning {{only applies to non-static data members and global variables}} + int value_with_multiple_guarded_args GUARDED_BY(mu1, mu2); int *ptr_with_multiple_guarded_args PT_GUARDED_BY(mu1, mu2); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
