mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(ts): bound recursive member and method lookup
Signed-off-by: dianshu <dianshu@123.com>
This commit is contained in:
1 parent
56700bdb71
commit
e5c3ddd573
3 files changed
+245
-56
No files matched your search
+131
-39
@@ -59,6 +59,10 @@ void cbm_ts_full_registry_builds_reset(void) {
|
||||
* constructs it does not model. -1 = unlimited (no entry point armed it). */
|
||||
static _Thread_local long g_ts_type_budget = -1;
|
||||
static _Thread_local bool g_ts_type_budget_warned;
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
static _Thread_local int g_ts_max_member_lookup_steps;
|
||||
static _Thread_local int g_ts_max_method_lookup_steps;
|
||||
#endif
|
||||
|
||||
static void ts_type_budget_reset(size_t source_len) {
|
||||
const char *e = cbm_runtime_getenv("CBM_TS_TYPE_BUDGET");
|
||||
@@ -170,6 +174,12 @@ long cbm_ts_lsp_test_budget_remaining(void) {
|
||||
bool cbm_ts_lsp_test_budget_warned(void) {
|
||||
return g_ts_type_budget_warned;
|
||||
}
|
||||
int cbm_ts_lsp_test_max_member_lookup_steps(void) {
|
||||
return g_ts_max_member_lookup_steps;
|
||||
}
|
||||
int cbm_ts_lsp_test_max_method_lookup_steps(void) {
|
||||
return g_ts_max_method_lookup_steps;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define TS_LSP_MAX_EVAL_DEPTH 64
|
||||
@@ -190,6 +200,10 @@ static const CBMType *type_of_identifier(TSLSPContext *ctx, const char *name);
|
||||
static const CBMType *lookup_member_type(TSLSPContext *ctx, const CBMType *recv, const char *name);
|
||||
static const CBMRegisteredFunc *lookup_method(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name);
|
||||
typedef struct TSLookupState TSLookupState;
|
||||
static const CBMRegisteredFunc *lookup_method_at(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name, int depth,
|
||||
TSLookupState *state);
|
||||
static char *node_text(TSLSPContext *ctx, TSNode node);
|
||||
|
||||
// Collect a node's children into an arena array via a single O(n) cursor pass.
|
||||
@@ -1569,28 +1583,77 @@ static const char *builtin_wrapper_class(const char *builtin_name) {
|
||||
}
|
||||
|
||||
// Look up a property `name` on a receiver type. Returns the property type or UNKNOWN.
|
||||
static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *name);
|
||||
|
||||
#define TS_LSP_MAX_MEMBER_DEPTH 64
|
||||
#define TS_LSP_INLINE_VISITED_TYPES 16
|
||||
|
||||
/* Depth-guarded entry: member lookup recurses through wrapper classes, union
|
||||
* members and registered-type expansion; cyclic type graphs in real-world TS
|
||||
* (microsoft/TypeScript reallyLargeFile.ts) recursed without bound — SIGBUS
|
||||
* stack overflow under endless lookup_member_type frames. Past the cap the
|
||||
* member resolves as unknown — graceful degradation, not a crash. */
|
||||
static const CBMType *lookup_member_type(TSLSPContext *ctx, const CBMType *recv, const char *name) {
|
||||
if (!ctx || ctx->member_depth >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
return cbm_type_unknown();
|
||||
ctx->member_depth++;
|
||||
const CBMType *r = lookup_member_type_inner(ctx, recv, name);
|
||||
ctx->member_depth--;
|
||||
return r;
|
||||
typedef struct {
|
||||
const char *qn;
|
||||
int depth;
|
||||
} TSVisitedType;
|
||||
|
||||
struct TSLookupState {
|
||||
TSVisitedType inline_visited[TS_LSP_INLINE_VISITED_TYPES];
|
||||
TSVisitedType *visited;
|
||||
int visited_count;
|
||||
int visited_capacity;
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
int steps;
|
||||
#endif
|
||||
};
|
||||
|
||||
/* A named type needs another visit only when reached along a shallower path.
|
||||
* Grow the visited table rather than silently dropping entries at a fixed cap:
|
||||
* the depth bound then limits each named type to at most 64 admitted visits. */
|
||||
static bool ts_lookup_visit_named(CBMArena *arena, TSLookupState *state, const char *qn,
|
||||
int depth) {
|
||||
for (int i = 0; i < state->visited_count; i++) {
|
||||
if (strcmp(state->visited[i].qn, qn) == 0) {
|
||||
if (state->visited[i].depth <= depth)
|
||||
return false;
|
||||
state->visited[i].depth = depth;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!state->visited) {
|
||||
state->visited = state->inline_visited;
|
||||
state->visited_capacity = TS_LSP_INLINE_VISITED_TYPES;
|
||||
} else if (state->visited_count == state->visited_capacity) {
|
||||
int capacity = state->visited_capacity * 2;
|
||||
TSVisitedType *larger =
|
||||
(TSVisitedType *)cbm_arena_alloc(arena, (size_t)capacity * sizeof(*larger));
|
||||
if (!larger) {
|
||||
fprintf(stderr, "[tslsp] lookup visited allocation failed\n");
|
||||
return false;
|
||||
}
|
||||
memcpy(larger, state->visited, (size_t)state->visited_count * sizeof(*larger));
|
||||
state->visited = larger;
|
||||
state->visited_capacity = capacity;
|
||||
}
|
||||
state->visited[state->visited_count++] = (TSVisitedType){.qn = qn, .depth = depth};
|
||||
return true;
|
||||
}
|
||||
|
||||
static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *name) {
|
||||
if (!ctx || !recv || !name)
|
||||
static const CBMType *lookup_member_type_at(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *name, int depth, TSLookupState *state);
|
||||
|
||||
/* Depth and shallower-only named-type visits bound work on cyclic inheritance. */
|
||||
static const CBMType *lookup_member_type(TSLSPContext *ctx, const CBMType *recv, const char *name) {
|
||||
TSLookupState state = {0};
|
||||
const CBMType *result = lookup_member_type_at(ctx, recv, name, 0, &state);
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
if (state.steps > g_ts_max_member_lookup_steps)
|
||||
g_ts_max_member_lookup_steps = state.steps;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
static const CBMType *lookup_member_type_at(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *name, int depth, TSLookupState *state) {
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
if (state)
|
||||
state->steps++;
|
||||
#endif
|
||||
if (!ctx || !recv || !name || !state || depth >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
return cbm_type_unknown();
|
||||
const CBMType *base = simplify_type(ctx, recv);
|
||||
if (!base)
|
||||
@@ -1604,7 +1667,7 @@ static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType
|
||||
const char *wrap = builtin_wrapper_class(base->data.builtin.name);
|
||||
if (wrap) {
|
||||
const CBMType *wrapped = cbm_type_named(ctx->arena, wrap);
|
||||
return lookup_member_type(ctx, wrapped, name);
|
||||
return lookup_member_type_at(ctx, wrapped, name, depth + 1, state);
|
||||
}
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
@@ -1659,7 +1722,10 @@ static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType
|
||||
// first hit instead of building a union of results.)
|
||||
if (base->data.union_type.members) {
|
||||
for (int i = 0; i < base->data.union_type.count; i++) {
|
||||
const CBMType *m = lookup_member_type(ctx, base->data.union_type.members[i], name);
|
||||
if (depth + 1 >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
break;
|
||||
const CBMType *m = lookup_member_type_at(ctx, base->data.union_type.members[i],
|
||||
name, depth + 1, state);
|
||||
if (!cbm_type_is_unknown(m))
|
||||
return m;
|
||||
}
|
||||
@@ -1670,7 +1736,10 @@ static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType
|
||||
if (base->kind == CBM_TYPE_INTERSECTION) {
|
||||
if (base->data.union_type.members) {
|
||||
for (int i = 0; i < base->data.union_type.count; i++) {
|
||||
const CBMType *m = lookup_member_type(ctx, base->data.union_type.members[i], name);
|
||||
if (depth + 1 >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
break;
|
||||
const CBMType *m = lookup_member_type_at(ctx, base->data.union_type.members[i],
|
||||
name, depth + 1, state);
|
||||
if (!cbm_type_is_unknown(m))
|
||||
return m;
|
||||
}
|
||||
@@ -1682,6 +1751,10 @@ static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType
|
||||
return cbm_type_unknown();
|
||||
|
||||
const char *recv_qn = base->data.named.qualified_name;
|
||||
if (!recv_qn)
|
||||
return cbm_type_unknown();
|
||||
if (!ts_lookup_visit_named(ctx->arena, state, recv_qn, depth))
|
||||
return cbm_type_unknown();
|
||||
const CBMRegisteredType *rt = cbm_registry_resolve_alias(ctx->registry, recv_qn);
|
||||
if (!rt) {
|
||||
rt = cbm_registry_lookup_type(ctx->registry, recv_qn);
|
||||
@@ -1711,8 +1784,10 @@ static const CBMType *lookup_member_type_inner(TSLSPContext *ctx, const CBMType
|
||||
// Walk extends/implements.
|
||||
if (rt->embedded_types) {
|
||||
for (int i = 0; rt->embedded_types[i]; i++) {
|
||||
if (depth + 1 >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
break;
|
||||
const CBMType *parent = cbm_type_named(ctx->arena, rt->embedded_types[i]);
|
||||
const CBMType *m = lookup_member_type(ctx, parent, name);
|
||||
const CBMType *m = lookup_member_type_at(ctx, parent, name, depth + 1, state);
|
||||
if (!cbm_type_is_unknown(m))
|
||||
return m;
|
||||
}
|
||||
@@ -1795,9 +1870,6 @@ static const CBMType *eval_indexed_access(TSLSPContext *ctx, const CBMType *obj,
|
||||
return cbm_type_unknown();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1806,18 +1878,24 @@ static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBM
|
||||
* 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;
|
||||
TSLookupState state = {0};
|
||||
const CBMRegisteredFunc *result = lookup_method_at(ctx, recv, method_name, 0, &state);
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
if (state.steps > g_ts_max_method_lookup_steps)
|
||||
g_ts_max_method_lookup_steps = state.steps;
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
// 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)
|
||||
/* Depth and shallower-only named-type visits bound cyclic inheritance work. */
|
||||
static const CBMRegisteredFunc *lookup_method_at(TSLSPContext *ctx, const CBMType *recv,
|
||||
const char *method_name, int depth,
|
||||
TSLookupState *state) {
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
if (state)
|
||||
state->steps++;
|
||||
#endif
|
||||
if (!ctx || !recv || !method_name || !state || depth >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
return NULL;
|
||||
const CBMType *base = simplify_type(ctx, recv);
|
||||
if (!base)
|
||||
@@ -1831,7 +1909,7 @@ static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBM
|
||||
const char *wrap = builtin_wrapper_class(base->data.builtin.name);
|
||||
if (wrap) {
|
||||
const CBMType *wrapped = cbm_type_named(ctx->arena, wrap);
|
||||
return lookup_method(ctx, wrapped, method_name);
|
||||
return lookup_method_at(ctx, wrapped, method_name, depth + 1, state);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -1860,8 +1938,10 @@ static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBM
|
||||
if (base->kind == CBM_TYPE_UNION || base->kind == CBM_TYPE_INTERSECTION) {
|
||||
if (base->data.union_type.members) {
|
||||
for (int i = 0; i < base->data.union_type.count; i++) {
|
||||
const CBMRegisteredFunc *f =
|
||||
lookup_method(ctx, base->data.union_type.members[i], method_name);
|
||||
if (depth + 1 >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
break;
|
||||
const CBMRegisteredFunc *f = lookup_method_at(ctx, base->data.union_type.members[i],
|
||||
method_name, depth + 1, state);
|
||||
if (f)
|
||||
return f;
|
||||
}
|
||||
@@ -1872,6 +1952,11 @@ static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBM
|
||||
if (base->kind != CBM_TYPE_NAMED)
|
||||
return NULL;
|
||||
const char *recv_qn = base->data.named.qualified_name;
|
||||
if (!recv_qn)
|
||||
return NULL;
|
||||
|
||||
if (!ts_lookup_visit_named(ctx->arena, state, recv_qn, depth))
|
||||
return NULL;
|
||||
|
||||
const CBMRegisteredFunc *f =
|
||||
cbm_registry_lookup_method_aliased(ctx->registry, recv_qn, method_name);
|
||||
@@ -1882,8 +1967,11 @@ static const CBMRegisteredFunc *lookup_method_inner(TSLSPContext *ctx, const CBM
|
||||
const CBMRegisteredType *rt = cbm_registry_lookup_type(ctx->registry, recv_qn);
|
||||
if (rt && rt->embedded_types) {
|
||||
for (int i = 0; rt->embedded_types[i]; i++) {
|
||||
if (depth + 1 >= TS_LSP_MAX_MEMBER_DEPTH)
|
||||
break;
|
||||
const CBMType *parent = cbm_type_named(ctx->arena, rt->embedded_types[i]);
|
||||
const CBMRegisteredFunc *pf = lookup_method(ctx, parent, method_name);
|
||||
const CBMRegisteredFunc *pf =
|
||||
lookup_method_at(ctx, parent, method_name, depth + 1, state);
|
||||
if (pf)
|
||||
return pf;
|
||||
}
|
||||
@@ -3838,6 +3926,10 @@ void ts_lsp_init(TSLSPContext *ctx, CBMArena *arena, const char *source, int sou
|
||||
bool jsx_mode, bool dts_mode, CBMResolvedCallArray *out) {
|
||||
if (!ctx)
|
||||
return;
|
||||
#ifdef CBM_ENABLE_TEST_SEAMS
|
||||
g_ts_max_member_lookup_steps = 0;
|
||||
g_ts_max_method_lookup_steps = 0;
|
||||
#endif
|
||||
memset(ctx, 0, sizeof(TSLSPContext));
|
||||
ctx->arena = arena;
|
||||
ctx->source = source;
|
||||
|
||||
@@ -65,13 +65,6 @@ typedef struct {
|
||||
// first completed eval; see TsEvalMemo in ts_lsp.c). Kills the exponential
|
||||
// re-evaluation of shared subexpressions under overload resolution.
|
||||
struct TsEvalMemo *eval_memo;
|
||||
// Recursion guard for lookup_member_type: cyclic type graphs (mutually
|
||||
// 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
|
||||
@@ -79,6 +72,10 @@ typedef struct {
|
||||
// for the calling thread, valid after a cbm_run_ts_lsp on the same thread.
|
||||
long cbm_ts_lsp_test_budget_remaining(void);
|
||||
bool cbm_ts_lsp_test_budget_warned(void);
|
||||
// Maximum recursive calls in one member/method lookup during the last TS LSP run,
|
||||
// including attempts rejected by the depth or visited-type guard.
|
||||
int cbm_ts_lsp_test_max_member_lookup_steps(void);
|
||||
int cbm_ts_lsp_test_max_method_lookup_steps(void);
|
||||
#endif
|
||||
|
||||
// --- Initialization ---
|
||||
|
||||
+110
-10
@@ -12,6 +12,7 @@
|
||||
#include "test_framework.h"
|
||||
#include "cbm.h"
|
||||
#include "lang_specs.h" /* cbm_ts_language — direct-parse GLR cap regression (#913) */
|
||||
#include "lsp/ts_lsp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -399,11 +400,11 @@ TEST(cpp_large_templated_header_no_crash_issue424) {
|
||||
* java_resolve_calls_in_node frames (bind_lambda_args), bitcoin → SIGSEGV
|
||||
* under deep c_resolve_calls_in_node frames (cbm_type_substitute via
|
||||
* c_adl_resolve), microsoft/TypeScript → SIGBUS under an unbounded
|
||||
* lookup_member_type cycle. The walks now carry depth guards; these
|
||||
* reproductions fork a child so a regression cannot kill the test runner
|
||||
* (the TS cyclic-type shape is only reachable with a real cross-file
|
||||
* registry, so that one is verified at the real-repo tier; the synthetic
|
||||
* cyclic fixture here guards the in-file path).
|
||||
* lookup_member_type cycle. The walks now carry depth guards. Non-TS crash
|
||||
* fixtures fork on POSIX and run in-process on Windows. TypeScript cycle work
|
||||
* runs in-process on both platforms to assert test-only lookup counters.
|
||||
* The synthetic single-file fixtures here guard the local inheritance path;
|
||||
* real-repo scale extraction is checked separately.
|
||||
* ═══════════════════════════════════════════════════════════════════ */
|
||||
|
||||
#if !defined(_WIN32)
|
||||
@@ -670,9 +671,106 @@ TEST(lsp_ts_cyclic_types_no_crash) {
|
||||
"interface D extends C { d: number; }\n"
|
||||
"declare const a: A;\n"
|
||||
"declare const c: C;\n"
|
||||
"function useIt(p: C) { return p.missing_member; }\n"
|
||||
/* Both lookup paths must traverse the inheritance cycle. */
|
||||
"function useIt(p: C) { p.missing(); return p.missing_member; }\n"
|
||||
"const y = c.also_missing;\n";
|
||||
ASSERT_FALSE(so_extract_crashes(src, CBM_LANG_TYPESCRIPT, "cycle.ts"));
|
||||
CBMFileResult *r = extract(src, CBM_LANG_TYPESCRIPT, "test", "cycle.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
/* C and D each have one parent: <= 64 admitted visits per type, plus
|
||||
* one attempted (possibly rejected) parent visit per admitted visit. */
|
||||
ASSERT_GT(cbm_ts_lsp_test_max_member_lookup_steps(), 0);
|
||||
ASSERT_GT(cbm_ts_lsp_test_max_method_lookup_steps(), 0);
|
||||
ASSERT_LTE(cbm_ts_lsp_test_max_member_lookup_steps(), 2 * 64 * 2);
|
||||
ASSERT_LTE(cbm_ts_lsp_test_max_method_lookup_steps(), 2 * 64 * 2);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(lsp_ts_branching_cycle_has_bounded_work) {
|
||||
const char *src =
|
||||
"interface A extends B, C {}\n"
|
||||
"interface B extends A, C {}\n"
|
||||
"interface C extends A, B {}\n"
|
||||
"function useIt(value: A) { value.missing(); return value.missing_member; }\n";
|
||||
CBMFileResult *r = extract(src, CBM_LANG_TYPESCRIPT, "test", "branching-cycle.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
/* A, B, C each have two parents: <= 64 admitted visits per type, plus
|
||||
* two attempted (possibly rejected) parent visits per admitted visit. */
|
||||
ASSERT_GT(cbm_ts_lsp_test_max_member_lookup_steps(), 0);
|
||||
ASSERT_GT(cbm_ts_lsp_test_max_method_lookup_steps(), 0);
|
||||
ASSERT_LTE(cbm_ts_lsp_test_max_member_lookup_steps(), 3 * 64 * 3);
|
||||
ASSERT_LTE(cbm_ts_lsp_test_max_method_lookup_steps(), 3 * 64 * 3);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(lsp_ts_shallower_revisit_preserves_inherited_method) {
|
||||
enum { CHAIN = 62, CAP = 16384 };
|
||||
char *src = malloc(CAP);
|
||||
ASSERT_NOT_NULL(src);
|
||||
size_t used =
|
||||
(size_t)snprintf(src, CAP,
|
||||
"interface Service { ping(): void; }\n"
|
||||
"interface Target { inherited(): void; inheritedField: Service; }\n"
|
||||
"interface Shared extends Target {}\n"
|
||||
"interface Root extends D0, Shared {}\n");
|
||||
for (int i = 0; i < CHAIN; i++) {
|
||||
const char *parent = i + 1 < CHAIN ? "D" : "Shared";
|
||||
int wrote = i + 1 < CHAIN
|
||||
? snprintf(src + used, CAP - used, "interface D%d extends %s%d {}\n", i,
|
||||
parent, i + 1)
|
||||
: snprintf(src + used, CAP - used, "interface D%d extends Shared {}\n", i);
|
||||
ASSERT_TRUE(wrote > 0 && (size_t)wrote < CAP - used);
|
||||
used += (size_t)wrote;
|
||||
}
|
||||
int wrote = snprintf(
|
||||
src + used, CAP - used,
|
||||
"function useIt(value: Root) { value.inherited(); value.inheritedField.ping(); }\n");
|
||||
ASSERT_TRUE(wrote > 0 && (size_t)wrote < CAP - used);
|
||||
|
||||
CBMFileResult *r = extract(src, CBM_LANG_TYPESCRIPT, "test", "shallower.ts");
|
||||
free(src);
|
||||
ASSERT_NOT_NULL(r);
|
||||
int inherited_method_found = 0;
|
||||
int inherited_field_method_found = 0;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *resolved = &r->resolved_calls.items[i];
|
||||
if (resolved->confidence > 0 && resolved->caller_qn &&
|
||||
strstr(resolved->caller_qn, "useIt") && resolved->callee_qn &&
|
||||
strstr(resolved->callee_qn, "inherited")) {
|
||||
inherited_method_found = 1;
|
||||
}
|
||||
if (resolved->confidence > 0 && resolved->caller_qn &&
|
||||
strstr(resolved->caller_qn, "useIt") && resolved->callee_qn &&
|
||||
strstr(resolved->callee_qn, "ping")) {
|
||||
inherited_field_method_found = 1;
|
||||
}
|
||||
}
|
||||
cbm_free_result(r);
|
||||
ASSERT_TRUE(inherited_method_found);
|
||||
ASSERT_TRUE(inherited_field_method_found);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(lsp_ts_inherited_method_lookup) {
|
||||
const char *src = "interface Base { inherited(): void; }\n"
|
||||
"interface Child extends Base {}\n"
|
||||
"function useIt(c: Child) { c.inherited(); }\n";
|
||||
CBMFileResult *r = extract(src, CBM_LANG_TYPESCRIPT, "test", "inherited.ts");
|
||||
ASSERT_NOT_NULL(r);
|
||||
|
||||
int found = 0;
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *resolved = &r->resolved_calls.items[i];
|
||||
if (resolved->confidence > 0 && resolved->caller_qn &&
|
||||
strstr(resolved->caller_qn, "useIt") && resolved->callee_qn &&
|
||||
strstr(resolved->callee_qn, "inherited")) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cbm_free_result(r);
|
||||
ASSERT_TRUE(found);
|
||||
PASS();
|
||||
}
|
||||
|
||||
@@ -777,9 +875,8 @@ TEST(lsp_kotlin_deep_nesting_no_crash) {
|
||||
|
||||
/* Split into three sub-suites so parallel/sharded runs are not serialized
|
||||
* behind one ~4-minute suite (it was the wall-clock critical path: every
|
||||
* other suite finished underneath it). Pure re-registration — the 20
|
||||
* RUN_TEST entries are exactly the ones the single suite carried; the
|
||||
* before/after test-count parity is asserted in the shard runner. */
|
||||
* other suite finished underneath it). New regressions stay in the matching
|
||||
* shard; the shard runner asserts aggregate test-count parity. */
|
||||
SUITE(stack_overflow_a) {
|
||||
cbm_init();
|
||||
|
||||
@@ -799,6 +896,9 @@ SUITE(stack_overflow_b) {
|
||||
|
||||
RUN_TEST(perl_glr_deep_parse_recursion_capped);
|
||||
RUN_TEST(lsp_ts_cyclic_types_no_crash);
|
||||
RUN_TEST(lsp_ts_branching_cycle_has_bounded_work);
|
||||
RUN_TEST(lsp_ts_shallower_revisit_preserves_inherited_method);
|
||||
RUN_TEST(lsp_ts_inherited_method_lookup);
|
||||
RUN_TEST(lsp_python_deep_nesting_no_crash);
|
||||
RUN_TEST(lsp_go_deep_nesting_no_crash);
|
||||
RUN_TEST(lsp_php_deep_nesting_no_crash);
|
||||
|
||||
Reference in new issue
Block a user