github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/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  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"math/big"
    24  	"os"
    25  	"strings"
    26  
    27  	"github.com/ethereum/go-ethereum/common/math"
    28  	"github.com/ethereum/go-ethereum/core/vm"
    29  )
    30  
    31  // Compiler contains information about the parsed source
    32  // and holds the tokens for the program.
    33  type Compiler struct {
    34  	tokens []token
    35  	out    []byte
    36  
    37  	labels map[string]int
    38  
    39  	pc, pos int
    40  
    41  	debug bool
    42  }
    43  
    44  // NewCompiler returns a new allocated compiler.
    45  func NewCompiler(debug bool) *Compiler {
    46  	return &Compiler{
    47  		labels: make(map[string]int),
    48  		debug:  debug,
    49  	}
    50  }
    51  
    52  // Feed feeds tokens into ch and are interpreted by
    53  // the compiler.
    54  //
    55  // feed is the first pass in the compile stage as it collects the used labels in the
    56  // program and keeps a program counter which is used to determine the locations of the
    57  // jump dests. The labels can than be used in the second stage to push labels and
    58  // determine the right position.
    59  func (c *Compiler) Feed(ch <-chan token) {
    60  	var prev token
    61  	for i := range ch {
    62  		switch i.typ {
    63  		case number:
    64  			num := math.MustParseBig256(i.text).Bytes()
    65  			if len(num) == 0 {
    66  				num = []byte{0}
    67  			}
    68  			c.pc += len(num)
    69  		case stringValue:
    70  			c.pc += len(i.text) - 2
    71  		case element:
    72  			c.pc++
    73  		case labelDef:
    74  			c.labels[i.text] = c.pc
    75  			c.pc++
    76  		case label:
    77  			c.pc += 4
    78  			if prev.typ == element && isJump(prev.text) {
    79  				c.pc++
    80  			}
    81  		}
    82  		c.tokens = append(c.tokens, i)
    83  		prev = i
    84  	}
    85  	if c.debug {
    86  		fmt.Fprintln(os.Stderr, "found", len(c.labels), "labels")
    87  	}
    88  }
    89  
    90  // Compile compiles the current tokens and returns a binary string that can be interpreted
    91  // by the EVM and an error if it failed.
    92  //
    93  // compile is the second stage in the compile phase which compiles the tokens to EVM
    94  // instructions.
    95  func (c *Compiler) Compile() (string, []error) {
    96  	var errors []error
    97  	// continue looping over the tokens until
    98  	// the stack has been exhausted.
    99  	for c.pos < len(c.tokens) {
   100  		if err := c.compileLine(); err != nil {
   101  			errors = append(errors, err)
   102  		}
   103  	}
   104  
   105  	// turn the binary to hex
   106  	h := hex.EncodeToString(c.out)
   107  	return h, errors
   108  }
   109  
   110  // next returns the next token and increments the
   111  // position.
   112  func (c *Compiler) next() token {
   113  	token := c.tokens[c.pos]
   114  	c.pos++
   115  	return token
   116  }
   117  
   118  // compileLine compiles a single line instruction e.g.
   119  // "push 1", "jump @label".
   120  func (c *Compiler) compileLine() error {
   121  	n := c.next()
   122  	if n.typ != lineStart {
   123  		return compileErr(n, n.typ.String(), lineStart.String())
   124  	}
   125  
   126  	lvalue := c.next()
   127  	switch lvalue.typ {
   128  	case eof:
   129  		return nil
   130  	case element:
   131  		if err := c.compileElement(lvalue); err != nil {
   132  			return err
   133  		}
   134  	case labelDef:
   135  		c.compileLabel()
   136  	case lineEnd:
   137  		return nil
   138  	default:
   139  		return compileErr(lvalue, lvalue.text, fmt.Sprintf("%v or %v", labelDef, element))
   140  	}
   141  
   142  	if n := c.next(); n.typ != lineEnd {
   143  		return compileErr(n, n.text, lineEnd.String())
   144  	}
   145  
   146  	return nil
   147  }
   148  
   149  // parseNumber compiles the number to bytes
   150  func parseNumber(tok token) ([]byte, error) {
   151  	if tok.typ != number {
   152  		panic("parseNumber of non-number token")
   153  	}
   154  	num, ok := math.ParseBig256(tok.text)
   155  	if !ok {
   156  		return nil, errors.New("invalid number")
   157  	}
   158  	bytes := num.Bytes()
   159  	if len(bytes) == 0 {
   160  		bytes = []byte{0}
   161  	}
   162  	return bytes, nil
   163  }
   164  
   165  // compileElement compiles the element (push & label or both)
   166  // to a binary representation and may error if incorrect statements
   167  // where fed.
   168  func (c *Compiler) compileElement(element token) error {
   169  	switch {
   170  	case isJump(element.text):
   171  		return c.compileJump(element.text)
   172  	case isPush(element.text):
   173  		return c.compilePush()
   174  	default:
   175  		c.outputOpcode(toBinary(element.text))
   176  		return nil
   177  	}
   178  }
   179  
   180  func (c *Compiler) compileJump(jumpType string) error {
   181  	rvalue := c.next()
   182  	switch rvalue.typ {
   183  	case number:
   184  		numBytes, err := parseNumber(rvalue)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		c.outputBytes(numBytes)
   189  
   190  	case stringValue:
   191  		// strings are quoted, remove them.
   192  		str := rvalue.text[1 : len(rvalue.text)-2]
   193  		c.outputBytes([]byte(str))
   194  
   195  	case label:
   196  		c.outputOpcode(vm.PUSH4)
   197  		pos := big.NewInt(int64(c.labels[rvalue.text])).Bytes()
   198  		pos = append(make([]byte, 4-len(pos)), pos...)
   199  		c.outputBytes(pos)
   200  
   201  	case lineEnd:
   202  		// push without argument is supported, it just takes the destination from the stack.
   203  		c.pos--
   204  
   205  	default:
   206  		return compileErr(rvalue, rvalue.text, "number, string or label")
   207  	}
   208  	// push the operation
   209  	c.outputOpcode(toBinary(jumpType))
   210  	return nil
   211  }
   212  
   213  func (c *Compiler) compilePush() error {
   214  	// handle pushes. pushes are read from left to right.
   215  	var value []byte
   216  	rvalue := c.next()
   217  	switch rvalue.typ {
   218  	case number:
   219  		value = math.MustParseBig256(rvalue.text).Bytes()
   220  		if len(value) == 0 {
   221  			value = []byte{0}
   222  		}
   223  	case stringValue:
   224  		value = []byte(rvalue.text[1 : len(rvalue.text)-1])
   225  	case label:
   226  		value = big.NewInt(int64(c.labels[rvalue.text])).Bytes()
   227  		value = append(make([]byte, 4-len(value)), value...)
   228  	default:
   229  		return compileErr(rvalue, rvalue.text, "number, string or label")
   230  	}
   231  	if len(value) > 32 {
   232  		return fmt.Errorf("%d: string or number size > 32 bytes", rvalue.lineno+1)
   233  	}
   234  	c.outputOpcode(vm.OpCode(int(vm.PUSH1) - 1 + len(value)))
   235  	c.outputBytes(value)
   236  	return nil
   237  }
   238  
   239  // compileLabel pushes a jumpdest to the binary slice.
   240  func (c *Compiler) compileLabel() {
   241  	c.outputOpcode(vm.JUMPDEST)
   242  }
   243  
   244  func (c *Compiler) outputOpcode(op vm.OpCode) {
   245  	if c.debug {
   246  		fmt.Printf("%d: %v\n", len(c.out), op)
   247  	}
   248  	c.out = append(c.out, byte(op))
   249  }
   250  
   251  // output pushes the value v to the binary stack.
   252  func (c *Compiler) outputBytes(b []byte) {
   253  	if c.debug {
   254  		fmt.Printf("%d: %x\n", len(c.out), b)
   255  	}
   256  	c.out = append(c.out, b...)
   257  }
   258  
   259  // isPush returns whether the string op is either any of
   260  // push(N).
   261  func isPush(op string) bool {
   262  	return strings.EqualFold(op, "PUSH")
   263  }
   264  
   265  // isJump returns whether the string op is jump(i)
   266  func isJump(op string) bool {
   267  	return strings.EqualFold(op, "JUMPI") || strings.EqualFold(op, "JUMP")
   268  }
   269  
   270  // toBinary converts text to a vm.OpCode
   271  func toBinary(text string) vm.OpCode {
   272  	return vm.StringToOp(strings.ToUpper(text))
   273  }
   274  
   275  type compileError struct {
   276  	got  string
   277  	want string
   278  
   279  	lineno int
   280  }
   281  
   282  func (err compileError) Error() string {
   283  	return fmt.Sprintf("%d: syntax error: unexpected %v, expected %v", err.lineno, err.got, err.want)
   284  }
   285  
   286  func compileErr(c token, got, want string) error {
   287  	return compileError{
   288  		got:    got,
   289  		want:   want,
   290  		lineno: c.lineno + 1,
   291  	}
   292  }