From 927b23997afb45d960cdb4180dce2fa98b4609ee Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:15 +0300 Subject: [PATCH] strconv: fix v_sprintf variadic voidptr rvalue (fixes #23767) --- vlib/strconv/format_test.v | 15 +++++++ vlib/strconv/vprintf.c.v | 32 ++++++++------- vlib/v/checker/fn.v | 2 + vlib/v/gen/c/fn.v | 41 ++++++++++++++++++- .../tests/fns/variadic_voidptr_rvalue_test.v | 15 +++++++ 5 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 vlib/v/tests/fns/variadic_voidptr_rvalue_test.v diff --git a/vlib/strconv/format_test.v b/vlib/strconv/format_test.v index 59b9debea..12881e81a 100644 --- a/vlib/strconv/format_test.v +++ b/vlib/strconv/format_test.v @@ -120,6 +120,21 @@ fn test_sprintf_with_escape() { assert s == '69 is 100% awesome' } +struct SprintfPromotedValue { +mut: + x u8 +} + +fn test_sprintf_promoted_variadic_values() { + mut value := SprintfPromotedValue{ + x: 1 + } + assert unsafe { strconv.v_sprintf('x=%02d', value.x) } == 'x=01' + assert unsafe { strconv.v_sprintf('x=%02d', int(value.x)) } == 'x=01' + assert unsafe { strconv.v_sprintf('%s', 'abc') } == 'abc' + assert unsafe { strconv.v_sprintf('%.1f', f32(1.5)) } == '1.5' +} + fn test_remove_tail_zeros() { assert strconv.remove_tail_zeros('1.234000000000') == '1.234' assert strconv.remove_tail_zeros('1.0000000') == '1' diff --git a/vlib/strconv/vprintf.c.v b/vlib/strconv/vprintf.c.v index a986f2598..05628bbb5 100644 --- a/vlib/strconv/vprintf.c.v +++ b/vlib/strconv/vprintf.c.v @@ -37,6 +37,7 @@ pub fn v_printf(str string, pt ...voidptr) { // Note, that this function is unsafe. // In most cases, you are better off using V's string interpolation, // when your format string is known at compile time. +// Small integer and `f32` arguments follow C-style default promotions. // Example: // ```v // x := 3.141516 @@ -97,7 +98,7 @@ pub fn v_sprintf(str string, pt ...voidptr) string { // single char, manage it here if ch == `c` && status == .field_char { v_sprintf_panic(p_index, pt.len) - d1 := unsafe { *(&u8(pt[p_index])) } + d1 := u8(unsafe { *(&int(pt[p_index])) }) res.write_u8(d1) status = .reset_params p_index++ @@ -251,15 +252,16 @@ pub fn v_sprintf(str string, pt ...voidptr) string { // h for 16 bit int // hh for 8 bit int `h` { + v_sprintf_panic(p_index, pt.len) + x := unsafe { *(&int(pt[p_index])) } if ch2 == `h` { - v_sprintf_panic(p_index, pt.len) - x := unsafe { *(&i8(pt[p_index])) } - positive = if x >= 0 { true } else { false } - d1 = if positive { u64(x) } else { u64(-x) } + sx := i8(x) + positive = if sx >= 0 { true } else { false } + d1 = if positive { u64(sx) } else { u64(-sx) } } else { - x := unsafe { *(&i16(pt[p_index])) } - positive = if x >= 0 { true } else { false } - d1 = if positive { u64(x) } else { u64(-x) } + sx := i16(x) + positive = if sx >= 0 { true } else { false } + d1 = if positive { u64(sx) } else { u64(-sx) } } } // l i64 @@ -318,10 +320,11 @@ pub fn v_sprintf(str string, pt ...voidptr) string { // h for 16 bit unsigned int // hh for 8 bit unsigned int `h` { + x := unsafe { *(&int(pt[p_index])) } if ch2 == `h` { - d1 = u64(unsafe { *(&u8(pt[p_index])) }) + d1 = u64(u8(x)) } else { - d1 = u64(unsafe { *(&u16(pt[p_index])) }) + d1 = u64(u16(x)) } } // l u64 @@ -339,7 +342,7 @@ pub fn v_sprintf(str string, pt ...voidptr) string { } // default int else { - d1 = u64(unsafe { *(&u32(pt[p_index])) }) + d1 = u64(u32(unsafe { *(&int(pt[p_index])) })) } } @@ -366,12 +369,11 @@ pub fn v_sprintf(str string, pt ...voidptr) string { // h for 16 bit int // hh fot 8 bit int `h` { + x := unsafe { *(&int(pt[p_index])) } if ch2 == `h` { - x := unsafe { *(&i8(pt[p_index])) } - s = x.hex() + s = i8(x).hex() } else { - x := unsafe { *(&i16(pt[p_index])) } - s = x.hex() + s = i16(x).hex() } } // l i64 diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index e8d1101f1..58cfebd1a 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2041,11 +2041,13 @@ fn (mut c Checker) fn_call(mut node ast.CallExpr, mut continue_check &bool) ast. // it can lead to codegen errors (except for 'magic' functions like `json.encode` that, // the compiler has special codegen support for), so it should be opt in, that is it // should require an explicit voidptr(x) cast (and probably unsafe{} ?) . + // V variadic ...voidptr calls are boxed in cgen, so rvalues are safe there. if call_arg.typ != param.typ && (param.typ == ast.voidptr_type || final_param_sym.idx == ast.voidptr_type_idx || param.typ == ast.nil_type || final_param_sym.idx == ast.nil_type_idx) && !call_arg.typ.is_any_kind_of_pointer() && func.language == .v && !call_arg.expr.is_lvalue() && !c.pref.translated && !c.file.is_translated + && !(func.is_variadic && final_param_sym.idx == ast.voidptr_type_idx) && !func.is_c_variadic && func.name !in ['json.encode', 'json.encode_pretty'] { c.error('expression cannot be passed as `voidptr`', call_arg.expr.pos()) } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 35b93d3a0..b0e020670 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -5549,8 +5549,12 @@ fn (mut g Gen) call_args(node ast.CallExpr) { } g.write('builtin__new_array_from_c_array${noscan}(${variadic_count}, ${variadic_count}, sizeof(${elem_type}), _MOV((${elem_type}[${variadic_count}]){') for j in arg_nr .. args.len { - g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language, - false) + if arr_info.elem_type == ast.voidptr_type { + g.write_variadic_voidptr_arg(args[j], node.language) + } else { + g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language, + false) + } if j < args.len - 1 { g.write(', ') } @@ -5619,6 +5623,39 @@ fn (mut g Gen) keep_alive_call_postgen(node ast.CallExpr, tmp_cnt_save int) { } } +fn (g &Gen) variadic_voidptr_promotion_type(arg_typ ast.Type) ast.Type { + final_sym := g.table.final_sym(arg_typ) + return match final_sym.kind { + .i8, .i16, .u8, .u16, .rune, .enum { ast.int_type } + .f32 { ast.f64_type } + else { arg_typ } + } +} + +fn (mut g Gen) write_variadic_voidptr_arg(arg ast.CallArg, lang ast.Language) { + arg_typ := if arg.ct_expr { + g.unwrap_generic(g.type_resolver.get_type(arg.expr)) + } else { + g.unwrap_generic(arg.typ) + } + arg_sym := g.table.sym(arg_typ) + is_alias_pointer := arg_sym.kind == .alias + && g.table.unaliased_type(arg_typ).is_any_kind_of_pointer() + if arg_typ.is_any_kind_of_pointer() || is_alias_pointer || arg_sym.kind == .function + || arg.expr is ast.None { + g.ref_or_deref_arg(arg, ast.voidptr_type, lang, false) + return + } + promoted_type := g.variadic_voidptr_promotion_type(arg_typ) + if promoted_type != arg_typ || !arg.expr.is_lvalue() { + g.write('(voidptr)ADDR(${g.styp(promoted_type)}, ') + g.expr(arg.expr) + g.write(')') + return + } + g.ref_or_deref_arg(arg, ast.voidptr_type, lang, false) +} + @[inline] fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type_ ast.Type, lang ast.Language, is_smartcast bool) { expected_type := g.unwrap_generic(expected_type_) diff --git a/vlib/v/tests/fns/variadic_voidptr_rvalue_test.v b/vlib/v/tests/fns/variadic_voidptr_rvalue_test.v new file mode 100644 index 000000000..a09e99de4 --- /dev/null +++ b/vlib/v/tests/fns/variadic_voidptr_rvalue_test.v @@ -0,0 +1,15 @@ +import strconv + +struct VariadicVoidptrValue { +mut: + x u8 +} + +fn test_variadic_voidptr_rvalues_are_boxed_with_promotions() { + mut value := VariadicVoidptrValue{ + x: 1 + } + assert unsafe { strconv.v_sprintf('x=%02d', value.x) } == 'x=01' + assert unsafe { strconv.v_sprintf('x=%02d', int(value.x)) } == 'x=01' + assert unsafe { strconv.v_sprintf('%s %.1f', 'abc', f32(1.5)) } == 'abc 1.5' +} -- 2.39.5