Repository: crytic/slither

Issue:
AssertionError parsing function members
The following contract causes an assertion error:
```
library FunctionExtensions {
	function g(function() internal _t) internal {  }
}
contract FunctionMembers {
	using FunctionExtensions for *;

	function f() public {
		return f.g();
	}
}
```

Output:
```
ERROR:root:Error in .\function_members.sol
ERROR:root: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 702, in slithir_generation
    self._irs = convert_expression(expression, self)
  File "c:\users\x\documents\github\slither\slither\slithir\convert.py", line 64, in convert_expression
    visitor = ExpressionToSlithIR(expression, node)
  File "c:\users\x\documents\github\slither\slither\visitors\slithir\expression_to_slithir.py", line 103, in __init__
    self._visit_expression(self.expression)
  File "c:\users\x\documents\github\slither\slither\visitors\expression\expression.py", line 51, in _visit_expression
    self._visit_call_expression(expression)
  File "c:\users\x\documents\github\slither\slither\visitors\expression\expression.py", line 108, in _visit_call_expression
    self._visit_expression(expression.called)
  File "c:\users\x\documents\github\slither\slither\visitors\expression\expression.py", line 95, in _visit_expression
    self._post_visit(expression)
  File "c:\users\x\documents\github\slither\slither\visitors\expression\expression.py", line 289, in _post_visit
    self._post_member_access(expression)
  File "c:\users\x\documents\github\slither\slither\visitors\slithir\expression_to_slithir.py", line 318, in _post_member_access
    member = Member(expr, Constant(expression.member_name), val)
  File "c:\users\x\documents\github\slither\slither\slithir\operations\member.py", line 14, in __init__
    assert is_valid_rvalue(variable_left) or isinstance(variable_left, (Contract, Enum))
AssertionError
```


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
@@ -211,6 +211,20 @@ def is_temporary(ins):
     )
 
 
+def _make_function_type(func: Function) -> FunctionType:
+    parameters = []
+    returns = []
+    for parameter in func.parameters:
+        v = FunctionTypeVariable()
+        v.name = parameter.name
+        parameters.append(v)
+    for return_var in func.returns:
+        v = FunctionTypeVariable()
+        v.name = return_var.name
+        returns.append(v)
+    return FunctionType(parameters, returns)
+
+
 # endregion
 ###################################################################################
 ###################################################################################
@@ -337,6 +351,9 @@ def propagate_type_and_convert_call(result, node):
                 ins.call_gas = calls_gas[ins.call_id]
 
         if isinstance(ins, (Call, NewContract, NewStructure)):
+            # We might have stored some arguments for libraries
+            if ins.arguments:
+                call_data = ins.arguments + call_data
             ins.arguments = call_data
             call_data = []
 
@@ -603,17 +620,7 @@ def propagate_types(ir, node):  # pylint: disable=too-many-locals
                             if x.name == str(ir.variable_right)
                         )
                     )
-                    parameters = []
-                    returns = []
-                    for parameter in targeted_function.parameters:
-                        v = FunctionTypeVariable()
-                        v.name = parameter.name
-                        parameters.append(v)
-                    for return_var in targeted_function.returns:
-                        v = FunctionTypeVariable()
-                        v.name = return_var.name
-                        returns.append(v)
-                    t = FunctionType(parameters, returns)
+                    t = _make_function_type(targeted_function)
                     ir.lvalue.set_type(t)
                 elif isinstance(left, (Variable, SolidityVariable)):
                     t = ir.variable_left.type
@@ -702,11 +709,11 @@ def propagate_types(ir, node):  # pylint: disable=too-many-locals
                 # temporary operation; they will be removed
                 pass
             else:
-                raise SlithIRError("Not handling {} during type propgation".format(type(ir)))
+                raise SlithIRError("Not handling {} during type propagation".format(type(ir)))
     return None
 
 
-def extract_tmp_call(ins, contract):
+def extract_tmp_call(ins, contract):  # pylint: disable=too-many-locals
     assert isinstance(ins, TmpCall)
 
     if isinstance(ins.called, Variable) and isinstance(ins.called.type, FunctionType):
