From eec9ce8d2d7af686160fd07be7f9e2da7d266e2e Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Mon, 26 Feb 2024 15:23:06 +0100 Subject: [PATCH 1/2] Fix range overflow when using signed integers --- .../codegen/features/iteration/test_for_range.py | 16 ++++++++++++++++ vyper/codegen/stmt.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/functional/codegen/features/iteration/test_for_range.py b/tests/functional/codegen/features/iteration/test_for_range.py index eedce46829..e64a35811d 100644 --- a/tests/functional/codegen/features/iteration/test_for_range.py +++ b/tests/functional/codegen/features/iteration/test_for_range.py @@ -414,3 +414,19 @@ def foo(a: {typ}) -> {typ}: assert c.foo(100) == 6 assert c.foo(1) == 666 assert c.foo(0) == 31337 + + +def test_for_range_signed_int_overflow(get_contract, tx_failed): + code = """ +@external +def foo() -> DynArray[int256, 10]: + res: DynArray[int256, 10] = empty(DynArray[int256, 10]) + x:int256 = max_value(int256) + y:int256 = min_value(int256)+2 + for i:int256 in range(x,y , bound=10): + res.append(i) + return res + """ + c = get_contract(code) + with tx_failed(): + c.foo() diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index e6baea75f7..084b4a1a49 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -255,7 +255,7 @@ def _parse_For_range(self): with end.cache_when_complex("end") as (b1, end): # note: the check for rounds<=rounds_bound happens in asm # generation for `repeat`. - clamped_start = clamp("le", start, end) + clamped_start = clamp("sle" if target_type.is_signed else "le", start, end) rounds = b1.resolve(IRnode.from_list(["sub", end, clamped_start])) rounds_bound = kwargs.pop("bound").int_value() else: From f8412ee90957cf738fcd5fc76842a666fafbe84d Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 27 Feb 2024 15:41:10 +0100 Subject: [PATCH 2/2] Refactor to `clamp_le` --- vyper/builtins/_convert.py | 4 ++-- vyper/codegen/core.py | 5 +++++ vyper/codegen/stmt.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/vyper/builtins/_convert.py b/vyper/builtins/_convert.py index adc2c233b8..156bee418e 100644 --- a/vyper/builtins/_convert.py +++ b/vyper/builtins/_convert.py @@ -10,6 +10,7 @@ bytes_data_ptr, clamp, clamp_basetype, + clamp_le, get_bytearray_length, int_clamp, is_bytes_m_type, @@ -110,8 +111,7 @@ def _clamp_numeric_convert(arg, arg_bounds, out_bounds, arg_is_signed): # out_hi must be smaller than MAX_UINT256, so clample makes sense. # add an assertion, just in case this assumption ever changes. assert out_hi < 2**256 - 1, "bad assumption in numeric convert" - CLAMP_OP = "sle" if arg_is_signed else "le" - arg = clamp(CLAMP_OP, arg, out_hi) + arg = clamp_le(arg, out_hi, arg_is_signed) return arg diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index 8a186fe683..09eb2f08c9 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -1222,6 +1222,11 @@ def clamp_nonzero(arg): return IRnode.from_list(b1.resolve(ret), typ=arg.typ) +def clamp_le(arg, hi, signed): + LE = "sle" if signed else "le" + return clamp(LE, arg, hi) + + def clamp2(lo, arg, hi, signed): with IRnode.from_list(arg).cache_when_complex("clamp2_arg") as (b1, arg): GE = "sge" if signed else "ge" diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 084b4a1a49..263b0c25e8 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -10,7 +10,7 @@ IRnode, append_dyn_array, check_assign, - clamp, + clamp_le, dummy_node_for_type, get_dyn_array_count, get_element_ptr, @@ -255,7 +255,7 @@ def _parse_For_range(self): with end.cache_when_complex("end") as (b1, end): # note: the check for rounds<=rounds_bound happens in asm # generation for `repeat`. - clamped_start = clamp("sle" if target_type.is_signed else "le", start, end) + clamped_start = clamp_le(start, end, target_type.is_signed) rounds = b1.resolve(IRnode.from_list(["sub", end, clamped_start])) rounds_bound = kwargs.pop("bound").int_value() else: