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