Skip to content

Commit

Permalink
Fix: msg.data memory allocation removed in favor of builtin function …
Browse files Browse the repository at this point in the history
…handling (vyperlang#2419)

Refactored the msg.data environment variable.

Instead of handling each distinctive use case in expr.py, uses are handled in the respective functions which operate on msg.data.

Len builtin simply returns calldatasize
Slice builtin returns a bytes array containing the sliced portion of calldata
The length argument to slice should be a compile time literal as calldatasize is unbounded, and we need a maxsize
on the return type of the slice function instead of defaulting to the maxsize of msg.data

The test suite for msg.data is unchanged, so passing tests verify this refactoring worked.
  • Loading branch information
skellet0r authored Aug 16, 2021
1 parent 31dd776 commit f1c65b7
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 28 deletions.
19 changes: 19 additions & 0 deletions vyper/builtin_functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@ def build_LLL(self, expr, args, kwargs, context):
"Invalid start / length values needs to be between 0 and 32.", expr,
)
sub_typ_maxlen = 32
elif sub.location == "calldata":
# if we are slicing msg.data, the length should
# be a constant, since msg.data can be of dynamic length
# we can't use it's length as the maxlen
assert isinstance(length.value, int) # sanity check
sub_typ_maxlen = length.value
else:
sub_typ_maxlen = sub.typ.maxlen

Expand All @@ -280,6 +286,15 @@ def build_LLL(self, expr, args, kwargs, context):
adj_sub = LLLnode.from_list(
["add", sub, ["add", ["div", "_start", 32], 1]], typ=sub.typ, location=sub.location,
)
elif sub.location == "calldata":
node = [
"seq",
["assert", ["le", ["add", start, length], "calldatasize"]], # runtime bounds check
["mstore", np, length],
["calldatacopy", np + 32, start, length],
np,
]
return LLLnode.from_list(node, typ=ByteArrayType(length.value), location="memory")
else:
adj_sub = LLLnode.from_list(
["add", sub, ["add", ["sub", "_start", ["mod", "_start", 32]], 32]],
Expand Down Expand Up @@ -351,6 +366,10 @@ def evaluate(self, node):
return vy_ast.Int.from_node(node, value=length)

def build_LLL(self, node, context):
if isinstance(node.args[0], vy_ast.Attribute):
key = f"{node.args[0].value.id}.{node.args[0].attr}"
if key == "msg.data":
return LLLnode.from_list(["calldatasize"], typ="uint256")
arg = Expr(node.args[0], context).lll_node
return get_bytearray_length(arg)

Expand Down
20 changes: 1 addition & 19 deletions vyper/old_codegen/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,25 +375,7 @@ def parse_Attribute(self):
if key == "msg.sender" and not self.context.is_internal:
return LLLnode.from_list(["caller"], typ="address", pos=getpos(self.expr))
elif key == "msg.data" and not self.context.is_internal:
is_len = self.expr._metadata.get("is_len")
if is_len is True:
typ = ByteArrayType(32)
pos = self.context.new_internal_variable(typ)
node = ["seq", ["mstore", pos, "calldatasize"], pos]
return LLLnode.from_list(
node, typ=typ, pos=getpos(self.expr), location="memory"
)
size = self.expr._metadata.get("size")
typ = ByteArrayType(size + 32)
pos = self.context.new_internal_variable(typ)
node = [
"seq",
["assert", ["le", size, "calldatasize"]],
["mstore", pos, size],
["calldatacopy", pos + 32, 0, size],
pos,
]
return LLLnode.from_list(node, typ=typ, pos=getpos(self.expr), location="memory")
return LLLnode(0, typ=ByteArrayType(0), location="calldata")
elif key == "msg.value" and self.context.is_payable:
return LLLnode.from_list(
["callvalue"], typ=BaseType("uint256"), pos=getpos(self.expr),
Expand Down
9 changes: 3 additions & 6 deletions vyper/semantics/validation/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,9 @@ def visit(self, node):
super().visit(node)

def visit_Attribute(self, node):
if node.get("value.id") == "msg" and node.attr == "data":
parent = node.get_ancestor()
if parent.get("func.id") == "slice":
node._metadata["size"] = parent.args[1].value + parent.args[2].value
elif parent.get("func.id") == "len":
node._metadata["is_len"] = True
# NOTE: required for msg.data node, removing borks and results in
# vyper.exceptions.StructureException: Cannot annotate: Attribute
pass

def visit_AnnAssign(self, node):
type_ = get_exact_type_from_node(node.target)
Expand Down
4 changes: 1 addition & 3 deletions vyper/semantics/validation/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,7 @@ def visit(self, node):
def visit_Attribute(self, node):
if node.get("value.id") == "msg" and node.attr == "data":
parent = node.get_ancestor()
is_slice = parent.get("func.id") == "slice"
is_len = parent.get("func.id") == "len"
if not is_slice and not is_len:
if parent.get("func.id") not in ("slice", "len"):
raise SyntaxException(
"msg.data is only allowed inside of the slice or len functions",
node.node_source_code,
Expand Down

0 comments on commit f1c65b7

Please sign in to comment.