Home | History | Annotate | Download | only in vet
      1 // Copyright 2013 The Go Authors.  All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Identify mismatches between assembly files and Go func declarations.
      6 
      7 package main
      8 
      9 import (
     10 	"bytes"
     11 	"fmt"
     12 	"go/ast"
     13 	"go/token"
     14 	"regexp"
     15 	"strconv"
     16 	"strings"
     17 )
     18 
     19 // 'kind' is a kind of assembly variable.
     20 // The kinds 1, 2, 4, 8 stand for values of that size.
     21 type asmKind int
     22 
     23 // These special kinds are not valid sizes.
     24 const (
     25 	asmString asmKind = 100 + iota
     26 	asmSlice
     27 	asmInterface
     28 	asmEmptyInterface
     29 )
     30 
     31 // An asmArch describes assembly parameters for an architecture
     32 type asmArch struct {
     33 	name      string
     34 	ptrSize   int
     35 	intSize   int
     36 	maxAlign  int
     37 	bigEndian bool
     38 	stack     string
     39 	lr        bool
     40 }
     41 
     42 // An asmFunc describes the expected variables for a function on a given architecture.
     43 type asmFunc struct {
     44 	arch        *asmArch
     45 	size        int // size of all arguments
     46 	vars        map[string]*asmVar
     47 	varByOffset map[int]*asmVar
     48 }
     49 
     50 // An asmVar describes a single assembly variable.
     51 type asmVar struct {
     52 	name  string
     53 	kind  asmKind
     54 	typ   string
     55 	off   int
     56 	size  int
     57 	inner []*asmVar
     58 }
     59 
     60 var (
     61 	asmArch386      = asmArch{"386", 4, 4, 4, false, "SP", false}
     62 	asmArchArm      = asmArch{"arm", 4, 4, 4, false, "R13", true}
     63 	asmArchArm64    = asmArch{"arm64", 8, 8, 8, false, "RSP", true}
     64 	asmArchAmd64    = asmArch{"amd64", 8, 8, 8, false, "SP", false}
     65 	asmArchAmd64p32 = asmArch{"amd64p32", 4, 4, 8, false, "SP", false}
     66 	asmArchPpc64    = asmArch{"ppc64", 8, 8, 8, true, "R1", true}
     67 	asmArchPpc64LE  = asmArch{"ppc64le", 8, 8, 8, false, "R1", true}
     68 
     69 	arches = []*asmArch{
     70 		&asmArch386,
     71 		&asmArchArm,
     72 		&asmArchArm64,
     73 		&asmArchAmd64,
     74 		&asmArchAmd64p32,
     75 		&asmArchPpc64,
     76 		&asmArchPpc64LE,
     77 	}
     78 )
     79 
     80 var (
     81 	re           = regexp.MustCompile
     82 	asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
     83 	asmTEXT      = re(`\bTEXT\b.*([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
     84 	asmDATA      = re(`\b(DATA|GLOBL)\b`)
     85 	asmNamedFP   = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
     86 	asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
     87 	asmSP        = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
     88 	asmOpcode    = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
     89 	ppc64Suff    = re(`([BHWD])(ZU|Z|U|BR)?$`)
     90 )
     91 
     92 func asmCheck(pkg *Package) {
     93 	if !vet("asmdecl") {
     94 		return
     95 	}
     96 
     97 	// No work if no assembly files.
     98 	if !pkg.hasFileWithSuffix(".s") {
     99 		return
    100 	}
    101 
    102 	// Gather declarations. knownFunc[name][arch] is func description.
    103 	knownFunc := make(map[string]map[string]*asmFunc)
    104 
    105 	for _, f := range pkg.files {
    106 		if f.file != nil {
    107 			for _, decl := range f.file.Decls {
    108 				if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
    109 					knownFunc[decl.Name.Name] = f.asmParseDecl(decl)
    110 				}
    111 			}
    112 		}
    113 	}
    114 
    115 Files:
    116 	for _, f := range pkg.files {
    117 		if !strings.HasSuffix(f.name, ".s") {
    118 			continue
    119 		}
    120 		Println("Checking file", f.name)
    121 
    122 		// Determine architecture from file name if possible.
    123 		var arch string
    124 		var archDef *asmArch
    125 		for _, a := range arches {
    126 			if strings.HasSuffix(f.name, "_"+a.name+".s") {
    127 				arch = a.name
    128 				archDef = a
    129 				break
    130 			}
    131 		}
    132 
    133 		lines := strings.SplitAfter(string(f.content), "\n")
    134 		var (
    135 			fn                 *asmFunc
    136 			fnName             string
    137 			localSize, argSize int
    138 			wroteSP            bool
    139 			haveRetArg         bool
    140 			retLine            []int
    141 		)
    142 
    143 		flushRet := func() {
    144 			if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
    145 				v := fn.vars["ret"]
    146 				for _, line := range retLine {
    147 					f.Badf(token.NoPos, "%s:%d: [%s] %s: RET without writing to %d-byte ret+%d(FP)", f.name, line, arch, fnName, v.size, v.off)
    148 				}
    149 			}
    150 			retLine = nil
    151 		}
    152 		for lineno, line := range lines {
    153 			lineno++
    154 
    155 			badf := func(format string, args ...interface{}) {
    156 				f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, arch, fnName, fmt.Sprintf(format, args...))
    157 			}
    158 
    159 			if arch == "" {
    160 				// Determine architecture from +build line if possible.
    161 				if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
    162 				Fields:
    163 					for _, fld := range strings.Fields(m[1]) {
    164 						for _, a := range arches {
    165 							if a.name == fld {
    166 								arch = a.name
    167 								archDef = a
    168 								break Fields
    169 							}
    170 						}
    171 					}
    172 				}
    173 			}
    174 
    175 			if m := asmTEXT.FindStringSubmatch(line); m != nil {
    176 				flushRet()
    177 				if arch == "" {
    178 					f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
    179 					continue Files
    180 				}
    181 				fnName = m[1]
    182 				fn = knownFunc[m[1]][arch]
    183 				if fn != nil {
    184 					size, _ := strconv.Atoi(m[4])
    185 					if size != fn.size && (m[2] != "7" && !strings.Contains(m[2], "NOSPLIT") || size != 0) {
    186 						badf("wrong argument size %d; expected $...-%d", size, fn.size)
    187 					}
    188 				}
    189 				localSize, _ = strconv.Atoi(m[3])
    190 				localSize += archDef.intSize
    191 				if archDef.lr {
    192 					// Account for caller's saved LR
    193 					localSize += archDef.intSize
    194 				}
    195 				argSize, _ = strconv.Atoi(m[4])
    196 				if fn == nil && !strings.Contains(fnName, "<>") {
    197 					badf("function %s missing Go declaration", fnName)
    198 				}
    199 				wroteSP = false
    200 				haveRetArg = false
    201 				continue
    202 			} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
    203 				// function, but not visible from Go (didn't match asmTEXT), so stop checking
    204 				flushRet()
    205 				fn = nil
    206 				fnName = ""
    207 				continue
    208 			}
    209 
    210 			if strings.Contains(line, "RET") {
    211 				retLine = append(retLine, lineno)
    212 			}
    213 
    214 			if fnName == "" {
    215 				continue
    216 			}
    217 
    218 			if asmDATA.FindStringSubmatch(line) != nil {
    219 				fn = nil
    220 			}
    221 
    222 			if archDef == nil {
    223 				continue
    224 			}
    225 
    226 			if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) {
    227 				wroteSP = true
    228 				continue
    229 			}
    230 
    231 			for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
    232 				if m[3] != archDef.stack || wroteSP {
    233 					continue
    234 				}
    235 				off := 0
    236 				if m[1] != "" {
    237 					off, _ = strconv.Atoi(m[2])
    238 				}
    239 				if off >= localSize {
    240 					if fn != nil {
    241 						v := fn.varByOffset[off-localSize]
    242 						if v != nil {
    243 							badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
    244 							continue
    245 						}
    246 					}
    247 					if off >= localSize+argSize {
    248 						badf("use of %s points beyond argument frame", m[1])
    249 						continue
    250 					}
    251 					badf("use of %s to access argument frame", m[1])
    252 				}
    253 			}
    254 
    255 			if fn == nil {
    256 				continue
    257 			}
    258 
    259 			for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
    260 				off, _ := strconv.Atoi(m[2])
    261 				v := fn.varByOffset[off]
    262 				if v != nil {
    263 					badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
    264 				} else {
    265 					badf("use of unnamed argument %s", m[1])
    266 				}
    267 			}
    268 
    269 			for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
    270 				name := m[1]
    271 				off := 0
    272 				if m[2] != "" {
    273 					off, _ = strconv.Atoi(m[2])
    274 				}
    275 				if name == "ret" || strings.HasPrefix(name, "ret_") {
    276 					haveRetArg = true
    277 				}
    278 				v := fn.vars[name]
    279 				if v == nil {
    280 					// Allow argframe+0(FP).
    281 					if name == "argframe" && off == 0 {
    282 						continue
    283 					}
    284 					v = fn.varByOffset[off]
    285 					if v != nil {
    286 						badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
    287 					} else {
    288 						badf("unknown variable %s", name)
    289 					}
    290 					continue
    291 				}
    292 				asmCheckVar(badf, fn, line, m[0], off, v)
    293 			}
    294 		}
    295 		flushRet()
    296 	}
    297 }
    298 
    299 // asmParseDecl parses a function decl for expected assembly variables.
    300 func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
    301 	var (
    302 		arch   *asmArch
    303 		fn     *asmFunc
    304 		offset int
    305 		failed bool
    306 	)
    307 
    308 	addVar := func(outer string, v asmVar) {
    309 		if vo := fn.vars[outer]; vo != nil {
    310 			vo.inner = append(vo.inner, &v)
    311 		}
    312 		fn.vars[v.name] = &v
    313 		for i := 0; i < v.size; i++ {
    314 			fn.varByOffset[v.off+i] = &v
    315 		}
    316 	}
    317 
    318 	addParams := func(list []*ast.Field) {
    319 		for i, fld := range list {
    320 			// Determine alignment, size, and kind of type in declaration.
    321 			var align, size int
    322 			var kind asmKind
    323 			names := fld.Names
    324 			typ := f.gofmt(fld.Type)
    325 			switch t := fld.Type.(type) {
    326 			default:
    327 				switch typ {
    328 				default:
    329 					f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ)
    330 					failed = true
    331 					return
    332 				case "int8", "uint8", "byte", "bool":
    333 					size = 1
    334 				case "int16", "uint16":
    335 					size = 2
    336 				case "int32", "uint32", "float32":
    337 					size = 4
    338 				case "int64", "uint64", "float64":
    339 					align = arch.maxAlign
    340 					size = 8
    341 				case "int", "uint":
    342 					size = arch.intSize
    343 				case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer":
    344 					size = arch.ptrSize
    345 				case "string", "ErrorString":
    346 					size = arch.ptrSize * 2
    347 					align = arch.ptrSize
    348 					kind = asmString
    349 				}
    350 			case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr:
    351 				size = arch.ptrSize
    352 			case *ast.InterfaceType:
    353 				align = arch.ptrSize
    354 				size = 2 * arch.ptrSize
    355 				if len(t.Methods.List) > 0 {
    356 					kind = asmInterface
    357 				} else {
    358 					kind = asmEmptyInterface
    359 				}
    360 			case *ast.ArrayType:
    361 				if t.Len == nil {
    362 					size = arch.ptrSize + 2*arch.intSize
    363 					align = arch.ptrSize
    364 					kind = asmSlice
    365 					break
    366 				}
    367 				f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
    368 				failed = true
    369 			case *ast.StructType:
    370 				f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
    371 				failed = true
    372 			}
    373 			if align == 0 {
    374 				align = size
    375 			}
    376 			if kind == 0 {
    377 				kind = asmKind(size)
    378 			}
    379 			offset += -offset & (align - 1)
    380 
    381 			// Create variable for each name being declared with this type.
    382 			if len(names) == 0 {
    383 				name := "unnamed"
    384 				if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 {
    385 					// Assume assembly will refer to single unnamed result as r.
    386 					name = "ret"
    387 				}
    388 				names = []*ast.Ident{{Name: name}}
    389 			}
    390 			for _, id := range names {
    391 				name := id.Name
    392 				addVar("", asmVar{
    393 					name: name,
    394 					kind: kind,
    395 					typ:  typ,
    396 					off:  offset,
    397 					size: size,
    398 				})
    399 				switch kind {
    400 				case 8:
    401 					if arch.ptrSize == 4 {
    402 						w1, w2 := "lo", "hi"
    403 						if arch.bigEndian {
    404 							w1, w2 = w2, w1
    405 						}
    406 						addVar(name, asmVar{
    407 							name: name + "_" + w1,
    408 							kind: 4,
    409 							typ:  "half " + typ,
    410 							off:  offset,
    411 							size: 4,
    412 						})
    413 						addVar(name, asmVar{
    414 							name: name + "_" + w2,
    415 							kind: 4,
    416 							typ:  "half " + typ,
    417 							off:  offset + 4,
    418 							size: 4,
    419 						})
    420 					}
    421 
    422 				case asmEmptyInterface:
    423 					addVar(name, asmVar{
    424 						name: name + "_type",
    425 						kind: asmKind(arch.ptrSize),
    426 						typ:  "interface type",
    427 						off:  offset,
    428 						size: arch.ptrSize,
    429 					})
    430 					addVar(name, asmVar{
    431 						name: name + "_data",
    432 						kind: asmKind(arch.ptrSize),
    433 						typ:  "interface data",
    434 						off:  offset + arch.ptrSize,
    435 						size: arch.ptrSize,
    436 					})
    437 
    438 				case asmInterface:
    439 					addVar(name, asmVar{
    440 						name: name + "_itable",
    441 						kind: asmKind(arch.ptrSize),
    442 						typ:  "interface itable",
    443 						off:  offset,
    444 						size: arch.ptrSize,
    445 					})
    446 					addVar(name, asmVar{
    447 						name: name + "_data",
    448 						kind: asmKind(arch.ptrSize),
    449 						typ:  "interface data",
    450 						off:  offset + arch.ptrSize,
    451 						size: arch.ptrSize,
    452 					})
    453 
    454 				case asmSlice:
    455 					addVar(name, asmVar{
    456 						name: name + "_base",
    457 						kind: asmKind(arch.ptrSize),
    458 						typ:  "slice base",
    459 						off:  offset,
    460 						size: arch.ptrSize,
    461 					})
    462 					addVar(name, asmVar{
    463 						name: name + "_len",
    464 						kind: asmKind(arch.intSize),
    465 						typ:  "slice len",
    466 						off:  offset + arch.ptrSize,
    467 						size: arch.intSize,
    468 					})
    469 					addVar(name, asmVar{
    470 						name: name + "_cap",
    471 						kind: asmKind(arch.intSize),
    472 						typ:  "slice cap",
    473 						off:  offset + arch.ptrSize + arch.intSize,
    474 						size: arch.intSize,
    475 					})
    476 
    477 				case asmString:
    478 					addVar(name, asmVar{
    479 						name: name + "_base",
    480 						kind: asmKind(arch.ptrSize),
    481 						typ:  "string base",
    482 						off:  offset,
    483 						size: arch.ptrSize,
    484 					})
    485 					addVar(name, asmVar{
    486 						name: name + "_len",
    487 						kind: asmKind(arch.intSize),
    488 						typ:  "string len",
    489 						off:  offset + arch.ptrSize,
    490 						size: arch.intSize,
    491 					})
    492 				}
    493 				offset += size
    494 			}
    495 		}
    496 	}
    497 
    498 	m := make(map[string]*asmFunc)
    499 	for _, arch = range arches {
    500 		fn = &asmFunc{
    501 			arch:        arch,
    502 			vars:        make(map[string]*asmVar),
    503 			varByOffset: make(map[int]*asmVar),
    504 		}
    505 		offset = 0
    506 		addParams(decl.Type.Params.List)
    507 		if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
    508 			offset += -offset & (arch.maxAlign - 1)
    509 			addParams(decl.Type.Results.List)
    510 		}
    511 		fn.size = offset
    512 		m[arch.name] = fn
    513 	}
    514 
    515 	if failed {
    516 		return nil
    517 	}
    518 	return m
    519 }
    520 
    521 // asmCheckVar checks a single variable reference.
    522 func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
    523 	m := asmOpcode.FindStringSubmatch(line)
    524 	if m == nil {
    525 		if !strings.HasPrefix(strings.TrimSpace(line), "//") {
    526 			badf("cannot find assembly opcode")
    527 		}
    528 		return
    529 	}
    530 
    531 	// Determine operand sizes from instruction.
    532 	// Typically the suffix suffices, but there are exceptions.
    533 	var src, dst, kind asmKind
    534 	op := m[1]
    535 	switch fn.arch.name + "." + op {
    536 	case "386.FMOVLP":
    537 		src, dst = 8, 4
    538 	case "arm.MOVD":
    539 		src = 8
    540 	case "arm.MOVW":
    541 		src = 4
    542 	case "arm.MOVH", "arm.MOVHU":
    543 		src = 2
    544 	case "arm.MOVB", "arm.MOVBU":
    545 		src = 1
    546 	// LEA* opcodes don't really read the second arg.
    547 	// They just take the address of it.
    548 	case "386.LEAL":
    549 		dst = 4
    550 	case "amd64.LEAQ":
    551 		dst = 8
    552 	case "amd64p32.LEAL":
    553 		dst = 4
    554 	default:
    555 		switch fn.arch.name {
    556 		case "386", "amd64":
    557 			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
    558 				// FMOVDP, FXCHD, etc
    559 				src = 8
    560 				break
    561 			}
    562 			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
    563 				// FMOVFP, FXCHF, etc
    564 				src = 4
    565 				break
    566 			}
    567 			if strings.HasSuffix(op, "SD") {
    568 				// MOVSD, SQRTSD, etc
    569 				src = 8
    570 				break
    571 			}
    572 			if strings.HasSuffix(op, "SS") {
    573 				// MOVSS, SQRTSS, etc
    574 				src = 4
    575 				break
    576 			}
    577 			if strings.HasPrefix(op, "SET") {
    578 				// SETEQ, etc
    579 				src = 1
    580 				break
    581 			}
    582 			switch op[len(op)-1] {
    583 			case 'B':
    584 				src = 1
    585 			case 'W':
    586 				src = 2
    587 			case 'L':
    588 				src = 4
    589 			case 'D', 'Q':
    590 				src = 8
    591 			}
    592 		case "ppc64", "ppc64le":
    593 			// Strip standard suffixes to reveal size letter.
    594 			m := ppc64Suff.FindStringSubmatch(op)
    595 			if m != nil {
    596 				switch m[1][0] {
    597 				case 'B':
    598 					src = 1
    599 				case 'H':
    600 					src = 2
    601 				case 'W':
    602 					src = 4
    603 				case 'D':
    604 					src = 8
    605 				}
    606 			}
    607 		}
    608 	}
    609 	if dst == 0 {
    610 		dst = src
    611 	}
    612 
    613 	// Determine whether the match we're holding
    614 	// is the first or second argument.
    615 	if strings.Index(line, expr) > strings.Index(line, ",") {
    616 		kind = dst
    617 	} else {
    618 		kind = src
    619 	}
    620 
    621 	vk := v.kind
    622 	vt := v.typ
    623 	switch vk {
    624 	case asmInterface, asmEmptyInterface, asmString, asmSlice:
    625 		// allow reference to first word (pointer)
    626 		vk = v.inner[0].kind
    627 		vt = v.inner[0].typ
    628 	}
    629 
    630 	if off != v.off {
    631 		var inner bytes.Buffer
    632 		for i, vi := range v.inner {
    633 			if len(v.inner) > 1 {
    634 				fmt.Fprintf(&inner, ",")
    635 			}
    636 			fmt.Fprintf(&inner, " ")
    637 			if i == len(v.inner)-1 {
    638 				fmt.Fprintf(&inner, "or ")
    639 			}
    640 			fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
    641 		}
    642 		badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
    643 		return
    644 	}
    645 	if kind != 0 && kind != vk {
    646 		var inner bytes.Buffer
    647 		if len(v.inner) > 0 {
    648 			fmt.Fprintf(&inner, " containing")
    649 			for i, vi := range v.inner {
    650 				if i > 0 && len(v.inner) > 2 {
    651 					fmt.Fprintf(&inner, ",")
    652 				}
    653 				fmt.Fprintf(&inner, " ")
    654 				if i > 0 && i == len(v.inner)-1 {
    655 					fmt.Fprintf(&inner, "and ")
    656 				}
    657 				fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
    658 			}
    659 		}
    660 		badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String())
    661 	}
    662 }
    663