https://github.com/bcardosolopes created 
https://github.com/llvm/llvm-project/pull/214928

…varargs

arrangeFreeFunctionLikeCall added getNumPassObjectSizeParams(proto) to the 
extra-slot count it passed to RequiredArgs::getFromProtoWithExtraSlots, which 
adds that same count itself. The pass_object_size slots were therefore counted 
twice, and the resulting signature claimed more required arguments than the 
CallArgList held.

requiredArguments() builds an ArrayRef of getNumRequiredArgs() entries over the 
trailing-object array, so the over-count read past the end and handed a null 
QualType to convertType -- a segfault in release, an assertion in +asserts. The 
call had to supply zero variadic arguments for the counts to disagree; one or 
more explicit varargs padded the array back over the required count and hid it.

```
  extern int f(const char *__attribute__((pass_object_size(1))) s, ...);
  int main(void) { f("x"); return 0; }    // crashed in every -fclangir mode
```

Classic CodeGen passes only the genuine prefix count (0 here) to 
RequiredArgs::forPrototypePlus; match that. Every other CIR caller of 
getFromProtoWithExtraSlots already passes a prefix count, so this was the only 
double-count.

Recent glibc declares the fortified printf family for clang exactly this way, 
so on such a glibc any printf("...") with a constant format string and no 
arguments crashed the compiler -- 9 of 515 coreutils TUs, per issue #214442.

Also assert the invariant in CIRGenFunctionInfo::create, so a future 
miscomputation fails there instead of as a null QualType deep in convertType.

Fixes #214442.

>From 1cbbc135e003a5813bbf6845a426c9720a01c8b9 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <[email protected]>
Date: Fri, 7 Aug 2026 21:09:06 -0700
Subject: [PATCH] [CIR] Fix crash calling a variadic pass_object_size function
 with no varargs

arrangeFreeFunctionLikeCall added getNumPassObjectSizeParams(proto) to the
extra-slot count it passed to RequiredArgs::getFromProtoWithExtraSlots, which
adds that same count itself. The pass_object_size slots were therefore counted
twice, and the resulting signature claimed more required arguments than the
CallArgList held.

requiredArguments() builds an ArrayRef of getNumRequiredArgs() entries over the
trailing-object array, so the over-count read past the end and handed a null
QualType to convertType -- a segfault in release, an assertion in +asserts.
The call had to supply zero variadic arguments for the counts to disagree; one
or more explicit varargs padded the array back over the required count and hid
it.

  extern int f(const char *__attribute__((pass_object_size(1))) s, ...);
  int main(void) { f("x"); return 0; }    // crashed in every -fclangir mode

Classic CodeGen passes only the genuine prefix count (0 here) to
RequiredArgs::forPrototypePlus; match that. Every other CIR caller of
getFromProtoWithExtraSlots already passes a prefix count, so this was the only
double-count.

Recent glibc declares the fortified printf family for clang exactly this way,
so on such a glibc any printf("...") with a constant format string and no
arguments crashed the compiler -- 9 of 515 coreutils TUs, per issue #214442.

Also assert the invariant in CIRGenFunctionInfo::create, so a future
miscomputation fails there instead of as a null QualType deep in convertType.

Fixes #214442.
---
 clang/lib/CIR/CodeGen/CIRGenCall.cpp      | 16 +++++++-
 clang/test/CIR/CodeGen/pass-object-size.c | 48 +++++++++++++++++++++++
 2 files changed, 62 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 28670cf31694a..ce689cc4110bd 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -43,6 +43,14 @@ CIRGenFunctionInfo *CIRGenFunctionInfo::create(
   fi->required = required;
   fi->numArgs = argTypes.size();
 
+  // requiredArguments() reads getNumRequiredArgs() entries out of the
+  // trailing-object array, so a signature that claims more required arguments
+  // than we have types for reads past the end and hands a null QualType to
+  // convertType.
+  assert((!required.allowsOptionalArgs() ||
+          required.getNumRequiredArgs() <= fi->numArgs) &&
+         "more required arguments than argument types");
+
   fi->getArgTypes()[0] = resultType;
   std::copy(argTypes.begin(), argTypes.end(), fi->argTypesBegin());
   assert(!cir::MissingFeatures::opCallCIRGenFuncInfoExtParamInfo());
@@ -930,9 +938,13 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule 
&cgm,
   RequiredArgs required = RequiredArgs::All;
 
   if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
-    unsigned numExtraSlots = getNumPassObjectSizeParams(proto);
+    // A free function call has no extra prefix arguments. Note that
+    // getFromProtoWithExtraSlots already accounts for the prototype's
+    // pass_object_size parameters; adding them here too would double-count
+    // them and make the signature claim more required arguments than `args`
+    // actually holds.
     if (proto->isVariadic())
-      required = RequiredArgs::getFromProtoWithExtraSlots(proto, 
numExtraSlots);
+      required = RequiredArgs::getFromProtoWithExtraSlots(proto, 0);
   } else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
                  cast<FunctionNoProtoType>(fnType)))
     cgm.errorNYI("call to function without a prototype");
diff --git a/clang/test/CIR/CodeGen/pass-object-size.c 
b/clang/test/CIR/CodeGen/pass-object-size.c
index 23b95e8f38951..c6166bc992fae 100644
--- a/clang/test/CIR/CodeGen/pass-object-size.c
+++ b/clang/test/CIR/CodeGen/pass-object-size.c
@@ -63,3 +63,51 @@ void test_vla(int n) {
 // OGCG:   call void @b(ptr noundef %[[VLA]], i64 noundef %[[SIZE1]])
 // OGCG:   %[[SIZE2:.*]] = call i64 @llvm.objectsize.i64.p0(ptr %[[VLA]], i1 
true, i1 true, i1 false)
 // OGCG:   call void @e(ptr noundef %[[VLA]], i64 noundef %[[SIZE2]])
+
+// A pass_object_size parameter on a variadic callee occupies one of the
+// signature's required argument slots. Calling such a function with no
+// variadic arguments must still work -- this is how glibc declares the
+// fortified printf family for clang, so printf("hello") hits it.
+
+void v(void *__attribute__((pass_object_size(0))), ...);
+
+void test_variadic_no_varargs(void) {
+  int a;
+  v(&a);
+}
+
+// CIR: cir.func private @v(!cir.ptr<!void> {llvm.noundef}, !u64i 
{llvm.noundef}, ...)
+
+// CIR: cir.func {{.*}} @test_variadic_no_varargs()
+// CIR:   %[[ALLOCA:.*]] = cir.alloca {{.*}} : !cir.ptr<!s32i>
+// CIR:   %[[CAST:.*]] = cir.cast bitcast %[[ALLOCA]] : !cir.ptr<!s32i> -> 
!cir.ptr<!void>
+// CIR:   %[[SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR:   cir.call @v(%[[CAST]], %[[SIZE]]) : (!cir.ptr<!void> {{.*}}, !u64i 
{{.*}}) -> ()
+
+// LLVM: define dso_local void @test_variadic_no_varargs()
+// LLVM:   %[[ALLOCA:.*]] = alloca i32
+// LLVM:   call void (ptr, i64, ...) @v(ptr noundef %[[ALLOCA]], i64 noundef 4)
+
+// OGCG: define dso_local void @test_variadic_no_varargs()
+// OGCG:   %[[A:.*]] = alloca i32
+// OGCG:   call void (ptr, i64, ...) @v(ptr noundef %[[A]], i64 noundef 4)
+
+void test_variadic_with_varargs(void) {
+  int a;
+  v(&a, 1);
+}
+
+// CIR: cir.func {{.*}} @test_variadic_with_varargs()
+// CIR:   %[[ALLOCA:.*]] = cir.alloca {{.*}} : !cir.ptr<!s32i>
+// CIR:   %[[CAST:.*]] = cir.cast bitcast %[[ALLOCA]] : !cir.ptr<!s32i> -> 
!cir.ptr<!void>
+// CIR:   %[[SIZE:.*]] = cir.const #cir.int<4> : !u64i
+// CIR:   %[[ARG:.*]] = cir.const #cir.int<1> : !s32i
+// CIR:   cir.call @v(%[[CAST]], %[[SIZE]], %[[ARG]]) : (!cir.ptr<!void> 
{{.*}}, !u64i {{.*}}, !s32i {{.*}}) -> ()
+
+// LLVM: define dso_local void @test_variadic_with_varargs()
+// LLVM:   %[[ALLOCA:.*]] = alloca i32
+// LLVM:   call void (ptr, i64, ...) @v(ptr noundef %[[ALLOCA]], i64 noundef 
4, i32 noundef 1)
+
+// OGCG: define dso_local void @test_variadic_with_varargs()
+// OGCG:   %[[A:.*]] = alloca i32
+// OGCG:   call void (ptr, i64, ...) @v(ptr noundef %[[A]], i64 noundef 4, i32 
noundef 1)

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to