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 }