mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	Raise RuntimeError if Kernel#binding is called from a non-Ruby frame
Check whether the current or previous frame is a Ruby frame in call_trace_func before attempting to create a binding for the frame. Fixes [Bug #18487] Co-authored-by: Alan Wu <XrXr@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									33b13bd9f1
								
							
						
					
					
						commit
						343ea9967e
					
				
				
				Notes:
				
					git
				
				2022-03-25 04:31:32 +09:00 
				
			
			
			
		
		
					 6 changed files with 59 additions and 72 deletions
				
			
		| 
						 | 
				
			
			@ -1,34 +1,3 @@
 | 
			
		|||
assert_equal '2022', %q{
 | 
			
		||||
 def contrivance(hash, key)
 | 
			
		||||
    # Expect this to compile to an `opt_aref`.
 | 
			
		||||
    hash[key]
 | 
			
		||||
 | 
			
		||||
    # The [] call above tracks that the `hash` local has a VALUE that
 | 
			
		||||
    # is a heap pointer and the guard for the Kernel#itself call below
 | 
			
		||||
    # doesn't check that it's a heap pointer VALUE.
 | 
			
		||||
    #
 | 
			
		||||
    # As you can see from the crash, the call to rb_hash_aref() can set the
 | 
			
		||||
    # `hash` local, making eliding the heap object guard unsound.
 | 
			
		||||
    hash.itself
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # This is similar to ->(recv, mid) { send(recv, mid).local_variable_set(...) }.
 | 
			
		||||
  # By composing we avoid creating new Ruby frames and so sending :binding
 | 
			
		||||
  # captures the environment of the frame that does the missing key lookup.
 | 
			
		||||
  # We use it to capture the environment inside of `contrivance`.
 | 
			
		||||
  cap_then_set =
 | 
			
		||||
    Kernel.instance_method(:send).method(:bind_call).to_proc >>
 | 
			
		||||
      ->(binding) { binding.local_variable_set(:hash, 2022) }
 | 
			
		||||
  special_missing = Hash.new(&cap_then_set)
 | 
			
		||||
 | 
			
		||||
  # Make YJIT speculate that it's a hash and generate code
 | 
			
		||||
  # that calls rb_hash_aref().
 | 
			
		||||
  contrivance({}, :warmup)
 | 
			
		||||
  contrivance({}, :warmup)
 | 
			
		||||
 | 
			
		||||
  contrivance(special_missing, :binding)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
assert_equal '18374962167983112447', %q{
 | 
			
		||||
  # regression test for incorrectly discarding 32 bits of a pointer when it
 | 
			
		||||
  # comes to default values.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,12 +8,21 @@ describe "CApiBindingSpecs" do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  describe "Kernel#binding" do
 | 
			
		||||
    it "gives the top-most Ruby binding when called from C" do
 | 
			
		||||
      foo = 14
 | 
			
		||||
      b = @b.get_binding
 | 
			
		||||
      b.local_variable_get(:foo).should == 14
 | 
			
		||||
      b.local_variable_set :foo, 12
 | 
			
		||||
      foo.should == 12
 | 
			
		||||
    ruby_version_is '3.2' do
 | 
			
		||||
      it "raises when called from C" do
 | 
			
		||||
        foo = 14
 | 
			
		||||
        -> { @b.get_binding }.should raise_error(RuntimeError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ruby_version_is ''...'3.2' do
 | 
			
		||||
      it "gives the top-most Ruby binding when called from C" do
 | 
			
		||||
        foo = 14
 | 
			
		||||
        b = @b.get_binding
 | 
			
		||||
        b.local_variable_get(:foo).should == 14
 | 
			
		||||
        b.local_variable_set :foo, 12
 | 
			
		||||
        foo.should == 12
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -445,6 +445,11 @@ class TestProc < Test::Unit::TestCase
 | 
			
		|||
    assert_equal(@@line_of_source_location_test, lineno, 'Bug #2427')
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_binding_error_unless_ruby_frame
 | 
			
		||||
    define_singleton_method :binding_from_c!, method(:binding).to_proc >> ->(bndg) {bndg}
 | 
			
		||||
    assert_raise(RuntimeError) { binding_from_c! }
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def test_proc_lambda
 | 
			
		||||
    assert_raise(ArgumentError) { proc }
 | 
			
		||||
    assert_raise(ArgumentError) { assert_warn(/deprecated/) {lambda} }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -594,7 +594,7 @@ CODE
 | 
			
		|||
 | 
			
		||||
    eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
 | 
			
		||||
    1: set_trace_func(lambda{|event, file, line, id, binding, klass|
 | 
			
		||||
    2:   events << [event, line, file, klass, id, binding.eval('self'), binding.eval("_local_var")] if file == 'xyzzy'
 | 
			
		||||
    2:   events << [event, line, file, klass, id, binding&.eval('self'), binding&.eval("_local_var")] if file == 'xyzzy'
 | 
			
		||||
    3: })
 | 
			
		||||
    4: 1.times{|;_local_var| _local_var = :inner
 | 
			
		||||
    5:   tap{}
 | 
			
		||||
| 
						 | 
				
			
			@ -619,31 +619,31 @@ CODE
 | 
			
		|||
 | 
			
		||||
    answer_events = [
 | 
			
		||||
     #
 | 
			
		||||
     [:c_return, 1, "xyzzy", TracePoint,  :trace,           TracePoint,  :outer,  trace],
 | 
			
		||||
     [:c_return, 1, "xyzzy", TracePoint,  :trace,           TracePoint,  nil,  nil],
 | 
			
		||||
     [:line,     4, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,   4, 'xyzzy', Integer,     :times,           1,           :outer, :nothing],
 | 
			
		||||
     [:c_call,   4, 'xyzzy', Integer,     :times,           1,           nil, nil],
 | 
			
		||||
     [:line,     4, 'xyzzy', self.class,  method,           self,        nil,    :nothing],
 | 
			
		||||
     [:line,     5, 'xyzzy', self.class,  method,           self,        :inner, :nothing],
 | 
			
		||||
     [:c_return, 4, "xyzzy", Integer,     :times,           1,           :outer, 1],
 | 
			
		||||
     [:c_return, 4, "xyzzy", Integer,     :times,           1,           nil, nil],
 | 
			
		||||
     [:line,     7, 'xyzzy', self.class,  method,           self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      :outer, :nothing],
 | 
			
		||||
     [:c_return, 7, "xyzzy", Class,       :inherited,       Object,      :outer, nil],
 | 
			
		||||
     [:c_call,   7, "xyzzy", Class,       :const_added,     Object,      :outer, :nothing],
 | 
			
		||||
     [:c_return, 7, "xyzzy", Class,       :const_added,     Object,      :outer, nil],
 | 
			
		||||
     [:c_call,   7, "xyzzy", Class,       :inherited,       Object,      nil, nil],
 | 
			
		||||
     [:c_return, 7, "xyzzy", Class,       :inherited,       Object,      nil, nil],
 | 
			
		||||
     [:c_call,   7, "xyzzy", Class,       :const_added,     Object,      nil, nil],
 | 
			
		||||
     [:c_return, 7, "xyzzy", Class,       :const_added,     Object,      nil, nil],
 | 
			
		||||
     [:class,    7, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
 | 
			
		||||
     [:line,     8, "xyzzy", nil,         nil,              xyzzy.class, nil,    :nothing],
 | 
			
		||||
     [:line,     9, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
 | 
			
		||||
     [:c_call,   9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
 | 
			
		||||
     [:c_return, 9, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
 | 
			
		||||
     [:c_call,   9, "xyzzy", Module,      :method_added,    xyzzy.class, nil, nil],
 | 
			
		||||
     [:c_return, 9, "xyzzy", Module,      :method_added,    xyzzy.class, nil, nil],
 | 
			
		||||
     [:line,    13, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
 | 
			
		||||
     [:c_call,  13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, :nothing],
 | 
			
		||||
     [:c_return,13, "xyzzy", Module,      :method_added,    xyzzy.class, :XYZZY_outer, nil],
 | 
			
		||||
     [:c_call,  13, "xyzzy", Module,      :method_added,    xyzzy.class, nil, nil],
 | 
			
		||||
     [:c_return,13, "xyzzy", Module,      :method_added,    xyzzy.class, nil, nil],
 | 
			
		||||
     [:end,     17, "xyzzy", nil,         nil,              xyzzy.class, :XYZZY_outer, :nothing],
 | 
			
		||||
     [:line,    18, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,  18, "xyzzy", Class,       :new,             xyzzy.class, :outer, :nothing],
 | 
			
		||||
     [:c_call,  18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, :nothing],
 | 
			
		||||
     [:c_return,18, "xyzzy", BasicObject, :initialize,      xyzzy,       :outer, nil],
 | 
			
		||||
     [:c_return,18, "xyzzy", Class,       :new,             xyzzy.class, :outer, xyzzy],
 | 
			
		||||
     [:c_call,  18, "xyzzy", Class,       :new,             xyzzy.class, nil, nil],
 | 
			
		||||
     [:c_call,  18, "xyzzy", BasicObject, :initialize,      xyzzy,       nil, nil],
 | 
			
		||||
     [:c_return,18, "xyzzy", BasicObject, :initialize,      xyzzy,       nil, nil],
 | 
			
		||||
     [:c_return,18, "xyzzy", Class,       :new,             xyzzy.class, nil, nil],
 | 
			
		||||
     [:line,    19, "xyzzy", TestSetTraceFunc, method,      self, :outer, :nothing],
 | 
			
		||||
     [:call,     9, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
 | 
			
		||||
     [:line,    10, "xyzzy", xyzzy.class, :foo,             xyzzy,       nil,  :nothing],
 | 
			
		||||
| 
						 | 
				
			
			@ -654,19 +654,19 @@ CODE
 | 
			
		|||
     [:return,  16, "xyzzy", xyzzy.class, :bar,             xyzzy,       :XYZZY_bar, xyzzy],
 | 
			
		||||
     [:return,  12, "xyzzy", xyzzy.class, :foo,             xyzzy,       :XYZZY_foo, xyzzy],
 | 
			
		||||
     [:line,    20, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Kernel,      :raise,           self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, :nothing],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, :nothing],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :initialize,      raised_exc,  :outer, raised_exc],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :exception,       RuntimeError, :outer, raised_exc],
 | 
			
		||||
     [:c_return,20, "xyzzy", Kernel,      :raise,           self,        :outer, nil],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, :nothing],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :backtrace,       raised_exc,  :outer, nil],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Kernel,      :raise,           self,        nil, nil],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :exception,       RuntimeError, nil, nil],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :initialize,      raised_exc,  nil, nil],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :initialize,      raised_exc,  nil, nil],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :exception,       RuntimeError, nil, nil],
 | 
			
		||||
     [:c_return,20, "xyzzy", Kernel,      :raise,           self,        nil, nil],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Exception,   :backtrace,       raised_exc,  nil, nil],
 | 
			
		||||
     [:c_return,20, "xyzzy", Exception,   :backtrace,       raised_exc,  nil, nil],
 | 
			
		||||
     [:raise,   20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Module,      :===,             RuntimeError,:outer, :nothing],
 | 
			
		||||
     [:c_return,20, "xyzzy", Module,      :===,             RuntimeError,:outer, true],
 | 
			
		||||
     [:c_call,  20, "xyzzy", Module,      :===,             RuntimeError, nil, nil],
 | 
			
		||||
     [:c_return,20, "xyzzy", Module,      :===,             RuntimeError, nil, nil],
 | 
			
		||||
     [:line,    21, "xyzzy", TestSetTraceFunc, method,      self,        :outer, :nothing],
 | 
			
		||||
     [:c_call,  21, "xyzzy", TracePoint,  :disable,         trace,       :outer, :nothing],
 | 
			
		||||
     [:c_call,  21, "xyzzy", TracePoint,  :disable,         trace,       nil, nil],
 | 
			
		||||
     ]
 | 
			
		||||
    return events, answer_events
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								vm.c
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								vm.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1254,15 +1254,12 @@ rb_vm_make_binding(const rb_execution_context_t *ec, const rb_control_frame_t *s
 | 
			
		|||
    if (cfp == 0 || ruby_level_cfp == 0) {
 | 
			
		||||
	rb_raise(rb_eRuntimeError, "Can't create Binding Object on top of Fiber.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    while (1) {
 | 
			
		||||
	envval = vm_make_env_object(ec, cfp);
 | 
			
		||||
	if (cfp == ruby_level_cfp) {
 | 
			
		||||
	    break;
 | 
			
		||||
	}
 | 
			
		||||
	cfp = rb_vm_get_binding_creatable_next_cfp(ec, RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp));
 | 
			
		||||
    if (!VM_FRAME_RUBYFRAME_P(src_cfp) &&
 | 
			
		||||
        !VM_FRAME_RUBYFRAME_P(RUBY_VM_PREVIOUS_CONTROL_FRAME(src_cfp))) {
 | 
			
		||||
        rb_raise(rb_eRuntimeError, "Cannot create Binding object for non-Ruby caller");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    envval = vm_make_env_object(ec, cfp);
 | 
			
		||||
    bindval = rb_binding_alloc(rb_cBinding);
 | 
			
		||||
    GetBindingPtr(bindval, bind);
 | 
			
		||||
    vm_bind_update_env(bindval, bind, envval);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -719,7 +719,14 @@ call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klas
 | 
			
		|||
    argv[1] = filename;
 | 
			
		||||
    argv[2] = INT2FIX(line);
 | 
			
		||||
    argv[3] = id ? ID2SYM(id) : Qnil;
 | 
			
		||||
    argv[4] = (self && (filename != Qnil)) ? rb_binding_new() : Qnil;
 | 
			
		||||
    argv[4] = Qnil;
 | 
			
		||||
    if (self && (filename != Qnil) &&
 | 
			
		||||
        event != RUBY_EVENT_C_CALL &&
 | 
			
		||||
        event != RUBY_EVENT_C_RETURN &&
 | 
			
		||||
        (VM_FRAME_RUBYFRAME_P(ec->cfp) ||
 | 
			
		||||
         VM_FRAME_RUBYFRAME_P(RUBY_VM_PREVIOUS_CONTROL_FRAME(ec->cfp)))) {
 | 
			
		||||
        argv[4] = rb_binding_new();
 | 
			
		||||
    }
 | 
			
		||||
    argv[5] = klass ? klass : Qnil;
 | 
			
		||||
 | 
			
		||||
    rb_proc_call_with_block(proc, 6, argv, Qnil);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue