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:
NARUSE, Yui 2023-03-15 16:36:32 +09:00
Родитель db28f7003f
Коммит b73a073597
3 изменённых файлов: 31 добавлений и 16 удалений

Просмотреть файл

@ -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);