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 }