From 64ac984129a7a4645efe5ac57c168ef880b479b2 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 21 May 2021 11:01:06 -0700 Subject: Make RubyVM::AbstractSyntaxTree.of raise for method/proc created in eval This changes Thread::Location::Backtrace#absolute_path to return nil for methods/procs defined in eval. If the realpath of an iseq is nil, that indicates it was defined in eval, in which case you cannot use RubyVM::AbstractSyntaxTree.of. Fixes [Bug #16983] Co-authored-by: Koichi Sasada --- ast.c | 3 +++ iseq.c | 6 ++++++ iseq.h | 1 + .../backtrace/location/absolute_path_spec.rb | 18 ++++++++++++---- test/ruby/test_ast.rb | 20 +++++++++++++++++ vm_eval.c | 25 ++++++++++++++++++---- 6 files changed, 65 insertions(+), 8 deletions(-) diff --git a/ast.c b/ast.c index 32d8ab9eb6..20c4588cd7 100644 --- a/ast.c +++ b/ast.c @@ -213,6 +213,9 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE save_script else { iseq = rb_method_iseq(body); } + if (rb_iseq_from_eval_p(iseq)) { + rb_raise(rb_eArgError, "cannot get AST for method defined in eval"); + } path = rb_iseq_path(iseq); node_id = iseq->body->location.node_id; } diff --git a/iseq.c b/iseq.c index 9af7b54eff..47bc108d3e 100644 --- a/iseq.c +++ b/iseq.c @@ -1115,6 +1115,12 @@ rb_iseq_absolute_path(const rb_iseq_t *iseq) return rb_iseq_realpath(iseq); } +int +rb_iseq_from_eval_p(const rb_iseq_t *iseq) +{ + return NIL_P(rb_iseq_realpath(iseq)); +} + VALUE rb_iseq_label(const rb_iseq_t *iseq) { diff --git a/iseq.h b/iseq.h index 87e498227b..b792e13703 100644 --- a/iseq.h +++ b/iseq.h @@ -192,6 +192,7 @@ VALUE rb_iseqw_new(const rb_iseq_t *iseq); const rb_iseq_t *rb_iseqw_to_iseq(VALUE iseqw); VALUE rb_iseq_absolute_path(const rb_iseq_t *iseq); /* obsolete */ +int rb_iseq_from_eval_p(const rb_iseq_t *iseq); VALUE rb_iseq_label(const rb_iseq_t *iseq); VALUE rb_iseq_base_label(const rb_iseq_t *iseq); VALUE rb_iseq_first_lineno(const rb_iseq_t *iseq); diff --git a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb index b0ae28beee..4136f09348 100644 --- a/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb +++ b/spec/ruby/core/thread/backtrace/location/absolute_path_spec.rb @@ -18,10 +18,20 @@ describe 'Thread::Backtrace::Location#absolute_path' do end context "when used in eval with a given filename" do - it "returns filename" do - code = "caller_locations(0)[0].absolute_path" - eval(code, nil, "foo.rb").should == "foo.rb" - eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" + code = "caller_locations(0)[0].absolute_path" + + ruby_version_is ""..."3.1" do + it "returns filename with absolute_path" do + eval(code, nil, "foo.rb").should == "foo.rb" + eval(code, nil, "foo/bar.rb").should == "foo/bar.rb" + end + end + + ruby_version_is "3.1" do + it "returns nil with absolute_path" do + eval(code, nil, "foo.rb").should == nil + eval(code, nil, "foo/bar.rb").should == nil + end end end diff --git a/test/ruby/test_ast.rb b/test/ruby/test_ast.rb index 5a229eabd4..3b4cbc7dee 100644 --- a/test/ruby/test_ast.rb +++ b/test/ruby/test_ast.rb @@ -211,6 +211,26 @@ class TestAst < Test::Unit::TestCase end end + def test_of_eval + method = self.method(eval("def example_method_#{$$}; end")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("def self.example_singleton_method_#{$$}; end")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("proc{}") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("singleton_class.define_method(:example_define_method_#{$$}){}")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = self.method(eval("define_singleton_method(:example_dsm_#{$$}){}")) + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + + method = eval("Class.new{def example_method; end}.instance_method(:example_method)") + assert_raise(ArgumentError) { RubyVM::AbstractSyntaxTree.of(method) } + end + def test_scope_local_variables node = RubyVM::AbstractSyntaxTree.parse("_x = 0") lv, _, body = *node.children diff --git a/vm_eval.c b/vm_eval.c index bc40c15b6c..19ba6dd96b 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -1661,13 +1661,15 @@ rb_each(VALUE obj) } void rb_parser_warn_location(VALUE, int); + +static VALUE eval_default_path; + static const rb_iseq_t * eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, const struct rb_block *base_block) { const VALUE parser = rb_parser_new(); const rb_iseq_t *const parent = vm_block_iseq(base_block); - VALUE realpath = Qnil; rb_iseq_t *iseq = NULL; rb_ast_t *ast; int isolated_depth = 0; @@ -1694,10 +1696,14 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, if (fname != Qundef) { if (!NIL_P(fname)) fname = rb_fstring(fname); - realpath = fname; } else { fname = rb_fstring_lit("(eval)"); + if (!eval_default_path) { + eval_default_path = rb_fstring_lit("(eval)"); + rb_gc_register_mark_object(eval_default_path); + } + fname = eval_default_path; } rb_parser_set_context(parser, parent, FALSE); @@ -1705,7 +1711,7 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, if (ast->body.root) { iseq = rb_iseq_new_eval(&ast->body, parent->body->location.label, - fname, realpath, INT2FIX(line), + fname, Qnil, INT2FIX(line), parent, isolated_depth); } rb_ast_dispose(ast); @@ -2590,7 +2596,18 @@ rb_current_realfilepath(void) const rb_execution_context_t *ec = GET_EC(); rb_control_frame_t *cfp = ec->cfp; cfp = vm_get_ruby_level_caller_cfp(ec, RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)); - if (cfp != 0) return rb_iseq_realpath(cfp->iseq); + if (cfp != NULL) { + VALUE path = rb_iseq_realpath(cfp->iseq); + if (RTEST(path)) return path; + // eval context + path = rb_iseq_path(cfp->iseq); + if (path == eval_default_path) { + return Qnil; + } + else { + return path; + } + } return Qnil; } -- cgit v1.2.1