github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/protocol/vm/assemble.go (about)

     1  package vm
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"math"
     9  	"strconv"
    10  	"strings"
    11  	"unicode"
    12  
    13  	"github.com/bytom/bytom/errors"
    14  )
    15  
    16  // Assemble converts a string like "2 3 ADD 5 NUMEQUAL" into 0x525393559c.
    17  // The input should not include PUSHDATA (or OP_<num>) ops; those will
    18  // be inferred.
    19  // Input may include jump-target labels of the form $foo, which can
    20  // then be used as JUMP:$foo or JUMPIF:$foo.
    21  func Assemble(s string) (res []byte, err error) {
    22  	// maps labels to the location each refers to
    23  	locations := make(map[string]uint32)
    24  
    25  	// maps unresolved uses of labels to the locations that need to be filled in
    26  	unresolved := make(map[string][]int)
    27  
    28  	handleJump := func(addrStr string, opcode Op) error {
    29  		res = append(res, byte(opcode))
    30  		l := len(res)
    31  
    32  		var fourBytes [4]byte
    33  		res = append(res, fourBytes[:]...)
    34  
    35  		if strings.HasPrefix(addrStr, "$") {
    36  			unresolved[addrStr] = append(unresolved[addrStr], l)
    37  			return nil
    38  		}
    39  
    40  		address, err := strconv.ParseUint(addrStr, 10, 32)
    41  		if err != nil {
    42  			return err
    43  		}
    44  		binary.LittleEndian.PutUint32(res[l:], uint32(address))
    45  		return nil
    46  	}
    47  
    48  	scanner := bufio.NewScanner(strings.NewReader(s))
    49  	scanner.Split(split)
    50  	for scanner.Scan() {
    51  		token := scanner.Text()
    52  		if info, ok := opsByName[token]; ok {
    53  			if strings.HasPrefix(token, "PUSHDATA") || strings.HasPrefix(token, "JUMP") {
    54  				return nil, errors.Wrap(ErrToken, token)
    55  			}
    56  			res = append(res, byte(info.op))
    57  		} else if strings.HasPrefix(token, "JUMP:") {
    58  			// TODO (Dan): add IF/ELSE/ENDIF and BEGIN/WHILE/REPEAT
    59  			err = handleJump(strings.TrimPrefix(token, "JUMP:"), OP_JUMP)
    60  			if err != nil {
    61  				return nil, err
    62  			}
    63  		} else if strings.HasPrefix(token, "JUMPIF:") {
    64  			err = handleJump(strings.TrimPrefix(token, "JUMPIF:"), OP_JUMPIF)
    65  			if err != nil {
    66  				return nil, err
    67  			}
    68  		} else if strings.HasPrefix(token, "$") {
    69  			if _, seen := locations[token]; seen {
    70  				return nil, fmt.Errorf("label %s redefined", token)
    71  			}
    72  			if len(res) > math.MaxInt32 {
    73  				return nil, fmt.Errorf("program too long")
    74  			}
    75  			locations[token] = uint32(len(res))
    76  		} else if strings.HasPrefix(token, "0x") {
    77  			bytes, err := hex.DecodeString(strings.TrimPrefix(token, "0x"))
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  			res = append(res, PushdataBytes(bytes)...)
    82  		} else if len(token) >= 2 && token[0] == '\'' && token[len(token)-1] == '\'' {
    83  			bytes := make([]byte, 0, len(token)-2)
    84  			var b int
    85  			for i := 1; i < len(token)-1; i++ {
    86  				if token[i] == '\\' {
    87  					i++
    88  				}
    89  				bytes = append(bytes, token[i])
    90  				b++
    91  			}
    92  			res = append(res, PushdataBytes(bytes)...)
    93  		} else if num, err := strconv.ParseInt(token, 10, 64); err == nil {
    94  			res = append(res, PushdataInt64(num)...)
    95  		} else {
    96  			return nil, errors.Wrap(ErrToken, token)
    97  		}
    98  	}
    99  	err = scanner.Err()
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	for label, uses := range unresolved {
   105  		location, ok := locations[label]
   106  		if !ok {
   107  			return nil, fmt.Errorf("undefined label %s", label)
   108  		}
   109  		for _, use := range uses {
   110  			binary.LittleEndian.PutUint32(res[use:], location)
   111  		}
   112  	}
   113  
   114  	return res, nil
   115  }
   116  
   117  func Disassemble(prog []byte) (string, error) {
   118  	var (
   119  		insts []Instruction
   120  
   121  		// maps program locations (used as jump targets) to a label for each
   122  		labels = make(map[uint32]string)
   123  	)
   124  
   125  	// first pass: look for jumps
   126  	for i := uint32(0); i < uint32(len(prog)); {
   127  		inst, err := ParseOp(prog, i)
   128  		if err != nil {
   129  			return "", err
   130  		}
   131  		switch inst.Op {
   132  		case OP_JUMP, OP_JUMPIF:
   133  			addr := binary.LittleEndian.Uint32(inst.Data)
   134  			if _, ok := labels[addr]; !ok {
   135  				labelNum := len(labels)
   136  				label := words[labelNum%len(words)]
   137  				if labelNum >= len(words) {
   138  					label += fmt.Sprintf("%d", labelNum/len(words)+1)
   139  				}
   140  				labels[addr] = label
   141  			}
   142  		}
   143  		insts = append(insts, inst)
   144  		i += inst.Len
   145  	}
   146  
   147  	var (
   148  		loc  uint32
   149  		strs []string
   150  	)
   151  
   152  	for _, inst := range insts {
   153  		if label, ok := labels[loc]; ok {
   154  			strs = append(strs, "$"+label)
   155  		}
   156  
   157  		var str string
   158  		switch inst.Op {
   159  		case OP_JUMP, OP_JUMPIF:
   160  			addr := binary.LittleEndian.Uint32(inst.Data)
   161  			str = fmt.Sprintf("%s:$%s", inst.Op.String(), labels[addr])
   162  		default:
   163  			if len(inst.Data) > 0 {
   164  				str = fmt.Sprintf("0x%x", inst.Data)
   165  			} else {
   166  				str = inst.Op.String()
   167  			}
   168  		}
   169  		strs = append(strs, str)
   170  
   171  		loc += inst.Len
   172  	}
   173  
   174  	if label, ok := labels[loc]; ok {
   175  		strs = append(strs, "$"+label)
   176  	}
   177  
   178  	return strings.Join(strs, " "), nil
   179  }
   180  
   181  // split is a bufio.SplitFunc for scanning the input to Compile.
   182  // It starts like bufio.ScanWords but adjusts the return value to
   183  // account for quoted strings.
   184  func split(inp []byte, atEOF bool) (advance int, token []byte, err error) {
   185  	advance, token, err = bufio.ScanWords(inp, atEOF)
   186  	if err != nil {
   187  		return
   188  	}
   189  	if len(token) > 1 && token[0] != '\'' {
   190  		return
   191  	}
   192  	var start int
   193  	for ; start < len(inp); start++ {
   194  		if !unicode.IsSpace(rune(inp[start])) {
   195  			break
   196  		}
   197  	}
   198  	if start == len(inp) || inp[start] != '\'' {
   199  		return
   200  	}
   201  	var escape bool
   202  	for i := start + 1; i < len(inp); i++ {
   203  		if escape {
   204  			escape = false
   205  		} else {
   206  			switch inp[i] {
   207  			case '\'':
   208  				advance = i + 1
   209  				token = inp[start:advance]
   210  				return
   211  			case '\\':
   212  				escape = true
   213  			}
   214  		}
   215  	}
   216  	// Reached the end of the input with no closing quote.
   217  	if atEOF {
   218  		return 0, nil, ErrToken
   219  	}
   220  	return 0, nil, nil
   221  }
   222  
   223  var words = []string{
   224  	"alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel",
   225  	"india", "juliet", "kilo", "lima", "mike", "november", "oscar", "papa",
   226  	"quebec", "romeo", "sierra", "tango", "uniform", "victor", "whisky", "xray",
   227  	"yankee", "zulu",
   228  }