Procházet zdrojové kódy

Solidity keywords were not correctly handled in yul (#1289)

* Solidity keywords were not correctly handled in yul

Signed-off-by: Sean Young <sean@mess.org>
Sean Young před 2 roky
rodič
revize
97ba03f3bc

+ 49 - 1
solang-parser/src/solidity.lalrpop

@@ -879,9 +879,57 @@ YulIdentifier: Identifier = {
     <l:@L> <n:identifier> <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: n.to_string()},
     <l:@L> "return" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "return".to_string()},
     <l:@L> "revert" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "revert".to_string()},
-    <l:@L> "bool" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "bool".to_string()},
     <l:@L> "address" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "address".to_string()},
+    <l:@L> "abstract" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "abstract".to_string()},
+    <l:@L> "anonymous" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "anonymous".to_string()},
+    <l:@L> "as" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "as".to_string()},
+    <l:@L> "assembly" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "assembly".to_string()},
+    <l:@L> "bool" <r:@R> => Identifier{loc: Loc::File(file_no, l, r), name: "bool".to_string()},
     <l:@L> "byte" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "byte".to_string()},
+    <l:@L> "bytes" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "bytes".to_string()},
+    <l:@L> "catch" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "catch".to_string()},
+    <l:@L> "calldata" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "calldata".to_string()},
+    <l:@L> "constant" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "constant".to_string()},
+    <l:@L> "constructor" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "constructor".to_string()},
+    <l:@L> "contract" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "contract".to_string()},
+    <l:@L> "do" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "do".to_string()},
+    <l:@L> "else" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "else".to_string()},
+    <l:@L> "enum" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "enum".to_string()},
+    <l:@L> "emit" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "emit".to_string()},
+    <l:@L> "event" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "event".to_string()},
+    <l:@L> "external" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "external".to_string()},
+    <l:@L> "fallback" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "fallback".to_string()},
+    <l:@L> "indexed" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "indexed".to_string()},
+    <l:@L> "interface" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "interface".to_string()},
+    <l:@L> "internal" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "internal".to_string()},
+    <l:@L> "immutable" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "immutable".to_string()},
+    <l:@L> "import" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "import".to_string()},
+    <l:@L> "is" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "is".to_string()},
+    <l:@L> "library" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "library".to_string()},
+    <l:@L> "mapping" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "mapping".to_string()},
+    <l:@L> "memory" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "memory".to_string()},
+    <l:@L> "modifier" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "modifier".to_string()},
+    <l:@L> "new" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "new".to_string()},
+    <l:@L> "override" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "override".to_string()},
+    <l:@L> "payable" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "payable".to_string()},
+    <l:@L> "public" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "public".to_string()},
+    <l:@L> "pragma" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "pragma".to_string()},
+    <l:@L> "private" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "private".to_string()},
+    <l:@L> "pure" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "pure".to_string()},
+    <l:@L> "receive" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "receive".to_string()},
+    <l:@L> "returns" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "returns".to_string()},
+    <l:@L> "storage" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "storage".to_string()},
+    <l:@L> "struct" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "struct".to_string()},
+    <l:@L> "throw" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "throw".to_string()},
+    <l:@L> "try" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "try".to_string()},
+    <l:@L> "using" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "using".to_string()},
+    <l:@L> "view" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "view".to_string()},
+    <l:@L> "virtual" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "virtual".to_string()},
+    <l:@L> "while" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "while".to_string()},
+    <l:@L> <i:Int> <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: format!("int{}", i)},
+    <l:@L> <u:Uint> <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: format!("uint{}", u)},
+    <l:@L> "string" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "string".to_string()},
+    <l:@L> "unchecked" <r:@L> => Identifier{loc: Loc::File(file_no, l, r), name: "unchecked".to_string()},
 }
 
 YulStatement: YulStatement = {

+ 14 - 2
src/codegen/cfg.rs

@@ -181,6 +181,12 @@ pub enum Instr {
     },
     /// Return a code at the end of a function
     ReturnCode { code: ReturnCode },
+    /// For unimplemented code, e.g. unsupported yul builtins. This instruction should
+    /// only occur for the evm target, for which no emit is implemented yet. Once evm emit
+    /// is implemented and all yul builtins are supported, this instruction should
+    /// be removed. We only have this so we can pass evm code through sema/codegen, which is used
+    /// by the language server and the ethereum solidity tests.
+    Unimplemented { reachable: bool },
 }
 
 /// This struct defined the return codes that we send to the execution environment when we return
