From f225dcbfe4c2289adceafa40dfd0bd562cbedf63 Mon Sep 17 00:00:00 2001 From: Andrew Hundt Date: Wed, 1 Jul 2026 09:45:48 -0400 Subject: [PATCH] fix(extract): restore LLVM IR call edges Use the LLVM grammar's instruction_call, instruction_invoke, and instruction_callbr node names instead of dead call/invoke token names, so call extraction reaches real instruction nodes. Teach the shared field-based callee resolver to read a callee field and unwrap simple value/var/expression wrappers, which keeps the fix reusable for grammars that expose a semantic callee field. Add an exact LLVM IR canary for call void @callee(). Failed-first validation produced 246 passed / 1 failed before the fix; after the fix, extraction passes 247/247. Also passed git diff --check, scripts/check-source-safety.sh, and make -j8 -f Makefile.cbm build/c/test-runner. Signed-off-by: Andrew Hundt --- internal/cbm/extract_calls.c | 13 +++++++++++++ internal/cbm/lang_specs.c | 3 ++- tests/test_extraction.c | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index b9f9cb280..f68fefb05 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -312,6 +312,19 @@ static char *extract_callee_from_fields(CBMArena *a, TSNode node, const char *so return method; } + TSNode callee_node = ts_node_child_by_field_name(node, TS_FIELD("callee")); + if (!ts_node_is_null(callee_node)) { + while (ts_node_named_child_count(callee_node) == 1) { + const char *ck = ts_node_type(callee_node); + if (strcmp(ck, "value") != 0 && strcmp(ck, "var") != 0 && + strcmp(ck, "expression") != 0) { + break; + } + callee_node = ts_node_named_child(callee_node, 0); + } + return cbm_node_text(a, callee_node, source); + } + return NULL; } diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 869e9fcd8..4d50142a7 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -973,7 +973,8 @@ static const char *d_throw_types[] = {"throw_expression", NULL}; // ==================== LLVM IR ==================== static const char *llvm_func_types[] = {"function_header", NULL}; -static const char *llvm_call_types[] = {"call", "invoke", NULL}; +static const char *llvm_call_types[] = {"instruction_call", "instruction_invoke", + "instruction_callbr", NULL}; static const char *llvm_branch_types[] = {"br", "switch", NULL}; static const char *llvm_var_types[] = {"local_var", "global_var", NULL}; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 029a51b78..c8dcec0c2 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -2207,6 +2207,21 @@ TEST(just_function_call_edge) { PASS(); } +TEST(llvm_call_edge) { + CBMFileResult *r = extract("declare void @callee()\n" + "define void @caller() {\n" + "entry:\n" + " call void @callee()\n" + " ret void\n" + "}\n", + CBM_LANG_LLVM_IR, "test", "calls.ll"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call_exact(r, "@callee")); + cbm_free_result(r); + PASS(); +} + TEST(puppet_function_call_edge) { CBMFileResult *r = extract("notice('ready')\n", CBM_LANG_PUPPET, "test", "site.pp"); ASSERT_NOT_NULL(r); @@ -3445,6 +3460,7 @@ SUITE(extraction) { RUN_TEST(makefile_variable_extraction); RUN_TEST(makefile_builtin_call_edges); RUN_TEST(just_function_call_edge); + RUN_TEST(llvm_call_edge); RUN_TEST(puppet_function_call_edge); RUN_TEST(vimscript_function_extraction); RUN_TEST(vimscript_function_without_bang);