From 735b94535cd1b52bd215e78a243f94f86b5c708f Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Fri, 5 Jun 2026 11:23:29 +0900 Subject: [PATCH] numeric.c: raise TypeError for non-Integer bitwise operands Integer#&, #| and #^ read the right-hand operand with mrb_integer() without checking its type. For a Float (or any non-Integer) this reads an unrelated union field and returns a garbage value instead of raising, as CRuby does. Raise TypeError via mrb_int_noconv() when the operand is not an Integer. Bigint operands are still handled before this point, and the shift operators keep coercing their width as before. Co-authored-by: Claude --- src/numeric.c | 1 + test/t/integer.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/numeric.c b/src/numeric.c index 5d695c77e..a6a59e96b 100644 --- a/src/numeric.c +++ b/src/numeric.c @@ -1352,6 +1352,7 @@ int_rev(mrb_state *mrb, mrb_value num) } #define bit_op(x,y,op1,op2) do {\ + if (!mrb_integer_p(y)) mrb_int_noconv(mrb, y);\ return mrb_int_value(mrb, (mrb_integer(x) op2 mrb_integer(y)));\ } while(0) diff --git a/test/t/integer.rb b/test/t/integer.rb index 1767f06da..b51826829 100644 --- a/test/t/integer.rb +++ b/test/t/integer.rb @@ -129,6 +129,18 @@ assert('Integer#^', '15.2.8.3.11') do assert_equal 6, 5 ^ 3 end +assert('Integer bitwise ops reject non-Integer operands') do + # A non-Integer operand has no bit pattern to combine, so &, |, ^ raise + # TypeError instead of silently reading garbage (Float used to return a + # bogus value via an unchecked union access). + assert_raise(TypeError) { 5 & 5.0 } + assert_raise(TypeError) { 5 | 5.0 } + assert_raise(TypeError) { 5 ^ 5.0 } + assert_raise(TypeError) { 5 | "3" } + assert_raise(TypeError) { 5 & nil } + assert_raise(TypeError) { 5 ^ :sym } +end + assert('Integer#<<', '15.2.8.3.12') do # Left Shift by one # 00010111 (23)