https://github.com/ChenMiaoi updated https://github.com/llvm/llvm-project/pull/229010
>From 7a0b2f93ae262b941600ce2e813ca5621a9020d3 Mon Sep 17 00:00:00 2001 From: Chen Miao <[email protected]> Date: Mon, 5 Oct 2026 16:41:51 +0800 Subject: [PATCH 1/3] [clang][Sema] Avoid out-of-bounds format_arg access Clang implicitly adds `format_arg(1)` to `__CFStringMakeConstantString`, including declarations created during error recovery. A zero-argument call can therefore make `checkFormatStringExpr` access a nonexistent argument. Reproducer: ```c void a(char *) __attribute__((format(__CFString__, 1, 2))); void b() { a(__CFStringMakeConstantString()); } ``` Before this change, an assertions-enabled build crashes after reporting the source errors (diagnostic excerpts): ```text error: call to undeclared function '__CFStringMakeConstantString'; ISO C99 and later do not support implicit function declarations error: incompatible integer to pointer conversion passing 'int' to parameter of type 'char *' Assertion `Arg < getNumArgs() && "Arg access out of range!"' failed. ``` Check the `format_arg` index against `CE->getNumArgs()` before calling `CE->getArg()`. Return `SLCT_NotALiteral` when the argument is missing so normal diagnostics can continue. After this change, the compiler reports diagnostics and exits with status 1 without crashing (diagnostic excerpts): ```text error: call to undeclared function '__CFStringMakeConstantString'; ISO C99 and later do not support implicit function declarations error: incompatible integer to pointer conversion passing 'int' to parameter of type 'char *' warning: format string is not a string literal (potentially insecure) ``` Fixes #225034 --- clang/lib/Sema/SemaChecking.cpp | 7 ++++++- clang/test/Sema/format-strings-cfstring.c | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 clang/test/Sema/format-strings-cfstring.c diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 0531dfa877fbdf..eb5982f10e7630 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7710,7 +7710,12 @@ checkFormatStringExpr(Sema &S, const StringLiteral *ReferenceFormatString, bool IsFirst = true; StringLiteralCheckType CommonResult; for (const auto *FA : ND->specific_attrs<FormatArgAttr>()) { - const Expr *Arg = CE->getArg(FA->getFormatIdx().getASTIndex()); + // An implicitly added attribute may refer to a missing argument. + // https://github.com/llvm/llvm-project/issues/225034 + unsigned ArgIndex = FA->getFormatIdx().getASTIndex(); + if (ArgIndex >= CE->getNumArgs()) + return SLCT_NotALiteral; + const Expr *Arg = CE->getArg(ArgIndex); StringLiteralCheckType Result = checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, diff --git a/clang/test/Sema/format-strings-cfstring.c b/clang/test/Sema/format-strings-cfstring.c new file mode 100644 index 00000000000000..776a82e7c3eb7d --- /dev/null +++ b/clang/test/Sema/format-strings-cfstring.c @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c11 -fsyntax-only -Wno-gcc-compat -verify=expected,implicit %s +// RUN: %clang_cc1 -std=c11 -fsyntax-only -Wno-gcc-compat -DDECLARE_CFSTRING -verify %s + +// An implicitly added format_arg attribute may refer to a missing argument, +// both during error recovery and with a non-prototype declaration. +// https://github.com/llvm/llvm-project/issues/225034 +#ifdef DECLARE_CFSTRING +char *__CFStringMakeConstantString(); +#endif + +void a(char *) __attribute__((format(__CFString__, 1, 2))); // implicit-note {{passing argument to parameter here}} + +void b(void) { + a(__CFStringMakeConstantString()); // expected-warning {{format string is not a string literal (potentially insecure)}} + // expected-note@-1 {{treat the string as an argument to avoid this}} + // implicit-error@-2 {{call to undeclared function '__CFStringMakeConstantString'}} + // implicit-error@-3 {{incompatible integer to pointer conversion passing 'int' to parameter of type 'char *'}} +} >From 72e0d4ff6d7cf9d8ea8fa483fb4999c753832120 Mon Sep 17 00:00:00 2001 From: Chen Miao <[email protected]> Date: Thu, 8 Oct 2026 23:55:57 +0800 Subject: [PATCH 2/3] [clang] Fix crash caused by invalid implicit format attributes Clang implicitly adds `format_arg(1)` to the prototypeless declaration created during error recovery for an undeclared `__CFStringMakeConstantString`. Format string checking then accesses the call's nonexistent first argument, triggering an out-of-bounds assertion. In `Sema::AddKnownFunctionAttributes()`, check that the function type is a prototype function type and that the referenced format string parameter exists before implicitly adding `format` or `format_arg` attributes. Fixes #225034 --- clang/docs/ReleaseNotes.md | 3 ++ clang/lib/Sema/SemaChecking.cpp | 7 +-- clang/lib/Sema/SemaDecl.cpp | 31 ++++++------ clang/test/Sema/format-strings-cfstring.c | 18 ------- .../Sema/format-strings-implicit-attributes.c | 50 +++++++++++++++++++ 5 files changed, 70 insertions(+), 39 deletions(-) delete mode 100644 clang/test/Sema/format-strings-cfstring.c create mode 100644 clang/test/Sema/format-strings-implicit-attributes.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index c3ec56c5741f29..85b2e9399c0652 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -689,6 +689,9 @@ features cannot lower the translation-unit ABI level; #### Bug Fixes to Attribute Support +- Fixed a crash when using a zero-argument call to an undeclared + `__CFStringMakeConstantString` as a format string. (#GH225034) + - Fixed an assertion failure when parsing malformed GNU `__attribute__` syntax followed by a parenthesized expression list in C code. (#GH225045) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index eb5982f10e7630..0531dfa877fbdf 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -7710,12 +7710,7 @@ checkFormatStringExpr(Sema &S, const StringLiteral *ReferenceFormatString, bool IsFirst = true; StringLiteralCheckType CommonResult; for (const auto *FA : ND->specific_attrs<FormatArgAttr>()) { - // An implicitly added attribute may refer to a missing argument. - // https://github.com/llvm/llvm-project/issues/225034 - unsigned ArgIndex = FA->getFormatIdx().getASTIndex(); - if (ArgIndex >= CE->getNumArgs()) - return SLCT_NotALiteral; - const Expr *Arg = CE->getArg(ArgIndex); + const Expr *Arg = CE->getArg(FA->getFormatIdx().getASTIndex()); StringLiteralCheckType Result = checkFormatStringExpr( S, ReferenceFormatString, Arg, Args, APK, format_idx, firstDataArg, Type, CallType, InFunctionCall, CheckedVarArgs, UncoveredArg, diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1459b71326375a..84e7d37ed82ad7 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -17630,18 +17630,20 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { if (FD->isInvalidDecl()) return; + // Both format and format_arg attributes require a function prototype. + const bool HasPrototype = FD->getType()->isFunctionProtoType(); + // If this is a built-in function, map its builtin attributes to // actual attributes. if (unsigned BuiltinID = FD->getBuiltinID()) { // Handle printf-formatting attributes. unsigned FormatIdx; bool HasVAListArg; - if (Context.BuiltinInfo.isPrintfLike(BuiltinID, FormatIdx, HasVAListArg)) { - if (!FD->hasAttr<FormatAttr>()) { + if (HasPrototype && + Context.BuiltinInfo.isPrintfLike(BuiltinID, FormatIdx, HasVAListArg)) { + if (!FD->hasAttr<FormatAttr>() && FormatIdx < FD->getNumParams()) { const char *fmt = "printf"; - unsigned int NumParams = FD->getNumParams(); - if (FormatIdx < NumParams && // NumParams may be 0 (e.g. vfprintf) - FD->getParamDecl(FormatIdx)->getType()->isObjCObjectPointerType()) + if (FD->getParamDecl(FormatIdx)->getType()->isObjCObjectPointerType()) fmt = "NSString"; FD->addAttr(FormatAttr::CreateImplicit(Context, &Context.Idents.get(fmt), @@ -17650,14 +17652,12 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { FD->getLocation())); } } - if (Context.BuiltinInfo.isScanfLike(BuiltinID, FormatIdx, - HasVAListArg)) { - if (!FD->hasAttr<FormatAttr>()) - FD->addAttr(FormatAttr::CreateImplicit(Context, - &Context.Idents.get("scanf"), - FormatIdx+1, - HasVAListArg ? 0 : FormatIdx+2, - FD->getLocation())); + if (HasPrototype && + Context.BuiltinInfo.isScanfLike(BuiltinID, FormatIdx, HasVAListArg)) { + if (!FD->hasAttr<FormatAttr>() && FormatIdx < FD->getNumParams()) + FD->addAttr(FormatAttr::CreateImplicit( + Context, &Context.Idents.get("scanf"), FormatIdx + 1, + HasVAListArg ? 0 : FormatIdx + 2, FD->getLocation())); } // Handle automatically recognized callbacks. @@ -17807,7 +17807,7 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { if (Name->isStr("asprintf") || Name->isStr("vasprintf")) { // FIXME: asprintf and vasprintf aren't C99 functions. Should they be // target-specific builtins, perhaps? - if (!FD->hasAttr<FormatAttr>()) + if (HasPrototype && FD->getNumParams() >= 2 && !FD->hasAttr<FormatAttr>()) FD->addAttr(FormatAttr::CreateImplicit(Context, &Context.Idents.get("printf"), 2, Name->isStr("vasprintf") ? 0 : 3, @@ -17817,7 +17817,8 @@ void Sema::AddKnownFunctionAttributes(FunctionDecl *FD) { if (Name->isStr("__CFStringMakeConstantString")) { // We already have a __builtin___CFStringMakeConstantString, // but builds that use -fno-constant-cfstrings don't go through that. - if (!FD->hasAttr<FormatArgAttr>()) + if (HasPrototype && FD->getNumParams() >= 1 && + !FD->hasAttr<FormatArgAttr>()) FD->addAttr(FormatArgAttr::CreateImplicit(Context, ParamIdx(1, FD), FD->getLocation())); } diff --git a/clang/test/Sema/format-strings-cfstring.c b/clang/test/Sema/format-strings-cfstring.c deleted file mode 100644 index 776a82e7c3eb7d..00000000000000 --- a/clang/test/Sema/format-strings-cfstring.c +++ /dev/null @@ -1,18 +0,0 @@ -// RUN: %clang_cc1 -std=c11 -fsyntax-only -Wno-gcc-compat -verify=expected,implicit %s -// RUN: %clang_cc1 -std=c11 -fsyntax-only -Wno-gcc-compat -DDECLARE_CFSTRING -verify %s - -// An implicitly added format_arg attribute may refer to a missing argument, -// both during error recovery and with a non-prototype declaration. -// https://github.com/llvm/llvm-project/issues/225034 -#ifdef DECLARE_CFSTRING -char *__CFStringMakeConstantString(); -#endif - -void a(char *) __attribute__((format(__CFString__, 1, 2))); // implicit-note {{passing argument to parameter here}} - -void b(void) { - a(__CFStringMakeConstantString()); // expected-warning {{format string is not a string literal (potentially insecure)}} - // expected-note@-1 {{treat the string as an argument to avoid this}} - // implicit-error@-2 {{call to undeclared function '__CFStringMakeConstantString'}} - // implicit-error@-3 {{incompatible integer to pointer conversion passing 'int' to parameter of type 'char *'}} -} diff --git a/clang/test/Sema/format-strings-implicit-attributes.c b/clang/test/Sema/format-strings-implicit-attributes.c new file mode 100644 index 00000000000000..e96ee514f1ee3e --- /dev/null +++ b/clang/test/Sema/format-strings-implicit-attributes.c @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -std=c11 -ast-dump -Wno-gcc-compat -verify=expected,implicit %s | FileCheck %s --check-prefix=NO-FORMAT-ARG +// RUN: %clang_cc1 -std=c11 -ast-dump -Wno-gcc-compat -DNO_PROTOTYPE -verify %s | FileCheck %s --check-prefix=NO-FORMAT-ARG +// RUN: %clang_cc1 -std=c11 -ast-dump -Wno-gcc-compat -DMISSING_FORMAT_PARAM -verify %s | FileCheck %s --check-prefix=NO-FORMAT-ARG +// RUN: %clang_cc1 -std=c11 -ast-dump -DCFSTRING_VALID_PROTO -verify %s | FileCheck %s --check-prefix=FORMAT-ARG + +#ifdef CFSTRING_VALID_PROTO +// expected-no-diagnostics + +// Keep inferring format_arg(1) when the referenced parameter exists. +char *__CFStringMakeConstantString(const char *); + +// FORMAT-ARG-LABEL: FunctionDecl{{.*}} __CFStringMakeConstantString +// FORMAT-ARG: FormatArgAttr{{.*}}Implicit 1 +#else + +// Do not infer format attributes without a prototype or the format parameter. +// https://github.com/llvm/llvm-project/issues/225034 +#if defined(MISSING_FORMAT_PARAM) +char *__CFStringMakeConstantString(void); +int asprintf(char **); +int vasprintf(char **); + +void test_missing_format_param(char **out) { + asprintf(out); + vasprintf(out); +} +#elif defined(NO_PROTOTYPE) +char *__CFStringMakeConstantString(); +int asprintf(); +int vasprintf(); + +void test_no_prototype(void) { + asprintf(); + vasprintf(); +} +#endif + +void a(char *) __attribute__((format(__CFString__, 1, 2))); // implicit-note {{passing argument to parameter here}} + +void b(void) { + a(__CFStringMakeConstantString()); // expected-warning {{format string is not a string literal (potentially insecure)}} + // expected-note@-1 {{treat the string as an argument to avoid this}} + // implicit-error@-2 {{call to undeclared function '__CFStringMakeConstantString'}} + // implicit-error@-3 {{incompatible integer to pointer conversion passing 'int' to parameter of type 'char *'}} +} + +// NO-FORMAT-ARG-NOT: FormatArgAttr +// NO-FORMAT-ARG: FunctionDecl{{.*}} b 'void (void)' +// NO-FORMAT-ARG-NOT: FormatArgAttr +#endif >From 88891f5081a5c6a79c25613a8ab5f67f587b5b9e Mon Sep 17 00:00:00 2001 From: Chen Miao <[email protected]> Date: Fri, 9 Oct 2026 03:43:59 +0800 Subject: [PATCH 3/3] [clang][Sema] Test CFString declarations with an integer parameter --- clang/test/Sema/format-strings-implicit-attributes.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clang/test/Sema/format-strings-implicit-attributes.c b/clang/test/Sema/format-strings-implicit-attributes.c index e96ee514f1ee3e..52706c504306c7 100644 --- a/clang/test/Sema/format-strings-implicit-attributes.c +++ b/clang/test/Sema/format-strings-implicit-attributes.c @@ -2,6 +2,7 @@ // RUN: %clang_cc1 -std=c11 -ast-dump -Wno-gcc-compat -DNO_PROTOTYPE -verify %s | FileCheck %s --check-prefix=NO-FORMAT-ARG // RUN: %clang_cc1 -std=c11 -ast-dump -Wno-gcc-compat -DMISSING_FORMAT_PARAM -verify %s | FileCheck %s --check-prefix=NO-FORMAT-ARG // RUN: %clang_cc1 -std=c11 -ast-dump -DCFSTRING_VALID_PROTO -verify %s | FileCheck %s --check-prefix=FORMAT-ARG +// RUN: %clang_cc1 -std=c11 -fsyntax-only -Wno-gcc-compat -DCFSTRING_INT_PARAM -verify %s #ifdef CFSTRING_VALID_PROTO // expected-no-diagnostics @@ -11,6 +12,17 @@ char *__CFStringMakeConstantString(const char *); // FORMAT-ARG-LABEL: FunctionDecl{{.*}} __CFStringMakeConstantString // FORMAT-ARG: FormatArgAttr{{.*}}Implicit 1 +#elif defined(CFSTRING_INT_PARAM) + +// A non-string parameter must not crash format checking. +char *__CFStringMakeConstantString(int); + +void a(char *) __attribute__((format(__CFString__, 1, 2))); + +void b(void) { + a(__CFStringMakeConstantString(1)); // expected-warning {{format string is not a string literal (potentially insecure)}} + // expected-note@-1 {{treat the string as an argument to avoid this}} +} #else // Do not infer format attributes without a prototype or the format parameter. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
