[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 <mimischly@MIMIS-MAC-120.local>
Co-authored-by: Aaron Ballman <aaron@aaronballman.com>
Co-authored-by: Mimis Chlympatsos <mimischly@gmail.com>