From c46583b86bed573c4ff30685dae59874f124e664 Mon Sep 17 00:00:00 2001 From: James O'Leary <65884233+jpohhhh@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:37:22 -0700 Subject: [PATCH] common/parser : fix out_of_range crash in throw path (#20424 regression) (#20777) * chat : fix out_of_range crash in throw path (#20424 regression) #20424 introduced effective_input = generation_prompt + input, but the throw path uses input.substr(result.end) where result.end is a position within effective_input. Every thinking model with a non-empty generation_prompt crashes with std::out_of_range instead of the intended error message. Test crashes on unpatched master, passes with fix: cmake -B build -DLLAMA_BUILD_TESTS=ON -DLLAMA_BUILD_TOOLS=OFF cmake --build build --target test-chat ./build/bin/test-chat * Update test-chat.cpp * Update test-chat.cpp * Update test-chat.cpp --------- Co-authored-by: Piotr Wilkin (ilintar) --- common/chat.cpp | 2 +- tests/test-chat.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/common/chat.cpp b/common/chat.cpp index e129581fd2..a79d564b34 100644 --- a/common/chat.cpp +++ b/common/chat.cpp @@ -1774,7 +1774,7 @@ common_chat_msg common_chat_peg_parse(const common_peg_arena & src_pars return msg; } throw std::runtime_error(std::string("Failed to parse input at pos ") + std::to_string(result.end) + ": " + - input.substr(result.end)); + effective_input.substr(result.end)); } common_chat_msg msg; diff --git a/tests/test-chat.cpp b/tests/test-chat.cpp index 58fef8e99c..faac9e7306 100644 --- a/tests/test-chat.cpp +++ b/tests/test-chat.cpp @@ -1954,6 +1954,61 @@ static void test_template_output_peg_parsers(bool detailed_debug) { } } + // Verify the throw path produces a readable error message, not std::out_of_range. + // #20424 introduced effective_input = generation_prompt + input, but the throw + // uses input.substr(result.end) where result.end is in effective_input space. + { + auto tmpls = common_chat_templates_ptr( + common_chat_templates_init(nullptr, read_file("models/templates/GLM-4.7-Flash.jinja"))); + + static common_chat_tool weather_tool{ + "get_weather", "Get weather", + R"({"type":"object","properties":{"city":{"type":"string"}},"required":["city"]})", + }; + + common_chat_templates_inputs inputs; + inputs.tools = { weather_tool }; + inputs.enable_thinking = true; + inputs.reasoning_format = COMMON_REASONING_FORMAT_AUTO; + inputs.add_generation_prompt = true; + inputs.use_jinja = true; + common_chat_msg msg; + msg.role = "user"; + msg.content = "get_weather"; + inputs.messages = { msg }; + + auto params = common_chat_templates_apply(tmpls.get(), inputs); + common_peg_arena arena; + arena.load(params.parser); + common_chat_parser_params pp(params); + + // generation_prompt is non-empty for thinking models, so result.end + // will be offset by generation_prompt.size() into effective_input space. + assert(!pp.generation_prompt.empty()); + + std::string bad_input = + "Thinking.\n" + "" + "get_weather" + "cityTokyo" + "\n"; + + bool got_runtime_error = false; + bool got_out_of_range = false; + std::string error_msg; + try { + common_chat_peg_parse(arena, bad_input, /*is_partial=*/false, pp); + } catch (const std::out_of_range & e) { + got_out_of_range = true; + error_msg = e.what(); + } catch (const std::runtime_error & e) { + got_runtime_error = true; + error_msg = e.what(); + } + GGML_ASSERT(!got_out_of_range && "throw path crashed with out_of_range (input.substr in effective_input space)"); + GGML_ASSERT(got_runtime_error && "throw path should produce std::runtime_error with parse position"); + } + // Kimi-K2-Thinking tests - custom parser // Unique feature: tool call ID embeds function name as functions.: {