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 <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-06-05 11:23:29 +09:00
parent 9982bc1223
commit 735b94535c
2 changed files with 13 additions and 0 deletions
+1
View File
@@ -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)
+12
View File
@@ -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)