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

Reply via email to