From 4cf75fb0354a3b79910e31c74f12735f9ed15036 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 4 Dec 2025 16:09:35 +0900 Subject: [PATCH] mruby-bin-mirb: restrict evaluation to simple receivers fixed a bug where tab completion on complex expressions like "d.new(1).a" would corrupt local variables, causing them to become nil. the root cause was that evaluating complex receiver expressions during tab completion ran mrb_vm_run() without proper stack management (stack_keep) and environment adjustment that mirb's main REPL loop performs. this corrupted the local variable storage. the fix restricts tab completion to only evaluate simple receiver expressions (variable/constant names without operators or method calls). complex expressions are skipped for completion. this means: - works: d. completes methods of variable d - works: String. completes methods of constant String - skipped: d.new(1). provides no completion this is a reasonable trade-off that prevents the corruption bug while still supporting the most common completion scenarios. also updated mirb_eval_receiver() to use the compiler context for proper local variable resolution, with argument order matching mrb_parse_string. Co-authored-by: Claude --- .../tools/mirb/mirb_completion.c | 37 ++++++++++++++++--- .../tools/mirb/mirb_completion.h | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.c index 3276c172e..5fbcd3ed6 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.c @@ -199,16 +199,36 @@ mirb_extract_receiver(const char *line, int cursor_pos, int *recv_end) * Receiver Evaluation * ============================================================ */ +/* Check if receiver expression is simple (just a name, no method calls) */ +static mrb_bool +is_simple_receiver(const char *expr) +{ + int i; + + /* Empty is not simple */ + if (!expr || expr[0] == '\0') return FALSE; + + /* Check if it contains only alphanumeric, underscore, or scope resolution */ + for (i = 0; expr[i]; i++) { + char c = expr[i]; + if (!(ISALNUM(c) || c == '_' || c == ':')) { + return FALSE; /* Contains operators, parentheses, etc. */ + } + } + + return TRUE; +} + mrb_value -mirb_eval_receiver(mrb_state *mrb, const char *receiver_expr) +mirb_eval_receiver(mrb_state *mrb, const char *receiver_expr, mrb_ccontext *cxt) { struct mrb_parser_state *parser; struct RProc *proc; mrb_value result; int ai = mrb_gc_arena_save(mrb); - /* Parse the receiver expression */ - parser = mrb_parse_string(mrb, receiver_expr, NULL); + /* Parse the receiver expression WITH compiler context to access local variables */ + parser = mrb_parse_string(mrb, receiver_expr, cxt); if (!parser || parser->nerr > 0) { if (parser) mrb_parser_free(parser); return mrb_nil_value(); @@ -454,9 +474,14 @@ mirb_generate_completions(mirb_completion_ctx *ctx, const char *line, int cursor case COMPLETION_METHOD: receiver_expr = mirb_extract_receiver(line, cursor_pos, &recv_end); if (receiver_expr) { - receiver = mirb_eval_receiver(ctx->mrb, receiver_expr); - if (!mrb_nil_p(receiver)) { - mirb_complete_methods(ctx, receiver); + /* Only evaluate simple receivers to avoid corrupting VM state. + * Complex expressions like "obj.method()" are skipped for now. + * This prevents local variables from being cleared during tab completion. */ + if (is_simple_receiver(receiver_expr)) { + receiver = mirb_eval_receiver(ctx->mrb, receiver_expr, ctx->cxt); + if (!mrb_nil_p(receiver)) { + mirb_complete_methods(ctx, receiver); + } } free(receiver_expr); } diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.h b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.h index 4649e91d2..63ecbb3a4 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.h +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_completion.h @@ -95,7 +95,7 @@ void mirb_add_completion(mirb_completion_ctx *ctx, const char *text); char *mirb_extract_receiver(const char *line, int cursor_pos, int *recv_end); /* Evaluate receiver expression to get object */ -mrb_value mirb_eval_receiver(mrb_state *mrb, const char *receiver_expr); +mrb_value mirb_eval_receiver(mrb_state *mrb, const char *receiver_expr, mrb_ccontext *cxt);; /* Check if in file completion context */ mrb_bool mirb_in_file_context(const char *line, int quote_pos);