From 5505dec85adba6d6371f6548724a9618ca8a87e1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 26 Feb 2021 16:24:08 -0800 Subject: [PATCH] Expose information about which basic methods have been redefined I would like to tell if code is redefining methods that can impact MRI's optimizations. This commit exposes which basic methods have been redefined. For example: ```ruby class Integer def +(x); x ** self; end end p RubyVM.redefined_methods # => {Integer=>[:+]} ``` This will allow us to prevent basic method redefinitions from happening by checking for them in CI environments. For example: ```ruby Minitest.after_run { fail "Basic methods have been redefine" if RubyVM.redefined_methods.any? } ``` --- vm.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/vm.c b/vm.c index ef1bf0facb..74dec3c2d6 100644 --- a/vm.c +++ b/vm.c @@ -3333,6 +3333,80 @@ vm_mtbl2(VALUE self, VALUE obj, VALUE sym) return Qnil; } +static void +check_and_record_bop(ID mid, VALUE klass, VALUE redefines, short bv, short mask) +{ + if (bv & mask) { + VALUE redefined_method_ids; + + if (RTEST(rb_hash_has_key(redefines, klass))) { + redefined_method_ids = rb_hash_aref(redefines, klass); + } else { + redefined_method_ids = rb_ary_new(); + rb_hash_aset(redefines, klass, redefined_method_ids); + } + + rb_ary_push(redefined_method_ids, rb_id2sym(mid)); + } +} + +static void +check_bop(ID mid, enum ruby_basic_operators bop, VALUE redefines) +{ + if (ruby_vm_redefined_flag[bop]) { + short bv = ruby_vm_redefined_flag[bop]; + check_and_record_bop(mid, rb_cInteger, redefines, bv, INTEGER_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cFloat, redefines, bv, FLOAT_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cString, redefines, bv, STRING_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cArray, redefines, bv, ARRAY_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cHash, redefines, bv, HASH_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cSymbol, redefines, bv, SYMBOL_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cRegexp, redefines, bv, REGEXP_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cNilClass, redefines, bv, NIL_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cTrueClass, redefines, bv, TRUE_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cFalseClass, redefines, bv, FALSE_REDEFINED_OP_FLAG); + check_and_record_bop(mid, rb_cProc, redefines, bv, PROC_REDEFINED_OP_FLAG); + } +} + +static VALUE +vm_redefined_methods(VALUE mod) +{ + VALUE redefines = rb_hash_new(); + +#define OP(mid_, bop_) (check_bop(id##mid_, BOP_##bop_, redefines)) + OP(PLUS, PLUS); + OP(MINUS, MINUS); + OP(MULT, MULT); + OP(DIV, DIV); + OP(MOD, MOD); + OP(Eq, EQ); + OP(Eqq, EQQ); + OP(LT, LT); + OP(LE, LE); + OP(GT, GT); + OP(GE, GE); + OP(LTLT, LTLT); + OP(AREF, AREF); + OP(ASET, ASET); + OP(Length, LENGTH); + OP(Size, SIZE); + OP(EmptyP, EMPTY_P); + OP(Succ, SUCC); + OP(EqTilde, MATCH); + OP(Freeze, FREEZE); + OP(UMinus, UMINUS); + OP(Max, MAX); + OP(Min, MIN); + OP(Call, CALL); + OP(And, AND); + OP(Or, OR); + OP(NilP, NIL_P); +#undef OP + + return redefines; +} + void Init_VM(void) { @@ -3356,6 +3430,7 @@ Init_VM(void) rb_undef_alloc_func(rb_cRubyVM); rb_undef_method(CLASS_OF(rb_cRubyVM), "new"); rb_define_singleton_method(rb_cRubyVM, "stat", vm_stat, -1); + rb_define_singleton_method(rb_cRubyVM, "redefined_methods", vm_redefined_methods, 0); #if USE_DEBUG_COUNTER rb_define_singleton_method(rb_cRubyVM, "reset_debug_counters", rb_debug_counter_reset, 0); rb_define_singleton_method(rb_cRubyVM, "show_debug_counters", rb_debug_counter_show, 0); -- 2.26.2