Repository: crytic/slither

Issue:
Exception: Incorrect parsing of array push return value (?)
During an array `.push()` operation, a default value is pushed into the array, and is returned. In a `uint[][]`, a push operation should then add a `uint[]` and return it. Thus, this contract should work:
```
contract Array3DPushPush {
	uint[][] arr;

	function f() public {
		arr.push().push();
	}
}
```
But you are met with a similar exception to #592 (related?):
```
Traceback (most recent call last):
  File "c:\users\x\documents\github\slither\slither\__main__.py", line 610, in main_impl
    (slither_instances, results_detectors, results_printers, number_contracts) = process_all(filename, args,
  File "c:\users\x\documents\github\slither\slither\__main__.py", line 67, in process_all
    (slither, current_results_detectors, current_results_printers, current_analyzed_count) = process_single(
  File "c:\users\x\documents\github\slither\slither\__main__.py", line 53, in process_single
    slither = Slither(target,
  File "c:\users\x\documents\github\slither\slither\slither.py", line 86, in __init__
    self._parser.analyze_contracts()
  File "c:\users\x\documents\github\slither\slither\solc_parsing\slitherSolc.py", line 345, in analyze_contracts
    self._convert_to_slithir()
  File "c:\users\x\documents\github\slither\slither\solc_parsing\slitherSolc.py", line 489, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "c:\users\x\documents\github\slither\slither\core\declarations\function.py", line 1652, in generate_slithir_and_analyze
    node.slithir_generation()
  File "c:\users\x\documents\github\slither\slither\core\cfg\node.py", line 704, in slithir_generation
    self._find_read_write_call()
  File "c:\users\x\documents\github\slither\slither\core\cfg\node.py", line 889, in _find_read_write_call
    raise SlitherException(
slither.exceptions.SlitherException: Function not found on TMP_1(None) = HIGH_LEVEL_CALL, dest:TMP_0(None), function:push, arguments:[]  . Please try compiling with a recent Solidity version.
ERROR:root:Error:
ERROR:root:Function not found on TMP_1(None) = HIGH_LEVEL_CALL, dest:TMP_0(None), function:push, arguments:[]  . Please try compiling with a recent Solidity version.
ERROR:root:Please report an issue to https://github.com/crytic/slither/issues
```


Discussion:

Resolving patch:
diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py
--- a/slither/slithir/convert.py
+++ b/slither/slithir/convert.py
@@ -363,39 +363,12 @@ def propagate_type_and_convert_call(result, node):
         new_ins = propagate_types(ins, node)
         if new_ins:
             if isinstance(new_ins, (list,)):
-                if len(new_ins) == 2:
-                    new_ins[0].set_node(ins.node)
-                    new_ins[1].set_node(ins.node)
-                    del result[idx]
-                    result.insert(idx, new_ins[0])
-                    result.insert(idx + 1, new_ins[1])
-                    idx = idx + 1
-                elif len(new_ins) == 3:
-                    new_ins[0].set_node(ins.node)
-                    new_ins[1].set_node(ins.node)
-                    new_ins[2].set_node(ins.node)
-                    del result[idx]
-                    result.insert(idx, new_ins[0])
-                    result.insert(idx + 1, new_ins[1])
-                    result.insert(idx + 2, new_ins[2])
-                    idx = idx + 2
-                else:
-                    # Pop conversion
-                    assert len(new_ins) == 6
-                    new_ins[0].set_node(ins.node)
-                    new_ins[1].set_node(ins.node)
-                    new_ins[2].set_node(ins.node)
-                    new_ins[3].set_node(ins.node)
-                    new_ins[4].set_node(ins.node)
-                    new_ins[5].set_node(ins.node)
-                    del result[idx]
-                    result.insert(idx, new_ins[0])
-                    result.insert(idx + 1, new_ins[1])
-                    result.insert(idx + 2, new_ins[2])
-                    result.insert(idx + 3, new_ins[3])
-                    result.insert(idx + 4, new_ins[4])
-                    result.insert(idx + 5, new_ins[5])
-                    idx = idx + 5
+                for new_ins_ in new_ins:
+                    new_ins_.set_node(ins.node)
+                del result[idx]
+                for i, ins in enumerate(new_ins):
+                    result.insert(idx + i, ins)
+                idx = idx + len(new_ins) - 1
             else:
                 new_ins.set_node(ins.node)
                 result[idx] = new_ins
@@ -518,7 +491,7 @@ def propagate_types(ir, node):  # pylint: disable=too-many-locals
                 if isinstance(t, ArrayType) or (
                     isinstance(t, ElementaryType) and t.type == "bytes"
                 ):
-                    if ir.function_name == "push" and len(ir.arguments) == 1:
+                    if ir.function_name == "push" and len(ir.arguments) <= 1:
                         return convert_to_push(ir, node)
                     if ir.function_name == "pop" and len(ir.arguments) == 0:
                         return convert_to_pop(ir, node)
@@ -1024,67 +997,94 @@ def convert_to_solidity_func(ir):
     return new_ir
 
 