@@ -340,7 +346,8 @@ impl Instr {
             | Instr::Nop
             | Instr::ReturnCode { .. }
             | Instr::Branch { .. }
-            | Instr::PopMemory { .. } => {}
+            | Instr::PopMemory { .. }
+            | Instr::Unimplemented { .. } => {}
         }
     }
 }
@@ -440,7 +447,8 @@ impl BasicBlock {
                 | Instr::SelfDestruct { .. }
                 | Instr::ReturnCode { .. }
                 | Instr::ReturnData { .. }
-                | Instr::Return { .. } => {
+                | Instr::Return { .. }
+                | Instr::Unimplemented { reachable: false } => {
                     assert_eq!(i, 0, "instruction should be last in block");
                 }
 
@@ -1330,6 +1338,10 @@ impl ControlFlowGraph {
             Instr::ReturnCode { code } => {
                 format!("return code: {code}")
             }
+
+            Instr::Unimplemented { .. } => {
+                "unimplemented".into()
+            }
         }
     }
 

+ 5 - 1
src/codegen/solana_accounts.rs

@@ -296,7 +296,11 @@ fn check_instruction(instr: &Instr, data: &mut RecurseData) {
                 item.recurse(data, check_expression);
             }
         }
-        Instr::Branch { .. } | Instr::Nop | Instr::ReturnCode { .. } | Instr::PopMemory { .. } => {}
+        Instr::Branch { .. }
+        | Instr::Nop
+        | Instr::ReturnCode { .. }
+        | Instr::PopMemory { .. }
+        | Instr::Unimplemented { .. } => {}
         Instr::Store {
             dest,
             data: store_data,

+ 2 - 1
src/codegen/subexpression_elimination/instruction.rs

@@ -195,7 +195,8 @@ impl<'a, 'b: 'a> AvailableExpressionSet<'a> {
             | Instr::Nop
             | Instr::ReturnCode { .. }
             | Instr::Branch { .. }
-            | Instr::PopMemory { .. } => {}
+            | Instr::PopMemory { .. }
+            | Instr::Unimplemented { .. } => {}
         }
     }
 

+ 2 - 1
src/codegen/vector_to_slice.rs

@@ -117,7 +117,8 @@ fn find_writable_vectors(
             | Instr::Print { .. }
             | Instr::AssertFailure { .. }
             | Instr::ReturnData { .. }
-            | Instr::ValueTransfer { .. } => {
+            | Instr::ValueTransfer { .. }
+            | Instr::Unimplemented { .. } => {
                 apply_transfers(&block.transfers[instr_no], vars, writable);
             }
         }

+ 23 - 14
src/codegen/yul/builtin.rs

@@ -1,17 +1,20 @@
 // SPDX-License-Identifier: Apache-2.0
 
-use crate::codegen::expression::{assert_failure, log_runtime_error};
-use crate::codegen::{
-    cfg::{ControlFlowGraph, Instr},
-    vartable::Vartable,
-    yul::expression::expression,
-    {Builtin, Expression, Options},
-};
-use crate::sema::ast::{Namespace, RetrieveType, Type};
-use crate::sema::{
-    diagnostics::Diagnostics,
-    expression::integers::coerce_number,
-    yul::{ast, builtin::YulBuiltInFunction},
+use crate::{
+    codegen::{
+        cfg::{ControlFlowGraph, Instr},
+        expression::{assert_failure, log_runtime_error},
+        vartable::Vartable,
+        yul::expression::expression,
+        {Builtin, Expression, Options},
+    },
+    sema::{
+        ast::{Namespace, RetrieveType, Type},
+        diagnostics::Diagnostics,
+        expression::integers::coerce_number,
+        yul::{ast, builtin::YulBuiltInFunction},
+    },
+    Target,
 };
 use num_bigint::BigInt;
 use num_traits::{FromPrimitive, Zero};
@@ -135,8 +138,14 @@ pub(crate) fn process_builtin(
         // origin is the same as tx.origin and is not implemented
         | YulBuiltInFunction::Origin
         => {
-            let function_ty = builtin_ty.get_prototype_info();
-            unreachable!("{} yul builtin not implemented", function_ty.name);
+            if ns.target != Target::EVM {
+                let function_ty = builtin_ty.get_prototype_info();
+                unreachable!("{} yul builtin not implemented", function_ty.name);
+            }
+
+            // Sema will only allow this for EVM. This is a placeholder until correct codegen is in place
+            cfg.add(vartab, Instr::Unimplemented { reachable: !matches!(builtin_ty, YulBuiltInFunction::Return | YulBuiltInFunction::Revert | YulBuiltInFunction::Stop) });
+            Expression::Poison
         }
 
         YulBuiltInFunction::Gas => {

+ 2 - 0
src/emit/instructions.rs

@@ -1094,6 +1094,8 @@ pub(super) fn process_instruction<'a, T: TargetRuntime<'a> + ?Sized>(
         Instr::ReturnCode { code } => {
             target.return_code(bin, bin.return_values[code]);
         }
+
+        Instr::Unimplemented { .. } => unimplemented!(),
     }
 }
 

+ 2 - 1
src/sema/ast.rs

@@ -1474,7 +1474,8 @@ impl CodeLocation for Instr {
             | Instr::ReturnCode { .. }
             | Instr::Nop
             | Instr::AssertFailure { .. }
-            | Instr::PopMemory { .. } => pt::Loc::Codegen,
+            | Instr::PopMemory { .. }
+            | Instr::Unimplemented { .. } => pt::Loc::Codegen,
         }
     }
 }

+ 1 - 1
src/sema/yul/tests/expression.rs

@@ -1059,7 +1059,7 @@ contract testTypes {
 
     let ns = parse(file);
     assert!(ns.diagnostics.contains_message(
-        r#"unrecognised token ':=', expected ")", ",", "address", "bool", "break", "byte", "case", "continue", "default", "for", "function", "if", "leave", "let", "return", "revert", "switch", "{", "}", identifier"#
+        r#"unrecognised token ':=', expected ")", ",", "abstract", "address", "anonymous", "as", "assembly", "bool", "break", "byte", "bytes", "calldata", "case", "catch", "constant", "constructor", "continue", "contract", "default", "do", "else", "emit", "enum", "event", "external", "fallback", "for", "function", "if", "immutable", "import", "indexed", "interface", "internal", "is", "leave", "let", "library", "mapping", "memory", "modifier", "new", "override", "payable", "pragma", "private", "public", "pure", "receive", "return", "returns", "revert", "storage", "string", "struct", "switch", "throw", "try", "unchecked", "using", "view", "virtual", "while", "{", "}", Int, Uint, identifier"#
     ));
 
     let file = r#"

+ 2 - 2
src/sema/yul/tests/switch.rs

@@ -45,7 +45,7 @@ contract testTypes {
 
     let ns = parse(file);
     assert!(ns.diagnostics.contains_message(
-        r#"unrecognised token 'case', expected "address", "bool", "break", "byte", "continue", "for", "function", "if", "leave", "let", "return", "revert", "switch", "{", "}", identifier"#
+        r#"unrecognised token 'case', expected "abstract", "address", "anonymous", "as", "assembly", "bool", "break", "byte", "bytes", "calldata", "catch", "constant", "constructor", "continue", "contract", "do", "else", "emit", "enum", "event", "external", "fallback", "for", "function", "if", "immutable", "import", "indexed", "interface", "internal", "is", "leave", "let", "library", "mapping", "memory", "modifier", "new", "override", "payable", "pragma", "private", "public", "pure", "receive", "return", "returns", "revert", "storage", "string", "struct", "switch", "throw", "try", "unchecked", "using", "view", "virtual", "while", "{", "}", Int, Uint, identifier"#
     ));
 }
 
@@ -68,7 +68,7 @@ contract testTypes {
 
     let ns = parse(file);
     assert!(ns.diagnostics.contains_message(
-        r#"unrecognised token 'default', expected "address", "bool", "break", "byte", "continue", "for", "function", "if", "leave", "let", "return", "revert", "switch", "{", "}", identifier"#
+        r#"unrecognised token 'default', expected "abstract", "address", "anonymous", "as", "assembly", "bool", "break", "byte", "bytes", "calldata", "catch", "constant", "constructor", "continue", "contract", "do", "else", "emit", "enum", "event", "external", "fallback", "for", "function", "if", "immutable", "import", "indexed", "interface", "internal", "is", "leave", "let", "library", "mapping", "memory", "modifier", "new", "override", "payable", "pragma", "private", "public", "pure", "receive", "return", "returns", "revert", "storage", "string", "struct", "switch", "throw", "try", "unchecked", "using", "view", "virtual", "while", "{", "}", Int, Uint, identifier"#
     ));
 }
 

+ 1 - 1
tests/evm.rs

@@ -255,7 +255,7 @@ fn ethereum_solidity_tests() {
         })
         .sum();
 
-    assert_eq!(errors, 1094);
+    assert_eq!(errors, 1093);
 }
 
 fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {