зеркало из https://github.com/github/ruby.git
merge revision(s) 0eb634ae73cb327ede833b72492f912792a4a9d5: [Backport #19464]
YJIT: Detect and reject `send(:alias_for_send, :foo)` Previously, YJIT failed to put the stack into the correct shape when `BasicObject#send` calls an alias method for the send method itself. This can manifest as strange `NoMethodError`s in the final non-send receiver, as [seen][1] with the kt-paperclip gem. I also found a case where it makes YJIT fail the stack size assertion while compiling `leave`. YJIT's `BasicObject#__send__` implementation already rejects sends to `send`, but didn't detect sends to aliases of `send`. Adjust the detection and reject these cases. Fixes [Bug #19464] [1]: https://github.com/Shopify/yjit/issues/306 --- test/ruby/test_yjit.rb | 20 ++++++++++++++++++++ yjit/src/codegen.rs | 25 ++++++++++--------------- 2 files changed, 30 insertions(+), 15 deletions(-)
This commit is contained in:
Родитель
db28f7003f
Коммит
b73a073597
|
@ -1121,6 +1121,26 @@ class TestYJIT < Test::Unit::TestCase
|
|||
RUBY
|
||||
end
|
||||
|
||||
def test_nested_send
|
||||
#[Bug #19464]
|
||||
assert_compiles(<<~RUBY, result: [:ok, :ok])
|
||||
klass = Class.new do
|
||||
class << self
|
||||
alias_method :my_send, :send
|
||||
|
||||
def bar = :ok
|
||||
|
||||
def foo = bar
|
||||
end
|
||||
end
|
||||
|
||||
with_break = -> { break klass.send(:my_send, :foo) }
|
||||
wo_break = -> { klass.send(:my_send, :foo) }
|
||||
|
||||
[with_break[], wo_break[]]
|
||||
RUBY
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def code_gc_helpers
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
|
||||
#define RUBY_VERSION_TEENY 1
|
||||
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
|
||||
#define RUBY_PATCHLEVEL 39
|
||||
#define RUBY_PATCHLEVEL 40
|
||||
|
||||
#include "ruby/version.h"
|
||||
#include "ruby/internal/abi.h"
|
||||
|
|
|
@ -5822,7 +5822,6 @@ fn gen_send_general(
|
|||
let opt_type = unsafe { get_cme_def_body_optimized_type(cme) };
|
||||
match opt_type {
|
||||
OPTIMIZED_METHOD_TYPE_SEND => {
|
||||
|
||||
// This is for method calls like `foo.send(:bar)`
|
||||
// The `send` method does not get its own stack frame.
|
||||
// instead we look up the method and call it,
|
||||
|
@ -5830,6 +5829,16 @@ fn gen_send_general(
|
|||
|
||||
let starting_context = ctx.clone();
|
||||
|
||||
// Reject nested cases such as `send(:send, :alias_for_send, :foo))`.
|
||||
// We would need to do some stack manipulation here or keep track of how
|
||||
// many levels deep we need to stack manipulate. Because of how exits
|
||||
// currently work, we can't do stack manipulation until we will no longer
|
||||
// side exit.
|
||||
if flags & VM_CALL_OPT_SEND != 0 {
|
||||
gen_counter_incr!(asm, send_send_nested);
|
||||
return CantCompile;
|
||||
}
|
||||
|
||||
if argc == 0 {
|
||||
gen_counter_incr!(asm, send_send_wrong_args);
|
||||
return CantCompile;
|
||||
|
@ -5856,20 +5865,6 @@ fn gen_send_general(
|
|||
return CantCompile;
|
||||
}
|
||||
|
||||
// We aren't going to handle `send(send(:foo))`. We would need to
|
||||
// do some stack manipulation here or keep track of how many levels
|
||||
// deep we need to stack manipulate
|
||||
// Because of how exits currently work, we can't do stack manipulation
|
||||
// until we will no longer side exit.
|
||||
let def_type = unsafe { get_cme_def_type(cme) };
|
||||
if let VM_METHOD_TYPE_OPTIMIZED = def_type {
|
||||
let opt_type = unsafe { get_cme_def_body_optimized_type(cme) };
|
||||
if let OPTIMIZED_METHOD_TYPE_SEND = opt_type {
|
||||
gen_counter_incr!(asm, send_send_nested);
|
||||
return CantCompile;
|
||||
}
|
||||
}
|
||||
|
||||
flags |= VM_CALL_FCALL | VM_CALL_OPT_SEND;
|
||||
|
||||
assume_method_lookup_stable(jit, ocb, cme);
|
||||
|
|
Загрузка…
Ссылка в новой задаче