Source file src/pkg/cmd/vendor/golang.org/x/tools/go/analysis/passes/asmdecl/asmdecl.go
1
2
3
4
5
6
7 package asmdecl
8
9 import (
10 "bytes"
11 "fmt"
12 "go/ast"
13 "go/build"
14 "go/token"
15 "go/types"
16 "log"
17 "regexp"
18 "strconv"
19 "strings"
20
21 "golang.org/x/tools/go/analysis"
22 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
23 )
24
25 var Analyzer = &analysis.Analyzer{
26 Name: "asmdecl",
27 Doc: "report mismatches between assembly files and Go declarations",
28 Run: run,
29 }
30
31
32
33 type asmKind int
34
35
36 const (
37 asmString asmKind = 100 + iota
38 asmSlice
39 asmArray
40 asmInterface
41 asmEmptyInterface
42 asmStruct
43 asmComplex
44 )
45
46
47 type asmArch struct {
48 name string
49 bigEndian bool
50 stack string
51 lr bool
52
53 sizes types.Sizes
54 intSize int
55 ptrSize int
56 maxAlign int
57 }
58
59
60 type asmFunc struct {
61 arch *asmArch
62 size int
63 vars map[string]*asmVar
64 varByOffset map[int]*asmVar
65 }
66
67
68 type asmVar struct {
69 name string
70 kind asmKind
71 typ string
72 off int
73 size int
74 inner []*asmVar
75 }
76
77 var (
78 asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
79 asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
80 asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
81 asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
82 asmArchAmd64p32 = asmArch{name: "amd64p32", bigEndian: false, stack: "SP", lr: false}
83 asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
84 asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
85 asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
86 asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
87 asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true}
88 asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true}
89 asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
90 asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
91
92 arches = []*asmArch{
93 &asmArch386,
94 &asmArchArm,
95 &asmArchArm64,
96 &asmArchAmd64,
97 &asmArchAmd64p32,
98 &asmArchMips,
99 &asmArchMipsLE,
100 &asmArchMips64,
101 &asmArchMips64LE,
102 &asmArchPpc64,
103 &asmArchPpc64LE,
104 &asmArchS390X,
105 &asmArchWasm,
106 }
107 )
108
109 func init() {
110 for _, arch := range arches {
111 arch.sizes = types.SizesFor("gc", arch.name)
112 if arch.sizes == nil {
113
114
115
116
117
118
119 arch.sizes = types.SizesFor("gc", "amd64")
120 log.Printf("unknown architecture %s", arch.name)
121 }
122 arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
123 arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
124 arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
125 }
126 }
127
128 var (
129 re = regexp.MustCompile
130 asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
131 asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
132 asmDATA = re(`\b(DATA|GLOBL)\b`)
133 asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
134 asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
135 asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
136 asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
137 ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
138 )
139
140 func run(pass *analysis.Pass) (interface{}, error) {
141
142 var sfiles []string
143 for _, fname := range pass.OtherFiles {
144 if strings.HasSuffix(fname, ".s") {
145 sfiles = append(sfiles, fname)
146 }
147 }
148 if sfiles == nil {
149 return nil, nil
150 }
151
152
153 knownFunc := make(map[string]map[string]*asmFunc)
154
155 for _, f := range pass.Files {
156 for _, decl := range f.Decls {
157 if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
158 knownFunc[decl.Name.Name] = asmParseDecl(pass, decl)
159 }
160 }
161 }
162
163 Files:
164 for _, fname := range sfiles {
165 content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
166 if err != nil {
167 return nil, err
168 }
169
170
171 var arch string
172 var archDef *asmArch
173 for _, a := range arches {
174 if strings.HasSuffix(fname, "_"+a.name+".s") {
175 arch = a.name
176 archDef = a
177 break
178 }
179 }
180
181 lines := strings.SplitAfter(string(content), "\n")
182 var (
183 fn *asmFunc
184 fnName string
185 localSize, argSize int
186 wroteSP bool
187 noframe bool
188 haveRetArg bool
189 retLine []int
190 )
191
192 flushRet := func() {
193 if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
194 v := fn.vars["ret"]
195 for _, line := range retLine {
196 pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
197 }
198 }
199 retLine = nil
200 }
201 for lineno, line := range lines {
202 lineno++
203
204 badf := func(format string, args ...interface{}) {
205 pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
206 }
207
208 if arch == "" {
209
210 if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
211
212
213
214 var archCandidates []*asmArch
215 for _, fld := range strings.Fields(m[1]) {
216 for _, a := range arches {
217 if a.name == fld {
218 archCandidates = append(archCandidates, a)
219 }
220 }
221 }
222 for _, a := range archCandidates {
223 if a.name == build.Default.GOARCH {
224 archCandidates = []*asmArch{a}
225 break
226 }
227 }
228 if len(archCandidates) > 0 {
229 arch = archCandidates[0].name
230 archDef = archCandidates[0]
231 }
232 }
233 }
234
235
236 if i := strings.Index(line, "//"); i >= 0 {
237 line = line[:i]
238 }
239
240 if m := asmTEXT.FindStringSubmatch(line); m != nil {
241 flushRet()
242 if arch == "" {
243
244
245 for _, a := range arches {
246 if a.name == build.Default.GOARCH {
247 arch = a.name
248 archDef = a
249 break
250 }
251 }
252 if arch == "" {
253 log.Printf("%s: cannot determine architecture for assembly file", fname)
254 continue Files
255 }
256 }
257 fnName = m[2]
258 if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
259
260
261 pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
262 if pkgPath != pass.Pkg.Path() {
263
264 fn = nil
265 fnName = ""
266 continue
267 }
268 }
269 flag := m[3]
270 fn = knownFunc[fnName][arch]
271 if fn != nil {
272 size, _ := strconv.Atoi(m[5])
273 if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
274 badf("wrong argument size %d; expected $...-%d", size, fn.size)
275 }
276 }
277 localSize, _ = strconv.Atoi(m[4])
278 localSize += archDef.intSize
279 if archDef.lr && !strings.Contains(flag, "NOFRAME") {
280
281 localSize += archDef.intSize
282 }
283 argSize, _ = strconv.Atoi(m[5])
284 noframe = strings.Contains(flag, "NOFRAME")
285 if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
286 badf("function %s missing Go declaration", fnName)
287 }
288 wroteSP = false
289 haveRetArg = false
290 continue
291 } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
292
293 flushRet()
294 fn = nil
295 fnName = ""
296 continue
297 }
298
299 if strings.Contains(line, "RET") {
300 retLine = append(retLine, lineno)
301 }
302
303 if fnName == "" {
304 continue
305 }
306
307 if asmDATA.FindStringSubmatch(line) != nil {
308 fn = nil
309 }
310
311 if archDef == nil {
312 continue
313 }
314
315 if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) {
316 wroteSP = true
317 continue
318 }
319
320 if arch == "wasm" && strings.Contains(line, "CallImport") {
321
322 haveRetArg = true
323 }
324
325 for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
326 if m[3] != archDef.stack || wroteSP || noframe {
327 continue
328 }
329 off := 0
330 if m[1] != "" {
331 off, _ = strconv.Atoi(m[2])
332 }
333 if off >= localSize {
334 if fn != nil {
335 v := fn.varByOffset[off-localSize]
336 if v != nil {
337 badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
338 continue
339 }
340 }
341 if off >= localSize+argSize {
342 badf("use of %s points beyond argument frame", m[1])
343 continue
344 }
345 badf("use of %s to access argument frame", m[1])
346 }
347 }
348
349 if fn == nil {
350 continue
351 }
352
353 for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
354 off, _ := strconv.Atoi(m[2])
355 v := fn.varByOffset[off]
356 if v != nil {
357 badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
358 } else {
359 badf("use of unnamed argument %s", m[1])
360 }
361 }
362
363 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
364 name := m[1]
365 off := 0
366 if m[2] != "" {
367 off, _ = strconv.Atoi(m[2])
368 }
369 if name == "ret" || strings.HasPrefix(name, "ret_") {
370 haveRetArg = true
371 }
372 v := fn.vars[name]
373 if v == nil {
374
375 if name == "argframe" && off == 0 {
376 continue
377 }
378 v = fn.varByOffset[off]
379 if v != nil {
380 badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
381 } else {
382 badf("unknown variable %s", name)
383 }
384 continue
385 }
386 asmCheckVar(badf, fn, line, m[0], off, v, archDef)
387 }
388 }
389 flushRet()
390 }
391 return nil, nil
392 }
393
394 func asmKindForType(t types.Type, size int) asmKind {
395 switch t := t.Underlying().(type) {
396 case *types.Basic:
397 switch t.Kind() {
398 case types.String:
399 return asmString
400 case types.Complex64, types.Complex128:
401 return asmComplex
402 }
403 return asmKind(size)
404 case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
405 return asmKind(size)
406 case *types.Struct:
407 return asmStruct
408 case *types.Interface:
409 if t.Empty() {
410 return asmEmptyInterface
411 }
412 return asmInterface
413 case *types.Array:
414 return asmArray
415 case *types.Slice:
416 return asmSlice
417 }
418 panic("unreachable")
419 }
420
421
422
423 type component struct {
424 size int
425 offset int
426 kind asmKind
427 typ string
428 suffix string
429 outer string
430 }
431
432 func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
433 return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
434 }
435
436
437
438 func componentsOfType(arch *asmArch, t types.Type) []component {
439 return appendComponentsRecursive(arch, t, nil, "", 0)
440 }
441
442
443
444
445 func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
446 s := t.String()
447 size := int(arch.sizes.Sizeof(t))
448 kind := asmKindForType(t, size)
449 cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
450
451 switch kind {
452 case 8:
453 if arch.ptrSize == 4 {
454 w1, w2 := "lo", "hi"
455 if arch.bigEndian {
456 w1, w2 = w2, w1
457 }
458 cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
459 cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
460 }
461
462 case asmEmptyInterface:
463 cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
464 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
465
466 case asmInterface:
467 cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
468 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
469
470 case asmSlice:
471 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
472 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
473 cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
474
475 case asmString:
476 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
477 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
478
479 case asmComplex:
480 fsize := size / 2
481 cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
482 cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
483
484 case asmStruct:
485 tu := t.Underlying().(*types.Struct)
486 fields := make([]*types.Var, tu.NumFields())
487 for i := 0; i < tu.NumFields(); i++ {
488 fields[i] = tu.Field(i)
489 }
490 offsets := arch.sizes.Offsetsof(fields)
491 for i, f := range fields {
492 cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
493 }
494
495 case asmArray:
496 tu := t.Underlying().(*types.Array)
497 elem := tu.Elem()
498
499 fields := []*types.Var{
500 types.NewVar(token.NoPos, nil, "fake0", elem),
501 types.NewVar(token.NoPos, nil, "fake1", elem),
502 }
503 offsets := arch.sizes.Offsetsof(fields)
504 elemoff := int(offsets[1])
505 for i := 0; i < int(tu.Len()); i++ {
506 cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff)
507 }
508 }
509
510 return cc
511 }
512
513
514 func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
515 var (
516 arch *asmArch
517 fn *asmFunc
518 offset int
519 )
520
521
522
523
524
525 addParams := func(list []*ast.Field, isret bool) {
526 argnum := 0
527 for _, fld := range list {
528 t := pass.TypesInfo.Types[fld.Type].Type
529
530
531 if t == nil {
532 if ell, ok := fld.Type.(*ast.Ellipsis); ok {
533 t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type)
534 }
535 }
536
537 align := int(arch.sizes.Alignof(t))
538 size := int(arch.sizes.Sizeof(t))
539 offset += -offset & (align - 1)
540 cc := componentsOfType(arch, t)
541
542
543 names := fld.Names
544 if len(names) == 0 {
545
546
547 name := "arg"
548 if isret {
549 name = "ret"
550 }
551 if argnum > 0 {
552 name += strconv.Itoa(argnum)
553 }
554 names = []*ast.Ident{ast.NewIdent(name)}
555 }
556 argnum += len(names)
557
558
559 for _, id := range names {
560 name := id.Name
561 for _, c := range cc {
562 outer := name + c.outer
563 v := asmVar{
564 name: name + c.suffix,
565 kind: c.kind,
566 typ: c.typ,
567 off: offset + c.offset,
568 size: c.size,
569 }
570 if vo := fn.vars[outer]; vo != nil {
571 vo.inner = append(vo.inner, &v)
572 }
573 fn.vars[v.name] = &v
574 for i := 0; i < v.size; i++ {
575 fn.varByOffset[v.off+i] = &v
576 }
577 }
578 offset += size
579 }
580 }
581 }
582
583 m := make(map[string]*asmFunc)
584 for _, arch = range arches {
585 fn = &asmFunc{
586 arch: arch,
587 vars: make(map[string]*asmVar),
588 varByOffset: make(map[int]*asmVar),
589 }
590 offset = 0
591 addParams(decl.Type.Params.List, false)
592 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
593 offset += -offset & (arch.maxAlign - 1)
594 addParams(decl.Type.Results.List, true)
595 }
596 fn.size = offset
597 m[arch.name] = fn
598 }
599
600 return m
601 }
602
603
604 func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
605 m := asmOpcode.FindStringSubmatch(line)
606 if m == nil {
607 if !strings.HasPrefix(strings.TrimSpace(line), "//") {
608 badf("cannot find assembly opcode")
609 }
610 return
611 }
612
613 addr := strings.HasPrefix(expr, "$")
614
615
616
617 var src, dst, kind asmKind
618 op := m[1]
619 switch fn.arch.name + "." + op {
620 case "386.FMOVLP":
621 src, dst = 8, 4
622 case "arm.MOVD":
623 src = 8
624 case "arm.MOVW":
625 src = 4
626 case "arm.MOVH", "arm.MOVHU":
627 src = 2
628 case "arm.MOVB", "arm.MOVBU":
629 src = 1
630
631
632 case "386.LEAL":
633 dst = 4
634 addr = true
635 case "amd64.LEAQ":
636 dst = 8
637 addr = true
638 case "amd64p32.LEAL":
639 dst = 4
640 addr = true
641 default:
642 switch fn.arch.name {
643 case "386", "amd64":
644 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
645
646 src = 8
647 break
648 }
649 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
650
651 src = 4
652 break
653 }
654 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
655
656 src = 4
657 break
658 }
659 if strings.HasSuffix(op, "SD") {
660
661 src = 8
662 break
663 }
664 if strings.HasSuffix(op, "SS") {
665
666 src = 4
667 break
668 }
669 if strings.HasPrefix(op, "SET") {
670
671 src = 1
672 break
673 }
674 switch op[len(op)-1] {
675 case 'B':
676 src = 1
677 case 'W':
678 src = 2
679 case 'L':
680 src = 4
681 case 'D', 'Q':
682 src = 8
683 }
684 case "ppc64", "ppc64le":
685
686 m := ppc64Suff.FindStringSubmatch(op)
687 if m != nil {
688 switch m[1][0] {
689 case 'B':
690 src = 1
691 case 'H':
692 src = 2
693 case 'W':
694 src = 4
695 case 'D':
696 src = 8
697 }
698 }
699 case "mips", "mipsle", "mips64", "mips64le":
700 switch op {
701 case "MOVB", "MOVBU":
702 src = 1
703 case "MOVH", "MOVHU":
704 src = 2
705 case "MOVW", "MOVWU", "MOVF":
706 src = 4
707 case "MOVV", "MOVD":
708 src = 8
709 }
710 case "s390x":
711 switch op {
712 case "MOVB", "MOVBZ":
713 src = 1
714 case "MOVH", "MOVHZ":
715 src = 2
716 case "MOVW", "MOVWZ", "FMOVS":
717 src = 4
718 case "MOVD", "FMOVD":
719 src = 8
720 }
721 }
722 }
723 if dst == 0 {
724 dst = src
725 }
726
727
728
729 if strings.Index(line, expr) > strings.Index(line, ",") {
730 kind = dst
731 } else {
732 kind = src
733 }
734
735 vk := v.kind
736 vs := v.size
737 vt := v.typ
738 switch vk {
739 case asmInterface, asmEmptyInterface, asmString, asmSlice:
740
741 vk = v.inner[0].kind
742 vs = v.inner[0].size
743 vt = v.inner[0].typ
744 }
745 if addr {
746 vk = asmKind(archDef.ptrSize)
747 vs = archDef.ptrSize
748 vt = "address"
749 }
750
751 if off != v.off {
752 var inner bytes.Buffer
753 for i, vi := range v.inner {
754 if len(v.inner) > 1 {
755 fmt.Fprintf(&inner, ",")
756 }
757 fmt.Fprintf(&inner, " ")
758 if i == len(v.inner)-1 {
759 fmt.Fprintf(&inner, "or ")
760 }
761 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
762 }
763 badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
764 return
765 }
766 if kind != 0 && kind != vk {
767 var inner bytes.Buffer
768 if len(v.inner) > 0 {
769 fmt.Fprintf(&inner, " containing")
770 for i, vi := range v.inner {
771 if i > 0 && len(v.inner) > 2 {
772 fmt.Fprintf(&inner, ",")
773 }
774 fmt.Fprintf(&inner, " ")
775 if i > 0 && i == len(v.inner)-1 {
776 fmt.Fprintf(&inner, "and ")
777 }
778 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
779 }
780 }
781 badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
782 }
783 }
784
View as plain text