Repository: crytic/slither

Issue:
[Bug]:`string.concat` fails `assert isinstance(destination, (Variable, SolidityVariable))` and panics 
### What happened?

`A bug happened!`

Using `string.concat` seems to trigger an `AssertionError` and panic.

### Can you share code with us to reproduce this bug?
```
pragma solidity 0.8.12;

contract ContractMe  {

    function foo() public pure returns (string memory) {
        return string.concat("bar");
    }

}
```

### Version

0.8.2

### Relevant log output

```shell
Traceback (most recent call last):
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 743, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 84, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 67, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/home/user/.local/lib/python3.9/site-packages/slither/slither.py", line 125, in __init__
    parser.analyze_contracts()
  File "/home/user/.local/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 442, in analyze_contracts
    self._convert_to_slithir()
  File "/home/user/.local/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 626, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/home/user/.local/lib/python3.9/site-packages/slither/core/declarations/function.py", line 1708, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/home/user/.local/lib/python3.9/site-packages/slither/core/cfg/node.py", line 720, in slithir_generation
    self._irs = convert_expression(expression, self)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 118, in convert_expression
    result = apply_ir_heuristics(result, node)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 1725, in apply_ir_heuristics
    irs = propagate_type_and_convert_call(irs, node)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 396, in propagate_type_and_convert_call
    new_ins = extract_tmp_call(ins, contract)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 947, in extract_tmp_call
    msgcall = HighLevelCall(
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/operations/high_level_call.py", line 22, in __init__
    self._check_destination(destination)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/operations/high_level_call.py", line 38, in _check_destination
    assert isinstance(destination, (Variable, SolidityVariable))
AssertionError
None
Error in C.sol
Traceback (most recent call last):
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 743, in main_impl
    ) = process_all(filename, args, detector_classes, printer_classes)
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 84, in process_all
    ) = process_single(compilation, args, detector_classes, printer_classes)
  File "/home/user/.local/lib/python3.9/site-packages/slither/__main__.py", line 67, in process_single
    slither = Slither(target, ast_format=ast, **vars(args))
  File "/home/user/.local/lib/python3.9/site-packages/slither/slither.py", line 125, in __init__
    parser.analyze_contracts()
  File "/home/user/.local/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 442, in analyze_contracts
    self._convert_to_slithir()
  File "/home/user/.local/lib/python3.9/site-packages/slither/solc_parsing/slither_compilation_unit_solc.py", line 626, in _convert_to_slithir
    func.generate_slithir_and_analyze()
  File "/home/user/.local/lib/python3.9/site-packages/slither/core/declarations/function.py", line 1708, in generate_slithir_and_analyze
    node.slithir_generation()
  File "/home/user/.local/lib/python3.9/site-packages/slither/core/cfg/node.py", line 720, in slithir_generation
    self._irs = convert_expression(expression, self)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 118, in convert_expression
    result = apply_ir_heuristics(result, node)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 1725, in apply_ir_heuristics
    irs = propagate_type_and_convert_call(irs, node)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 396, in propagate_type_and_convert_call
    new_ins = extract_tmp_call(ins, contract)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/convert.py", line 947, in extract_tmp_call
    msgcall = HighLevelCall(
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/operations/high_level_call.py", line 22, in __init__
    self._check_destination(destination)
  File "/home/user/.local/lib/python3.9/site-packages/slither/slithir/operations/high_level_call.py", line 38, in _check_destination
    assert isinstance(destination, (Variable, SolidityVariable))
AssertionError
```



Discussion:
Hi @joshieDo,

Thank you for reporting this. We will add the support as soon as possible

Resolving patch:
diff --git a/slither/core/declarations/solidity_variables.py b/slither/core/declarations/solidity_variables.py
--- a/slither/core/declarations/solidity_variables.py
+++ b/slither/core/declarations/solidity_variables.py
@@ -71,6 +71,7 @@
     "abi.encodeWithSelector()": ["bytes"],
     "abi.encodeWithSignature()": ["bytes"],
     "bytes.concat()": ["bytes"],
+    "string.concat()": ["string"],
     # abi.decode returns an a list arbitrary types
     "abi.decode()": [],
     "type(address)": [],
diff --git a/slither/slithir/convert.py b/slither/slithir/convert.py
--- a/slither/slithir/convert.py
+++ b/slither/slithir/convert.py
@@ -944,6 +944,18 @@ def extract_tmp_call(ins: TmpCall, contract: Optional[Contract]):  # pylint: dis
             s.set_expression(ins.expression)
             return s
 
+        if ins.ori.variable_left == ElementaryType("string") and ins.ori.variable_right == Constant(
+            "concat"
+        ):
+            s = SolidityCall(
+                SolidityFunction("string.concat()"),
+                ins.nbr_arguments,
+                ins.lvalue,
+                ins.type_call,
+            )
+            s.set_expression(ins.expression)
+            return s
+
         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
@@ -22,7 +22,7 @@ def __init__(self, variable_left, variable_right, result):
         #         f.h(1);
         #     }
         # }
-        # Can be an ElementaryType because of bytes.concat
+        # Can be an ElementaryType because of bytes.concat, string.concat
         assert is_valid_rvalue(variable_left) or isinstance(
             variable_left,
             (Contract, Enum, Function, CustomError, SolidityImportPlaceHolder, ElementaryType),


Test patch:
diff --git a/tests/ast-parsing/compile/units_and_global_variables-0.8.12-compact.zip b/tests/ast-parsing/compile/units_and_global_variables-0.8.12-compact.zip
Binary files a/tests/ast-parsing/compile/units_and_global_variables-0.8.12-compact.zip and b/tests/ast-parsing/compile/units_and_global_variables-0.8.12-compact.zip differ
diff --git a/tests/ast-parsing/expected/units_and_global_variables-0.8.12-compact.json b/tests/ast-parsing/expected/units_and_global_variables-0.8.12-compact.json
--- a/tests/ast-parsing/expected/units_and_global_variables-0.8.12-compact.json
+++ b/tests/ast-parsing/expected/units_and_global_variables-0.8.12-compact.json
@@ -4,9 +4,8 @@
   "Test": {
     "ether_unit()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n}\n",
     "time_unit()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n}\n",
-    "block_and_transactions()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n5->6;\n6[label=\"Node Type: EXPRESSION 6\n\"];\n6->7;\n7[label=\"Node Type: EXPRESSION 7\n\"];\n7->8;\n8[label=\"Node Type: EXPRESSION 8\n\"];\n8->9;\n9[label=\"Node Type: EXPRESSION 9\n\"];\n9->10;\n10[label=\"Node Type: EXPRESSION 10\n\"];\n10->11;\n11[label=\"Node Type: EXPRESSION 11\n\"];\n11->12;\n12[label=\"Node Type: EXPRESSION 12\n\"];\n12->13;\n13[label=\"Node Type: EXPRESSION 13\n\"];\n13->14;\n14[label=\"Node Type: EXPRESSION 14\n\"];\n14->15;\n15[label=\"Node Type: EXPRESSION 15\n\"];\n15->16;\n16[label=\"Node Type: EXPRESSION 16\n\"];\n}\n",
+    "block_and_transactions()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n5->6;\n6[label=\"Node Type: EXPRESSION 6\n\"];\n6->7;\n7[label=\"Node Type: EXPRESSION 7\n\"];\n7->8;\n8[label=\"Node Type: EXPRESSION 8\n\"];\n8->9;\n9[label=\"Node Type: EXPRESSION 9\n\"];\n9->10;\n10[label=\"Node Type: EXPRESSION 10\n\"];\n10->11;\n11[label=\"Node Type: EXPRESSION 11\n\"];\n11->12;\n12[label=\"Node Type: EXPRESSION 12\n\"];\n12->13;\n13[label=\"Node Type: EXPRESSION 13\n\"];\n13->14;\n14[label=\"Node Type: EXPRESSION 14\n\"];\n14->15;\n15[label=\"Node Type: EXPRESSION 15\n\"];\n}\n",
     "abi_encode()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: NEW VARIABLE 5\n\"];\n5->6;\n6[label=\"Node Type: EXPRESSION 6\n\"];\n6->7;\n7[label=\"Node Type: NEW VARIABLE 7\n\"];\n7->8;\n8[label=\"Node Type: EXPRESSION 8\n\"];\n}\n",
-    "member()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: NEW VARIABLE 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n}\n",
     "error_handling()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n}\n",
     "math_and_crypto()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: EXPRESSION 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n5->6;\n6[label=\"Node Type: NEW VARIABLE 6\n\"];\n6->7;\n7[label=\"Node Type: NEW VARIABLE 7\n\"];\n7->8;\n8[label=\"Node Type: NEW VARIABLE 8\n\"];\n8->9;\n9[label=\"Node Type: NEW VARIABLE 9\n\"];\n9->10;\n10[label=\"Node Type: EXPRESSION 10\n\"];\n}\n",
     "address_related()": "digraph{\n0[label=\"Node Type: ENTRY_POINT 0\n\"];\n0->1;\n1[label=\"Node Type: NEW VARIABLE 1\n\"];\n1->2;\n2[label=\"Node Type: EXPRESSION 2\n\"];\n2->3;\n3[label=\"Node Type: EXPRESSION 3\n\"];\n3->4;\n4[label=\"Node Type: EXPRESSION 4\n\"];\n4->5;\n5[label=\"Node Type: EXPRESSION 5\n\"];\n5->6;\n6[label=\"Node Type: EXPRESSION 6\n\"];\n6->7;\n7[label=\"Node Type: EXPRESSION 7\n\"];\n7->8;\n8[label=\"Node Type: EXPRESSION 8\n\"];\n8->9;\n9[label=\"Node Type: EXPRESSION 9\n\"];\n}\n",
diff --git a/tests/ast-parsing/units_and_global_variables-0.8.12.sol b/tests/ast-parsing/units_and_global_variables-0.8.12.sol
new file mode 100644
--- /dev/null
+++ b/tests/ast-parsing/units_and_global_variables-0.8.12.sol
@@ -0,0 +1,119 @@
+pragma experimental ABIEncoderV2;
+
+contract A{}
+
+interface I{}
+
+contract Test{
+
+    function ether_unit() public{
+        1 wei;
+        1 ether;
+    }
+
+    function time_unit() public{
+        1 seconds;
+        1 minutes;
+        1 hours;
+        1 days;
+        1 weeks;
+    }
+
+    function block_and_transactions() payable public{
+        blockhash(0);
+        block.basefee;
+        block.chainid;
+        block.coinbase;
+        block.difficulty;
+        block.gaslimit;
+        block.number;
+        block.timestamp;
+        gasleft();
+        msg.data;
+        msg.sender;
+        msg.sig;
+        msg.value;
+        block.timestamp;
+        tx.gasprice;
+        tx.origin;
+    }
+
+    function abi_encode() public{
+        bytes memory m;
+        abi.decode(m, (uint, uint));
+        abi.encode(10);
+        abi.encodePacked(uint(10));
+        bytes4 selector;
+        abi.encodeWithSelector(selector, 10);
+        string memory signature;
+        abi.encodeWithSignature(signature, 10);
+    }
+
+    function member() public{
+        bytes1 b1;
+        bytes32 b32;
+        bytes.concat(b1, b32);
+        string.concat("", "");
+    }
+
+    function error_handling() public{
+        assert(true);
+        require(true);
+        require(true, "something");
+        revert();
+        revert("something");
+    }
+
+    function math_and_crypto() public{
+        addmod(0, 0, 1);
+        mulmod(0, 0, 1);
+        keccak256("");
+        sha256("");
+        ripemd160("");
+        bytes32 hash;
+        uint8 v;
+        bytes32 r;
+        bytes32 s;
+        ecrecover(hash,v,r,s);
+    }
+
+    function address_related() public{
+        address payable a;
+        a.balance;
+        a.code;
+        a.codehash;
+        a.send(0);
+        a.transfer(0);
+        a.call("");
+        a.delegatecall("");
+        a.staticcall("");
+    }
+
+
+    function return_addr() internal returns(address){}
+    function address_edge_case() public{
+        // For now slithIR loss precision on this edge case
+        // And create a Ref variable instead of a Temporary one
+        return_addr().balance;
+        return_addr().code;
+        return_addr().codehash;
+    }
+
+
+    function contract_related() public{
+        this;
+        address payable a;
+        selfdestruct(a);
+    }
+
+    function type_related() public{
+        type(A).name;
+        type(A).creationCode;
+        type(A).runtimeCode;
+        type(I).interfaceId;
+        type(uint256).min;
+        type(uint256).min;
+    }
+
+
+}