[clang] 1f9f944 - [CIR] Fix crash calling a variadic pass_object_size function with no varargs (#214928)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 10 10:47:41 PDT 2026
Author: Bruno Cardoso Lopes
Date: 2026-08-10T10:47:35-07:00
New Revision: 1f9f944e60362b6d25f245e124ddde098ed327cc
URL: https://github.com/llvm/llvm-project/commit/1f9f944e60362b6d25f245e124ddde098ed327cc
DIFF: https://github.com/llvm/llvm-project/commit/1f9f944e60362b6d25f245e124ddde098ed327cc.diff
LOG: [CIR] Fix crash calling a variadic pass_object_size function with no varargs (#214928)
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.
Added:
Modified:
clang/lib/CIR/CodeGen/CIRGenCall.cpp
clang/test/CIR/CodeGen/pass-object-size.c
Removed:
################################################################################
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 940294c923179..d3b3a8a471dbf 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)
More information about the cfe-commits
mailing list