Skip to content

Commit 85475e4

Browse files
Merge pull request RustPython#347 from silmeth/master
Fix Range’s len() + division and mod by 0
2 parents 869b91a + b924530 commit 85475e4

7 files changed

Lines changed: 152 additions & 21 deletions

File tree

tests/snippets/builtin_range.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def assert_raises(expr, exc_type):
1818

1919
assert range(2**63+1)[2**63] == 9223372036854775808
2020

21+
# len tests
22+
assert len(range(10, 5)) == 0, 'Range with no elements should have length = 0'
23+
assert len(range(10, 5, -2)) == 3, 'Expected length 3, for elements: 10, 8, 6'
24+
assert len(range(5, 10, 2)) == 3, 'Expected length 3, for elements: 5, 7, 9'
25+
2126
# index tests
2227
assert range(10).index(6) == 6
2328
assert range(4, 10).index(6) == 2

tests/snippets/division_by_zero.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
try:
2+
5 / 0
3+
except ZeroDivisionError:
4+
pass
5+
else:
6+
assert False, 'Expected ZeroDivisionError'
7+
8+
try:
9+
5 / -0.0
10+
except ZeroDivisionError:
11+
pass
12+
else:
13+
assert False, 'Expected ZeroDivisionError'
14+
15+
try:
16+
5 / (2-2)
17+
except ZeroDivisionError:
18+
pass
19+
else:
20+
assert False, 'Expected ZeroDivisionError'
21+
22+
try:
23+
5 % 0
24+
except ZeroDivisionError:
25+
pass
26+
else:
27+
assert False, 'Expected ZeroDivisionError'
28+
29+
try:
30+
raise ZeroDivisionError('Is an ArithmeticError subclass?')
31+
except ArithmeticError:
32+
pass
33+
else:
34+
assert False, 'Expected ZeroDivisionError'

vm/src/builtins.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
692692
ctx.exceptions.base_exception_type.clone(),
693693
);
694694
ctx.set_attr(&py_mod, "Exception", ctx.exceptions.exception_type.clone());
695+
ctx.set_attr(
696+
&py_mod,
697+
"ArithmeticError",
698+
ctx.exceptions.arithmetic_error.clone(),
699+
);
695700
ctx.set_attr(
696701
&py_mod,
697702
"AssertionError",
@@ -703,6 +708,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
703708
ctx.exceptions.attribute_error.clone(),
704709
);
705710
ctx.set_attr(&py_mod, "NameError", ctx.exceptions.name_error.clone());
711+
ctx.set_attr(
712+
&py_mod,
713+
"OverflowError",
714+
ctx.exceptions.overflow_error.clone(),
715+
);
706716
ctx.set_attr(
707717
&py_mod,
708718
"RuntimeError",
@@ -722,6 +732,11 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
722732
"StopIteration",
723733
ctx.exceptions.stop_iteration.clone(),
724734
);
735+
ctx.set_attr(
736+
&py_mod,
737+
"ZeroDivisionError",
738+
ctx.exceptions.zero_division_error.clone(),
739+
);
725740

726741
py_mod
727742
}

vm/src/exceptions.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ fn exception_str(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
8181

8282
#[derive(Debug)]
8383
pub struct ExceptionZoo {
84+
pub arithmetic_error: PyObjectRef,
8485
pub assertion_error: PyObjectRef,
8586
pub attribute_error: PyObjectRef,
8687
pub base_exception_type: PyObjectRef,
@@ -93,12 +94,14 @@ pub struct ExceptionZoo {
9394
pub name_error: PyObjectRef,
9495
pub not_implemented_error: PyObjectRef,
9596
pub os_error: PyObjectRef,
97+
pub overflow_error: PyObjectRef,
9698
pub permission_error: PyObjectRef,
9799
pub runtime_error: PyObjectRef,
98100
pub stop_iteration: PyObjectRef,
99101
pub syntax_error: PyObjectRef,
100102
pub type_error: PyObjectRef,
101103
pub value_error: PyObjectRef,
104+
pub zero_division_error: PyObjectRef,
102105
}
103106

104107
impl ExceptionZoo {
@@ -113,6 +116,8 @@ impl ExceptionZoo {
113116

114117
let exception_type = create_type("Exception", &type_type, &base_exception_type, &dict_type);
115118

119+
let arithmetic_error =
120+
create_type("ArithmeticError", &type_type, &exception_type, &dict_type);
116121
let assertion_error =
117122
create_type("AssertionError", &type_type, &exception_type, &dict_type);
118123
let attribute_error =
@@ -128,8 +133,18 @@ impl ExceptionZoo {
128133
let type_error = create_type("TypeError", &type_type, &exception_type, &dict_type);
129134
let value_error = create_type("ValueError", &type_type, &exception_type, &dict_type);
130135

136+
let overflow_error =
137+
create_type("OverflowError", &type_type, &arithmetic_error, &dict_type);
138+
let zero_division_error = create_type(
139+
"ZeroDivisionError",
140+
&type_type,
141+
&arithmetic_error,
142+
&dict_type,
143+
);
144+
131145
let module_not_found_error =
132146
create_type("ModuleNotFoundError", &type_type, &import_error, &dict_type);
147+
133148
let not_implemented_error = create_type(
134149
"NotImplementedError",
135150
&type_type,
@@ -142,6 +157,7 @@ impl ExceptionZoo {
142157
let permission_error = create_type("PermissionError", &type_type, &os_error, &dict_type);
143158

144159
ExceptionZoo {
160+
arithmetic_error,
145161
assertion_error,
146162
attribute_error,
147163
base_exception_type,
@@ -154,12 +170,14 @@ impl ExceptionZoo {
154170
name_error,
155171
not_implemented_error,
156172
os_error,
173+
overflow_error,
157174
permission_error,
158175
runtime_error,
159176
stop_iteration,
160177
syntax_error,
161178
type_error,
162179
value_error,
180+
zero_division_error,
163181
}
164182
}
165183
}

vm/src/obj/objint.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -366,17 +366,25 @@ fn int_truediv(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
366366
args,
367367
required = [(i, Some(vm.ctx.int_type())), (i2, None)]
368368
);
369-
let v1 = get_value(i);
370-
if objtype::isinstance(i2, &vm.ctx.int_type()) {
371-
Ok(vm
372-
.ctx
373-
.new_float(v1.to_f64().unwrap() / get_value(i2).to_f64().unwrap()))
369+
370+
let v1 = get_value(i)
371+
.to_f64()
372+
.ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))?;
373+
374+
let v2 = if objtype::isinstance(i2, &vm.ctx.int_type()) {
375+
get_value(i2)
376+
.to_f64()
377+
.ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string()))?
374378
} else if objtype::isinstance(i2, &vm.ctx.float_type()) {
375-
Ok(vm
376-
.ctx
377-
.new_float(v1.to_f64().unwrap() / objfloat::get_value(i2)))
379+
objfloat::get_value(i2)
380+
} else {
381+
return Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow())));
382+
};
383+
384+
if v2 == 0.0 {
385+
Err(vm.new_zero_division_error("integer division by zero".to_string()))
378386
} else {
379-
Err(vm.new_type_error(format!("Cannot divide {} and {}", i.borrow(), i2.borrow())))
387+
Ok(vm.ctx.new_float(v1 / v2))
380388
}
381389
}
382390

@@ -388,7 +396,13 @@ fn int_mod(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
388396
);
389397
let v1 = get_value(i);
390398
if objtype::isinstance(i2, &vm.ctx.int_type()) {
391-
Ok(vm.ctx.new_int(v1 % get_value(i2)))
399+
let v2 = get_value(i2);
400+
401+
if v2 != BigInt::zero() {
402+
Ok(vm.ctx.new_int(v1 % get_value(i2)))
403+
} else {
404+
Err(vm.new_zero_division_error("integer modulo by zero".to_string()))
405+
}
392406
} else {
393407
Err(vm.new_type_error(format!("Cannot modulo {} and {}", i.borrow(), i2.borrow())))
394408
}

vm/src/obj/objrange.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,24 @@ pub struct RangeType {
1818
}
1919

