diff --git a/internal/cbm/lsp/c_lsp.c b/internal/cbm/lsp/c_lsp.c index 2513a7d0..0ee3294c 100644 --- a/internal/cbm/lsp/c_lsp.c +++ b/internal/cbm/lsp/c_lsp.c @@ -279,10 +279,15 @@ static void c_add_pending_template_call(CLSPContext* ctx, const char* func_qn, ctx->pending_template_calls = new_arr; ctx->pending_tc_cap = new_cap; } + const char* func_qn_copy = cbm_arena_strdup(ctx->arena, func_qn); + const char* type_param_copy = cbm_arena_strdup(ctx->arena, type_param); + const char* method_name_copy = cbm_arena_strdup(ctx->arena, method_name); + if (!func_qn_copy || !type_param_copy || !method_name_copy) return; + int i = ctx->pending_tc_count++; - ctx->pending_template_calls[i].func_qn = cbm_arena_strdup(ctx->arena, func_qn); - ctx->pending_template_calls[i].type_param = cbm_arena_strdup(ctx->arena, type_param); - ctx->pending_template_calls[i].method_name = cbm_arena_strdup(ctx->arena, method_name); + ctx->pending_template_calls[i].func_qn = func_qn_copy; + ctx->pending_template_calls[i].type_param = type_param_copy; + ctx->pending_template_calls[i].method_name = method_name_copy; ctx->pending_template_calls[i].arg_count = arg_count; } @@ -298,10 +303,16 @@ static void c_resolve_pending_template_calls(CLSPContext* ctx, int tpn_count = 0; while (tpn[tpn_count] && tpn_count < 8) tpn_count++; - // Match call arg types against function param types to deduce type params + // Match call arg types against function param types to deduce type params. + // The call site may contain more arguments than the parsed function signature + // knows about (invalid code, macros, variadic calls, or parser recovery). The + // signature arrays are NULL-terminated, so never index past the sentinel. if (callee->signature && callee->signature->kind == CBM_TYPE_FUNC && callee->signature->data.func.param_types) { - for (int i = 0; i < call_arg_count; i++) { + int formal_count = 0; + while (callee->signature->data.func.param_types[formal_count]) formal_count++; + int limit = call_arg_count < formal_count ? call_arg_count : formal_count; + for (int i = 0; i < limit; i++) { const CBMType* formal = callee->signature->data.func.param_types[i]; if (!formal || !call_arg_types[i]) continue; // Unwrap references/pointers @@ -327,6 +338,10 @@ static void c_resolve_pending_template_calls(CLSPContext* ctx, const char* saved_func_qn = ctx->enclosing_func_qn; ctx->enclosing_func_qn = callee->qualified_name; for (int i = 0; i < ctx->pending_tc_count; i++) { + if (!ctx->pending_template_calls[i].func_qn || + !ctx->pending_template_calls[i].type_param || + !ctx->pending_template_calls[i].method_name) + continue; if (strcmp(ctx->pending_template_calls[i].func_qn, callee->qualified_name) != 0) continue; const char* tp = ctx->pending_template_calls[i].type_param; diff --git a/tests/test_c_lsp.c b/tests/test_c_lsp.c index a146b23e..548f149c 100644 --- a/tests/test_c_lsp.c +++ b/tests/test_c_lsp.c @@ -540,6 +540,28 @@ TEST(clsp_nocrash_template_expression) { PASS(); } +TEST(clsp_nocrash_template_extra_call_args) { + CBMFileResult *r = extract_cpp("\n" + "class Widget {\n" + "public:\n" + " void render() {}\n" + "};\n" + "\n" + "template\n" + "void invoke(T item) {\n" + " item.render();\n" + "}\n" + "\n" + "void test() {\n" + " Widget w;\n" + " invoke(w, 1, 2);\n" + "}\n" + ""); + ASSERT_NOT_NULL(r); + cbm_free_result(r); + PASS(); +} + TEST(clsp_nocrash_lambda) { CBMFileResult *r = extract_cpp("\n" "void test() {\n" @@ -15101,6 +15123,7 @@ SUITE(c_lsp) { RUN_TEST(clsp_operator_stream); RUN_TEST(clsp_cross_file); RUN_TEST(clsp_nocrash_template_expression); + RUN_TEST(clsp_nocrash_template_extra_call_args); RUN_TEST(clsp_nocrash_lambda); RUN_TEST(clsp_nocrash_nested_namespace); RUN_TEST(clsp_nocrash_empty_source);