From 21e8cf56c8fdbc71d752b75857964f26570547a0 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:18 +0300 Subject: [PATCH] json: fix decode() segfault on recursive struct with ?&t (fixes #26385) --- vlib/json/tests/json_decode_struct_ptr_test.v | 20 +++++++++++++++++++ vlib/v/gen/c/json.v | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/vlib/json/tests/json_decode_struct_ptr_test.v b/vlib/json/tests/json_decode_struct_ptr_test.v index 954d025c2..509f06031 100644 --- a/vlib/json/tests/json_decode_struct_ptr_test.v +++ b/vlib/json/tests/json_decode_struct_ptr_test.v @@ -7,6 +7,13 @@ mut: reply_to &Message } +struct OptionalMessage { +mut: + id int + text string + reply_to ?&OptionalMessage +} + fn test_main() { mut json_data := '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi"}}' mut message := json.decode(Message, json_data)! @@ -20,3 +27,16 @@ fn test_main() { message = json.decode(Message, json_data)! assert message.reply_to.reply_to.id == 5 } + +fn test_recursive_optional_struct_ptr() { + json_data := '{"id": 1, "text": "Hello", "reply_to": {"id": 2, "text": "Hi", "reply_to": null}}' + message := json.decode(OptionalMessage, json_data)! + reply := message.reply_to or { + assert false + return + } + + assert reply.id == 2 + assert reply.text == 'Hi' + assert reply.reply_to == none +} diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index 8a277fcdc..acbcb0759 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -129,7 +129,14 @@ ${dec_fn_dec} { base_type := utyp.clear_flag(.option) base_type_str := g.styp(base_type) - dec.writeln('\tbuiltin___option_ok(&(${base_type_str}[]){ ${g.type_default(base_type)} }, (${styp}*)&res, sizeof(${base_type_str}));\n') + // Optional struct pointers need storage before field decoding writes through them. + base_value := if base_type.is_ptr() && sym.info is ast.Struct { + ptr_styp := g.styp(base_type.set_nr_muls(base_type.nr_muls() - 1)) + 'HEAP(${ptr_styp}, {0})' + } else { + g.type_default(base_type) + } + dec.writeln('\tbuiltin___option_ok(&(${base_type_str}[]){ ${base_value} }, (${styp}*)&res, sizeof(${base_type_str}));\n') } extern_str := if g.pref.parallel_cc { 'extern ' } else { '' } -- 2.39.5