From 4953fcaf21d6b414b2a219fe011e6d4c843f3b90 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Thu, 25 Jun 2026 21:42:31 +0200 Subject: [PATCH] test(extract): guard C++ out-of-line method/ctor/dtor enclosing attribution After the C declarator-name walker was de-duplicated into one shared helper (cbm_resolve_c_declarator_name_node), the C/C++ enclosing-function resolver now resolves qualified names (Foo::bar) via resolve_qualified_name() and no longer treats type_identifier as a terminal name. Add reproduce-first coverage so the #438 fix cannot silently regress on the qualified-declarator path: - cpp_out_of_line_method_caller_attribution: a call inside `void Foo::bar()` must attribute to the method, not the module. - cpp_out_of_line_ctor_dtor_caller_attribution: calls inside `Foo::Foo()` and `Foo::~Foo()` must attribute to the special member, not the module. Signed-off-by: Martin Vogel --- tests/test_extraction.c | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 9aba9dc7..7b2a1071 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -1812,6 +1812,62 @@ TEST(c_caller_attribution) { PASS(); } +/* adc8304 (the dedup refactor bundled into #463) re-pointed the C/C++ enclosing- + * function resolver at the canonical declarator walker: qualified names (Foo::bar) + * now resolve via resolve_qualified_name(), and `type_identifier` was dropped from + * the terminal-name set. These guard that out-of-line C++ method / ctor / dtor + * definitions still attribute their inner calls to the enclosing function, not the + * module — i.e. that the dedup did not reintroduce the #438 regression on the + * qualified-declarator path. Module QN for "m.cpp" under prefix "t" is "t.m". */ +TEST(cpp_out_of_line_method_caller_attribution) { + CBMFileResult *r = extract("struct Foo { void bar(); };\n" + "int helper(int x) { return x; }\n" + "void Foo::bar() { helper(1); }\n", + CBM_LANG_CPP, "t", "m.cpp"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_GT(r->calls.count, 0); + int saw_helper = 0; + for (int i = 0; i < r->calls.count; i++) { + if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { + saw_helper = 1; + /* enclosing must be the out-of-line method, NOT empty and NOT the module. */ + ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); + ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); + ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.m") == 0); + } + } + ASSERT(saw_helper); + cbm_free_result(r); + PASS(); +} + +/* Out-of-line constructor (Foo::Foo) and destructor (Foo::~Foo) exercise the + * identifier and destructor_name branches of resolve_qualified_name(). A call + * inside either must attribute to that special member, not the module. */ +TEST(cpp_out_of_line_ctor_dtor_caller_attribution) { + CBMFileResult *r = extract("struct Foo { Foo(); ~Foo(); };\n" + "int helper(int x) { return x; }\n" + "Foo::Foo() { helper(1); }\n" + "Foo::~Foo() { helper(2); }\n", + CBM_LANG_CPP, "t", "m.cpp"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_GT(r->calls.count, 0); + int helper_calls = 0; + for (int i = 0; i < r->calls.count; i++) { + if (strcmp(r->calls.items[i].callee_name, "helper") == 0) { + helper_calls++; + ASSERT_NOT_NULL(r->calls.items[i].enclosing_func_qn); + ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "") == 0); + ASSERT_FALSE(strcmp(r->calls.items[i].enclosing_func_qn, "t.m") == 0); + } + } + ASSERT(helper_calls >= 1); + cbm_free_result(r); + PASS(); +} + /* --- Wolfram parse (simple assignment) --- */ TEST(wolfram_parse) { CBMFileResult *r = extract("x = 42;\ny = x + 1;\n", CBM_LANG_WOLFRAM, "t", "simple.wl"); @@ -3117,6 +3173,8 @@ SUITE(extraction) { RUN_TEST(wolfram_call); RUN_TEST(wolfram_caller_attribution); RUN_TEST(c_caller_attribution); + RUN_TEST(cpp_out_of_line_method_caller_attribution); + RUN_TEST(cpp_out_of_line_ctor_dtor_caller_attribution); RUN_TEST(wolfram_parse); RUN_TEST(wolfram_import); RUN_TEST(wolfram_nested_def);