mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(lsp): bound structured-binding field reads and TS method heritage walk (#1743)
Two deterministic indexing-worker crashes reported on #1743. C/C++ (the original LLVM report, "segfault at 9"): structured-binding decomposition read rt->field_types[binding_idx] guarded only by `rt->field_types[binding_idx] != NULL`. field_types[] is parallel to field_names[] and NULL-terminated, so a binding list longer than the struct's field list (ill-formed, but present in compiler test suites, e.g. llvm clang/test/CXX/dcl.decl/dcl.decomp/p3.cpp) read past the terminator into the next arena object. That object is a CBMType whose first word is its kind (9), so the name was bound to the pointer 0x9 and decltype(name) dereferenced it in c_resolve_name_to_type. All three decomposition sites now go through c_struct_field_type_at(), which bounds the index by field_names[] and yields NULL (unknown) past the end. TypeScript (kilocode report on v0.11.0): lookup_method walked the extends/implements chain recursively with no depth bound. The Effect idiom `class Service extends Context.Service<Service, I>()(...)` gets its base resolved to the class itself, so `Service.of(...)` (or a sibling class whose base resolves to that Service) recursed until the resolver thread's stack overflowed. lookup_method now has the same depth-guarded entry as lookup_member_type (TS_LSP_MAX_MEMBER_DEPTH); past the cap the method is simply not found. Proof on real input (macOS arm64, fresh cache each run): - llvm/llvm-project llvmorg-15.0.7: before, 5 worker SIGSEGVs at 0x9 in c_resolve_name_to_type and 3 files quarantined; after, 0 crashes, 0 quarantined, 1,375,933 nodes / 4,257,770 edges. - Kilo-Org/kilocode main: before, the resolve worker died every attempt (SIGBUS, lookup_method recursion depth ~65k); after, indexed cleanly, 90,745 nodes / 440,654 edges. Tests: c_lsp::clsp_structured_binding_more_names_than_fields and ts_lsp::tslsp_nocrash_cyclic_extends_method_call (both crash RED without the fix). Fixes #1743 Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
5958f546dc
commit
a8c4040e8a
5 files changed
+96
-8
No files matched your search
@@ -2880,6 +2880,23 @@ static bool c_base_declares_member(CLSPContext *ctx, const char *type_qn, const
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Type of the idx-th declared field, for structured-binding decomposition.
|
||||
* field_types[] is parallel to field_names[] and may hold NULL entries, so
|
||||
* only field_names[] bounds it: a binding list longer than the field list
|
||||
* (ill-formed, but present in compiler test suites) must yield NULL, never
|
||||
* read past the array into the next arena object (#1743). */
|
||||
static const CBMType *c_struct_field_type_at(const CBMRegisteredType *rt, int idx) {
|
||||
if (!rt || !rt->field_names || !rt->field_types || idx < 0) {
|
||||
return NULL;
|
||||
}
|
||||
for (int i = 0; i < idx; i++) {
|
||||
if (!rt->field_names[i]) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return rt->field_names[idx] ? rt->field_types[idx] : NULL;
|
||||
}
|
||||
|
||||
// Field type lookup
|
||||
static const CBMType *c_lookup_field_type(CLSPContext *ctx, const char *type_qn,
|
||||
const char *field_name, int depth) {
|
||||
@@ -3055,9 +3072,10 @@ void c_process_statement(CLSPContext *ctx, TSNode node) {
|
||||
ctx->registry,
|
||||
cbm_arena_sprintf(ctx->arena, "%s.%s",
|
||||
ctx->module_qn, rhs_qn));
|
||||
if (rt && rt->field_types &&
|
||||
rt->field_types[binding_idx])
|
||||
elem_type = rt->field_types[binding_idx];
|
||||
const CBMType *ft =
|
||||
c_struct_field_type_at(rt, binding_idx);
|
||||
if (ft)
|
||||
elem_type = ft;
|
||||
}
|
||||
cbm_scope_bind(ctx->current_scope, bname, elem_type);
|
||||
binding_idx++;
|
||||
@@ -3140,8 +3158,9 @@ void c_process_statement(CLSPContext *ctx, TSNode node) {
|
||||
ctx->registry,
|
||||
cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn,
|
||||
rhs_qn));
|
||||
if (rt && rt->field_types && rt->field_types[binding_idx]) {
|
||||
elem_type = rt->field_types[binding_idx];
|
||||
const CBMType *ft = c_struct_field_type_at(rt, binding_idx);
|
||||
if (ft) {
|
||||
elem_type = ft;
|
||||
}
|
||||
}
|
||||
cbm_scope_bind(ctx->current_scope, bname, elem_type);
|
||||
@@ -3551,8 +3570,9 @@ void c_process_statement(CLSPContext *ctx, TSNode node) {
|
||||
rt = cbm_registry_lookup_type(
|
||||
ctx->registry,
|
||||
cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module_qn, eq));
|
||||
if (rt && rt->field_types && rt->field_types[binding_idx])
|
||||
bt = rt->field_types[binding_idx];
|
||||
const CBMType *ft = c_struct_field_type_at(rt, binding_idx);
|
||||
if (ft)
|
||||
bt = ft;
|
||||
}
|
||||
cbm_scope_bind(ctx->current_scope, bname, bt);
|
||||
binding_idx++;
|
||||
|
||||
@@ -1785,9 +1785,28 @@ static const CBMType *eval_indexed_access(TSLSPContext *ctx, const CBMType *obj,
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
|
||||
// Look up a method on a receiver type — returns the registered func.
|
||||
static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name);
|
||||
|
||||
/* Depth-guarded entry, same contract as lookup_member_type: the method walk
|
||||
* recurses through wrapper classes, union members and the extends/implements
|
||||
* chain. A cyclic chain (#1743: the Effect idiom `class Service extends
|
||||
* Context.Service<Service, I>()(...)` whose base resolved to the class itself)
|
||||
* recursed without bound and overflowed the resolver thread's stack. Past the
|
||||
* cap the method is simply not found. */
|
||||
static const CBMRegisteredFunc *lookup_method(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name) {
|
||||
if (!ctx || ctx->method_depth >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
return NULL;
|
||||
ctx->method_depth++;
|
||||
const CBMRegisteredFunc *f = lookup_method_inner(ctx, recv, method_name);
|
||||
ctx->method_depth--;
|
||||
return f;
|
||||
}
|
||||
|
||||
// Look up a method on a receiver type — returns the registered func.
|
||||
static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name) {
|
||||
if (!ctx || !recv || !method_name)
|
||||
return NULL;
|
||||
const CBMType *base = simplify_type(ctx, recv);
|
||||
|
||||
@@ -69,6 +69,9 @@ typedef struct {
|
||||
// recursive unions/wrappers across registered types) otherwise recurse
|
||||
// without bound — stack overflow on real repos.
|
||||
int member_depth;
|
||||
// Same guard for lookup_method: its extends/implements walk recursed
|
||||
// without bound on a cyclic heritage chain (#1743).
|
||||
int method_depth;
|
||||
} TSLSPContext;
|
||||
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
|
||||
@@ -2086,6 +2086,28 @@ TEST(clsp_structured_binding_struct) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1743: a structured binding with more names than the struct has fields
|
||||
* (ill-formed C++, but common in compiler test suites such as LLVM's
|
||||
* dcl.decomp tests) indexed field_types[] past its NULL terminator. The read
|
||||
* landed in the next arena object and bound a garbage pointer (0x9) as the
|
||||
* type, which decltype() then dereferenced: a deterministic SIGSEGV at
|
||||
* address 9 in c_resolve_name_to_type. Extra names must bind as unknown. */
|
||||
TEST(clsp_structured_binding_more_names_than_fields) {
|
||||
CBMFileResult *r = extract_cpp("struct A { int x, y; };\n"
|
||||
"const auto &[acr0, acr1, acr2] = A();\n"
|
||||
"using ConstFloatRef = decltype(acr2);\n"
|
||||
"void test() {\n"
|
||||
" auto [b0, b1, b2, b3] = A();\n"
|
||||
" using T = decltype(b3);\n"
|
||||
" for (auto [c0, c1, c2, c3] : (A *)0) {\n"
|
||||
" using U = decltype(c3);\n"
|
||||
" }\n"
|
||||
"}\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(clsp_ternary_type) {
|
||||
CBMFileResult *r = extract_cpp("\n"
|
||||
"class Widget {\n"
|
||||
@@ -16486,6 +16508,7 @@ SUITE(c_lsp) {
|
||||
RUN_TEST(clsp_tad_make_pair_like);
|
||||
RUN_TEST(clsp_structured_binding_pair);
|
||||
RUN_TEST(clsp_structured_binding_struct);
|
||||
RUN_TEST(clsp_structured_binding_more_names_than_fields);
|
||||
RUN_TEST(clsp_ternary_type);
|
||||
RUN_TEST(clsp_chained_method_calls);
|
||||
RUN_TEST(clsp_std_vector_push_back);
|
||||
|
||||
@@ -952,6 +952,28 @@ TEST(tslsp_nocrash_circular_extends) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1743 (kilocode report): the Effect idiom `class Service extends
|
||||
* Context.Service<Service, I>()(...)` resolved its base to the class itself,
|
||||
* so a static call `Service.of(...)` walked a self-cycle in the extends chain
|
||||
* with no depth bound and overflowed the resolver thread's stack. Method and
|
||||
* member lookups over cyclic heritage must terminate. */
|
||||
TEST(tslsp_nocrash_cyclic_extends_method_call) {
|
||||
CBMFileResult *r =
|
||||
extract_ts("export class Loader extends Context.Service<Loader, LoaderI>()(\n"
|
||||
") {}\n"
|
||||
"export class Service extends Context.Service<Service, I>()(\"@x/S\") {}\n"
|
||||
"export function fromSDK(sdk: SDK): LoaderI {\n"
|
||||
" return Loader.of({\n"
|
||||
" })\n"
|
||||
"}\n"
|
||||
"class A extends B {}\n"
|
||||
"class B extends A {}\n"
|
||||
"function go(a: A) { a.missing(); return a.field.other(); }\n");
|
||||
ASSERT_NOT_NULL(r);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(tslsp_nocrash_recursive_type) {
|
||||
CBMFileResult *r = extract_ts("interface List { next?: List; value: number; }\n"
|
||||
"function go(l: List) { l.next?.value; }\n");
|
||||
@@ -4365,6 +4387,7 @@ SUITE(ts_lsp) {
|
||||
|
||||
/* Category 26: more crash safety */
|
||||
RUN_TEST(tslsp_nocrash_circular_extends);
|
||||
RUN_TEST(tslsp_nocrash_cyclic_extends_method_call);
|
||||
RUN_TEST(tslsp_nocrash_recursive_type);
|
||||
RUN_TEST(tslsp_nocrash_unicode_identifier);
|
||||
RUN_TEST(tslsp_nocrash_template_with_call);
|
||||
|
||||
Reference in new issue
Block a user