fix(extract): restore NASM call edges

Add a failed-first NASM canary for ordinary call puts syntax and restore extraction by matching the grammar's actual_instruction node.

The NASM resolver is language-gated, requires the opcode to be call using portable ASCII case folding, and returns the first operand symbol so non-call instructions such as ret are ignored.

Validation: failed-first extraction produced 247 passed / 1 failed at nasm_call_edge; after the fix, git diff --check, scripts/check-source-safety.sh, make -j8 -f Makefile.cbm build/c/test-runner, and CBM_ONLY_SUITE=extraction ./build/c/test-runner passed with 248/248.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
Andrew Hundt
2026-07-01 09:53:05 -04:00
parent f225dcbfe4
commit d032e0484b
3 changed files with 54 additions and 1 deletions
+38
View File
@@ -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);
+1 -1
View File
@@ -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};
+15
View File
@@ -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);