in go/analysis/passes/asmdecl/asmdecl.go [148:426]
func run(pass *analysis.Pass) (interface{}, error) {
// No work if no assembly files.
var sfiles []string
for _, fname := range pass.OtherFiles {
if strings.HasSuffix(fname, ".s") {
sfiles = append(sfiles, fname)
}
}
if sfiles == nil {
return nil, nil
}
// Gather declarations. knownFunc[name][arch] is func description.
knownFunc := make(map[string]map[string]*asmFunc)
for _, f := range pass.Files {
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
knownFunc[decl.Name.Name] = asmParseDecl(pass, decl)
}
}
}
Files:
for _, fname := range sfiles {
content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
if err != nil {
return nil, err
}
// Determine architecture from file name if possible.
var arch string
var archDef *asmArch
for _, a := range arches {
if strings.HasSuffix(fname, "_"+a.name+".s") {
arch = a.name
archDef = a
break
}
}
lines := strings.SplitAfter(string(content), "\n")
var (
fn *asmFunc
fnName string
abi string
localSize, argSize int
wroteSP bool
noframe bool
haveRetArg bool
retLine []int
)
flushRet := func() {
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
v := fn.vars["ret"]
resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off)
if abi == "ABIInternal" {
resultStr = "result register"
}
for _, line := range retLine {
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
}
}
retLine = nil
}
trimABI := func(fnName string) (string, string) {
m := abiSuff.FindStringSubmatch(fnName)
if m != nil {
return m[1], m[2]
}
return fnName, ""
}
for lineno, line := range lines {
lineno++
badf := func(format string, args ...interface{}) {
pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
}
if arch == "" {
// Determine architecture from +build line if possible.
if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
// There can be multiple architectures in a single +build line,
// so accumulate them all and then prefer the one that
// matches build.Default.GOARCH.
var archCandidates []*asmArch
for _, fld := range strings.Fields(m[1]) {
for _, a := range arches {
if a.name == fld {
archCandidates = append(archCandidates, a)
}
}
}
for _, a := range archCandidates {
if a.name == build.Default.GOARCH {
archCandidates = []*asmArch{a}
break
}
}
if len(archCandidates) > 0 {
arch = archCandidates[0].name
archDef = archCandidates[0]
}
}
}
// Ignore comments and commented-out code.
if i := strings.Index(line, "//"); i >= 0 {
line = line[:i]
}
if m := asmTEXT.FindStringSubmatch(line); m != nil {
flushRet()
if arch == "" {
// Arch not specified by filename or build tags.
// Fall back to build.Default.GOARCH.
for _, a := range arches {
if a.name == build.Default.GOARCH {
arch = a.name
archDef = a
break
}
}
if arch == "" {
log.Printf("%s: cannot determine architecture for assembly file", fname)
continue Files
}
}
fnName = m[2]
if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
// The assembler uses Unicode division slash within
// identifiers to represent the directory separator.
pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
if pkgPath != pass.Pkg.Path() {
// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
fn = nil
fnName = ""
abi = ""
continue
}
}
// Trim off optional ABI selector.
fnName, abi = trimABI(fnName)
flag := m[3]
fn = knownFunc[fnName][arch]
if fn != nil {
size, _ := strconv.Atoi(m[5])
if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
badf("wrong argument size %d; expected $...-%d", size, fn.size)
}
}
localSize, _ = strconv.Atoi(m[4])
localSize += archDef.intSize
if archDef.lr && !strings.Contains(flag, "NOFRAME") {
// Account for caller's saved LR
localSize += archDef.intSize
}
argSize, _ = strconv.Atoi(m[5])
noframe = strings.Contains(flag, "NOFRAME")
if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
badf("function %s missing Go declaration", fnName)
}
wroteSP = false
haveRetArg = false
continue
} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
// function, but not visible from Go (didn't match asmTEXT), so stop checking
flushRet()
fn = nil
fnName = ""
abi = ""
continue
}
if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
// RET f(SB) is a tail call. It is okay to not write the results.
retLine = append(retLine, lineno)
}
if fnName == "" {
continue
}
if asmDATA.FindStringSubmatch(line) != nil {
fn = nil
}
if archDef == nil {
continue
}
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) {
wroteSP = true
continue
}
if arch == "wasm" && strings.Contains(line, "CallImport") {
// CallImport is a call out to magic that can write the result.
haveRetArg = true
}
if abi == "ABIInternal" && !haveRetArg {
for _, reg := range archDef.retRegs {
if strings.Contains(line, reg) {
haveRetArg = true
break
}
}
}
for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
if m[3] != archDef.stack || wroteSP || noframe {
continue
}
off := 0
if m[1] != "" {
off, _ = strconv.Atoi(m[2])
}
if off >= localSize {
if fn != nil {
v := fn.varByOffset[off-localSize]
if v != nil {
badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
continue
}
}
if off >= localSize+argSize {
badf("use of %s points beyond argument frame", m[1])
continue
}
badf("use of %s to access argument frame", m[1])
}
}
if fn == nil {
continue
}
for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
off, _ := strconv.Atoi(m[2])
v := fn.varByOffset[off]
if v != nil {
badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
} else {
badf("use of unnamed argument %s", m[1])
}
}
for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
name := m[1]
off := 0
if m[2] != "" {
off, _ = strconv.Atoi(m[2])
}
if name == "ret" || strings.HasPrefix(name, "ret_") {
haveRetArg = true
}
v := fn.vars[name]
if v == nil {
// Allow argframe+0(FP).
if name == "argframe" && off == 0 {
continue
}
v = fn.varByOffset[off]
if v != nil {
badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
} else {
badf("unknown variable %s", name)
}
continue
}
asmCheckVar(badf, fn, line, m[0], off, v, archDef)
}
}
flushRet()
}
return nil, nil
}