https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/214928
>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
