diff --git a/tests/compiler/test_source_map.py b/tests/compiler/test_source_map.py index 4aec55869c..0c832e9734 100644 --- a/tests/compiler/test_source_map.py +++ b/tests/compiler/test_source_map.py @@ -68,6 +68,19 @@ def test_pos_map_offsets(): ) +def test_error_map(): + code = """ +foo: uint256 + +@external +def update_foo(): + self.foo += 1 + """ + error_map = compile_code(code, ["source_map"])["source_map"]["error_map"] + assert "safeadd" in list(error_map.values()) + assert "fallback function" in list(error_map.values()) + + def test_compress_source_map(): code = """ @external diff --git a/vyper/codegen/arithmetic.py b/vyper/codegen/arithmetic.py index 55b5087e2c..55148bbdef 100644 --- a/vyper/codegen/arithmetic.py +++ b/vyper/codegen/arithmetic.py @@ -155,7 +155,8 @@ def safe_add(x, y): # TODO push down into optimizer rules. ok = ["ge", res, x] - ret = IRnode.from_list(["seq", ["assert", ok], res]) + check = IRnode.from_list(["assert", ok], error_msg="safeadd") + ret = IRnode.from_list(["seq", check, res]) return b1.resolve(ret) @@ -184,7 +185,8 @@ def safe_sub(x, y): # TODO push down into optimizer rules. ok = ["le", res, x] - ret = IRnode.from_list(["seq", ["assert", ok], res]) + check = IRnode.from_list(["assert", ok], error_msg="safesub") + ret = IRnode.from_list(["seq", check, res]) return b1.resolve(ret) @@ -250,7 +252,8 @@ def safe_mul(x, y): # (if bits == 256, clamp_basetype is a no-op) res = clamp_basetype(res) - res = IRnode.from_list(["seq", ["assert", ok], res], typ=res.typ) + check = IRnode.from_list(["assert", ok], error_msg="safediv") + res = IRnode.from_list(["seq", check, res], typ=res.typ) return b1.resolve(res) @@ -308,7 +311,7 @@ def safe_div(x, y): # TODO maybe use safe_mul res = clamp_basetype(res) - check = ["assert", ok] + check = IRnode.from_list(["assert", ok], error_msg="safemul") return IRnode.from_list(b1.resolve(["seq", check, res])) diff --git a/vyper/codegen/core.py b/vyper/codegen/core.py index fa98b43868..82882f5a9f 100644 --- a/vyper/codegen/core.py +++ b/vyper/codegen/core.py @@ -976,7 +976,7 @@ def clamp_basetype(ir_node): else: # pragma: nocover raise CompilerPanic(f"{t} passed to clamp_basetype") - return IRnode.from_list(ret, typ=ir_node.typ) + return IRnode.from_list(ret, typ=ir_node.typ, error_msg=f"validate {t}") def int_clamp(ir_node, bits, signed=False): @@ -1024,15 +1024,16 @@ def promote_signed_int(x, bits): # general clamp function for all ops and numbers def clamp(op, arg, bound): with IRnode.from_list(arg).cache_when_complex("clamp_arg") as (b1, arg): - assertion = ["assert", [op, arg, bound]] - ret = ["seq", assertion, arg] + check = IRnode.from_list(["assert", [op, arg, bound]], error_msg=f"clamp {op} {bound}") + ret = ["seq", check, arg] return IRnode.from_list(b1.resolve(ret), typ=arg.typ) def clamp_nonzero(arg): # TODO: use clamp("ne", arg, 0) once optimizer rules can handle it with IRnode.from_list(arg).cache_when_complex("should_nonzero") as (b1, arg): - ret = ["seq", ["assert", arg], arg] + check = IRnode.from_list(["assert", arg], error_msg="clamp_nonzero") + ret = ["seq", check, arg] return IRnode.from_list(b1.resolve(ret), typ=arg.typ) diff --git a/vyper/codegen/ir_node.py b/vyper/codegen/ir_node.py index 48abecdbae..eb2802aafd 100644 --- a/vyper/codegen/ir_node.py +++ b/vyper/codegen/ir_node.py @@ -115,6 +115,7 @@ def __init__( location: Optional[AddrSpace] = None, source_pos: Optional[Tuple[int, int]] = None, annotation: Optional[str] = None, + error_msg: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, @@ -129,6 +130,7 @@ def __init__( self.typ = typ self.location = location self.source_pos = source_pos + self.error_msg = error_msg self.annotation = annotation self.mutable = mutable self.add_gas_estimate = add_gas_estimate @@ -494,6 +496,7 @@ def from_list( location: Optional[AddrSpace] = None, source_pos: Optional[Tuple[int, int]] = None, annotation: Optional[str] = None, + error_msg: Optional[str] = None, mutable: bool = True, add_gas_estimate: int = 0, encoding: Encoding = Encoding.VYPER, @@ -512,6 +515,8 @@ def from_list( obj.location = location if obj.encoding is None: obj.encoding = encoding + if obj.error_msg is None: + obj.error_msg = error_msg return obj elif not isinstance(obj, list): @@ -523,7 +528,9 @@ def from_list( annotation=annotation, mutable=mutable, add_gas_estimate=add_gas_estimate, + source_pos=source_pos, encoding=encoding, + error_msg=error_msg, ) else: return cls( @@ -536,4 +543,5 @@ def from_list( source_pos=source_pos, add_gas_estimate=add_gas_estimate, encoding=encoding, + error_msg=error_msg, ) diff --git a/vyper/codegen/module.py b/vyper/codegen/module.py index 99b7f59cb0..3c023ba50f 100644 --- a/vyper/codegen/module.py +++ b/vyper/codegen/module.py @@ -150,7 +150,9 @@ def _runtime_ir(runtime_functions, all_sigs, global_ctx): default_function, all_sigs, global_ctx, skip_nonpayable_check ) else: - fallback_ir = IRnode.from_list(["revert", 0, 0], annotation="Default function") + fallback_ir = IRnode.from_list( + ["revert", 0, 0], annotation="Default function", error_msg="fallback function" + ) # ensure the external jumptable section gets closed out # (for basic block hygiene and also for zksync interpreter) diff --git a/vyper/codegen/stmt.py b/vyper/codegen/stmt.py index 005deb218e..aa9a2de73f 100644 --- a/vyper/codegen/stmt.py +++ b/vyper/codegen/stmt.py @@ -160,7 +160,9 @@ def parse_Call(self): def _assert_reason(self, test_expr, msg): if isinstance(msg, vy_ast.Name) and msg.id == "UNREACHABLE": - return IRnode.from_list(["assert_unreachable", test_expr]) + return IRnode.from_list( + ["assert_unreachable", test_expr], error_msg="assert unreachable" + ) # set constant so that revert reason str is well behaved try: @@ -209,7 +211,7 @@ def _get_last(ir): else: ir_node = revert_seq - return IRnode.from_list(ir_node) + return IRnode.from_list(ir_node, error_msg="user revert with reason") def parse_Assert(self): test_expr = Expr.parse_value_expr(self.stmt.test, self.context) @@ -217,13 +219,13 @@ def parse_Assert(self): if self.stmt.msg: return self._assert_reason(test_expr, self.stmt.msg) else: - return IRnode.from_list(["assert", test_expr]) + return IRnode.from_list(["assert", test_expr], error_msg="user assert") def parse_Raise(self): if self.stmt.exc: return self._assert_reason(None, self.stmt.exc) else: - return IRnode.from_list(["revert", 0, 0]) + return IRnode.from_list(["revert", 0, 0], error_msg="user raise") def _check_valid_range_constant(self, arg_ast_node): with self.context.range_scope(): diff --git a/vyper/ir/compile_ir.py b/vyper/ir/compile_ir.py index b363c95d55..b37bc501cb 100644 --- a/vyper/ir/compile_ir.py +++ b/vyper/ir/compile_ir.py @@ -161,8 +161,10 @@ class Instruction(str): def __new__(cls, sstr, *args, **kwargs): return super().__new__(cls, sstr) - def __init__(self, sstr, source_pos=None): + def __init__(self, sstr, source_pos=None, error_msg=None): + self.error_msg = error_msg self.pc_debugger = False + if source_pos is not None: self.lineno, self.col_offset, self.end_lineno, self.end_col_offset = source_pos else: @@ -174,8 +176,9 @@ def apply_line_numbers(func): def apply_line_no_wrapper(*args, **kwargs): code = args[0] ret = func(*args, **kwargs) + new_ret = [ - Instruction(i, code.source_pos) + Instruction(i, code.source_pos, code.error_msg) if isinstance(i, str) and not isinstance(i, Instruction) else i for i in ret @@ -726,7 +729,12 @@ def note_line_num(line_number_map, item, pos): offsets = (item.lineno, item.col_offset, item.end_lineno, item.end_col_offset) else: offsets = None + line_number_map["pc_pos_map"][pos] = offsets + + if item.error_msg is not None: + line_number_map["error_map"][pos] = item.error_msg + added_line_breakpoint = note_breakpoint(line_number_map, item, pos) return added_line_breakpoint @@ -938,6 +946,7 @@ def assembly_to_evm(assembly, start_pos=0, insert_vyper_signature=False): "pc_breakpoints": set(), "pc_jump_map": {0: "-"}, "pc_pos_map": {}, + "error_map": {}, } posmap = {} diff --git a/vyper/ir/optimizer.py b/vyper/ir/optimizer.py index bdcb4c578e..6e7c5930c5 100644 --- a/vyper/ir/optimizer.py +++ b/vyper/ir/optimizer.py @@ -424,6 +424,7 @@ def _optimize(node: IRnode, parent: Optional[IRnode]) -> Tuple[bool, IRnode]: typ = node.typ location = node.location source_pos = node.source_pos + error_msg = node.error_msg annotation = node.annotation add_gas_estimate = node.add_gas_estimate @@ -445,6 +446,7 @@ def finalize(val, args): typ=typ, location=location, source_pos=source_pos, + error_msg=error_msg, annotation=annotation, add_gas_estimate=add_gas_estimate, )