From 545e9d39df5251a31ed12724e4cdad15038e7c8c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Mon, 23 Sep 2019 16:17:12 +0900 Subject: [PATCH] Fix int type casting error with negative base value Change base type from u32 to PyIntRef Fixed: #1405 --- tests/snippets/ints.py | 9 +++++++++ vm/src/obj/objbyteinner.rs | 2 +- vm/src/obj/objint.rs | 37 ++++++++++++++++++++++++------------- vm/src/stdlib/pystruct.rs | 2 +- wasm/lib/Cargo.toml | 1 + wasm/lib/src/convert.rs | 4 +++- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index b3ae5b457c..fe3d046f9b 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -167,6 +167,15 @@ with assert_raises(ValueError): int(' 1 ', base=37) +with assert_raises(ValueError): + int(' 1 ', base=-1) + +with assert_raises(ValueError): + int(' 1 ', base=1000000000000000) + +with assert_raises(ValueError): + int(' 1 ', base=-1000000000000000) + with assert_raises(TypeError): int(base=2) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index a2d6a8e447..4545c931b3 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -135,7 +135,7 @@ impl ByteInnerNewOptions { let mut data_bytes = vec![]; for elem in elements.unwrap() { - let v = objint::to_int(vm, &elem, 10)?; + let v = objint::to_int(vm, &elem, &BigInt::from(10))?; if let Some(i) = v.to_u8() { data_bytes.push(i); } else { diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 87cabf22ff..b9ea0872ce 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -704,7 +704,7 @@ struct IntOptions { #[pyarg(positional_only, optional = true)] val_options: OptionalArg, #[pyarg(positional_or_keyword, optional = true)] - base: OptionalArg, + base: OptionalArg, } impl IntOptions { @@ -720,9 +720,9 @@ impl IntOptions { } base } else { - 10 + PyInt::new(10).into_ref(vm) }; - to_int(vm, &val, base) + to_int(vm, &val, base.as_bigint()) } else if let OptionalArg::Present(_) = self.base { Err(vm.new_type_error("int() missing string argument".to_string())) } else { @@ -736,8 +736,14 @@ fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResul } // Casting function: -pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { - if base != 0 && (base < 2 || base > 36) { +pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: &BigInt) -> PyResult { + let base_u32 = match base.to_u32() { + Some(base_u32) => base_u32, + None => { + return Err(vm.new_value_error("int() base must be >= 2 and <= 36, or 0".to_string())) + } + }; + if base_u32 != 0 && (base_u32 < 2 || base_u32 > 36) { return Err(vm.new_value_error("int() base must be >= 2 and <= 36, or 0".to_string())); } @@ -772,19 +778,24 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult PyResult { +fn str_to_int(vm: &VirtualMachine, literal: &str, base: &BigInt) -> PyResult { let mut buf = validate_literal(vm, literal, base)?; let is_signed = buf.starts_with('+') || buf.starts_with('-'); let radix_range = if is_signed { 1..3 } else { 0..2 }; let radix_candidate = buf.get(radix_range.clone()); + let mut base_u32 = match base.to_u32() { + Some(base_u32) => base_u32, + None => return Err(invalid_literal(vm, literal, base)), + }; + // try to find base if let Some(radix_candidate) = radix_candidate { if let Some(matched_radix) = detect_base(&radix_candidate) { - if base != 0 && base != matched_radix { + if base_u32 != 0 && base_u32 != matched_radix { return Err(invalid_literal(vm, literal, base)); } else { - base = matched_radix; + base_u32 = matched_radix; } buf.drain(radix_range); @@ -792,18 +803,18 @@ fn str_to_int(vm: &VirtualMachine, literal: &str, mut base: u32) -> PyResult PyResult { +fn validate_literal(vm: &VirtualMachine, literal: &str, base: &BigInt) -> PyResult { if literal.starts_with('_') || literal.ends_with('_') { return Err(invalid_literal(vm, literal, base)); } @@ -835,7 +846,7 @@ fn detect_base(literal: &str) -> Option { } } -fn invalid_literal(vm: &VirtualMachine, literal: &str, base: u32) -> PyObjectRef { +fn invalid_literal(vm: &VirtualMachine, literal: &str, base: &BigInt) -> PyObjectRef { vm.new_value_error(format!( "invalid literal for int() with base {}: '{}'", base, literal diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index e910e6f551..ea066d1d73 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -105,7 +105,7 @@ where } fn get_int(vm: &VirtualMachine, arg: &PyObjectRef) -> PyResult { - objint::to_int(vm, arg, 10) + objint::to_int(vm, arg, &BigInt::from(10)) } fn pack_i8(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut dyn Write) -> PyResult<()> { diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index cb227a8b47..48a9b0738e 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -26,6 +26,7 @@ serde = "1.0" js-sys = "0.3" futures = "0.1" num-traits = "0.2" +num-bigint = { version = "0.2.3", features = ["serde"] } [dependencies.web-sys] version = "0.3" diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 16b7fe3f74..1bba54629c 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -9,6 +9,8 @@ use rustpython_vm::py_serde; use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; +use num_bigint::BigInt; + use crate::browser_module; use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine}; @@ -43,7 +45,7 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { if objtype::isinstance(&top, &vm.ctx.tuple_type()) { let element = objsequence::get_elements_tuple(&top); - if let Some(lineno) = objint::to_int(vm, &element[1], 10) + if let Some(lineno) = objint::to_int(vm, &element[1], &BigInt::from(10)) .ok() .and_then(|lineno| lineno.to_u32()) {