@@ -754,6 +761,73 @@ def extract_tmp_call(ins, contract):
             libcall.set_expression(ins.expression)
             libcall.call_id = ins.call_id
             return libcall
+        if isinstance(ins.ori.variable_left, Function):
+            # Support for library call where the parameter is a function
+            # We could merge this with the standard library handling
+            # Except that we will have some troubles with using_for
+            # As the type of the funciton will not match function()
+            # Additionally we do not have a correct view on the parameters of the tmpcall
+            # At this level
+            #
+            # library FunctionExtensions {
+            #     function h(function() internal _t, uint8) internal {  }
+            # }
+            # contract FunctionMembers {
+            #     using FunctionExtensions for function();
+            #
+            #     function f() public {
+            #         f.h(1);
+            #     }
+            # }
+            using_for = ins.node.function.contract.using_for
+
+            targeted_libraries = (
+                [] + using_for.get("*", []) + using_for.get(FunctionType([], []), [])
+            )
+            lib_contract: Contract
+            candidates = []
+            for lib_contract_type in targeted_libraries:
+                if not isinstance(lib_contract_type, UserDefinedType) and isinstance(
+                    lib_contract_type.type, Contract
+                ):
+                    continue
+                lib_contract = lib_contract_type.type
+                for lib_func in lib_contract.functions:
+                    if lib_func.name == ins.ori.variable_right:
+                        candidates.append(lib_func)
+
+            if len(candidates) == 1:
+                lib_func = candidates[0]
+                lib_call = LibraryCall(
+                    lib_func.contract,
+                    Constant(lib_func.name),
+                    len(lib_func.parameters),
+                    ins.lvalue,
+                    "d",
+                )
+                lib_call.set_expression(ins.expression)
+                lib_call.set_node(ins.node)
+                lib_call.call_gas = ins.call_gas
+                lib_call.call_id = ins.call_id
+                lib_call.set_node(ins.node)
+                lib_call.function = lib_func
+                lib_call.arguments.append(ins.ori.variable_left)
+                return lib_call
+            # We do not support something lik
+            # library FunctionExtensions {
+            #     function h(function() internal _t, uint8) internal {  }
+            #     function h(function() internal _t, bool) internal {  }
+            # }
+            # contract FunctionMembers {
+            #     using FunctionExtensions for function();
+            #
+            #     function f() public {
+            #         f.h(1);
+            #     }
+            # }
+            to_log = "Slither does not support dynamic functions to libraries if functions have the same name"
+            to_log += f"{[candidate.full_name for candidate in candidates]}"
+            raise SlithIRError(to_log)
         msgcall = HighLevelCall(
             ins.ori.variable_left,
             ins.ori.variable_right,
diff --git a/slither/slithir/operations/member.py b/slither/slithir/operations/member.py
--- a/slither/slithir/operations/member.py
+++ b/slither/slithir/operations/member.py
@@ -1,4 +1,4 @@
-from slither.core.declarations import Contract
+from slither.core.declarations import Contract, Function
 from slither.core.declarations.enum import Enum
 from slither.slithir.operations.lvalue import OperationWithLValue
 from slither.slithir.utils.utils import is_valid_rvalue
@@ -8,7 +8,20 @@
 
 class Member(OperationWithLValue):
     def __init__(self, variable_left, variable_right, result):
-        assert is_valid_rvalue(variable_left) or isinstance(variable_left, (Contract, Enum))
+        # Function can happen for something like
+        # library FunctionExtensions {
+        #     function h(function() internal _t, uint8) internal {  }
+        # }
+        # contract FunctionMembers {
+        #     using FunctionExtensions for function();
+        #
+        #     function f() public {
+        #         f.h(1);
+        #     }
+        # }
+        assert is_valid_rvalue(variable_left) or isinstance(
+            variable_left, (Contract, Enum, Function)
+        )
         assert isinstance(variable_right, Constant)
         assert isinstance(result, ReferenceVariable)
         super().__init__()


Test patch: