From db51890598ade2b03c7bac55cc33f19703f70982 Mon Sep 17 00:00:00 2001 From: Smiie-2 Date: Sun, 17 May 2026 20:18:12 +0200 Subject: [PATCH] Fix C++ template call segfault --- internal/cbm/extract_defs.c | 35 +++++++++++++++++++++------------- internal/cbm/lsp/c_lsp.c | 38 +++++++++++++++++++++++++++++++++---- internal/cbm/lsp/type_rep.c | 4 ++-- tests/test_c_lsp.c | 20 +++++++++++++++++++ tests/test_type_rep.c | 16 ++++++++++++++++ 5 files changed, 94 insertions(+), 19 deletions(-) diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 38da65ef..ec8cb1e4 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -515,16 +515,32 @@ static TSNode resolve_r_func_name(TSNode node) { // Forward declaration for mutual recursion. static TSNode resolve_func_name(TSNode node, CBMLanguage lang); +static bool is_cpp_template_inner_kind(const char *kind) { + return strcmp(kind, "function_definition") == 0 || strcmp(kind, "declaration") == 0 || + strcmp(kind, "field_declaration") == 0; +} + // C++/CUDA: find inner function/declaration inside template_declaration. // Returns the inner node (not the resolved name) to break the recursive cycle. -static TSNode resolve_template_inner_node(TSNode node) { +static TSNode find_cpp_template_inner_node(TSNode node, CBMLanguage lang) { + if ((lang != CBM_LANG_CPP && lang != CBM_LANG_CUDA) || + strcmp(ts_node_type(node), "template_declaration") != 0) { + return node; + } + uint32_t nc = ts_node_named_child_count(node); for (uint32_t i = 0; i < nc; i++) { TSNode ch = ts_node_named_child(node, i); const char *ck = ts_node_type(ch); - if (strcmp(ck, "function_definition") == 0 || strcmp(ck, "declaration") == 0) { + if (is_cpp_template_inner_kind(ck)) { return ch; } + if (strcmp(ck, "template_declaration") == 0) { + TSNode nested = find_cpp_template_inner_node(ch, lang); + if (!ts_node_is_null(nested) && !ts_node_eq(nested, ch)) { + return nested; + } + } } TSNode null_node = {0}; return null_node; @@ -548,7 +564,7 @@ static TSNode resolve_toplevel_arrow_name(TSNode node, const char *kind) { static TSNode resolve_func_name_c_family(TSNode *node_ptr, CBMLanguage lang, const char *kind) { if ((lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA) && strcmp(kind, "template_declaration") == 0) { - TSNode inner = resolve_template_inner_node(*node_ptr); + TSNode inner = find_cpp_template_inner_node(*node_ptr, lang); if (!ts_node_is_null(inner)) { *node_ptr = inner; /* signal caller to retry */ } @@ -1588,16 +1604,9 @@ static const char **extract_param_types(CBMArena *a, TSNode params, const char * // For C++/CUDA template_declaration, find the inner function_definition or declaration. static TSNode unwrap_template_inner(TSNode node, CBMLanguage lang) { - if ((lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA) && - strcmp(ts_node_type(node), "template_declaration") == 0) { - uint32_t nc = ts_node_named_child_count(node); - for (uint32_t i = 0; i < nc; i++) { - TSNode ch = ts_node_named_child(node, i); - const char *ck = ts_node_type(ch); - if (strcmp(ck, "function_definition") == 0 || strcmp(ck, "declaration") == 0) { - return ch; - } - } + TSNode inner = find_cpp_template_inner_node(node, lang); + if (!ts_node_is_null(inner)) { + return inner; } return node; } diff --git a/internal/cbm/lsp/c_lsp.c b/internal/cbm/lsp/c_lsp.c index 12eda50e..9b76358c 100644 --- a/internal/cbm/lsp/c_lsp.c +++ b/internal/cbm/lsp/c_lsp.c @@ -298,11 +298,18 @@ 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 + const CBMType** formal_params = NULL; + int formal_count = 0; if (callee->signature && callee->signature->kind == CBM_TYPE_FUNC && callee->signature->data.func.param_types) { - for (int i = 0; i < call_arg_count; i++) { - const CBMType* formal = callee->signature->data.func.param_types[i]; + formal_params = callee->signature->data.func.param_types; + while (formal_params[formal_count]) formal_count++; + } + + // Match call arg types against function param types to deduce type params + if (formal_params) { + for (int i = 0; i < call_arg_count && i < formal_count; i++) { + const CBMType* formal = formal_params[i]; if (!formal || !call_arg_types[i]) continue; // Unwrap references/pointers while (formal && (formal->kind == CBM_TYPE_REFERENCE || @@ -1712,6 +1719,27 @@ static const CBMType* c_eval_expr_type_inner(CLSPContext* ctx, TSNode node) { const char* qn = c_build_qn(ctx, fname); rf = cbm_registry_lookup_func(ctx->registry, qn); } + } else if (strcmp(fn_type, "template_function") == 0) { + TSNode name_node = ts_node_child_by_field_name(func_node, "name", 4); + if (!ts_node_is_null(name_node)) { + char* fname = c_node_text(ctx, name_node); + if (fname) { + const char* nk = ts_node_type(name_node); + if (strcmp(nk, "qualified_identifier") == 0 || + strcmp(nk, "scoped_identifier") == 0) { + const char* qn = c_build_qn(ctx, fname); + rf = cbm_registry_lookup_func(ctx->registry, qn); + if (!rf && ctx->module_qn) { + rf = cbm_registry_lookup_func(ctx->registry, + cbm_arena_sprintf(ctx->arena, "%s.%s", + ctx->module_qn, qn)); + } + } else { + const char* fqn = c_resolve_name(ctx, fname); + if (fqn) rf = cbm_registry_lookup_func(ctx->registry, fqn); + } + } + } } if (rf && rf->type_param_names && rf->signature && @@ -1766,8 +1794,9 @@ static const CBMType* c_eval_expr_type_inner(CLSPContext* ctx, TSNode node) { if (deduced[ti]) { any_deduced = true; break; } } if (any_deduced) { - ret = cbm_type_substitute(ctx->arena, ret, + const CBMType* substituted_ret = cbm_type_substitute(ctx->arena, ret, rf->type_param_names, deduced); + if (substituted_ret) ret = substituted_ret; } } } @@ -1775,6 +1804,7 @@ static const CBMType* c_eval_expr_type_inner(CLSPContext* ctx, TSNode node) { } // Unwrap references in return type + if (!ret) return cbm_type_unknown(); if (ret->kind == CBM_TYPE_REFERENCE || ret->kind == CBM_TYPE_RVALUE_REF) ret = ret->data.reference.elem; return ret; diff --git a/internal/cbm/lsp/type_rep.c b/internal/cbm/lsp/type_rep.c index 0f74c49e..20d4e8b6 100644 --- a/internal/cbm/lsp/type_rep.c +++ b/internal/cbm/lsp/type_rep.c @@ -666,7 +666,7 @@ const CBMType* cbm_type_substitute(CBMArena* a, const CBMType* t, case CBM_TYPE_TYPE_PARAM: { for (int i = 0; type_params[i]; i++) { if (strcmp(t->data.type_param.name, type_params[i]) == 0) { - return type_args[i]; + return type_args[i] ? type_args[i] : t; } } return t; // unmatched param stays as-is @@ -682,7 +682,7 @@ const CBMType* cbm_type_substitute(CBMArena* a, const CBMType* t, for (int i = 0; type_params[i]; i++) { if (strcmp(qn, type_params[i]) == 0 || strcmp(short_name, type_params[i]) == 0) { - return type_args[i]; + return type_args[i] ? type_args[i] : t; } } } diff --git a/tests/test_c_lsp.c b/tests/test_c_lsp.c index a146b23e..889bc3fe 100644 --- a/tests/test_c_lsp.c +++ b/tests/test_c_lsp.c @@ -540,6 +540,25 @@ TEST(clsp_nocrash_template_expression) { PASS(); } +TEST(clsp_nocrash_template_function_multi_param_nested_call) { + CBMFileResult *r = extract_cpp("\n" + "void right_aligned_text(int color, int width, const char* fmt, float value) {}\n" + "\n" + "template\n" + "R format_units(T value, const char*& unit) { return value; }\n" + "\n" + "void f() {\n" + " const char* unit = nullptr;\n" + " right_aligned_text(0, 0, \"%.1f\", format_units(1, unit));\n" + "}\n" + ""); + ASSERT_NOT_NULL(r); + ASSERT_GTE(find_resolved(r, "f", "right_aligned_text"), 0); + ASSERT_GTE(find_resolved(r, "f", "format_units"), 0); + cbm_free_result(r); + PASS(); +} + TEST(clsp_nocrash_lambda) { CBMFileResult *r = extract_cpp("\n" "void test() {\n" @@ -15101,6 +15120,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_function_multi_param_nested_call); RUN_TEST(clsp_nocrash_lambda); RUN_TEST(clsp_nocrash_nested_namespace); RUN_TEST(clsp_nocrash_empty_source); diff --git a/tests/test_type_rep.c b/tests/test_type_rep.c index 4bfc49f7..e1281ce7 100644 --- a/tests/test_type_rep.c +++ b/tests/test_type_rep.c @@ -230,6 +230,20 @@ TEST(typerep_callable_equality) { PASS(); } +/* ── SUBSTITUTION ──────────────────────────────────────────────── */ + +TEST(typerep_substitute_unbound_param_preserved) { + CBMArena a; + cbm_arena_init(&a); + const CBMType *t = cbm_type_type_param(&a, "T"); + const char *params[] = {"T", NULL}; + const CBMType *args[] = {NULL, NULL}; + const CBMType *sub = cbm_type_substitute(&a, t, params, args); + ASSERT(sub == t); + cbm_arena_destroy(&a); + PASS(); +} + /* ── Suite ─────────────────────────────────────────────────────── */ SUITE(type_rep) { @@ -254,4 +268,6 @@ SUITE(type_rep) { RUN_TEST(typerep_callable_with_args_and_return); RUN_TEST(typerep_callable_elliptic_arity_minus_one); RUN_TEST(typerep_callable_equality); + /* SUBSTITUTION */ + RUN_TEST(typerep_substitute_unbound_param_preserved); }