Author: Mimis Chlympatsos Date: 2026-08-05T13:55:52Z New Revision: fa8a574ef22ad6571507ea2a5aa5b358f4ba9556
URL: https://github.com/llvm/llvm-project/commit/fa8a574ef22ad6571507ea2a5aa5b358f4ba9556 DIFF: https://github.com/llvm/llvm-project/commit/fa8a574ef22ad6571507ea2a5aa5b358f4ba9556.diff LOG: [clang][Sema] Handle alloc_align on all HasFunctionProto declarations (#210871) Fixes #122058. ## Overview Attribute `alloc_align`'s TableGen subject accepts any declaration satisfying `HasFunctionProto`, but `AddAllocAlignAttr` unconditionally casts the declaration to `FunctionDecl` (in `Sema::AddAllocAlignAttr()`). Since there exist `Decl`'s that have an underlying `FunctionProtoType` but are not `FunctionDecl` (e.g. function pointer variables and parameters), the unconditional `cast<FunctionDecl>` is too narrow and leads to a crash for `Decl`s that are meant to be compatible with the `alloc_align` attribute. For example, trying to compile `C` file ``` void *(*allocator)(unsigned long long) __attribute__((alloc_align(1))); ``` with ``` > clang example.c -fsyntax-only ``` (with assertions enabled in the build) crashes with ``` Assertion failed: (isa<To>(Val) && "cast<Ty>() argument of incompatible type!"), function cast, file Casting.h, line 572. PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and dumped files. ... ``` I remove the over-restrictive `cast<FunctionDecl>`, extend parameter-range lookup through `TypeSourceInfo` for pointer-like function declarations, and add clang lit tests for function pointers, member pointers, references, blocks, qualified pointers, and Objective-C methods. ## Solution The TableGen definition of the `alloc_align` attribute ```cpp def AllocAlign : InheritableAttr { ... let Subjects = SubjectList<[HasFunctionProto]>; ... } ``` from `clang/include/clang/Basic/Attr.td` guarantees that any `Decl` reaching the function ``` void Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, Expr *ParamExpr) {...} ``` in `clang/lib/Sema/SemaDeclAttr.cpp` has an underlying `QualType` that is a `FunctionProtoType` (and thus a valid candidate for the `alloc_align` attribute). Note that the `FunctionProtoType` may not be the `QualType` of the `Decl` instead, but rather wrapped inside pointer, reference, and so on. Therefore, we can completely drop the `cast<FunctionDecl>(D)` from `Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, Expr *ParamExpr)`. However, this introduced a subtle problem. In `Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, Expr *ParamExpr)`, after `Decl D` has been confirmed to have a `FunctionType` with return type `PointerType`, there is logic to check that the relevant parameter `ParamExpr` is valid as an input to `__attribute__((alloc_align(N)))`, and if it is not, we emit diagnostic: ``` Diag(ParamExpr->getBeginLoc(), diag::err_attribute_integers_only) << CI << getFunctionOrMethodParamRange(D, Idx.getASTIndex()); ``` The function `getFunctionOrMethodParamRange(const Decl *D, unsigned Idx)` from `clang/include/clang/Sema/Attr.h` calls `getFunctionOrMethodParam(const Decl *D, unsigned Idx)` (from the same file), which only handles declaration types that directly own a parameter list, specifically `FunctionDecl`, `ObjCMethodDecl`, and `BlockDecl`. But `getFunctionOrMethodParam` may be reached by `Decl`'s that have an underlying function (more precisely `hasFunctionProto(decl)` is true) but are not one of the three currently handled (and we do want to handle them, they are valid cases). Thus, I also modify `getFunctionOrMethodParam()` to (if we are not dealing with a `FunctionDecl/ObjCMethodDecl/BlockDecl`) use the TypeSourceInfo of the `Decl` to get a `FunctionProtoTypeLoc` for the underlying function, which in turn gives us access to the parameter declarations. More specifically, the process is: 1. Obtain the declaration's `TypeSourceInfo`. 2. Start from its unqualified `TypeLoc`. 3. Unwrap a pointer, member pointer, reference, or block pointer. 4. Find the underlying `FunctionProtoTypeLoc`. 5. Retrieve the indexed `ParmVarDecl`. The fallback logic is best-effort and can recover parameter declarations when the function prototype exists in the declaration's own `TypeSourceInfo`. It can handle: - ordinary function-pointer declarators at file scope, local scope, in fields, or as parameters, including top-level-qualified pointers; - member-function pointers (e.g. `int (someclass::*memberfunc)(...) = ...;` in cpp) - references to functions (e.g. `int (&ref)(int, int)` in cpp) - block-pointer declarators (`int (^block)(int);` in objective) - typedef and type-alias declarations that directly have the function prototype (e.g. `typedef void *(*f)(int);` in cpp) <!--for recovery `FieldDecls` (seen for example in including the glibc header in the reproducer from the issue), and for valid declarations such as func pointers and Objective C methods.--> ## Testing Added clang lit tests for: - a valid file-scope function-pointer declaration and the recovery `FieldDecl` from #122058, to check that they dont crash - parameter validation for function-pointer and member-function-pointer declarations (checking both valid integral and invalid non-integral cases) - valid and invalid parameter types on Objective-C methods - diagnostic source ranges for filescope function pointers, member-function pointers, function references, and block pointers. *** AI note: Used gpt-5.6-luna to help generate `CHECK:`'s in the new clang lit tests (giving it the expected output for example what source code should be underlined, to generate the `{[[@LINE-...]]...` syntax). Also used it to better understand the hierarchy and relationship between `*Loc` classes and the interface they expose. --------- Co-authored-by: Mimis Chlympatsos <[email protected]> Co-authored-by: Aaron Ballman <[email protected]> Co-authored-by: Mimis Chlympatsos <[email protected]> Added: clang/test/Misc/attr-source-range.m clang/test/SemaObjC/alloc-align-attr.m Modified: clang/docs/ReleaseNotes.md clang/include/clang/Basic/AttrDocs.td clang/include/clang/Sema/Attr.h clang/lib/Sema/SemaDeclAttr.cpp clang/test/Misc/attr-source-range.cpp clang/test/Sema/alloc-align-attr.c clang/test/SemaCXX/alloc-align-attr.cpp Removed: clang/test/CodeGen/xfail-alloc-align-fn-pointers.cpp ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 66346bf40193f..ac27b1fc74501 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -377,6 +377,8 @@ features cannot lower the translation-unit ABI level; #### Bug Fixes to Attribute Support +- Fixed crash (assertion) when the `alloc_align` attribute was applied to a declaration whose type has a `FunctionProtoType` but which is not itself a `FunctionDecl`, such as a function-pointer variable. (#GH122058) + - The `counted_by`/`counted_by_or_null` diagnostic that rejects a pointer whose pointee is a struct with a flexible array member (e.g. ``struct with_fam * __sized_by(size) ptr;``) was incorrectly also applied to diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 65ccc83aac33f..3b469437d21e4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1205,10 +1205,15 @@ code. See the documentation for `__declspec(code_seg)`_ on MSDN. def AllocAlignDocs : Documentation { let Category = DocCatFunction; let Content = [{ -Use ``__attribute__((alloc_align(<alignment>))`` on a function -declaration to specify that the return value of the function (which must be a -pointer type) is at least as aligned as the value of the indicated parameter. The -parameter is given by its index in the list of formal parameters; the first +Use ``__attribute__((alloc_align(<parameter-index>)))`` on a declaration with a +function prototype to specify that the prototype's return value (which must be a +pointer type) is at least as aligned as the value of the indicated parameter. +This includes functions, Objective-C methods, blocks, and declarations of +function pointer, member function pointer, function reference, and block pointer +types. The attribute can also be applied to typedef or type alias declarations +whose underlying type has a function prototype. + +The parameter is given by its index in the list of formal parameters; the first parameter has index 1 unless the function is a C++ non-static member function, in which case the first parameter has index 2 to account for the implicit ``this`` parameter. @@ -1218,6 +1223,10 @@ parameter. // The returned pointer has the alignment specified by the first parameter. void *a(size_t align) __attribute__((alloc_align(1))); + // The function pointer's returned pointer has the alignment specified by + // the first parameter of the pointed-to function. + void *(*allocator)(size_t align) __attribute__((alloc_align(1))); + // The returned pointer has the alignment specified by the second parameter. void *b(void *v, size_t align) __attribute__((alloc_align(2))); diff --git a/clang/include/clang/Sema/Attr.h b/clang/include/clang/Sema/Attr.h index 5836231818eec..eb24067d35668 100644 --- a/clang/include/clang/Sema/Attr.h +++ b/clang/include/clang/Sema/Attr.h @@ -19,6 +19,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Type.h" +#include "clang/AST/TypeLoc.h" #include "clang/Basic/AttributeCommonInfo.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/SourceLocation.h" @@ -69,6 +70,10 @@ inline unsigned getFunctionOrMethodNumParams(const Decl *D) { return cast<ObjCMethodDecl>(D)->param_size(); } +/// getFunctionOrMethodParam - Return parameter declaration for the given index +/// of the passed Decl, which must have a FunctionProtoType. When the Decl is +/// not a FunctionDecl/ObjCMethodDecl/BlockDecl, do best effort to find +/// underlying FunctionProtoType using the Decl's TypeSourceInfo. inline const ParmVarDecl *getFunctionOrMethodParam(const Decl *D, unsigned Idx) { if (const auto *FD = dyn_cast<FunctionDecl>(D)) @@ -77,6 +82,33 @@ inline const ParmVarDecl *getFunctionOrMethodParam(const Decl *D, return MD->getParamDecl(Idx); if (const auto *BD = dyn_cast<BlockDecl>(D)) return BD->getParamDecl(Idx); + + // Handle declarations that do not directly own parameters but have an + // underlying FunctionProtoType (e.g. function pointers). + const TypeSourceInfo *TSI = nullptr; + if (const auto *DD = dyn_cast<DeclaratorDecl>(D)) + TSI = DD->getTypeSourceInfo(); + else if (const auto *TD = dyn_cast<TypedefNameDecl>(D)) + TSI = TD->getTypeSourceInfo(); + + if (!TSI) + return nullptr; + + TypeLoc TL = TSI->getTypeLoc().getUnqualifiedLoc(); + + if (auto PTL = TL.getAsAdjusted<PointerTypeLoc>()) + TL = PTL.getPointeeLoc(); + else if (auto MPTL = TL.getAsAdjusted<MemberPointerTypeLoc>()) + TL = MPTL.getPointeeLoc(); + else if (auto RTL = TL.getAsAdjusted<ReferenceTypeLoc>()) + TL = RTL.getPointeeLoc(); + else if (auto BPTL = TL.getAsAdjusted<BlockPointerTypeLoc>()) + TL = BPTL.getPointeeLoc(); + + if (auto FPTL = TL.getAsAdjusted<FunctionProtoTypeLoc>()) + if (Idx < FPTL.getNumParams()) + return FPTL.getParam(Idx); + return nullptr; } diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 690821a8e9ef9..0645f99492433 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1512,8 +1512,7 @@ void Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, } ParamIdx Idx; - const auto *FuncDecl = cast<FunctionDecl>(D); - if (!checkFunctionOrMethodParameterIndex(FuncDecl, CI, + if (!checkFunctionOrMethodParameterIndex(D, CI, /*AttrArgNum=*/1, ParamExpr, Idx)) return; @@ -1521,7 +1520,7 @@ void Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, if (!Ty->isDependentType() && !Ty->isIntegralType(Context) && !Ty->isAlignValT()) { Diag(ParamExpr->getBeginLoc(), diag::err_attribute_integers_only) - << CI << FuncDecl->getParamDecl(Idx.getASTIndex())->getSourceRange(); + << CI << getFunctionOrMethodParamRange(D, Idx.getASTIndex()); return; } diff --git a/clang/test/CodeGen/xfail-alloc-align-fn-pointers.cpp b/clang/test/CodeGen/xfail-alloc-align-fn-pointers.cpp deleted file mode 100644 index 80067500284b1..0000000000000 --- a/clang/test/CodeGen/xfail-alloc-align-fn-pointers.cpp +++ /dev/null @@ -1,10 +0,0 @@ - -// RUN: %clang_cc1 %s - -// FIXME: These should not crash! -// XFAIL: * - -void aa_fn_ptr(char* (*member)(char*) __attribute__((alloc_align(1)))); - -struct Test; -void aa_member_fn_ptr(char* (Test::*member)(char*) __attribute__((alloc_align(1)))); diff --git a/clang/test/Misc/attr-source-range.cpp b/clang/test/Misc/attr-source-range.cpp index d5540ad64fa56..e648a1baa0940 100644 --- a/clang/test/Misc/attr-source-range.cpp +++ b/clang/test/Misc/attr-source-range.cpp @@ -1,4 +1,4 @@ -// RUN: not %clang_cc1 -fsyntax-only -fdiagnostics-print-source-range-info %s 2>&1 | FileCheck %s +// RUN: not %clang_cc1 -fblocks -fsyntax-only -fdiagnostics-print-source-range-info %s 2>&1 | FileCheck %s void f(int i) __attribute__((format_arg(1))); // CHECK: attr-source-range.cpp:3:30:{3:41-3:42}{3:8-3:13} @@ -14,3 +14,49 @@ void i(int j) __attribute__((nonnull(1))); void j(__attribute__((nonnull)) int i); // CHECK: attr-source-range.cpp:15:23:{15:8-15:38} + +void alloc_align_function_pointer( + char *(*fn)(char *) __attribute__((alloc_align(1)))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:52:{[[@LINE-1]]:17-[[@LINE-1]]:23}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +// A top-level qualifier wraps the PointerTypeLoc in a QualifiedTypeLoc. Make +// sure parameter lookup strips that wrapper before finding FunctionProtoTypeLoc. +void alloc_align_qualified_function_pointer( + char *(*const fn)(char *) __attribute__((alloc_align(1)))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:58:{[[@LINE-1]]:23-[[@LINE-1]]:29}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +struct S; +void alloc_align_member_function_pointer( + char *(S::*fn)(char *) __attribute__((alloc_align(1)))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:55:{[[@LINE-1]]:20-[[@LINE-1]]:26}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +void alloc_align_function_reference( + char *(&fn)(char *) __attribute__((alloc_align(1)))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:52:{[[@LINE-1]]:17-[[@LINE-1]]:23}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +void alloc_align_block_pointer( + char *(^fn)(char *) __attribute__((alloc_align(1)))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:52:{[[@LINE-1]]:17-[[@LINE-1]]:23}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +char *reference_target(char *); +char *(&alloc_align_function_reference_variable)(char *) + __attribute__((alloc_align(1))) = reference_target; +// CHECK: attr-source-range.cpp:[[@LINE-1]]:32:{[[@LINE-2]]:50-[[@LINE-2]]:56}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +struct MemberPointerField { + char *(S::*fn)(char *) __attribute__((alloc_align(1))); +}; +// CHECK: attr-source-range.cpp:[[@LINE-2]]:53:{[[@LINE-2]]:18-[[@LINE-2]]:24}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +struct StaticDataMember { + static char *(*fn)(char *) __attribute__((alloc_align(1))); +}; +// CHECK: attr-source-range.cpp:[[@LINE-2]]:57:{[[@LINE-2]]:22-[[@LINE-2]]:28}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +// type alias (TypedefNameDecl) +using alloc_align_alias __attribute__((alloc_align(1))) = char *(*)(char *); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:52:{[[@LINE-1]]:69-[[@LINE-1]]:75}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +// typedef of a function type +typedef char *alloc_align_function_t(char *) __attribute__((alloc_align(1))); +// CHECK: attr-source-range.cpp:[[@LINE-1]]:73:{[[@LINE-1]]:38-[[@LINE-1]]:44}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type diff --git a/clang/test/Misc/attr-source-range.m b/clang/test/Misc/attr-source-range.m new file mode 100644 index 0000000000000..db61699559014 --- /dev/null +++ b/clang/test/Misc/attr-source-range.m @@ -0,0 +1,12 @@ +// RUN: not %clang_cc1 -fsyntax-only -fdiagnostics-print-source-range-info %s 2>&1 | FileCheck %s +// Coverage for attributes attached to some ObjCMethodDecl + +@interface AllocAlignMethods + +- (char *)allocate:(char *)alignment __attribute__((alloc_align(1))); +// CHECK: attr-source-range.m:[[@LINE-1]]:65:{[[@LINE-1]]:21-[[@LINE-1]]:37}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type + +- (char *)allocate:(unsigned long)alignment context:(char *)context + __attribute__((alloc_align(2))); +// CHECK: attr-source-range.m:[[@LINE-1]]:32:{[[@LINE-2]]:54-[[@LINE-2]]:68}: error: 'alloc_align' attribute argument may only refer to a function parameter of integer type +@end diff --git a/clang/test/Sema/alloc-align-attr.c b/clang/test/Sema/alloc-align-attr.c index 377c4387814d1..ac1df240d2c72 100644 --- a/clang/test/Sema/alloc-align-attr.c +++ b/clang/test/Sema/alloc-align-attr.c @@ -3,8 +3,13 @@ // return values void test_void_alloc_align(void) __attribute__((alloc_align(1))); // expected-warning {{'alloc_align' attribute only applies to return values that are pointers}} void *test_ptr_alloc_align(unsigned long long a) __attribute__((alloc_align(1))); // no-warning +void *(*test_fn_ptr_alloc_align)(unsigned long long) __attribute__((alloc_align(1))); // no-warning int j __attribute__((alloc_align(1))); // expected-warning {{'alloc_align' attribute only applies to non-K&R-style functions}} +// GH122058 +struct InvalidFunctionField { + void *f(unsigned long long) __attribute__((alloc_align(1))); // expected-error {{field 'f' declared as a function}} +}; void *test_no_params_zero(void) __attribute__((alloc_align(0))); // expected-error {{'alloc_align' attribute parameter 1 is out of bounds}} void *test_no_params(void) __attribute__((alloc_align(1))); // expected-error {{'alloc_align' attribute parameter 1 is out of bounds}} void *test_incorrect_param_type(float a) __attribute__((alloc_align(1))); // expected-error {{'alloc_align' attribute argument may only refer to a function parameter of integer type}} diff --git a/clang/test/SemaCXX/alloc-align-attr.cpp b/clang/test/SemaCXX/alloc-align-attr.cpp index ccc75369b8cfb..3355597c3e8f2 100644 --- a/clang/test/SemaCXX/alloc-align-attr.cpp +++ b/clang/test/SemaCXX/alloc-align-attr.cpp @@ -59,3 +59,11 @@ void foo() { f<int>(0); // expected-note {{in instantiation of function template specialization 'GH26612::f<int>' requested here}} } } // namespace GH26612 + +void test_function_pointer( + char *(*member)(char *) __attribute__((alloc_align(1)))); // expected-error {{'alloc_align' attribute argument may only refer to a function parameter of integer type}} + char *(*another_member)(int) __attribute__((alloc_align(1))); // ok + +struct Test; +void test_member_function_pointer( + char *(Test::*member)(char *) __attribute__((alloc_align(1)))); // expected-error {{'alloc_align' attribute argument may only refer to a function parameter of integer type}} diff --git a/clang/test/SemaObjC/alloc-align-attr.m b/clang/test/SemaObjC/alloc-align-attr.m new file mode 100644 index 0000000000000..9857c855ea9de --- /dev/null +++ b/clang/test/SemaObjC/alloc-align-attr.m @@ -0,0 +1,8 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +@interface AllocAlignMethod +- (void *)allocate:(unsigned long)alignment + __attribute__((alloc_align(1))); +- (void *)allocateInvalid:(float)alignment + __attribute__((alloc_align(1))); // expected-error {{'alloc_align' attribute argument may only refer to a function parameter of integer type}} +@end _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
