mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Why: a function-like macro in a definition's declarator,
`void CBasePlayer::__API_HOOK(ReloadWeapons)(int slot)` or
`static void FNAME(first_caller)(void)`, parses as a function_declarator
nested directly inside the definition's own function_declarator. The
declarator walk descended to the terminal identifier, which is the MACRO,
so every definition wrapped by the same macro got one qualified name.
ReGameDLL_CS's hook definitions landed as 20 nodes literally named
__API_HOOK, and the Linux kernel's arch/x86/kvm/mmu/paging_tmpl.h (19
FNAME(...) definitions) as a single Function FNAME. The last definition
won, calls in every other body were sourced to it with a call line
outside its range, and callers never reached the real functions. The C
LSP had its own copy of the walk: it named the caller after the macro and
bound the macro's argument list as the function's parameters, so member
calls through a real parameter went unresolved.
Fix: cbm_c_macro_wrapped_name_node() recognises the shape. Valid C/C++
never nests two function_declarators directly (a function cannot return a
function; a function-pointer return has a parenthesized_declarator in
between), so the nesting always means a macro call. When that macro is a
plain or class-qualified name taking exactly one bare-name argument, the
definition is named after the argument; any other shape (multi-argument
wrappers, non-name arguments) keeps today's walk. The shared C-family
declarator resolver uses it, so the def name, the call-scope QN and the
unified extractor agree; the C LSP uses it too and keeps the outer
(real) parameter list. The class scope of `Cls::MACRO(x)` was already
derived from the qualified name and is unchanged.
Proof: dev builds of origin/main 1f9b4db1 (before) and this change
(after), same machine, fresh indexes.
rehlds/ReGameDLL_CS @ 4a50c42 (#946):
- nodes named __API_HOOK: 20 -> 0
- Function 986 -> 1003, Method 3768 -> 3859: 129 hook definitions now
have their own node, and every added definition starts on a
`__API_HOOK(<name>)(` line (0 false definitions). Removed: the 20
__API_HOOK nodes, plus CGrenade::Explode(Vector, Vector), which now
shares CGrenade.Explode with its hooked overload, as any C++ overload
pair does today (one node per QN).
- CALLS 9436 -> 9916; CALLS sourced from __API_HOOK 410 -> 0; callable-
sourced CALLS whose call line lies outside the caller's range 565 -> 198.
- parse_partial files 131 -> 131.
- 5 samples checked against source (CBasePlayer::Spawn 5771-6142 with 32
in-range out-calls incl. ReloadWeapons via lsp_implicit_this,
CBasePlayer::TakeDamage, PM_Move, CGrenade::ShootSmokeGrenade,
CBotManager::OnEvent).
- 18 of the 147 hook definitions are still not extracted, before and
after: the parser loses them to raw ERROR regions (a `T *EXT_FUNC`
pointer return misparsed as a multiplication, a preceding
semicolon-less LINK_HOOK_*(...) invocation, an #ifdef-split
signature). That is parser recovery, not naming, and is left for a
follow-up.
Linux arch/x86/kvm/mmu @ 2d2338c93 (#1957):
- paging_tmpl.h: 1 Function FNAME -> 19 distinct functions (all 19
FNAME(...) definitions; 0 false definitions in the directory).
- CALLS from paging_tmpl.h functions with the call line outside the
caller's range: 32 -> 0 (directory: 35 -> 3, the 3 pre-existing in
mmu.c); e.g. fetch 651-786 now owns its 13 calls, sync_spte 934-1005
its 8.
- 5 samples checked against source (prefetch_gpte, walk_addr_generic,
fetch, sync_spte, page_fault).
- #1957's own reproduction: first_caller and later_function are distinct
Functions, sink() at line 9 is sourced to first_caller (7-10), and
main -> first_caller resolves.
Tests: extraction::extract_cpp_macro_wrapped_declarator_names_def_issue946
(qualified + unqualified + EXT_FUNC hooks, macro in a header: def names,
QNs, range, real parameter count, per-body call scope),
extraction::extract_c_fname_macro_definitions_stay_distinct_issue1957
(the reported C reproduction incl. the LSP caller QN, and the kernel
template shape with a token-pasting FNAME defined only under #if),
extraction::extract_c_macro_wrapped_declarator_controls_issue946
(precision controls: file-scope and in-body macro call statements, a call
through a returned function pointer, a real function returning a function
pointer, a macro-wrapped prototype and a two-argument wrapper mint no
definition named after a macro argument), and
c_lsp::clsp_macro_wrapped_declarator_caller_qn_issue946 (LSP caller QN,
lsp_implicit_this, typed parameter). RED 3x before, GREEN after, RED on
revert; the LSP half alone binds the c_lsp test and the #1957 test.
Fixes #946
Fixes #1957
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
2cd0956560
commit
2ef6ba7101
5 files changed
+306
No files matched your search
@@ -1065,6 +1065,42 @@ TSNode cbm_c_recovered_func_name(TSNode func_declarator) {
|
||||
return last;
|
||||
}
|
||||
|
||||
// Macro-wrapped definition name — see the header for the full rationale (#946).
|
||||
TSNode cbm_c_macro_wrapped_name_node(TSNode decl) {
|
||||
TSNode null_node = {0};
|
||||
if (ts_node_is_null(decl) || strcmp(ts_node_type(decl), "function_declarator") != 0) {
|
||||
return null_node;
|
||||
}
|
||||
TSNode macro = ts_node_child_by_field_name(decl, TS_FIELD("declarator"));
|
||||
if (ts_node_is_null(macro) || strcmp(ts_node_type(macro), "function_declarator") != 0) {
|
||||
return null_node;
|
||||
}
|
||||
// The macro itself is a plain or class-qualified name: FNAME / Cls::__API_HOOK.
|
||||
TSNode macro_name = ts_node_child_by_field_name(macro, TS_FIELD("declarator"));
|
||||
const char *mk = ts_node_is_null(macro_name) ? "" : ts_node_type(macro_name);
|
||||
if (strcmp(mk, "identifier") != 0 && strcmp(mk, "qualified_identifier") != 0) {
|
||||
return null_node;
|
||||
}
|
||||
// Exactly one argument, and that argument is a bare name: the grammar reads
|
||||
// it as a parameter whose type is the name, with no declarator or qualifier.
|
||||
// A multi-argument wrapper does not say which argument names the function.
|
||||
TSNode args = ts_node_child_by_field_name(macro, TS_FIELD("parameters"));
|
||||
if (ts_node_is_null(args) || ts_node_named_child_count(args) != SKIP_ONE) {
|
||||
return null_node;
|
||||
}
|
||||
TSNode arg = ts_node_named_child(args, 0);
|
||||
if (strcmp(ts_node_type(arg), "parameter_declaration") != 0 ||
|
||||
ts_node_named_child_count(arg) != SKIP_ONE) {
|
||||
return null_node;
|
||||
}
|
||||
TSNode name = ts_node_child_by_field_name(arg, TS_FIELD("type"));
|
||||
const char *nk = ts_node_is_null(name) ? "" : ts_node_type(name);
|
||||
if (strcmp(nk, "type_identifier") != 0 && strcmp(nk, "identifier") != 0) {
|
||||
return null_node;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
// Resolve function name from C/C++/CUDA/GLSL declarator chain. Shared canonical
|
||||
// implementation — see the header for the full rationale (#438).
|
||||
TSNode cbm_resolve_c_declarator_name_node(TSNode func_node) {
|
||||
@@ -1093,6 +1129,12 @@ TSNode cbm_resolve_c_declarator_name_node(TSNode func_node) {
|
||||
}
|
||||
return resolve_qualified_name(decl);
|
||||
}
|
||||
/* #946: `FNAME(first)(void)` names the def `first`, not `FNAME` — every
|
||||
* definition wrapped by the same macro otherwise shares one QN. */
|
||||
TSNode wrapped = cbm_c_macro_wrapped_name_node(decl);
|
||||
if (!ts_node_is_null(wrapped)) {
|
||||
return wrapped;
|
||||
}
|
||||
TSNode inner = ts_node_child_by_field_name(decl, TS_FIELD("declarator"));
|
||||
if (ts_node_is_null(inner) && ts_node_named_child_count(decl) > 0) {
|
||||
inner = ts_node_named_child(decl, 0);
|
||||
|
||||
@@ -89,6 +89,18 @@ bool cbm_c_reserved_func_name(const char *name);
|
||||
// lets one of these files define a name more than once.
|
||||
bool cbm_is_c_preprocessor_lang(CBMLanguage lang);
|
||||
|
||||
// Macro-wrapped definition name (#946): `void FNAME(first)(void)` and
|
||||
// `void Cls::__API_HOOK(Spawn)()` parse as a function_declarator whose own
|
||||
// declarator is ANOTHER function_declarator — the macro call. Valid C/C++ never
|
||||
// nests them directly (a function cannot return a function; a function-pointer
|
||||
// return has a parenthesized_declarator in between), so the shape is always a
|
||||
// function-like macro spelling the name. When `decl` is such an outer
|
||||
// function_declarator and the macro takes exactly one bare name, returns that
|
||||
// name node ("first", "Spawn"); otherwise a null node, and callers keep their
|
||||
// ordinary declarator walk. Shared by the declarator resolver and the C LSP so
|
||||
// the def name and the LSP caller QN agree.
|
||||
TSNode cbm_c_macro_wrapped_name_node(TSNode decl);
|
||||
|
||||
// Convert a resolved function/method name node to its name string, normalizing a
|
||||
// C++ conversion-operator's `operator_cast` node (which spans the full
|
||||
// "operator bool() const") down to "operator bool". Shared by the defs and
|
||||
|
||||
@@ -5334,6 +5334,22 @@ static bool c_process_configured_test_body(CLSPContext *ctx, TSNode scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// #946: a macro-wrapped definition (`Cls::__API_HOOK(Spawn)`, `FNAME(first)`) is
|
||||
// named by the macro's argument, keeping any class qualifier: replace the last
|
||||
// `::` segment of the walked name (or the whole unqualified name) with it, the
|
||||
// same name cbm_resolve_c_declarator_name_node gives the def.
|
||||
static char *c_macro_wrapped_func_name(CLSPContext *ctx, char *func_name, TSNode macro_arg) {
|
||||
char *arg = c_node_text(ctx, macro_arg);
|
||||
if (!func_name || !arg || !arg[0])
|
||||
return func_name;
|
||||
const char *last_sep = NULL;
|
||||
for (const char *p = strstr(func_name, "::"); p; p = strstr(p + 2, "::"))
|
||||
last_sep = p;
|
||||
if (!last_sep)
|
||||
return arg;
|
||||
return cbm_arena_sprintf(ctx->arena, "%.*s::%s", (int)(last_sep - func_name), func_name, arg);
|
||||
}
|
||||
|
||||
static void c_process_function(CLSPContext *ctx, TSNode func_node) {
|
||||
if (c_process_configured_test_body(ctx, func_node))
|
||||
return;
|
||||
@@ -5346,6 +5362,7 @@ static void c_process_function(CLSPContext *ctx, TSNode func_node) {
|
||||
const char *saved_class_qn = ctx->enclosing_class_qn;
|
||||
const char *saved_func_qn = ctx->enclosing_func_qn;
|
||||
TSNode params_node = (TSNode){0};
|
||||
TSNode macro_arg = (TSNode){0};
|
||||
|
||||
// Navigate declarator to find name and parameters
|
||||
TSNode cur = decl;
|
||||
@@ -5366,6 +5383,12 @@ static void c_process_function(CLSPContext *ctx, TSNode func_node) {
|
||||
func_name = c_node_text(ctx, real);
|
||||
break;
|
||||
}
|
||||
/* #946: the nested declarator is a macro call. These parameters are
|
||||
* the real ones; step past the macro's own argument list to the
|
||||
* (possibly class-qualified) macro name, which still scopes it. */
|
||||
macro_arg = cbm_c_macro_wrapped_name_node(cur);
|
||||
if (!ts_node_is_null(macro_arg))
|
||||
fdecl = ts_node_child_by_field_name(fdecl, "declarator", 10);
|
||||
cur = fdecl;
|
||||
continue;
|
||||
}
|
||||
@@ -5425,6 +5448,8 @@ static void c_process_function(CLSPContext *ctx, TSNode func_node) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!ts_node_is_null(macro_arg))
|
||||
func_name = c_macro_wrapped_func_name(ctx, func_name, macro_arg);
|
||||
|
||||
// A keyword "name" is an error-recovery artifact (see cbm_c_reserved_func_name):
|
||||
// the def extractor mints no node for it, so no caller QN may name it either.
|
||||
|
||||
@@ -1168,6 +1168,42 @@ TEST(clsp_macro_wrapped_call) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #946: `Cls::__API_HOOK(Name)(params)` — the C LSP walked the macro-wrapped
|
||||
* declarator to the macro, so every hook's caller QN was Cls.__API_HOOK (no such
|
||||
* definition exists once the def walk names the hook), and it bound the MACRO's
|
||||
* argument list as the function's parameters, so `pOther` was untyped and the
|
||||
* member call through it went unresolved. */
|
||||
TEST(clsp_macro_wrapped_declarator_caller_qn_issue946) {
|
||||
CBMFileResult *r = extract_cpp("void helper();\n"
|
||||
"class CBaseEntity {\n"
|
||||
"public:\n"
|
||||
" void Touch();\n"
|
||||
"};\n"
|
||||
"class CBasePlayer {\n"
|
||||
"public:\n"
|
||||
" void ReloadWeapons(int slot);\n"
|
||||
" void Spawn();\n"
|
||||
" void Use(CBaseEntity *pOther);\n"
|
||||
"};\n"
|
||||
"void CBasePlayer::__API_HOOK(ReloadWeapons)(int slot) {\n"
|
||||
" helper();\n"
|
||||
"}\n"
|
||||
"void CBasePlayer::__API_HOOK(Spawn)() {\n"
|
||||
" ReloadWeapons(0);\n"
|
||||
"}\n"
|
||||
"void CBasePlayer::__API_HOOK(Use)(CBaseEntity *pOther) {\n"
|
||||
" pOther->Touch();\n"
|
||||
"}\n"
|
||||
"void helper() {}\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_EQ(count_resolved(r, "__API_HOOK", ""), 0);
|
||||
ASSERT_GTE(find_resolved(r, "CBasePlayer.ReloadWeapons", "helper"), 0);
|
||||
ASSERT_GTE(find_resolved(r, "CBasePlayer.Spawn", "CBasePlayer.ReloadWeapons"), 0);
|
||||
ASSERT_GTE(find_resolved(r, "CBasePlayer.Use", "CBaseEntity.Touch"), 0);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(clsp_macro_with_args) {
|
||||
CBMFileResult *r = extract_c("\n"
|
||||
"int printf(const char* fmt, ...);\n"
|
||||
@@ -16578,6 +16614,7 @@ SUITE(c_lsp) {
|
||||
RUN_TEST(clsp_dependent_member_access);
|
||||
RUN_TEST(clsp_nocrash_try_catch);
|
||||
RUN_TEST(clsp_macro_wrapped_call);
|
||||
RUN_TEST(clsp_macro_wrapped_declarator_caller_qn_issue946);
|
||||
RUN_TEST(clsp_macro_with_args);
|
||||
RUN_TEST(clsp_recursive_macro);
|
||||
RUN_TEST(clsp_conditional_macro);
|
||||
|
||||
@@ -8115,6 +8115,193 @@ TEST(extract_cpp_export_macro_collector_raw_string_unterminated_issue1989) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* The call to `callee` on source line `line`, or NULL. */
|
||||
static const CBMCall *call_at_line(CBMFileResult *r, const char *callee, int line) {
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
const CBMCall *c = &r->calls.items[i];
|
||||
if (c->start_line == line && c->callee_name && strcmp(c->callee_name, callee) == 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* #946: ReGameDLL_CS defines its hookable methods as
|
||||
* `void CBasePlayer::__API_HOOK(ReloadWeapons)(...)`, a function-like macro in
|
||||
* the declarator (`#define __API_HOOK(fname) fname`, from a header). The grammar
|
||||
* nests that macro call as a function_declarator directly inside the definition's
|
||||
* own function_declarator, and the declarator walk named the definition after the
|
||||
* macro: every hook of a class collapsed into one Method `__API_HOOK`, calls in
|
||||
* each body were sourced to whichever hook was minted last, and no caller of
|
||||
* ReloadWeapons could reach it. The macro lives in a header here, as it does in
|
||||
* ReGameDLL, so the preprocessed second pass cannot expand it either. */
|
||||
TEST(extract_cpp_macro_wrapped_declarator_names_def_issue946) {
|
||||
CBMFileResult *r = extract("#include \"precompiled.h\"\n" /* 1 */
|
||||
"void helper();\n" /* 2 */
|
||||
"class CBasePlayer {\n" /* 3 */
|
||||
"public:\n" /* 4 */
|
||||
" void ReloadWeapons(int slot);\n" /* 5 */
|
||||
" void Spawn();\n" /* 6 */
|
||||
"};\n" /* 7 */
|
||||
"void CBasePlayer::__API_HOOK(ReloadWeapons)(int slot)\n" /* 8 */
|
||||
"{\n" /* 9 */
|
||||
" helper();\n" /* 10 */
|
||||
"}\n" /* 11 */
|
||||
"void EXT_FUNC CBasePlayer::__API_HOOK(Spawn)()\n" /* 12 */
|
||||
"{\n" /* 13 */
|
||||
" ReloadWeapons(0);\n" /* 14 */
|
||||
"}\n" /* 15 */
|
||||
"void EXT_FUNC __API_HOOK(PM_Move)(int server)\n" /* 16 */
|
||||
"{\n" /* 17 */
|
||||
" helper();\n" /* 18 */
|
||||
"}\n", /* 19 */
|
||||
CBM_LANG_CPP, "p", "player.cpp");
|
||||
ASSERT_NOT_NULL(r);
|
||||
/* One definition per hook, named after the macro argument. */
|
||||
ASSERT_EQ(count_defs_named(r, "Method", "__API_HOOK"), 0);
|
||||
ASSERT_EQ(count_defs_named(r, "Function", "__API_HOOK"), 0);
|
||||
ASSERT_TRUE(has_def_qn(r, "p.player.CBasePlayer.ReloadWeapons"));
|
||||
ASSERT_TRUE(has_def_qn(r, "p.player.CBasePlayer.Spawn"));
|
||||
ASSERT_TRUE(has_def_qn(r, "p.player.PM_Move"));
|
||||
const CBMDefinition *reload = find_def(r, "ReloadWeapons");
|
||||
ASSERT_NOT_NULL(reload);
|
||||
ASSERT_STR_EQ(reload->label, "Method");
|
||||
ASSERT_EQ((int)reload->start_line, 8);
|
||||
ASSERT_EQ((int)reload->end_line, 11);
|
||||
/* The real parameter list, not the macro's argument list. */
|
||||
ASSERT_EQ(reload->param_count, 1);
|
||||
/* Each in-body call is sourced to the hook whose body contains it. */
|
||||
const CBMCall *c10 = call_at_line(r, "helper", 10);
|
||||
ASSERT_NOT_NULL(c10);
|
||||
ASSERT_STR_EQ(c10->enclosing_func_qn, "p.player.CBasePlayer.ReloadWeapons");
|
||||
const CBMCall *c14 = call_at_line(r, "ReloadWeapons", 14);
|
||||
ASSERT_NOT_NULL(c14);
|
||||
ASSERT_STR_EQ(c14->enclosing_func_qn, "p.player.CBasePlayer.Spawn");
|
||||
const CBMCall *c18 = call_at_line(r, "helper", 18);
|
||||
ASSERT_NOT_NULL(c18);
|
||||
ASSERT_STR_EQ(c18->enclosing_func_qn, "p.player.PM_Move");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #946 precision controls: only a DEFINITION whose declarator nests a one-name
|
||||
* macro call is renamed. Macro call statements (file scope or in a body), calls
|
||||
* through a returned function pointer, a real function returning a function
|
||||
* pointer, a prototype, and a multi-argument wrapper whose name is ambiguous must
|
||||
* not mint a definition named after a macro argument. */
|
||||
TEST(extract_c_macro_wrapped_declarator_controls_issue946) {
|
||||
CBMFileResult *r = extract("int (*get_handler(int k))(int);\n" /* 1 */
|
||||
"int (*pick(int k))(int)\n" /* 2 */
|
||||
"{\n" /* 3 */
|
||||
" return 0;\n" /* 4 */
|
||||
"}\n" /* 5 */
|
||||
"REGISTER(on_boot);\n" /* 6 */
|
||||
"void WRAP(proto_only)(void);\n" /* 7 */
|
||||
"void PAIR(alpha, beta)(void)\n" /* 8 */
|
||||
"{\n" /* 9 */
|
||||
"}\n" /* 10 */
|
||||
"void run(int v)\n" /* 11 */
|
||||
"{\n" /* 12 */
|
||||
" get_handler(v)(v);\n" /* 13 */
|
||||
" TRACE(v_trace);\n" /* 14 */
|
||||
"}\n", /* 15 */
|
||||
CBM_LANG_C, "p", "controls.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_TRUE(has_def(r, "Function", "pick"));
|
||||
ASSERT_TRUE(has_def(r, "Function", "run"));
|
||||
ASSERT_FALSE(has_def_any(r, "on_boot"));
|
||||
ASSERT_FALSE(has_def_any(r, "proto_only"));
|
||||
ASSERT_FALSE(has_def_any(r, "alpha"));
|
||||
ASSERT_FALSE(has_def_any(r, "beta"));
|
||||
ASSERT_FALSE(has_def_any(r, "v"));
|
||||
ASSERT_FALSE(has_def_any(r, "v_trace"));
|
||||
const CBMCall *handler = call_at_line(r, "get_handler", 13);
|
||||
ASSERT_NOT_NULL(handler);
|
||||
ASSERT_STR_EQ(handler->enclosing_func_qn, "p.controls.run");
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1957: the same root cause in C. `static void FNAME(first_caller)(void)` and
|
||||
* `static void FNAME(later_function)(void)` collapsed into one Function FNAME
|
||||
* (the later one won), and sink() inside first_caller, on line 9, was sourced
|
||||
* to it: a call site outside its caller's range, from the textual extractor and
|
||||
* the C LSP alike. The second file is the kernel template-header shape
|
||||
* (arch/x86/kvm/mmu/paging_tmpl.h): FNAME token-pastes and is defined only
|
||||
* inside #if branches, so no preprocessing can expand it. */
|
||||
TEST(extract_c_fname_macro_definitions_stay_distinct_issue1957) {
|
||||
CBMFileResult *r = extract("#define FNAME(x) x\n" /* 1 */
|
||||
"\n" /* 2 */
|
||||
"static void sink(void)\n" /* 3 */
|
||||
"{\n" /* 4 */
|
||||
"}\n" /* 5 */
|
||||
"\n" /* 6 */
|
||||
"static void FNAME(first_caller)(void)\n" /* 7 */
|
||||
"{\n" /* 8 */
|
||||
" sink();\n" /* 9 */
|
||||
"}\n" /* 10 */
|
||||
"\n" /* 11 */
|
||||
"static void FNAME(later_function)(void)\n" /* 12 */
|
||||
"{\n" /* 13 */
|
||||
"}\n" /* 14 */
|
||||
"\n" /* 15 */
|
||||
"int main(void)\n" /* 16 */
|
||||
"{\n" /* 17 */
|
||||
" first_caller();\n" /* 18 */
|
||||
" return 0;\n" /* 19 */
|
||||
"}\n", /* 20 */
|
||||
CBM_LANG_C, "p", "repro.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
ASSERT_EQ(count_defs_named(r, "Function", "FNAME"), 0);
|
||||
const CBMDefinition *first = find_def(r, "first_caller");
|
||||
ASSERT_NOT_NULL(first);
|
||||
ASSERT_EQ((int)first->start_line, 7);
|
||||
ASSERT_EQ((int)first->end_line, 10);
|
||||
const CBMDefinition *later = find_def(r, "later_function");
|
||||
ASSERT_NOT_NULL(later);
|
||||
ASSERT_EQ((int)later->start_line, 12);
|
||||
const CBMCall *sink = call_at_line(r, "sink", 9);
|
||||
ASSERT_NOT_NULL(sink);
|
||||
ASSERT_STR_EQ(sink->enclosing_func_qn, "p.repro.first_caller");
|
||||
int lsp_sink = 0;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
if (rc->callee_qn && strcmp(rc->callee_qn, "p.repro.sink") == 0) {
|
||||
ASSERT_STR_EQ(rc->caller_qn, "p.repro.first_caller");
|
||||
lsp_sink++;
|
||||
}
|
||||
}
|
||||
ASSERT_GTE(lsp_sink, 1);
|
||||
cbm_free_result(r);
|
||||
|
||||
CBMFileResult *t = extract("#if PTTYPE == 64\n" /* 1 */
|
||||
"\t#define FNAME(name) paging##64_##name\n" /* 2 */
|
||||
"#else\n" /* 3 */
|
||||
"\t#error Invalid PTTYPE value\n" /* 4 */
|
||||
"#endif\n" /* 5 */
|
||||
"static int FNAME(is_present)(unsigned long pte)\n" /* 6 */
|
||||
"{\n" /* 7 */
|
||||
"\treturn test_bit(pte);\n" /* 8 */
|
||||
"}\n" /* 9 */
|
||||
"static int FNAME(walk)(unsigned long pte)\n" /* 10 */
|
||||
"{\n" /* 11 */
|
||||
"\treturn check_pte(pte);\n" /* 12 */
|
||||
"}\n", /* 13 */
|
||||
CBM_LANG_C, "p", "paging_tmpl.h");
|
||||
ASSERT_NOT_NULL(t);
|
||||
ASSERT_EQ(count_defs_named(t, "Function", "FNAME"), 0);
|
||||
ASSERT_TRUE(has_def_qn(t, "p.paging_tmpl.is_present"));
|
||||
ASSERT_TRUE(has_def_qn(t, "p.paging_tmpl.walk"));
|
||||
const CBMCall *bit = call_at_line(t, "test_bit", 8);
|
||||
ASSERT_NOT_NULL(bit);
|
||||
ASSERT_STR_EQ(bit->enclosing_func_qn, "p.paging_tmpl.is_present");
|
||||
const CBMCall *chk = call_at_line(t, "check_pte", 12);
|
||||
ASSERT_NOT_NULL(chk);
|
||||
ASSERT_STR_EQ(chk->enclosing_func_qn, "p.paging_tmpl.walk");
|
||||
cbm_free_result(t);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #668: walk_defs used a fixed `walk_defs_frame_t stack[4096]` — a ~160 KB
|
||||
* C-stack frame that overflowed small thread stacks (the reporter's crash was in
|
||||
* the "definitions pass" on a large SQL file), and whose `top < 4096` push guards
|
||||
@@ -10889,6 +11076,9 @@ SUITE(extraction) {
|
||||
RUN_TEST(extract_cpp_export_macro_collector_raw_string_unterminated_issue1989);
|
||||
RUN_TEST(extract_cpp_export_macro_suffix_variants_issue1989);
|
||||
RUN_TEST(extract_cpp_export_macro_explicit_define_priority_issue1989);
|
||||
RUN_TEST(extract_cpp_macro_wrapped_declarator_names_def_issue946);
|
||||
RUN_TEST(extract_c_macro_wrapped_declarator_controls_issue946);
|
||||
RUN_TEST(extract_c_fname_macro_definitions_stay_distinct_issue1957);
|
||||
RUN_TEST(walk_defs_no_truncation_over_4096_issue668);
|
||||
RUN_TEST(extract_rust_test_attr_marks_is_test_issue855);
|
||||
RUN_TEST(extract_c_test_dir_marks_is_test_issue1294);
|
||||
|
||||
Reference in new issue
Block a user