[clang] Ignore GCC 11 [[malloc(x)]] attribute

Ignore the `[[malloc(x)]]` or `[[malloc(x, 1)]]` function attribute
syntax added in [GCC 11][1] and print a warning instead of an error.

Unlike `[[malloc]]` with no arguments (which is supported by Clang),
GCC uses the one or two argument form to specify a deallocator for
GCC's static analyzer.

Code currently compiled with `[[malloc(x)]]` or
`__attribute((malloc(x)))` fails with the following error:
`'malloc' attribute takes no arguments`.

[1]: https://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;f=gcc/doc/extend.texi;h=dce6c58db87ebf7f4477bd3126228e73e4eeee97#patch6

Fixes: https://github.com/llvm/llvm-project/issues/51607
Partial-Bug: https://github.com/llvm/llvm-project/issues/53152
This commit is contained in:
Alois Klink 2023-10-02 19:59:06 +01:00 committed by erichkeane
parent b2aba39001
commit 79a28aa0a4
9 changed files with 143 additions and 9 deletions

View File

@ -233,6 +233,11 @@ Bug Fixes to Attribute Support
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fixed crash when a parameter to the ``clang::annotate`` attribute evaluates to ``void``. See #GH119125
- Clang now emits a warning instead of an error when using the one or two
argument form of GCC 11's ``__attribute__((malloc(deallocator)))``
or ``__attribute__((malloc(deallocator, ptr-index)))``
(`#51607 <https://github.com/llvm/llvm-project/issues/51607>`_).
Bug Fixes to C++ Support
^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -1904,6 +1904,8 @@ def IFunc : Attr, TargetSpecificAttr<TargetIFuncSupport> {
def Restrict : InheritableAttr {
let Spellings = [Declspec<"restrict">, GCC<"malloc">];
let Args = [ExprArgument<"Deallocator", /*opt=*/ 1>,
ParamIdxArgument<"DeallocatorPtrArgIndex", /*opt=*/ 1>];
let Subjects = SubjectList<[Function]>;
let Documentation = [RestrictDocs];
}

View File

@ -4966,9 +4966,17 @@ def RestrictDocs : Documentation {
let Category = DocCatFunction;
let Heading = "malloc";
let Content = [{
The ``malloc`` attribute indicates that the function acts like a system memory
allocation function, returning a pointer to allocated storage disjoint from the
storage for any other object accessible to the caller.
The ``malloc`` attribute has two forms with different functionality. The first
is when it is used without arguments, where it marks that a function acts like
a system memory allocation function, returning a pointer to allocated storage
that does not alias storage from any other object accessible to the caller.
The second form is when ``malloc`` takes one or two arguments. The first
argument names a function that should be associated with this function as its
deallocation function. When this form is used, it enables the compiler to
diagnose when the incorrect deallocation function is used with this variable.
However the associated warning, spelled `-Wmismatched-dealloc` in GCC, is not
yet implemented in clang.
}];
}

View File

@ -3155,6 +3155,10 @@ def warn_auto_var_is_id : Warning<
InGroup<DiagGroup<"auto-var-id">>;
// Attributes
def warn_attribute_form_ignored : Warning<
"%0 attribute ignored because Clang does not yet support this attribute signature">,
InGroup<IgnoredAttributes>;
def warn_attribute_ignored_no_calls_in_stmt: Warning<
"%0 attribute is ignored because there exists no call expression inside the "
"statement">,
@ -4528,6 +4532,12 @@ def err_attribute_cleanup_func_must_take_one_arg : Error<
def err_attribute_cleanup_func_arg_incompatible_type : Error<
"'cleanup' function %0 parameter has "
"%diff{type $ which is incompatible with type $|incompatible type}1,2">;
def err_attribute_malloc_arg_not_function : Error<
"'malloc' argument %select{for deallocator |%1 |%1 }0is not a %select{||single }0function">;
def err_attribute_malloc_arg_not_function_with_pointer_arg : Error<
"'malloc' argument %0 must take a pointer type as its first argument">;
def err_attribute_malloc_arg_refers_to_non_pointer_type : Error<
"'malloc' argument '%0' refers to non-pointer type %1 of %2">; // in GCC, this is a warning, not an error
def err_attribute_regparm_wrong_platform : Error<
"'regparm' is not valid on this platform">;
def err_attribute_regparm_invalid_number : Error<

View File

@ -2434,7 +2434,8 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
FuncAttrs.addMemoryAttr(llvm::MemoryEffects::inaccessibleOrArgMemOnly());
FuncAttrs.addAttribute(llvm::Attribute::NoUnwind);
}
if (TargetDecl->hasAttr<RestrictAttr>())
if (const auto *RA = TargetDecl->getAttr<RestrictAttr>();
RA && RA->getDeallocator() == nullptr)
RetAttrs.addAttribute(llvm::Attribute::NoAlias);
if (TargetDecl->hasAttr<ReturnsNonNullAttr>() &&
!CodeGenOpts.NullPointerIsValid)