2020
impl RangeType {
21+
#[inline]
22+
pub fn try_len(&self) -> Option<usize> {
23+
match self.step.sign() {
24+
Sign::Plus if self.start < self.end => ((&self.end - &self.start - 1usize)
25+
/ &self.step)
26+
.to_usize()
27+
.map(|sz| sz + 1),
28+
Sign::Minus if self.start > self.end => ((&self.start - &self.end - 1usize)
29+
/ (-&self.step))
30+
.to_usize()
31+
.map(|sz| sz + 1),
32+
_ => Some(0),
33+
}
34+
}
35+
2136
#[inline]
2237
pub fn len(&self) -> usize {
23-
((self.end.clone() - self.start.clone()) / self.step.clone())
24-
.abs()
25-
.to_usize()
26-
.unwrap()
38+
self.try_len().unwrap()
2739
}
2840

2941
#[inline]
@@ -76,6 +88,15 @@ impl RangeType {
7688
None
7789
}
7890
}
91+
92+
#[inline]
93+
pub fn repr(&self) -> String {
94+
if self.step == BigInt::one() {
95+
format!("range({}, {})", self.start, self.end)
96+
} else {
97+
format!("range({}, {}, {})", self.start, self.end, self.step)
98+
}
99+
}
79100
}
80101

81102
pub fn init(context: &PyContext) {
@@ -88,6 +109,7 @@ pub fn init(context: &PyContext) {
88109
"__getitem__",
89110
context.new_rustfunc(range_getitem),
90111
);
112+
context.set_attr(&range_type, "__repr__", context.new_rustfunc(range_repr));
91113
context.set_attr(&range_type, "__bool__", context.new_rustfunc(range_bool));
92114
context.set_attr(
93115
&range_type,
@@ -153,12 +175,14 @@ fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
153175
fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
154176
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]);
155177

156-
let len = match zelf.borrow().payload {
157-
PyObjectPayload::Range { ref range } => range.len(),
178+
if let Some(len) = match zelf.borrow().payload {
179+
PyObjectPayload::Range { ref range } => range.try_len(),
158180
_ => unreachable!(),
159-
};
160-
161-
Ok(vm.ctx.new_int(len))
181+
} {
182+
Ok(vm.ctx.new_int(len))
183+
} else {
184+
Err(vm.new_overflow_error("Python int too large to convert to Rust usize".to_string()))
185+
}
162186
}
163187

164188
fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
@@ -224,6 +248,17 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
224248
}
225249
}
226250

251+
fn range_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
252+
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]);
253+
254+
let s = match zelf.borrow().payload {
255+
PyObjectPayload::Range { ref range } => range.repr(),
256+
_ => unreachable!(),
257+
};
258+
259+
Ok(vm.ctx.new_str(s))
260+
}
261+
227262
fn range_bool(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
228263
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]);
229264

vm/src/vm.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ impl VirtualMachine {
104104
self.new_exception(os_error, msg)
105105
}
106106

107+
pub fn new_overflow_error(&mut self, msg: String) -> PyObjectRef {
108+
let overflow_error = self.ctx.exceptions.overflow_error.clone();
109+
self.new_exception(overflow_error, msg)
110+
}
111+
107112
/// Create a new python ValueError object. Useful for raising errors from
108113
/// python functions implemented in rust.
109114
pub fn new_value_error(&mut self, msg: String) -> PyObjectRef {
@@ -122,8 +127,13 @@ impl VirtualMachine {
122127
}
123128

124129
pub fn new_not_implemented_error(&mut self, msg: String) -> PyObjectRef {
125-
let value_error = self.ctx.exceptions.not_implemented_error.clone();
126-
self.new_exception(value_error, msg)
130+
let not_implemented_error = self.ctx.exceptions.not_implemented_error.clone();
131+
self.new_exception(not_implemented_error, msg)
132+
}
133+
134+
pub fn new_zero_division_error(&mut self, msg: String) -> PyObjectRef {
135+
let zero_division_error = self.ctx.exceptions.zero_division_error.clone();
136+
self.new_exception(zero_division_error, msg)
127137
}
128138

129139
pub fn new_scope(&mut self, parent_scope: Option<PyObjectRef>) -> PyObjectRef {

0 commit comments

Comments
 (0)