Skip to content

Commit dbf2590

Browse files
committed
fix(py-lsp): cap expression evaluation depth
Signed-off-by: Dustin Persek <dustin.persek@protonmail.com>
1 parent 1cca858 commit dbf2590

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

internal/cbm/lsp/py_lsp.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323
* only from py_lsp.c, never compiled standalone. */
2424
#include "py_builtins.c"
2525

26+
#define PY_LSP_MAX_EVAL_DEPTH 64
27+
2628
// Forward decls
2729
static void py_resolve_calls_in(PyLSPContext *ctx, TSNode node);
2830
static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node);
31+
static const CBMType *py_eval_expr_type_inner(PyLSPContext *ctx, TSNode node);
2932
static void py_process_statement(PyLSPContext *ctx, TSNode node);
3033
static const CBMRegisteredFunc *py_lookup_attribute(PyLSPContext *ctx, const char *type_qn,
3134
const char *member_name);
@@ -693,6 +696,18 @@ static const CBMType *py_iterable_element_type(PyLSPContext *ctx, const CBMType
693696
}
694697

695698
static const CBMType *py_eval_expr_type(PyLSPContext *ctx, TSNode node) {
699+
if (!ctx || ts_node_is_null(node))
700+
return cbm_type_unknown();
701+
if (ctx->eval_depth >= PY_LSP_MAX_EVAL_DEPTH)
702+
return cbm_type_unknown();
703+
704+
ctx->eval_depth++;
705+
const CBMType *result = py_eval_expr_type_inner(ctx, node);
706+
ctx->eval_depth--;
707+
return result ? result : cbm_type_unknown();
708+
}
709+
710+
static const CBMType *py_eval_expr_type_inner(PyLSPContext *ctx, TSNode node) {
696711
if (!ctx || ts_node_is_null(node))
697712
return cbm_type_unknown();
698713

internal/cbm/lsp/py_lsp.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ typedef struct {
6969
int dict_literal_count;
7070
int dict_literal_cap;
7171

72+
// Expression evaluator recursion depth guard.
73+
int eval_depth;
74+
7275
// Debug mode (CBM_LSP_DEBUG env, shared across all language LSPs).
7376
bool debug;
7477
} PyLSPContext;

tests/test_stack_overflow.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,27 @@ TEST(lsp_cpp_deep_expression_no_crash) {
487487
PASS();
488488
}
489489

490+
TEST(lsp_python_deep_expression_no_crash) {
491+
/* Deep parenthesized expressions force repeated py_eval_expr_type
492+
* recursion through assignment RHS inference. The evaluator should hit
493+
* its depth cap and fall back to unknown instead of overflowing. */
494+
const int DEPTH = 256;
495+
size_t sz = (size_t)DEPTH * 2 + 256;
496+
char *src = malloc(sz);
497+
ASSERT_NOT_NULL(src);
498+
char *p = src;
499+
p += snprintf(p, sz, "def main():\n value = ");
500+
memset(p, '(', DEPTH);
501+
p += DEPTH;
502+
*p++ = '1';
503+
memset(p, ')', DEPTH);
504+
p += DEPTH;
505+
snprintf(p, sz - (size_t)(p - src), "\n return value\n");
506+
ASSERT_FALSE(so_extract_crashes(src, CBM_LANG_PYTHON, "deep_expr.py"));
507+
free(src);
508+
PASS();
509+
}
510+
490511
TEST(lsp_java_lambda_args_exceed_params_no_crash) {
491512
/* A call with MORE arguments than the resolved method's declared params:
492513
* bind_lambda_args indexed the NULL-terminated signature param_types array
@@ -528,6 +549,7 @@ SUITE(stack_overflow) {
528549
RUN_TEST(lsp_java_deep_nesting_no_crash);
529550
RUN_TEST(lsp_java_lambda_args_exceed_params_no_crash);
530551
RUN_TEST(lsp_cpp_deep_expression_no_crash);
552+
RUN_TEST(lsp_python_deep_expression_no_crash);
531553
RUN_TEST(lsp_ts_cyclic_types_no_crash);
532554

533555
RUN_TEST(js_calls_exceed_512);

0 commit comments

Comments
 (0)