View File

@ -1780,13 +1780,89 @@ static void handleTLSModelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
static void handleRestrictAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
QualType ResultType = getFunctionOrMethodResultType(D);
if (ResultType->isAnyPointerType() || ResultType->isBlockPointerType()) {
if (!ResultType->isAnyPointerType() && !ResultType->isBlockPointerType()) {
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
<< AL << getFunctionOrMethodResultSourceRange(D);
return;
}
if (AL.getNumArgs() == 0) {
D->addAttr(::new (S.Context) RestrictAttr(S.Context, AL));
return;
}
S.Diag(AL.getLoc(), diag::warn_attribute_return_pointers_only)
<< AL << getFunctionOrMethodResultSourceRange(D);
if (AL.getAttributeSpellingListIndex() == RestrictAttr::Declspec_restrict) {
// __declspec(restrict) accepts no arguments
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 0;
return;
}
// [[gnu::malloc(deallocator)]] with args specifies a deallocator function
Expr *DeallocE = AL.getArgAsExpr(0);
SourceLocation DeallocLoc = DeallocE->getExprLoc();
FunctionDecl *DeallocFD = nullptr;
DeclarationNameInfo DeallocNI;
if (auto *DRE = dyn_cast<DeclRefExpr>(DeallocE)) {
DeallocFD = dyn_cast<FunctionDecl>(DRE->getDecl());
DeallocNI = DRE->getNameInfo();
if (!DeallocFD) {
S.Diag(DeallocLoc, diag::err_attribute_malloc_arg_not_function)
<< 1 << DeallocNI.getName();
return;
}
} else if (auto *ULE = dyn_cast<UnresolvedLookupExpr>(DeallocE)) {
DeallocFD = S.ResolveSingleFunctionTemplateSpecialization(ULE, true);
DeallocNI = ULE->getNameInfo();
if (!DeallocFD) {
S.Diag(DeallocLoc, diag::err_attribute_malloc_arg_not_function)
<< 2 << DeallocNI.getName();
if (ULE->getType() == S.Context.OverloadTy)
S.NoteAllOverloadCandidates(ULE);
return;
}
} else {
S.Diag(DeallocLoc, diag::err_attribute_malloc_arg_not_function) << 0;
return;
}
// 2nd arg of [[gnu::malloc(deallocator, 2)]] with args specifies the param
// of deallocator that deallocates the pointer (defaults to 1)
ParamIdx DeallocPtrIdx;
if (AL.getNumArgs() == 1) {
DeallocPtrIdx = ParamIdx(1, DeallocFD);
if (!DeallocPtrIdx.isValid() ||
!getFunctionOrMethodParamType(DeallocFD, DeallocPtrIdx.getASTIndex())
.getCanonicalType()
->isPointerType()) {
S.Diag(DeallocLoc,
diag::err_attribute_malloc_arg_not_function_with_pointer_arg)
<< DeallocNI.getName();
return;
}
} else {
if (!S.checkFunctionOrMethodParameterIndex(
DeallocFD, AL, 2, AL.getArgAsExpr(1), DeallocPtrIdx,
/* CanIndexImplicitThis=*/false))
return;
QualType DeallocPtrArgType =
getFunctionOrMethodParamType(DeallocFD, DeallocPtrIdx.getASTIndex());
if (!DeallocPtrArgType.getCanonicalType()->isPointerType()) {
S.Diag(DeallocLoc,
diag::err_attribute_malloc_arg_refers_to_non_pointer_type)
<< DeallocPtrIdx.getSourceIndex() << DeallocPtrArgType
<< DeallocNI.getName();
return;
}
}
// FIXME: we should add this attribute to Clang's AST, so that clang-analyzer
// can use it, see -Wmismatched-dealloc in GCC for what we can do with this.
S.Diag(AL.getLoc(), diag::warn_attribute_form_ignored) << AL;
D->addAttr(::new (S.Context)
RestrictAttr(S.Context, AL, DeallocE, DeallocPtrIdx));
}
static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) {

View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s
int *Mem;
void dealloc(int*);
__attribute__((malloc)) int *MallocFunc(){ return Mem;}
// CHECK: define[[BEFORE:.*]] noalias[[AFTER:.*]]@MallocFunc
// Ensure these two do not generate noalias here.
__attribute__((malloc(dealloc))) int *MallocFunc2(){ return Mem;}
// CHECK: define[[BEFORE]][[AFTER]]@MallocFunc2
__attribute__((malloc(dealloc, 1))) int *MallocFunc3(){ return Mem;}
// CHECK: define[[BEFORE]][[AFTER]]@MallocFunc3

View File

@ -1,12 +1,24 @@
// RUN: %clang_cc1 -verify -Wunused -Wused-but-marked-unused -Wunused-parameter -fsyntax-only %s
// RUN: %clang_cc1 -verify -Wunused -Wused-but-marked-unused -Wunused-parameter -fsyntax-only -fdeclspec %s
int a;
void func_a(void * ptr, int a);
void func_b(int a);
void __attribute__((overloadable)) ambigious_func(void *); // expected-note {{candidate function}}
void __attribute__((overloadable)) ambigious_func(void *, int); // expected-note {{candidate function}}
inline __attribute__((noreturn(a))) void *f1(void); // expected-error {{'noreturn' attribute takes no arguments}}
inline __attribute__((always_inline(a))) void *f2(void); // expected-error {{'always_inline' attribute takes no arguments}}
inline __attribute__((cdecl(a))) void *f3(void); // expected-error {{'cdecl' attribute takes no arguments}}
inline __attribute__((const(a))) void *f4(void); // expected-error {{'const' attribute takes no arguments}}
inline __attribute__((fastcall(a))) void *f5(void); // expected-error {{'fastcall' attribute takes no arguments}}
inline __attribute__((malloc(a))) void *f5(void); // expected-error {{'malloc' attribute takes no arguments}}
inline __declspec(restrict(a)) void *f6_a(void); // expected-error {{'restrict' attribute takes no arguments}}
inline __attribute__((malloc(func_a, 1, a))) void *f6_b(void); // expected-error {{'malloc' attribute takes no more than 2 arguments}}
inline __attribute__((malloc(func_a, 1))) void *f6_c(void); // expected-warning {{'malloc' attribute ignored because Clang does not yet support this attribute signature}}
inline __attribute__((malloc(1234))) void *f6_d(void); // expected-error {{'malloc' argument for deallocator is not a function}}
inline __attribute__((malloc(a))) void *f6_e(void); // expected-error {{'malloc' argument 'a' is not a function}}
inline __attribute__((malloc(ambigious_func))) void *f6_f(void); // expected-error {{'malloc' argument 'ambigious_func' is not a single function}}
inline __attribute__((malloc(func_b))) void *f6_g(void); // expected-error {{'malloc' argument 'func_b' must take a pointer type as its first argument}}
inline __attribute__((malloc(func_a, 3))) void *f6_h(void); // expected-error {{'malloc' attribute parameter 2 is out of bounds}}
inline __attribute__((malloc(func_a, 2))) void *f6_i(void); // expected-error {{'malloc' argument '2' refers to non-pointer type 'int' of 'func_a'}}
inline __attribute__((nothrow(a))) void *f7(void); // expected-error {{'nothrow' attribute takes no arguments}}
inline __attribute__((stdcall(a))) void *f8(void); // expected-error {{'stdcall' attribute takes no arguments}}
inline __attribute__((used(a))) void *f9(void); // expected-error {{'used' attribute takes no arguments}}

View File

@ -28,6 +28,14 @@ int v __attribute__((visibility("hidden")));
// CHECK: char *PR24565() __attribute__((malloc))
char *PR24565() __attribute__((__malloc__));
void my_cleanup_func(char *);
// using __attribute__(malloc()) with args is currently ignored by Clang
// CHECK: char *PR52265_a()
__attribute__((malloc(my_cleanup_func))) char *PR52265_a();
// CHECK: char *PR52265_b()
__attribute__((malloc(my_cleanup_func, 1))) char *PR52265_b();
// CHECK: class __attribute__((consumable("unknown"))) AttrTester1
class __attribute__((consumable(unknown))) AttrTester1 {
// CHECK: void callableWhen() __attribute__((callable_when("unconsumed", "consumed")));