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  }