瀏覽代碼

a.b.selector adds b() as function to current contract (#1462)

Compile the Solidity below and we end up with the non-base function in
our contract.

```bash
$ solang compile --target polkadot --emit cfg
# function C::A::function::a public:true selector:0dbe671f nonpayable:true
# params: 
# returns: 
block0: # entry
	return 
```

```solidity
contract C {
    function ext_func_call(uint128 amount) public payable {
        A a = new A();
        (bool ok, bytes b) = address(a).call(
            bytes4(A.a.selector)
        );
	b = abi.encodeCall(A.a);
    }
}

contract A {
    function a() public pure {}
}
```

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 2 年之前
父節點
當前提交
12c8708cde

+ 28 - 1
src/codegen/external_functions.rs

@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::sema::ast::{DestructureField, Expression, Namespace, Statement};
+use crate::sema::ast::{Builtin, DestructureField, Expression, Namespace, Statement};
 use crate::sema::Recurse;
 use indexmap::IndexSet;
 use solang_parser::pt;
@@ -46,6 +46,19 @@ pub fn add_external_functions(contract_no: usize, ns: &mut Namespace) {
         // add functions to contract functions list
         for function_no in &call_list.solidity {
             if ns.functions[*function_no].loc != pt::Loc::Builtin {
+                let func = &ns.functions[*function_no];
+
+                // make sure we are not adding a public function which is not a base or library
+                if func.is_public() {
+                    // free function are not public, else this unwrap would panic
+                    let func_contract = func.contract_no.unwrap();
+
+                    assert!(
+                        ns.contract_bases(contract_no).contains(&func_contract)
+                            || ns.contracts[func_contract].is_library()
+                    );
+                }
+
                 ns.contracts[contract_no]
                     .all_functions
                     .insert(*function_no, usize::MAX);
@@ -98,6 +111,20 @@ fn check_expression(expr: &Expression, call_list: &mut CallList) -> bool {
         | Expression::InternalFunction { function_no, .. } => {
             call_list.solidity.insert(*function_no);
         }
+        Expression::Builtin {
+            kind: Builtin::AbiEncodeCall,
+            args,
+            ..
+        } => {
+            for expr in &args[1..] {
+                check_expression(expr, call_list);
+            }
+            return false;
+        }
+        Expression::Builtin {
+            kind: Builtin::FunctionSelector,
+            ..
+        } => return false,
         _ => (),
     }
 

+ 25 - 0
tests/codegen.rs

@@ -35,6 +35,7 @@ fn run_test_for_path(path: &str) {
 #[derive(Debug)]
 enum Test {
     Check(String),
+    CheckAbsent(String),
     NotCheck(String),
     Fail(String),
     Rewind,
@@ -54,18 +55,30 @@ fn testcase(path: PathBuf) {
     for line in reader.lines() {
         let mut line = line.unwrap();
         line = line.trim().parse().unwrap();
+        // The first line should be a command line (excluding "solang compile") after // RUN:
         if let Some(args) = line.strip_prefix("// RUN: ") {
             assert_eq!(command_line, None);
 
             command_line = Some(String::from(args));
+
+        // Read the contents of a file, e.g. the llvm-ir output of // RUN: --emit llvm-ir
+        // rather than the stdout of the command
         } else if let Some(check) = line.strip_prefix("// READ:") {
             read_from = Some(check.trim().to_string());
+        // Read more input until you find a line that contains the needle // CHECK: needle
         } else if let Some(check) = line.strip_prefix("// CHECK:") {
             checks.push(Test::Check(check.trim().to_string()));
+        //
         } else if let Some(fail) = line.strip_prefix("// FAIL:") {
             fails.push(Test::Fail(fail.trim().to_string()));
+        // Ensure that the following line in the input does not match
         } else if let Some(not_check) = line.strip_prefix("// NOT-CHECK:") {
             checks.push(Test::NotCheck(not_check.trim().to_string()));
+        // Check the output from here until the end of the file does not contain the needle
+        } else if let Some(check_absent) = line.strip_prefix("// CHECK-ABSENT:") {
+            checks.push(Test::CheckAbsent(check_absent.trim().to_string()));
+        // Go back to the beginning and find the needle from there, like // CHECK: but from
+        // the beginning of the file.
         } else if let Some(check) = line.strip_prefix("// BEGIN-CHECK:") {
             checks.push(Test::Rewind);
             checks.push(Test::Check(check.trim().to_string()));
@@ -113,6 +126,18 @@ fn testcase(path: PathBuf) {
                     current_line -= 1;
                 }
             }
+            Some(Test::CheckAbsent(needle)) => {
+                for line in lines.iter().skip(current_line) {
+                    if line.contains(needle) {
+                        panic!(
+                            "FOUND CHECK-ABSENT: {:?}, {}",
+                            checks[current_check],
+                            path.display()
+                        );
+                    }
+                }
+                current_check += 1;
+            }
             Some(Test::Rewind) => {
                 current_line = 0;
                 current_check += 1;

+ 19 - 0
tests/codegen_testcases/solidity/selector_adds_non_base_function.sol

@@ -0,0 +1,19 @@
+// RUN: --target polkadot --emit cfg
+
+// This test makes sure that we're not including the function a in contract C
+
+// CHECK-ABSENT: C::A::function::a
+
+contract C {
+    function ext_func_call(uint128 amount) public payable {
+        A a = new A();
+        (bool ok, bytes b) = address(a).call(
+            bytes4(A.a.selector)
+        );
+	b = abi.encodeCall(A.a);
+    }
+}
+
+contract A {
+    function a() public pure {}
+}