diff --git a/internal/cbm/extract_calls.c b/internal/cbm/extract_calls.c index f68fefb05..a6587aa3a 100644 --- a/internal/cbm/extract_calls.c +++ b/internal/cbm/extract_calls.c @@ -800,6 +800,40 @@ static char *extract_meson_callee(CBMArena *a, TSNode node, const char *source, return ts_node_is_null(cmd) ? NULL : cbm_node_text(a, cmd, source); } +static bool cbm_ascii_equals_ignore_case(const char *a, const char *b) { + if (!a || !b) { + return false; + } + while (*a && *b) { + if (tolower((unsigned char)*a) != tolower((unsigned char)*b)) { + return false; + } + a++; + b++; + } + return *a == '\0' && *b == '\0'; +} + +static char *extract_nasm_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { + if (strcmp(nk, "actual_instruction") != 0) { + return NULL; + } + TSNode opcode = ts_node_child_by_field_name(node, TS_FIELD("instruction")); + char *op = ts_node_is_null(opcode) ? NULL : cbm_node_text(a, opcode, source); + if (!cbm_ascii_equals_ignore_case(op, "call")) { + return NULL; + } + TSNode operands = ts_node_child_by_field_name(node, TS_FIELD("operands")); + if (ts_node_is_null(operands) || ts_node_named_child_count(operands) == 0) { + return NULL; + } + TSNode target = ts_node_named_child(operands, 0); + if (strcmp(ts_node_type(target), "operand") == 0 && ts_node_named_child_count(target) > 0) { + target = ts_node_named_child(target, 0); + } + return cbm_node_text(a, target, source); +} + // Puppet function_call stores its callee as the first named child. static char *extract_puppet_callee(CBMArena *a, TSNode node, const char *source, const char *nk) { @@ -878,6 +912,10 @@ static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char * char *c = extract_meson_callee(a, node, source, nk); return c ? c : extract_scripting_callee(a, node, source, lang, nk); } + if (lang == CBM_LANG_NASM) { + char *c = extract_nasm_callee(a, node, source, nk); + return c ? c : extract_scripting_callee(a, node, source, lang, nk); + } if (lang == CBM_LANG_PUPPET) { char *c = extract_puppet_callee(a, node, source, nk); return c ? c : extract_scripting_callee(a, node, source, lang, nk); diff --git a/internal/cbm/lang_specs.c b/internal/cbm/lang_specs.c index 4d50142a7..96ba3348a 100644 --- a/internal/cbm/lang_specs.c +++ b/internal/cbm/lang_specs.c @@ -1242,7 +1242,7 @@ static const char *sway_assign_types[] = {"assignment_expression", NULL}; static const char *sway_module_types[] = {"source_file", NULL}; static const char *nasm_func_types[] = {"label", "preproc_def", "preproc_multiline_macro", NULL}; static const char *nasm_class_types[] = {"struc_declaration", NULL}; -static const char *nasm_call_types[] = {"call_syntax_expression", NULL}; +static const char *nasm_call_types[] = {"actual_instruction", "call_syntax_expression", NULL}; static const char *nasm_import_types[] = {"preproc_include", NULL}; static const char *nasm_var_types[] = {"label", NULL}; static const char *nasm_module_types[] = {"source_file", NULL}; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index c8dcec0c2..b7c982efe 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -2222,6 +2222,20 @@ TEST(llvm_call_edge) { PASS(); } +TEST(nasm_call_edge) { + CBMFileResult *r = extract("global _start\n" + "extern puts\n" + "_start:\n" + " call puts\n" + " ret\n", + CBM_LANG_NASM, "test", "calls.asm"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_call_exact(r, "puts")); + 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); @@ -3461,6 +3475,7 @@ SUITE(extraction) { RUN_TEST(makefile_builtin_call_edges); RUN_TEST(just_function_call_edge); RUN_TEST(llvm_call_edge); + RUN_TEST(nasm_call_edge); RUN_TEST(puppet_function_call_edge); RUN_TEST(vimscript_function_extraction); RUN_TEST(vimscript_function_without_bang);