Skip to content

Commit bbaca2a

Browse files
authored
Merge pull request #447 from isc-tdyar/fix/string-dispatch-dead-code
fix(extract): string-dispatch CALLS edges never emitted — detection was in dead code
2 parents ca5c0d2 + 0b94f44 commit bbaca2a

4 files changed

Lines changed: 71 additions & 80 deletions

File tree

internal/cbm/cbm.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,6 @@ void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch);
584584
// --- Sub-extractor entry points ---
585585

586586
void cbm_extract_definitions(CBMExtractCtx *ctx);
587-
void cbm_extract_calls(CBMExtractCtx *ctx);
588587
void cbm_extract_imports(CBMExtractCtx *ctx);
589588
void cbm_extract_usages(CBMExtractCtx *ctx);
590589
void cbm_extract_semantic(CBMExtractCtx *ctx);

internal/cbm/extract_calls.c

Lines changed: 49 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,25 @@ static const char *strip_quotes(CBMArena *a, const char *text) {
5858
return text;
5959
}
6060

61+
// Callee suffixes for IRIS Python interop string-dispatch. Kept at file scope
62+
// (not inside the function) to satisfy cppcheck variableScope.
63+
static const char *s_py_dispatch_suffixes[] = {".classMethodValue", ".classMethodVoid",
64+
".classMethodBoolean", ".classMethodObject", NULL};
65+
66+
// Per-language callee-suffix dispatch table — returns a NULL-terminated list of
67+
// method-name suffixes whose calls should be resolved by extracting class+method
68+
// from the first two string arguments (e.g. IRIS Python interop). Kept here
69+
// rather than in CBMLangSpec to avoid -Wmissing-field-initializers across 155
70+
// language rows.
71+
const char **cbm_string_dispatch_suffixes(CBMLanguage lang) {
72+
if (lang == CBM_LANG_PYTHON) {
73+
return s_py_dispatch_suffixes;
74+
}
75+
return NULL;
76+
}
77+
6178
// Forward declarations
62-
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec);
6379
static char *extract_callee_name(CBMArena *a, TSNode node, const char *source, CBMLanguage lang);
64-
static void extract_jsx_refs(CBMExtractCtx *ctx, TSNode node);
6580
static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source);
6681

6782
// Lean 4: check if an apply node is inside a type annotation.
@@ -735,20 +750,6 @@ static const char *strip_and_validate_string_arg(CBMArena *a, char *text) {
735750
return text;
736751
}
737752

738-
// Extract first string argument from a call's arguments node.
739-
static const char *extract_first_string_arg(CBMExtractCtx *ctx, TSNode args) {
740-
uint32_t nc = ts_node_named_child_count(args);
741-
for (uint32_t ai = 0; ai < nc && ai < MAX_POSITIONAL_SCAN; ai++) {
742-
TSNode arg = ts_node_named_child(args, ai);
743-
const char *ak = ts_node_type(arg);
744-
if (is_string_like(ak)) {
745-
char *text = cbm_node_text(ctx->arena, arg, ctx->source);
746-
return strip_and_validate_string_arg(ctx->arena, text);
747-
}
748-
}
749-
return NULL;
750-
}
751-
752753
// Return the (dequoted) first string-literal child of a node, or NULL.
753754
static char *gotemplate_string_child(CBMArena *a, TSNode parent, const char *source) {
754755
TSNode s = cbm_find_child_by_kind(parent, "interpreted_string_literal");
@@ -791,72 +792,21 @@ static char *gotemplate_callee(CBMArena *a, TSNode node, const char *source) {
791792
return NULL;
792793
}
793794

794-
// Walk AST for call nodes (iterative)
795-
static void walk_calls(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
796-
TSNodeStack stack;
797-
ts_nstack_init(&stack, ctx->arena, CBM_SZ_512);
798-
ts_nstack_push(&stack, ctx->arena, root);
799-
800-
while (stack.count > 0) {
801-
TSNode node = ts_nstack_pop(&stack);
802-
const char *kind = ts_node_type(node);
803-
804-
if (cbm_kind_in_set(node, spec->call_node_types)) {
805-
char *callee = extract_callee_name(ctx->arena, node, ctx->source, ctx->language);
806-
if (callee && callee[0] && !cbm_is_keyword(callee, ctx->language)) {
807-
CBMCall call = {0};
808-
call.callee_name = callee;
809-
call.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
810-
811-
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
812-
if (!ts_node_is_null(args)) {
813-
call.first_string_arg = extract_first_string_arg(ctx, args);
814-
}
815-
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
816-
}
817-
}
818-
819-
if (ctx->language == CBM_LANG_TSX || ctx->language == CBM_LANG_JAVASCRIPT) {
820-
if (strcmp(kind, "jsx_self_closing_element") == 0 ||
821-
strcmp(kind, "jsx_opening_element") == 0) {
822-
extract_jsx_refs(ctx, node);
795+
static const char *extract_nth_string_arg(CBMExtractCtx *ctx, TSNode args, uint32_t n) {
796+
uint32_t nc = ts_node_named_child_count(args);
797+
uint32_t found = 0;
798+
for (uint32_t ai = 0; ai < nc && ai < MAX_POSITIONAL_SCAN + n; ai++) {
799+
TSNode arg = ts_node_named_child(args, ai);
800+
const char *ak = ts_node_type(arg);
801+
if (is_string_like(ak)) {
802+
if (found == n) {
803+
char *text = cbm_node_text(ctx->arena, arg, ctx->source);
804+
return strip_and_validate_string_arg(ctx->arena, text);
823805
}
806+
found++;
824807
}
825-
826-
ts_nstack_push_children(&stack, ctx->arena, node);
827808
}
828-
}
829-
830-
// Extract JSX component references (uppercase = component, lowercase = HTML)
831-
static void extract_jsx_refs(CBMExtractCtx *ctx, TSNode node) {
832-
TSNode name_node = ts_node_child_by_field_name(node, TS_FIELD("name"));
833-
if (ts_node_is_null(name_node)) {
834-
return;
835-
}
836-
837-
char *name = cbm_node_text(ctx->arena, name_node, ctx->source);
838-
if (!name || !name[0]) {
839-
return;
840-
}
841-
842-
// Only uppercase names are components
843-
if (name[0] < 'A' || name[0] > 'Z') {
844-
return;
845-
}
846-
847-
CBMCall call = {0};
848-
call.callee_name = name;
849-
call.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
850-
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
851-
}
852-
853-
void cbm_extract_calls(CBMExtractCtx *ctx) {
854-
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
855-
if (!spec || !spec->call_node_types || !spec->call_node_types[0]) {
856-
return;
857-
}
858-
859-
walk_calls(ctx, ctx->root, spec);
809+
return NULL;
860810
}
861811

862812
// --- Unified handler: called once per node by the cursor walk ---
@@ -1204,6 +1154,26 @@ void handle_calls(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec, Walk
12041154
}
12051155

12061156
cbm_calls_push(&ctx->result->calls, ctx->arena, call);
1157+
1158+
const char **dispatch_suffixes = cbm_string_dispatch_suffixes(ctx->language);
1159+
if (dispatch_suffixes && !ts_node_is_null(args)) {
1160+
const char *cn = call.callee_name;
1161+
size_t len = cn ? strlen(cn) : 0;
1162+
for (const char **nm = dispatch_suffixes; *nm; nm++) {
1163+
size_t nlen = strlen(*nm);
1164+
if (len >= nlen && strcmp(cn + len - nlen, *nm) == 0) {
1165+
const char *cls = extract_nth_string_arg(ctx, args, 0);
1166+
const char *mth = extract_nth_string_arg(ctx, args, 1);
1167+
if (cls && mth) {
1168+
CBMCall xcall = {0};
1169+
xcall.callee_name = cbm_arena_sprintf(ctx->arena, "%s.%s", cls, mth);
1170+
xcall.enclosing_func_qn = call.enclosing_func_qn;
1171+
cbm_calls_push(&ctx->result->calls, ctx->arena, xcall);
1172+
}
1173+
break;
1174+
}
1175+
}
1176+
}
12071177
}
12081178
}
12091179

internal/cbm/lang_specs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ typedef struct {
4141
const CBMEmbeddedLangSpec *embedded_imports;
4242
} CBMLangSpec;
4343

44+
// Returns a NULL-terminated list of callee-name suffixes that indicate a
45+
// string-dispatch call for a given language (e.g. ".classMethodValue" for
46+
// Python/IRIS), or NULL if the language has no such dispatch pattern.
47+
// Kept out of CBMLangSpec to avoid -Wmissing-field-initializers across 155
48+
// language rows; the table lives in extract_calls.c next to the dispatch code.
49+
const char **cbm_string_dispatch_suffixes(CBMLanguage lang);
50+
4451
// Get the language spec for a given language. Returns NULL for unsupported.
4552
const CBMLangSpec *cbm_lang_spec(CBMLanguage lang);
4653

tests/test_extraction.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,20 @@ TEST(python_calls) {
20192019
PASS();
20202020
}
20212021

2022+
TEST(python_iris_classMethodValue) {
2023+
CBMFileResult *r = extract(
2024+
"import iris\n"
2025+
"iris_obj = iris.cls('%Library.ObjectScript')\n"
2026+
"def call_bfs(n):\n"
2027+
" return iris_obj.classMethodValue('Graph.KG.TraversalBFS', 'BFSFastJson', n)\n",
2028+
CBM_LANG_PYTHON, "t", "store.py");
2029+
ASSERT_NOT_NULL(r);
2030+
ASSERT_FALSE(r->has_error);
2031+
ASSERT(has_call(r, "Graph.KG.TraversalBFS.BFSFastJson"));
2032+
cbm_free_result(r);
2033+
PASS();
2034+
}
2035+
20222036
TEST(go_calls) {
20232037
CBMFileResult *r =
20242038
extract("package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"hello\") }\n",
@@ -3100,6 +3114,7 @@ SUITE(extraction) {
31003114

31013115
/* Cross-cutting */
31023116
RUN_TEST(python_calls);
3117+
RUN_TEST(python_iris_classMethodValue);
31033118
RUN_TEST(go_calls);
31043119
RUN_TEST(python_imports);
31053120
RUN_TEST(js_imports);

0 commit comments

Comments
 (0)