mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(cs-lsp): bound the nested-type qualifier walk
Resolving a qualified type name A.B that no namespace binds now resolves the qualifier A as a type first (nested types). A using alias whose target is qualified by the alias itself re-enters that walk through the alias step without end: `using Loop = Loop.Inner;` resolves Loop to the target Loop.Inner, whose qualifier is Loop again, and a mutual pair (`using Ping = Pong.Inner; using Pong = Ping.Inner;`) cycles the same way. Both are legal C# when namespaces of those names exist, and the walk overflowed the stack on them. The walk now carries a depth counter in the per-file context and stops after CS_QUALIFIER_MAX_DEPTH (8) levels. One level is one type-nesting step: a namespace-qualified top-level type binds without recursing, so real nesting stays far below the bound. Test: cs_lsp::cslsp_cyclic_alias_qualifier_terminates (self and mutual alias cycles; the file must still resolve Svc.Go). RED without the bound: AddressSanitizer stack-overflow, exit 134. GREEN with it. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
c0a8ed8b93
commit
bc44de15d3
3 files changed
+44
-2
No files matched your search
@@ -51,6 +51,10 @@
|
||||
#define CS_USING_INITIAL_CAP 16
|
||||
#define CS_NAMESPACE_INITIAL_CAP 8
|
||||
#define CS_LSP_PARENT_WALK_MAX 32
|
||||
/* Nested-type qualifier walk depth: one level per type nesting step of `A.B.C`
|
||||
* (a namespace-qualified top-level type binds without recursing), so 8 covers
|
||||
* real nesting while a cyclic alias such as `using A = A.B;` stops after 8. */
|
||||
#define CS_QUALIFIER_MAX_DEPTH 8
|
||||
|
||||
extern const TSLanguage *tree_sitter_c_sharp(void);
|
||||
|
||||
@@ -431,9 +435,13 @@ static const char *cs_resolve_by_declared_namespace(CSLSPContext *ctx, const cha
|
||||
* member type of it. Nested QNs are the parent QN plus the simple name,
|
||||
* which step 8b's namespace scan can never produce because nested types
|
||||
* carry no namespace_qn (#2120 residual: `new Command.Result(...)`,
|
||||
* `new Grain.Handler(dep).Handle(...)`). */
|
||||
if (!best && qualifier) {
|
||||
* `new Grain.Handler(dep).Handle(...)`). The walk is depth-bounded: an
|
||||
* alias whose target names the alias itself (`using A = A.B;`, or a
|
||||
* mutual pair) re-enters this path through step 8 with no end. */
|
||||
if (!best && qualifier && ctx->qualifier_depth < CS_QUALIFIER_MAX_DEPTH) {
|
||||
ctx->qualifier_depth++;
|
||||
const char *qtype = cs_resolve_type_name(ctx, qualifier);
|
||||
ctx->qualifier_depth--;
|
||||
if (qtype) {
|
||||
const char *nested = cbm_arena_sprintf(ctx->arena, "%s.%s", qtype, simple);
|
||||
if (cs_lookup_type_qn(ctx, nested))
|
||||
|
||||
@@ -111,6 +111,11 @@ typedef struct {
|
||||
/* Recursion guard for cs_eval_expr_type. */
|
||||
int eval_depth;
|
||||
|
||||
/* Recursion guard for the nested-type qualifier walk of
|
||||
* cs_resolve_type_name: resolving `A.B` resolves the qualifier `A` as a
|
||||
* type first, and a `using A = A.B;` alias cycles back into it. */
|
||||
int qualifier_depth;
|
||||
|
||||
/* Debug mode (CBM_LSP_DEBUG env). */
|
||||
bool debug;
|
||||
} CSLSPContext;
|
||||
|
||||
@@ -1226,6 +1226,34 @@ TEST(cslsp_ordinary_same_leaf_calls_join_by_exact_site) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* A qualified type name resolves its qualifier as a TYPE when no namespace
|
||||
* binds it (nested types, #2120). A using alias whose target is qualified by
|
||||
* the alias itself (`using Loop = Loop.Inner;`) or by a mutual alias re-enters
|
||||
* that walk through the alias step forever: each `Loop` resolves the target
|
||||
* `Loop.Inner`, whose qualifier is `Loop` again. The walk is depth-bounded;
|
||||
* the file must still finish and resolve its ordinary calls. */
|
||||
TEST(cslsp_cyclic_alias_qualifier_terminates) {
|
||||
const char *src = "using Loop = Loop.Inner;\n"
|
||||
"using Ping = Pong.Inner;\n"
|
||||
"using Pong = Ping.Inner;\n"
|
||||
"namespace App {\n"
|
||||
" public class Svc { public void Go() { } }\n"
|
||||
" public class Runner {\n"
|
||||
" public void Run(Loop a, Ping b) {\n"
|
||||
" var x = new Loop();\n"
|
||||
" var y = new Ping.Leaf();\n"
|
||||
" var s = new Svc();\n"
|
||||
" s.Go();\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
"}\n";
|
||||
CBMFileResult *r = extract_cs(src);
|
||||
ASSERT(r);
|
||||
ASSERT(require_resolved(r, "Runner.Run", "Svc.Go") >= 0);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Suite ─────────────────────────────────────────────────── */
|
||||
|
||||
SUITE(cs_lsp) {
|
||||
@@ -1290,4 +1318,5 @@ SUITE(cs_lsp) {
|
||||
RUN_TEST(cslsp_stringbuilder_chain);
|
||||
RUN_TEST(cslsp_httpclient_chain);
|
||||
RUN_TEST(cslsp_ordinary_same_leaf_calls_join_by_exact_site);
|
||||
RUN_TEST(cslsp_cyclic_alias_qualifier_terminates);
|
||||
}
|
||||
Reference in new issue
Block a user