Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions internal/cbm/lsp/py_lsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
* only from py_lsp.c, never compiled standalone. */
#include "py_builtins.c"

#define PY_LSP_MAX_EVAL_DEPTH 64

// Forward decls
static void py_resolve_calls_in_inner(PyLSPContext *ctx, TSNode node);

Expand All @@ -41,6 +43,7 @@ static void py_resolve_calls_in(PyLSPContext *ctx, TSNode node) {
ctx->walk_depth--;
}
static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node);
static const CBMType *py_eval_expr_type_inner(PyLSPContext *ctx, TSNode node);
static void py_process_statement(PyLSPContext *ctx, TSNode node);
static const CBMRegisteredFunc *py_lookup_attribute(PyLSPContext *ctx, const char *type_qn,
const char *member_name);
Expand Down Expand Up @@ -708,6 +711,18 @@ static const CBMType *py_iterable_element_type(PyLSPContext *ctx, const CBMType
}

static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node) {
if (!ctx || ts_node_is_null(node))
return cbm_type_unknown();
if (ctx->eval_depth >= PY_LSP_MAX_EVAL_DEPTH)
return cbm_type_unknown();

ctx->eval_depth++;
const CBMType *result = py_eval_expr_type_inner(ctx, node);
ctx->eval_depth--;
return result ? result : cbm_type_unknown();
}

static const CBMType *py_eval_expr_type_inner(PyLSPContext *ctx, TSNode node) {
if (!ctx || ts_node_is_null(node))
return cbm_type_unknown();

Expand Down
3 changes: 3 additions & 0 deletions internal/cbm/lsp/py_lsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ typedef struct {
int dict_literal_count;
int dict_literal_cap;

// Expression evaluator recursion depth guard.
int eval_depth;

// AST-walk recursion depth for py_resolve_calls_in (guards stack overflow on
// deeply-nested/cyclic files; see cbm_lsp_max_walk_depth). Zero via memset.
int walk_depth;
Expand Down
22 changes: 22 additions & 0 deletions tests/test_stack_overflow.c
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,27 @@ TEST(lsp_cpp_deep_expression_no_crash) {
PASS();
}

TEST(lsp_python_deep_expression_no_crash) {
/* Deep parenthesized expressions force repeated py_eval_expr_type
* recursion through assignment RHS inference. The evaluator should hit
* its depth cap and fall back to unknown instead of overflowing. */
const int DEPTH = 256;
size_t sz = (size_t)DEPTH * 2 + 256;
char *src = malloc(sz);
ASSERT_NOT_NULL(src);
char *p = src;
p += snprintf(p, sz, "def main():\n value = ");
memset(p, '(', DEPTH);
p += DEPTH;
*p++ = '1';
memset(p, ')', DEPTH);
p += DEPTH;
snprintf(p, sz - (size_t)(p - src), "\n return value\n");
ASSERT_FALSE(so_extract_crashes(src, CBM_LANG_PYTHON, "deep_expr.py"));
free(src);
PASS();
}

TEST(lsp_java_lambda_args_exceed_params_no_crash) {
/* A call with MORE arguments than the resolved method's declared params:
* bind_lambda_args indexed the NULL-terminated signature param_types array
Expand Down Expand Up @@ -623,6 +644,7 @@ SUITE(stack_overflow) {
RUN_TEST(lsp_java_deep_nesting_no_crash);
RUN_TEST(lsp_java_lambda_args_exceed_params_no_crash);
RUN_TEST(lsp_cpp_deep_expression_no_crash);
RUN_TEST(lsp_python_deep_expression_no_crash);
RUN_TEST(lsp_ts_cyclic_types_no_crash);
RUN_TEST(lsp_python_deep_nesting_no_crash);
RUN_TEST(lsp_go_deep_nesting_no_crash);
Expand Down
Loading