llvm-project
fa8a574e - [clang][Sema] Handle alloc_align on all HasFunctionProto declarations (#210871)

Commit
10 days ago
[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>
Author
Parents
Loading