-def convert_to_push(ir, node):
-    """
-    Convert a call to a PUSH operaiton
+def convert_to_push_expand_arr(ir, node, ret):
+    arr = ir.destination
 
-    The funciton assume to receive a correct IR
-    The checks must be done by the caller
+    length = ReferenceVariable(node)
+    length.set_type(ElementaryType("uint256"))
 
-    May necessitate to create an intermediate operation (InitArray)
-    Necessitate to return the lenght (see push documentation)
-    As a result, the function return may return a list
-    """
+    ir_length = Length(arr, length)
+    ir_length.set_expression(ir.expression)
+    ir_length.set_node(ir.node)
+    ir_length.lvalue.points_to = arr
+    ret.append(ir_length)
 
-    # TODO remove Push Operator, and change this to existing operators
+    length_val = TemporaryVariable(node)
+    length_val.set_type(ElementaryType("uint256"))
+    ir_get_length = Assignment(length_val, length, ElementaryType("uint256"))
+    ir_get_length.set_expression(ir.expression)
+    ir_get_length.set_node(ir.node)
+    ret.append(ir_get_length)
 
-    lvalue = ir.lvalue
-    if isinstance(ir.arguments[0], list):
-        ret = []
+    new_length_val = TemporaryVariable(node)
+    ir_add_1 = Binary(
+        new_length_val, length_val, Constant("1", ElementaryType("uint256")), BinaryType.ADDITION
+    )
+    ir_add_1.set_expression(ir.expression)
+    ir_add_1.set_node(ir.node)
+    ret.append(ir_add_1)
+
+    ir_assign_length = Assignment(length, new_length_val, ElementaryType("uint256"))
+    ir_assign_length.set_expression(ir.expression)
+    ir_assign_length.set_node(ir.node)
+    ret.append(ir_assign_length)
 
-        val = TemporaryVariable(node)
-        operation = InitArray(ir.arguments[0], val)
-        operation.set_expression(ir.expression)
-        operation.set_node(ir.node)
-        ret.append(operation)
+    return length_val
 
-        prev_ir = ir
-        ir = Push(ir.destination, val)
-        ir.set_expression(prev_ir.expression)
-        ir.set_node(prev_ir.node)
 
-        length = Literal(len(operation.init_values), "uint256")
-        t = operation.init_values[0].type
-        ir.lvalue.set_type(ArrayType(t, length))
+def convert_to_push_set_val(ir, node, length_val, ret):
+    arr = ir.destination
 
-        ret.append(ir)
+    new_type = ir.destination.type.type
+
+    element_to_add = ReferenceVariable(node)
+    element_to_add.set_type(new_type)
+    ir_assign_element_to_add = Index(element_to_add, arr, length_val, ElementaryType("uint256"))
+    ir_assign_element_to_add.set_expression(ir.expression)
+    ir_assign_element_to_add.set_node(ir.node)
+    ret.append(ir_assign_element_to_add)
+
+    if len(ir.arguments) > 0:
+        assign_value = ir.arguments[0]
+        if isinstance(assign_value, list):
+            assign_value = TemporaryVariable(node)
+            assign_value.set_type(element_to_add.type)
+            ir_assign_value = InitArray(ir.arguments[0], assign_value)
+            ir_assign_value.set_expression(ir.expression)
+            ir_assign_value.set_node(ir.node)
+            ret.append(ir_assign_value)
+
+        ir_assign_value = Assignment(element_to_add, assign_value, assign_value.type)
+        ir_assign_value.set_expression(ir.expression)
+        ir_assign_value.set_node(ir.node)
+        ret.append(ir_assign_value)
+    else:
+        new_element = ir.lvalue
+        new_element.set_type(new_type)
+        ir_assign_value = Assignment(new_element, element_to_add, new_type)
+        ir_assign_value.set_expression(ir.expression)
+        ir_assign_value.set_node(ir.node)
+        ret.append(ir_assign_value)
 
-        if lvalue:
-            length = Length(ir.array, lvalue)
-            length.set_expression(ir.expression)
-            length.lvalue.points_to = ir.lvalue
-            length.set_node(ir.node)
-            ret.append(length)
 
-        return ret
+def convert_to_push(ir, node):
+    """
+    Convert a call to a series of operations to push a new value onto the array
 
-    prev_ir = ir
-    ir = Push(ir.destination, ir.arguments[0])
-    ir.set_expression(prev_ir.expression)
-    ir.set_node(prev_ir.node)
+    The function assume to receive a correct IR
+    The checks must be done by the caller
 
-    if lvalue:
-        ret = []
-        ret.append(ir)
+    May necessitate to create an intermediate operation (InitArray)
+    Necessitate to return the length (see push documentation)
+    As a result, the function return may return a list
+    """
 
-        length = Length(ir.array, lvalue)
-        length.set_expression(ir.expression)
-        length.lvalue.points_to = ir.lvalue
-        length.set_node(ir.node)
-        ret.append(length)
-        return ret
+    ret = []
 
-    return ir
+    length_val = convert_to_push_expand_arr(ir, node, ret)
+    convert_to_push_set_val(ir, node, length_val, ret)
+
+    return ret
 
 
 def convert_to_pop(ir, node):


Test patch: