github.com/luckypickle/go-ethereum-vet@v1.14.2/core/asm/compiler.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package asm 18 19 import ( 20 "fmt" 21 "math/big" 22 "os" 23 "strings" 24 25 "github.com/luckypickle/go-ethereum-vet/common/math" 26 "github.com/luckypickle/go-ethereum-vet/core/vm" 27 ) 28 29 // Compiler contains information about the parsed source 30 // and holds the tokens for the program. 31 type Compiler struct { 32 tokens []token 33 binary []interface{} 34 35 labels map[string]int 36 37 pc, pos int 38 39 debug bool 40 } 41 42 // newCompiler returns a new allocated compiler. 43 func NewCompiler(debug bool) *Compiler { 44 return &Compiler{ 45 labels: make(map[string]int), 46 debug: debug, 47 } 48 } 49 50 // Feed feeds tokens in to ch and are interpreted by 51 // the compiler. 52 // 53 // feed is the first pass in the compile stage as it 54 // collects the used labels in the program and keeps a 55 // program counter which is used to determine the locations 56 // of the jump dests. The labels can than be used in the 57 // second stage to push labels and determine the right 58 // position. 59 func (c *Compiler) Feed(ch <-chan token) { 60 for i := range ch { 61 switch i.typ { 62 case number: 63 num := math.MustParseBig256(i.text).Bytes() 64 if len(num) == 0 { 65 num = []byte{0} 66 } 67 c.pc += len(num) 68 case stringValue: 69 c.pc += len(i.text) - 2 70 case element: 71 c.pc++ 72 case labelDef: 73 c.labels[i.text] = c.pc 74 c.pc++ 75 case label: 76 c.pc += 5 77 } 78 79 c.tokens = append(c.tokens, i) 80 } 81 if c.debug { 82 fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels") 83 } 84 } 85 86 // Compile compiles the current tokens and returns a 87 // binary string that can be interpreted by the EVM 88 // and an error if it failed. 89 // 90 // compile is the second stage in the compile phase 91 // which compiles the tokens to EVM instructions. 92 func (c *Compiler) Compile() (string, []error) { 93 var errors []error 94 // continue looping over the tokens until 95 // the stack has been exhausted. 96 for c.pos < len(c.tokens) { 97 if err := c.compileLine(); err != nil { 98 errors = append(errors, err) 99 } 100 } 101 102 // turn the binary to hex 103 var bin string 104 for _, v := range c.binary { 105 switch v := v.(type) { 106 case vm.OpCode: 107 bin += fmt.Sprintf("%x", []byte{byte(v)}) 108 case []byte: 109 bin += fmt.Sprintf("%x", v) 110 } 111 } 112 return bin, errors 113 } 114 115 // next returns the next token and increments the 116 // position. 117 func (c *Compiler) next() token { 118 token := c.tokens[c.pos] 119 c.pos++ 120 return token 121 } 122 123 // compileLine compiles a single line instruction e.g. 124 // "push 1", "jump @label". 125 func (c *Compiler) compileLine() error { 126 n := c.next() 127 if n.typ != lineStart { 128 return compileErr(n, n.typ.String(), lineStart.String()) 129 } 130 131 lvalue := c.next() 132 switch lvalue.typ { 133 case eof: 134 return nil 135 case element: 136 if err := c.compileElement(lvalue); err != nil { 137 return err 138 } 139 case labelDef: 140 c.compileLabel() 141 case lineEnd: 142 return nil 143 default: 144 return compileErr(lvalue, lvalue.text, fmt.Sprintf("%v or %v", labelDef, element)) 145 } 146 147 if n := c.next(); n.typ != lineEnd { 148 return compileErr(n, n.text, lineEnd.String()) 149 } 150 151 return nil 152 } 153 154 // compileNumber compiles the number to bytes 155 func (c *Compiler) compileNumber(element token) (int, error) { 156 num := math.MustParseBig256(element.text).Bytes() 157 if len(num) == 0 { 158 num = []byte{0} 159 } 160 c.pushBin(num) 161 return len(num), nil 162 } 163 164 // compileElement compiles the element (push & label or both) 165 // to a binary representation and may error if incorrect statements 166 // where fed. 167 func (c *Compiler) compileElement(element token) error { 168 // check for a jump. jumps must be read and compiled 169 // from right to left. 170 if isJump(element.text) { 171 rvalue := c.next() 172 switch rvalue.typ { 173 case number: 174 // TODO figure out how to return the error properly 175 c.compileNumber(rvalue) 176 case stringValue: 177 // strings are quoted, remove them. 178 c.pushBin(rvalue.text[1 : len(rvalue.text)-2]) 179 case label: 180 c.pushBin(vm.PUSH4) 181 pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes() 182 pos = append(make([]byte, 4-len(pos)), pos...) 183 c.pushBin(pos) 184 default: 185 return compileErr(rvalue, rvalue.text, "number, string or label") 186 } 187 // push the operation 188 c.pushBin(toBinary(element.text)) 189 return nil 190 } else if isPush(element.text) { 191 // handle pushes. pushes are read from left to right. 192 var value []byte 193 194 rvalue := c.next() 195 switch rvalue.typ { 196 case number: 197 value = math.MustParseBig256(rvalue.text).Bytes() 198 if len(value) == 0 { 199 value = []byte{0} 200 } 201 case stringValue: 202 value = []byte(rvalue.text[1 : len(rvalue.text)-1]) 203 case label: 204 value = make([]byte, 4) 205 copy(value, big.NewInt(int64(c.labels[rvalue.text])).Bytes()) 206 default: 207 return compileErr(rvalue, rvalue.text, "number, string or label") 208 } 209 210 if len(value) > 32 { 211 return fmt.Errorf("%d type error: unsupported string or number with size > 32", rvalue.lineno) 212 } 213 214 c.pushBin(vm.OpCode(int(vm.PUSH1) - 1 + len(value))) 215 c.pushBin(value) 216 } else { 217 c.pushBin(toBinary(element.text)) 218 } 219 220 return nil 221 } 222 223 // compileLabel pushes a jumpdest to the binary slice. 224 func (c *Compiler) compileLabel() { 225 c.pushBin(vm.JUMPDEST) 226 } 227 228 // pushBin pushes the value v to the binary stack. 229 func (c *Compiler) pushBin(v interface{}) { 230 if c.debug { 231 fmt.Printf("%d: %v\n", len(c.binary), v) 232 } 233 c.binary = append(c.binary, v) 234 } 235 236 // isPush returns whether the string op is either any of 237 // push(N). 238 func isPush(op string) bool { 239 return strings.ToUpper(op) == "PUSH" 240 } 241 242 // isJump returns whether the string op is jump(i) 243 func isJump(op string) bool { 244 return strings.ToUpper(op) == "JUMPI" || strings.ToUpper(op) == "JUMP" 245 } 246 247 // toBinary converts text to a vm.OpCode 248 func toBinary(text string) vm.OpCode { 249 return vm.StringToOp(strings.ToUpper(text)) 250 } 251 252 type compileError struct { 253 got string 254 want string 255 256 lineno int 257 } 258 259 func (err compileError) Error() string { 260 return fmt.Sprintf("%d syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want) 261 } 262 263 func compileErr(c token, got, want string) error { 264 return compileError{ 265 got: got, 266 want: want, 267 lineno: c.lineno, 268 } 269 }