From 1be7b45b4c8359a408756f1a626897c173a5be94 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 27 Sep 2018 13:10:15 -0400 Subject: [PATCH] go/analysis/passes/vet: fork cmd/vet@31d19c0 This change creates a fork of vet from the standard distribution. It was created by this script: $ mkdir go/analysis/passes/vet/ $ cd go/analysis/passes/vet/ $ (cd $GOROOT/src/cmd/vet >/dev/null && git co 31d19c0 && tar cf - .) | tar xf - $ rm -fr all # We'll deal with cmd/vet/all later. $ rm -fr internal/cfg # Published as golang.org/x/tools/go/cfg. $ sed -i -e '1s?^?// +build ignore\n\n?' *.go All the Go files have been tagged "ignore" for now. A series of follow-up changes will convert each vet check into an instance of the new go/analysis API's Analyzer. At some point soon, cmd/vet in the standard distribution will use a vendored copy of this code. Until then we will periodically integrate any changes made to cmd/vet to this fork. The current version of cmd/vet will be recorded in the REVISION file. Change-Id: I0c63eeb17cc612b3f013679595dcbc71a90950f7 Reviewed-on: https://go-review.googlesource.com/138137 Reviewed-by: Michael Matloob --- go/analysis/passes/vet/README | 33 + go/analysis/passes/vet/REVISION | 1 + go/analysis/passes/vet/asmdecl.go | 736 ++++++ go/analysis/passes/vet/assign.go | 54 + go/analysis/passes/vet/atomic.go | 73 + go/analysis/passes/vet/bool.go | 199 ++ go/analysis/passes/vet/buildtag.go | 128 + go/analysis/passes/vet/cgo.go | 143 ++ go/analysis/passes/vet/composite.go | 88 + go/analysis/passes/vet/copylock.go | 268 +++ go/analysis/passes/vet/dead.go | 110 + go/analysis/passes/vet/deadcode.go | 300 +++ go/analysis/passes/vet/doc.go | 221 ++ go/analysis/passes/vet/httpresponse.go | 139 ++ .../vet/internal/whitelist/whitelist.go | 28 + go/analysis/passes/vet/lostcancel.go | 324 +++ go/analysis/passes/vet/main.go | 753 ++++++ go/analysis/passes/vet/method.go | 181 ++ go/analysis/passes/vet/nilfunc.go | 69 + go/analysis/passes/vet/print.go | 1072 +++++++++ go/analysis/passes/vet/rangeloop.go | 107 + go/analysis/passes/vet/shadow.go | 245 ++ go/analysis/passes/vet/shift.go | 100 + go/analysis/passes/vet/structtag.go | 243 ++ go/analysis/passes/vet/testdata/asm/asm.go | 48 + go/analysis/passes/vet/testdata/asm/asm1.s | 315 +++ go/analysis/passes/vet/testdata/asm/asm2.s | 257 ++ go/analysis/passes/vet/testdata/asm/asm3.s | 192 ++ go/analysis/passes/vet/testdata/asm/asm4.s | 26 + go/analysis/passes/vet/testdata/asm/asm5.s | 193 ++ go/analysis/passes/vet/testdata/asm/asm6.s | 193 ++ go/analysis/passes/vet/testdata/asm/asm7.s | 193 ++ go/analysis/passes/vet/testdata/asm8.s | 165 ++ go/analysis/passes/vet/testdata/assign.go | 31 + go/analysis/passes/vet/testdata/atomic.go | 62 + go/analysis/passes/vet/testdata/bool.go | 131 + .../passes/vet/testdata/buildtag/buildtag.go | 18 + .../vet/testdata/buildtag/buildtag_bad.go | 15 + go/analysis/passes/vet/testdata/cgo/cgo.go | 59 + go/analysis/passes/vet/testdata/cgo/cgo2.go | 12 + go/analysis/passes/vet/testdata/cgo/cgo3.go | 13 + go/analysis/passes/vet/testdata/cgo/cgo4.go | 15 + go/analysis/passes/vet/testdata/composite.go | 120 + go/analysis/passes/vet/testdata/copylock.go | 188 ++ .../passes/vet/testdata/copylock_func.go | 136 ++ .../passes/vet/testdata/copylock_range.go | 67 + go/analysis/passes/vet/testdata/deadcode.go | 2134 +++++++++++++++++ .../passes/vet/testdata/divergent/buf.go | 17 + .../passes/vet/testdata/divergent/buf_test.go | 35 + .../passes/vet/testdata/httpresponse.go | 85 + .../vet/testdata/incomplete/examples_test.go | 33 + go/analysis/passes/vet/testdata/lostcancel.go | 155 ++ go/analysis/passes/vet/testdata/method.go | 22 + go/analysis/passes/vet/testdata/nilfunc.go | 35 + go/analysis/passes/vet/testdata/print.go | 647 +++++ go/analysis/passes/vet/testdata/rangeloop.go | 90 + go/analysis/passes/vet/testdata/shadow.go | 91 + go/analysis/passes/vet/testdata/shift.go | 162 ++ go/analysis/passes/vet/testdata/structtag.go | 113 + .../passes/vet/testdata/tagtest/file1.go | 10 + .../passes/vet/testdata/tagtest/file2.go | 10 + .../passes/vet/testdata/testingpkg/tests.go | 1 + .../vet/testdata/testingpkg/tests_test.go | 74 + go/analysis/passes/vet/testdata/unsafeptr.go | 63 + go/analysis/passes/vet/testdata/unused.go | 29 + go/analysis/passes/vet/tests.go | 189 ++ go/analysis/passes/vet/types.go | 335 +++ go/analysis/passes/vet/unsafeptr.go | 99 + go/analysis/passes/vet/unused.go | 95 + go/analysis/passes/vet/vet_test.go | 441 ++++ 70 files changed, 13029 insertions(+) create mode 100644 go/analysis/passes/vet/README create mode 100644 go/analysis/passes/vet/REVISION create mode 100644 go/analysis/passes/vet/asmdecl.go create mode 100644 go/analysis/passes/vet/assign.go create mode 100644 go/analysis/passes/vet/atomic.go create mode 100644 go/analysis/passes/vet/bool.go create mode 100644 go/analysis/passes/vet/buildtag.go create mode 100644 go/analysis/passes/vet/cgo.go create mode 100644 go/analysis/passes/vet/composite.go create mode 100644 go/analysis/passes/vet/copylock.go create mode 100644 go/analysis/passes/vet/dead.go create mode 100644 go/analysis/passes/vet/deadcode.go create mode 100644 go/analysis/passes/vet/doc.go create mode 100644 go/analysis/passes/vet/httpresponse.go create mode 100644 go/analysis/passes/vet/internal/whitelist/whitelist.go create mode 100644 go/analysis/passes/vet/lostcancel.go create mode 100644 go/analysis/passes/vet/main.go create mode 100644 go/analysis/passes/vet/method.go create mode 100644 go/analysis/passes/vet/nilfunc.go create mode 100644 go/analysis/passes/vet/print.go create mode 100644 go/analysis/passes/vet/rangeloop.go create mode 100644 go/analysis/passes/vet/shadow.go create mode 100644 go/analysis/passes/vet/shift.go create mode 100644 go/analysis/passes/vet/structtag.go create mode 100644 go/analysis/passes/vet/testdata/asm/asm.go create mode 100644 go/analysis/passes/vet/testdata/asm/asm1.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm2.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm3.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm4.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm5.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm6.s create mode 100644 go/analysis/passes/vet/testdata/asm/asm7.s create mode 100644 go/analysis/passes/vet/testdata/asm8.s create mode 100644 go/analysis/passes/vet/testdata/assign.go create mode 100644 go/analysis/passes/vet/testdata/atomic.go create mode 100644 go/analysis/passes/vet/testdata/bool.go create mode 100644 go/analysis/passes/vet/testdata/buildtag/buildtag.go create mode 100644 go/analysis/passes/vet/testdata/buildtag/buildtag_bad.go create mode 100644 go/analysis/passes/vet/testdata/cgo/cgo.go create mode 100644 go/analysis/passes/vet/testdata/cgo/cgo2.go create mode 100644 go/analysis/passes/vet/testdata/cgo/cgo3.go create mode 100644 go/analysis/passes/vet/testdata/cgo/cgo4.go create mode 100644 go/analysis/passes/vet/testdata/composite.go create mode 100644 go/analysis/passes/vet/testdata/copylock.go create mode 100644 go/analysis/passes/vet/testdata/copylock_func.go create mode 100644 go/analysis/passes/vet/testdata/copylock_range.go create mode 100644 go/analysis/passes/vet/testdata/deadcode.go create mode 100644 go/analysis/passes/vet/testdata/divergent/buf.go create mode 100644 go/analysis/passes/vet/testdata/divergent/buf_test.go create mode 100644 go/analysis/passes/vet/testdata/httpresponse.go create mode 100644 go/analysis/passes/vet/testdata/incomplete/examples_test.go create mode 100644 go/analysis/passes/vet/testdata/lostcancel.go create mode 100644 go/analysis/passes/vet/testdata/method.go create mode 100644 go/analysis/passes/vet/testdata/nilfunc.go create mode 100644 go/analysis/passes/vet/testdata/print.go create mode 100644 go/analysis/passes/vet/testdata/rangeloop.go create mode 100644 go/analysis/passes/vet/testdata/shadow.go create mode 100644 go/analysis/passes/vet/testdata/shift.go create mode 100644 go/analysis/passes/vet/testdata/structtag.go create mode 100644 go/analysis/passes/vet/testdata/tagtest/file1.go create mode 100644 go/analysis/passes/vet/testdata/tagtest/file2.go create mode 100644 go/analysis/passes/vet/testdata/testingpkg/tests.go create mode 100644 go/analysis/passes/vet/testdata/testingpkg/tests_test.go create mode 100644 go/analysis/passes/vet/testdata/unsafeptr.go create mode 100644 go/analysis/passes/vet/testdata/unused.go create mode 100644 go/analysis/passes/vet/tests.go create mode 100644 go/analysis/passes/vet/types.go create mode 100644 go/analysis/passes/vet/unsafeptr.go create mode 100644 go/analysis/passes/vet/unused.go create mode 100644 go/analysis/passes/vet/vet_test.go diff --git a/go/analysis/passes/vet/README b/go/analysis/passes/vet/README new file mode 100644 index 00000000..5ab75494 --- /dev/null +++ b/go/analysis/passes/vet/README @@ -0,0 +1,33 @@ +Vet is a tool that checks correctness of Go programs. It runs a suite of tests, +each tailored to check for a particular class of errors. Examples include incorrect +Printf format verbs and malformed build tags. + +Over time many checks have been added to vet's suite, but many more have been +rejected as not appropriate for the tool. The criteria applied when selecting which +checks to add are: + +Correctness: + +Vet's checks are about correctness, not style. A vet check must identify real or +potential bugs that could cause incorrect compilation or execution. A check that +only identifies stylistic points or alternative correct approaches to a situation +is not acceptable. + +Frequency: + +Vet is run every day by many programmers, often as part of every compilation or +submission. The cost in execution time is considerable, especially in aggregate, +so checks must be likely enough to find real problems that they are worth the +overhead of the added check. A new check that finds only a handful of problems +across all existing programs, even if the problem is significant, is not worth +adding to the suite everyone runs daily. + +Precision: + +Most of vet's checks are heuristic and can generate both false positives (flagging +correct programs) and false negatives (not flagging incorrect ones). The rate of +both these failures must be very small. A check that is too noisy will be ignored +by the programmer overwhelmed by the output; a check that misses too many of the +cases it's looking for will give a false sense of security. Neither is acceptable. +A vet check must be accurate enough that everything it reports is worth examining, +and complete enough to encourage real confidence. diff --git a/go/analysis/passes/vet/REVISION b/go/analysis/passes/vet/REVISION new file mode 100644 index 00000000..dbd98e1c --- /dev/null +++ b/go/analysis/passes/vet/REVISION @@ -0,0 +1 @@ +cmd/vet@31d19c0 \ No newline at end of file diff --git a/go/analysis/passes/vet/asmdecl.go b/go/analysis/passes/vet/asmdecl.go new file mode 100644 index 00000000..1025d791 --- /dev/null +++ b/go/analysis/passes/vet/asmdecl.go @@ -0,0 +1,736 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Identify mismatches between assembly files and Go func declarations. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/token" + "go/types" + "regexp" + "strconv" + "strings" +) + +// 'kind' is a kind of assembly variable. +// The kinds 1, 2, 4, 8 stand for values of that size. +type asmKind int + +// These special kinds are not valid sizes. +const ( + asmString asmKind = 100 + iota + asmSlice + asmArray + asmInterface + asmEmptyInterface + asmStruct + asmComplex +) + +// An asmArch describes assembly parameters for an architecture +type asmArch struct { + name string + bigEndian bool + stack string + lr bool + // calculated during initialization + sizes types.Sizes + intSize int + ptrSize int + maxAlign int +} + +// An asmFunc describes the expected variables for a function on a given architecture. +type asmFunc struct { + arch *asmArch + size int // size of all arguments + vars map[string]*asmVar + varByOffset map[int]*asmVar +} + +// An asmVar describes a single assembly variable. +type asmVar struct { + name string + kind asmKind + typ string + off int + size int + inner []*asmVar +} + +var ( + asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false} + asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true} + asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true} + asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false} + asmArchAmd64p32 = asmArch{name: "amd64p32", bigEndian: false, stack: "SP", lr: false} + asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true} + asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true} + asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true} + asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true} + asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true} + asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true} + asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true} + asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false} + + arches = []*asmArch{ + &asmArch386, + &asmArchArm, + &asmArchArm64, + &asmArchAmd64, + &asmArchAmd64p32, + &asmArchMips, + &asmArchMipsLE, + &asmArchMips64, + &asmArchMips64LE, + &asmArchPpc64, + &asmArchPpc64LE, + &asmArchS390X, + &asmArchWasm, + } +) + +func init() { + for _, arch := range arches { + arch.sizes = types.SizesFor("gc", arch.name) + if arch.sizes == nil { + panic("missing SizesFor for gc/" + arch.name) + } + arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int])) + arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer])) + arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64])) + } + + registerPkgCheck("asmdecl", asmCheck) +} + +var ( + re = regexp.MustCompile + asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) + asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) + asmDATA = re(`\b(DATA|GLOBL)\b`) + asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) + asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) + asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) + asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) + ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) +) + +func asmCheck(pkg *Package) { + if vcfg.VetxOnly { + return + } + + // No work if no assembly files. + if !pkg.hasFileWithSuffix(".s") { + return + } + + // Gather declarations. knownFunc[name][arch] is func description. + knownFunc := make(map[string]map[string]*asmFunc) + + for _, f := range pkg.files { + if f.file != nil { + for _, decl := range f.file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil { + knownFunc[decl.Name.Name] = f.asmParseDecl(decl) + } + } + } + } + +Files: + for _, f := range pkg.files { + if !strings.HasSuffix(f.name, ".s") { + continue + } + Println("Checking file", f.name) + + // Determine architecture from file name if possible. + var arch string + var archDef *asmArch + for _, a := range arches { + if strings.HasSuffix(f.name, "_"+a.name+".s") { + arch = a.name + archDef = a + break + } + } + + lines := strings.SplitAfter(string(f.content), "\n") + var ( + fn *asmFunc + fnName string + localSize, argSize int + wroteSP bool + haveRetArg bool + retLine []int + ) + + flushRet := func() { + if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { + v := fn.vars["ret"] + for _, line := range retLine { + 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) + } + } + retLine = nil + } + for lineno, line := range lines { + lineno++ + + badf := func(format string, args ...interface{}) { + f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, 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] + } + } + } + + 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 == "" { + f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) + continue Files + } + } + fnName = m[2] + if pkgName := strings.TrimSpace(m[1]); pkgName != "" { + pathParts := strings.Split(pkgName, "∕") + pkgName = pathParts[len(pathParts)-1] + if pkgName != f.pkg.path { + f.Warnf(token.NoPos, "%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", f.name, lineno, arch, fnName, pkgName) + fn = nil + fnName = "" + continue + } + } + 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]) + if fn == nil && !strings.Contains(fnName, "<>") { + 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 = "" + continue + } + + if strings.Contains(line, "RET") { + 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) { + wroteSP = true + continue + } + + for _, m := range asmSP.FindAllStringSubmatch(line, -1) { + if m[3] != archDef.stack || wroteSP { + 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) + } + } + flushRet() + } +} + +func asmKindForType(t types.Type, size int) asmKind { + switch t := t.Underlying().(type) { + case *types.Basic: + switch t.Kind() { + case types.String: + return asmString + case types.Complex64, types.Complex128: + return asmComplex + } + return asmKind(size) + case *types.Pointer, *types.Chan, *types.Map, *types.Signature: + return asmKind(size) + case *types.Struct: + return asmStruct + case *types.Interface: + if t.Empty() { + return asmEmptyInterface + } + return asmInterface + case *types.Array: + return asmArray + case *types.Slice: + return asmSlice + } + panic("unreachable") +} + +// A component is an assembly-addressable component of a composite type, +// or a composite type itself. +type component struct { + size int + offset int + kind asmKind + typ string + suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine. + outer string // The suffix for immediately containing composite type. +} + +func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component { + return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer} +} + +// componentsOfType generates a list of components of type t. +// For example, given string, the components are the string itself, the base, and the length. +func componentsOfType(arch *asmArch, t types.Type) []component { + return appendComponentsRecursive(arch, t, nil, "", 0) +} + +// appendComponentsRecursive implements componentsOfType. +// Recursion is required to correct handle structs and arrays, +// which can contain arbitrary other types. +func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component { + s := t.String() + size := int(arch.sizes.Sizeof(t)) + kind := asmKindForType(t, size) + cc = append(cc, newComponent(suffix, kind, s, off, size, suffix)) + + switch kind { + case 8: + if arch.ptrSize == 4 { + w1, w2 := "lo", "hi" + if arch.bigEndian { + w1, w2 = w2, w1 + } + cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix)) + cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix)) + } + + case asmEmptyInterface: + cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix)) + cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) + + case asmInterface: + cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix)) + cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) + + case asmSlice: + cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix)) + cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix)) + cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix)) + + case asmString: + cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix)) + cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix)) + + case asmComplex: + fsize := size / 2 + cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix)) + cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix)) + + case asmStruct: + tu := t.Underlying().(*types.Struct) + fields := make([]*types.Var, tu.NumFields()) + for i := 0; i < tu.NumFields(); i++ { + fields[i] = tu.Field(i) + } + offsets := arch.sizes.Offsetsof(fields) + for i, f := range fields { + cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i])) + } + + case asmArray: + tu := t.Underlying().(*types.Array) + elem := tu.Elem() + // Calculate offset of each element array. + fields := []*types.Var{ + types.NewVar(token.NoPos, nil, "fake0", elem), + types.NewVar(token.NoPos, nil, "fake1", elem), + } + offsets := arch.sizes.Offsetsof(fields) + elemoff := int(offsets[1]) + for i := 0; i < int(tu.Len()); i++ { + cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff) + } + } + + return cc +} + +// asmParseDecl parses a function decl for expected assembly variables. +func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc { + var ( + arch *asmArch + fn *asmFunc + offset int + ) + + // addParams adds asmVars for each of the parameters in list. + // isret indicates whether the list are the arguments or the return values. + addParams := func(list []*ast.Field, isret bool) { + argnum := 0 + for _, fld := range list { + t := f.pkg.types[fld.Type].Type + align := int(arch.sizes.Alignof(t)) + size := int(arch.sizes.Sizeof(t)) + offset += -offset & (align - 1) + cc := componentsOfType(arch, t) + + // names is the list of names with this type. + names := fld.Names + if len(names) == 0 { + // Anonymous args will be called arg, arg1, arg2, ... + // Similarly so for return values: ret, ret1, ret2, ... + name := "arg" + if isret { + name = "ret" + } + if argnum > 0 { + name += strconv.Itoa(argnum) + } + names = []*ast.Ident{ast.NewIdent(name)} + } + argnum += len(names) + + // Create variable for each name. + for _, id := range names { + name := id.Name + for _, c := range cc { + outer := name + c.outer + v := asmVar{ + name: name + c.suffix, + kind: c.kind, + typ: c.typ, + off: offset + c.offset, + size: c.size, + } + if vo := fn.vars[outer]; vo != nil { + vo.inner = append(vo.inner, &v) + } + fn.vars[v.name] = &v + for i := 0; i < v.size; i++ { + fn.varByOffset[v.off+i] = &v + } + } + offset += size + } + } + } + + m := make(map[string]*asmFunc) + for _, arch = range arches { + fn = &asmFunc{ + arch: arch, + vars: make(map[string]*asmVar), + varByOffset: make(map[int]*asmVar), + } + offset = 0 + addParams(decl.Type.Params.List, false) + if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { + offset += -offset & (arch.maxAlign - 1) + addParams(decl.Type.Results.List, true) + } + fn.size = offset + m[arch.name] = fn + } + + return m +} + +// asmCheckVar checks a single variable reference. +func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { + m := asmOpcode.FindStringSubmatch(line) + if m == nil { + if !strings.HasPrefix(strings.TrimSpace(line), "//") { + badf("cannot find assembly opcode") + } + return + } + + // Determine operand sizes from instruction. + // Typically the suffix suffices, but there are exceptions. + var src, dst, kind asmKind + op := m[1] + switch fn.arch.name + "." + op { + case "386.FMOVLP": + src, dst = 8, 4 + case "arm.MOVD": + src = 8 + case "arm.MOVW": + src = 4 + case "arm.MOVH", "arm.MOVHU": + src = 2 + case "arm.MOVB", "arm.MOVBU": + src = 1 + // LEA* opcodes don't really read the second arg. + // They just take the address of it. + case "386.LEAL": + dst = 4 + case "amd64.LEAQ": + dst = 8 + case "amd64p32.LEAL": + dst = 4 + default: + switch fn.arch.name { + case "386", "amd64": + if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { + // FMOVDP, FXCHD, etc + src = 8 + break + } + if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") { + // PINSRD, PEXTRD, etc + src = 4 + break + } + if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { + // FMOVFP, FXCHF, etc + src = 4 + break + } + if strings.HasSuffix(op, "SD") { + // MOVSD, SQRTSD, etc + src = 8 + break + } + if strings.HasSuffix(op, "SS") { + // MOVSS, SQRTSS, etc + src = 4 + break + } + if strings.HasPrefix(op, "SET") { + // SETEQ, etc + src = 1 + break + } + switch op[len(op)-1] { + case 'B': + src = 1 + case 'W': + src = 2 + case 'L': + src = 4 + case 'D', 'Q': + src = 8 + } + case "ppc64", "ppc64le": + // Strip standard suffixes to reveal size letter. + m := ppc64Suff.FindStringSubmatch(op) + if m != nil { + switch m[1][0] { + case 'B': + src = 1 + case 'H': + src = 2 + case 'W': + src = 4 + case 'D': + src = 8 + } + } + case "mips", "mipsle", "mips64", "mips64le": + switch op { + case "MOVB", "MOVBU": + src = 1 + case "MOVH", "MOVHU": + src = 2 + case "MOVW", "MOVWU", "MOVF": + src = 4 + case "MOVV", "MOVD": + src = 8 + } + case "s390x": + switch op { + case "MOVB", "MOVBZ": + src = 1 + case "MOVH", "MOVHZ": + src = 2 + case "MOVW", "MOVWZ", "FMOVS": + src = 4 + case "MOVD", "FMOVD": + src = 8 + } + } + } + if dst == 0 { + dst = src + } + + // Determine whether the match we're holding + // is the first or second argument. + if strings.Index(line, expr) > strings.Index(line, ",") { + kind = dst + } else { + kind = src + } + + vk := v.kind + vs := v.size + vt := v.typ + switch vk { + case asmInterface, asmEmptyInterface, asmString, asmSlice: + // allow reference to first word (pointer) + vk = v.inner[0].kind + vs = v.inner[0].size + vt = v.inner[0].typ + } + + if off != v.off { + var inner bytes.Buffer + for i, vi := range v.inner { + if len(v.inner) > 1 { + fmt.Fprintf(&inner, ",") + } + fmt.Fprintf(&inner, " ") + if i == len(v.inner)-1 { + fmt.Fprintf(&inner, "or ") + } + fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) + } + badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) + return + } + if kind != 0 && kind != vk { + var inner bytes.Buffer + if len(v.inner) > 0 { + fmt.Fprintf(&inner, " containing") + for i, vi := range v.inner { + if i > 0 && len(v.inner) > 2 { + fmt.Fprintf(&inner, ",") + } + fmt.Fprintf(&inner, " ") + if i > 0 && i == len(v.inner)-1 { + fmt.Fprintf(&inner, "and ") + } + fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) + } + } + badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String()) + } +} diff --git a/go/analysis/passes/vet/assign.go b/go/analysis/passes/vet/assign.go new file mode 100644 index 00000000..40ced752 --- /dev/null +++ b/go/analysis/passes/vet/assign.go @@ -0,0 +1,54 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for useless assignments. +*/ + +package main + +import ( + "go/ast" + "go/token" + "reflect" +) + +func init() { + register("assign", + "check for useless assignments", + checkAssignStmt, + assignStmt) +} + +// TODO: should also check for assignments to struct fields inside methods +// that are on T instead of *T. + +// checkAssignStmt checks for assignments of the form " = ". +// These are almost always useless, and even when they aren't they are usually a mistake. +func checkAssignStmt(f *File, node ast.Node) { + stmt := node.(*ast.AssignStmt) + if stmt.Tok != token.ASSIGN { + return // ignore := + } + if len(stmt.Lhs) != len(stmt.Rhs) { + // If LHS and RHS have different cardinality, they can't be the same. + return + } + for i, lhs := range stmt.Lhs { + rhs := stmt.Rhs[i] + if hasSideEffects(f, lhs) || hasSideEffects(f, rhs) { + continue // expressions may not be equal + } + if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) { + continue // short-circuit the heavy-weight gofmt check + } + le := f.gofmt(lhs) + re := f.gofmt(rhs) + if le == re { + f.Badf(stmt.Pos(), "self-assignment of %s to %s", re, le) + } + } +} diff --git a/go/analysis/passes/vet/atomic.go b/go/analysis/passes/vet/atomic.go new file mode 100644 index 00000000..50351016 --- /dev/null +++ b/go/analysis/passes/vet/atomic.go @@ -0,0 +1,73 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("atomic", + "check for common mistaken usages of the sync/atomic package", + checkAtomicAssignment, + assignStmt) +} + +// checkAtomicAssignment walks the assignment statement checking for common +// mistaken usage of atomic package, such as: x = atomic.AddUint64(&x, 1) +func checkAtomicAssignment(f *File, node ast.Node) { + n := node.(*ast.AssignStmt) + if len(n.Lhs) != len(n.Rhs) { + return + } + if len(n.Lhs) == 1 && n.Tok == token.DEFINE { + return + } + + for i, right := range n.Rhs { + call, ok := right.(*ast.CallExpr) + if !ok { + continue + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + pkgIdent, _ := sel.X.(*ast.Ident) + pkgName, ok := f.pkg.uses[pkgIdent].(*types.PkgName) + if !ok || pkgName.Imported().Path() != "sync/atomic" { + continue + } + + switch sel.Sel.Name { + case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + f.checkAtomicAddAssignment(n.Lhs[i], call) + } + } +} + +// checkAtomicAddAssignment walks the atomic.Add* method calls checking for assigning the return value +// to the same variable being used in the operation +func (f *File) checkAtomicAddAssignment(left ast.Expr, call *ast.CallExpr) { + if len(call.Args) != 2 { + return + } + arg := call.Args[0] + broken := false + + if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND { + broken = f.gofmt(left) == f.gofmt(uarg.X) + } else if star, ok := left.(*ast.StarExpr); ok { + broken = f.gofmt(star.X) == f.gofmt(arg) + } + + if broken { + f.Bad(left.Pos(), "direct assignment to atomic value") + } +} diff --git a/go/analysis/passes/vet/bool.go b/go/analysis/passes/vet/bool.go new file mode 100644 index 00000000..56e33ece --- /dev/null +++ b/go/analysis/passes/vet/bool.go @@ -0,0 +1,199 @@ +// +build ignore + +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains boolean condition tests. + +package main + +import ( + "go/ast" + "go/token" +) + +func init() { + register("bool", + "check for mistakes involving boolean operators", + checkBool, + binaryExpr) +} + +func checkBool(f *File, n ast.Node) { + e := n.(*ast.BinaryExpr) + + var op boolOp + switch e.Op { + case token.LOR: + op = or + case token.LAND: + op = and + default: + return + } + + comm := op.commutativeSets(f, e) + for _, exprs := range comm { + op.checkRedundant(f, exprs) + op.checkSuspect(f, exprs) + } +} + +type boolOp struct { + name string + tok token.Token // token corresponding to this operator + badEq token.Token // token corresponding to the equality test that should not be used with this operator +} + +var ( + or = boolOp{"or", token.LOR, token.NEQ} + and = boolOp{"and", token.LAND, token.EQL} +) + +// commutativeSets returns all side effect free sets of +// expressions in e that are connected by op. +// For example, given 'a || b || f() || c || d' with the or op, +// commutativeSets returns {{b, a}, {d, c}}. +func (op boolOp) commutativeSets(f *File, e *ast.BinaryExpr) [][]ast.Expr { + exprs := op.split(e) + + // Partition the slice of expressions into commutative sets. + i := 0 + var sets [][]ast.Expr + for j := 0; j <= len(exprs); j++ { + if j == len(exprs) || hasSideEffects(f, exprs[j]) { + if i < j { + sets = append(sets, exprs[i:j]) + } + i = j + 1 + } + } + + return sets +} + +// checkRedundant checks for expressions of the form +// e && e +// e || e +// Exprs must contain only side effect free expressions. +func (op boolOp) checkRedundant(f *File, exprs []ast.Expr) { + seen := make(map[string]bool) + for _, e := range exprs { + efmt := f.gofmt(e) + if seen[efmt] { + f.Badf(e.Pos(), "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt) + } else { + seen[efmt] = true + } + } +} + +// checkSuspect checks for expressions of the form +// x != c1 || x != c2 +// x == c1 && x == c2 +// where c1 and c2 are constant expressions. +// If c1 and c2 are the same then it's redundant; +// if c1 and c2 are different then it's always true or always false. +// Exprs must contain only side effect free expressions. +func (op boolOp) checkSuspect(f *File, exprs []ast.Expr) { + // seen maps from expressions 'x' to equality expressions 'x != c'. + seen := make(map[string]string) + + for _, e := range exprs { + bin, ok := e.(*ast.BinaryExpr) + if !ok || bin.Op != op.badEq { + continue + } + + // In order to avoid false positives, restrict to cases + // in which one of the operands is constant. We're then + // interested in the other operand. + // In the rare case in which both operands are constant + // (e.g. runtime.GOOS and "windows"), we'll only catch + // mistakes if the LHS is repeated, which is how most + // code is written. + var x ast.Expr + switch { + case f.pkg.types[bin.Y].Value != nil: + x = bin.X + case f.pkg.types[bin.X].Value != nil: + x = bin.Y + default: + continue + } + + // e is of the form 'x != c' or 'x == c'. + xfmt := f.gofmt(x) + efmt := f.gofmt(e) + if prev, found := seen[xfmt]; found { + // checkRedundant handles the case in which efmt == prev. + if efmt != prev { + f.Badf(e.Pos(), "suspect %s: %s %s %s", op.name, efmt, op.tok, prev) + } + } else { + seen[xfmt] = efmt + } + } +} + +// hasSideEffects reports whether evaluation of e has side effects. +func hasSideEffects(f *File, e ast.Expr) bool { + safe := true + ast.Inspect(e, func(node ast.Node) bool { + switch n := node.(type) { + case *ast.CallExpr: + typVal := f.pkg.types[n.Fun] + switch { + case typVal.IsType(): + // Type conversion, which is safe. + case typVal.IsBuiltin(): + // Builtin func, conservatively assumed to not + // be safe for now. + safe = false + return false + default: + // A non-builtin func or method call. + // Conservatively assume that all of them have + // side effects for now. + safe = false + return false + } + case *ast.UnaryExpr: + if n.Op == token.ARROW { + safe = false + return false + } + } + return true + }) + return !safe +} + +// split returns a slice of all subexpressions in e that are connected by op. +// For example, given 'a || (b || c) || d' with the or op, +// split returns []{d, c, b, a}. +func (op boolOp) split(e ast.Expr) (exprs []ast.Expr) { + for { + e = unparen(e) + if b, ok := e.(*ast.BinaryExpr); ok && b.Op == op.tok { + exprs = append(exprs, op.split(b.Y)...) + e = b.X + } else { + exprs = append(exprs, e) + break + } + } + return +} + +// unparen returns e with any enclosing parentheses stripped. +func unparen(e ast.Expr) ast.Expr { + for { + p, ok := e.(*ast.ParenExpr) + if !ok { + return e + } + e = p.X + } +} diff --git a/go/analysis/passes/vet/buildtag.go b/go/analysis/passes/vet/buildtag.go new file mode 100644 index 00000000..ebc2bf89 --- /dev/null +++ b/go/analysis/passes/vet/buildtag.go @@ -0,0 +1,128 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "os" + "strings" + "unicode" +) + +var ( + nl = []byte("\n") + slashSlash = []byte("//") + plusBuild = []byte("+build") +) + +func badfLine(f *File, line int, format string, args ...interface{}) { + msg := fmt.Sprintf(format, args...) + fmt.Fprintf(os.Stderr, "%s:%d: %s\n", f.name, line, msg) + setExit(1) +} + +// checkBuildTag checks that build tags are in the correct location and well-formed. +func checkBuildTag(f *File) { + if !vet("buildtags") { + return + } + + // we must look at the raw lines, as build tags may appear in non-Go + // files such as assembly files. + lines := bytes.SplitAfter(f.content, nl) + + // lineWithComment reports whether a line corresponds to a comment in + // the source file. If the source file wasn't Go, the function always + // returns true. + lineWithComment := func(line int) bool { + if f.file == nil { + // Current source file is not Go, so be conservative. + return true + } + for _, group := range f.file.Comments { + startLine := f.fset.Position(group.Pos()).Line + endLine := f.fset.Position(group.End()).Line + if startLine <= line && line <= endLine { + return true + } + } + return false + } + + // Determine cutpoint where +build comments are no longer valid. + // They are valid in leading // comments in the file followed by + // a blank line. + var cutoff int + for i, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + cutoff = i + continue + } + if bytes.HasPrefix(line, slashSlash) { + continue + } + break + } + + for i, line := range lines { + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, slashSlash) { + continue + } + if !bytes.Contains(line, plusBuild) { + // Check that the comment contains "+build" early, to + // avoid unnecessary lineWithComment calls that may + // incur linear searches. + continue + } + if !lineWithComment(i + 1) { + // This is a line in a Go source file that looks like a + // comment, but actually isn't - such as part of a raw + // string. + continue + } + + text := bytes.TrimSpace(line[2:]) + if bytes.HasPrefix(text, plusBuild) { + fields := bytes.Fields(text) + if !bytes.Equal(fields[0], plusBuild) { + // Comment is something like +buildasdf not +build. + badfLine(f, i+1, "possible malformed +build comment") + continue + } + if i >= cutoff { + badfLine(f, i+1, "+build comment must appear before package clause and be followed by a blank line") + continue + } + // Check arguments. + Args: + for _, arg := range fields[1:] { + for _, elem := range strings.Split(string(arg), ",") { + if strings.HasPrefix(elem, "!!") { + badfLine(f, i+1, "invalid double negative in build constraint: %s", arg) + break Args + } + elem = strings.TrimPrefix(elem, "!") + for _, c := range elem { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + badfLine(f, i+1, "invalid non-alphanumeric build constraint: %s", arg) + break Args + } + } + } + } + continue + } + // Comment with +build but not at beginning. + if i < cutoff { + badfLine(f, i+1, "possible malformed +build comment") + continue + } + } +} diff --git a/go/analysis/passes/vet/cgo.go b/go/analysis/passes/vet/cgo.go new file mode 100644 index 00000000..693dac96 --- /dev/null +++ b/go/analysis/passes/vet/cgo.go @@ -0,0 +1,143 @@ +// +build ignore + +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for invalid cgo pointer passing. +// This looks for code that uses cgo to call C code passing values +// whose types are almost always invalid according to the cgo pointer +// sharing rules. +// Specifically, it warns about attempts to pass a Go chan, map, func, +// or slice to C, either directly, or via a pointer, array, or struct. + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("cgocall", + "check for types that may not be passed to cgo calls", + checkCgoCall, + callExpr) +} + +func checkCgoCall(f *File, node ast.Node) { + x := node.(*ast.CallExpr) + + // We are only looking for calls to functions imported from + // the "C" package. + sel, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + return + } + id, ok := sel.X.(*ast.Ident) + if !ok { + return + } + + pkgname, ok := f.pkg.uses[id].(*types.PkgName) + if !ok || pkgname.Imported().Path() != "C" { + return + } + + // A call to C.CBytes passes a pointer but is always safe. + if sel.Sel.Name == "CBytes" { + return + } + + for _, arg := range x.Args { + if !typeOKForCgoCall(cgoBaseType(f, arg), make(map[types.Type]bool)) { + f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") + } + + // Check for passing the address of a bad type. + if conv, ok := arg.(*ast.CallExpr); ok && len(conv.Args) == 1 && f.hasBasicType(conv.Fun, types.UnsafePointer) { + arg = conv.Args[0] + } + if u, ok := arg.(*ast.UnaryExpr); ok && u.Op == token.AND { + if !typeOKForCgoCall(cgoBaseType(f, u.X), make(map[types.Type]bool)) { + f.Badf(arg.Pos(), "possibly passing Go type with embedded pointer to C") + } + } + } +} + +// cgoBaseType tries to look through type conversions involving +// unsafe.Pointer to find the real type. It converts: +// unsafe.Pointer(x) => x +// *(*unsafe.Pointer)(unsafe.Pointer(&x)) => x +func cgoBaseType(f *File, arg ast.Expr) types.Type { + switch arg := arg.(type) { + case *ast.CallExpr: + if len(arg.Args) == 1 && f.hasBasicType(arg.Fun, types.UnsafePointer) { + return cgoBaseType(f, arg.Args[0]) + } + case *ast.StarExpr: + call, ok := arg.X.(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + break + } + // Here arg is *f(v). + t := f.pkg.types[call.Fun].Type + if t == nil { + break + } + ptr, ok := t.Underlying().(*types.Pointer) + if !ok { + break + } + // Here arg is *(*p)(v) + elem, ok := ptr.Elem().Underlying().(*types.Basic) + if !ok || elem.Kind() != types.UnsafePointer { + break + } + // Here arg is *(*unsafe.Pointer)(v) + call, ok = call.Args[0].(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + break + } + // Here arg is *(*unsafe.Pointer)(f(v)) + if !f.hasBasicType(call.Fun, types.UnsafePointer) { + break + } + // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(v)) + u, ok := call.Args[0].(*ast.UnaryExpr) + if !ok || u.Op != token.AND { + break + } + // Here arg is *(*unsafe.Pointer)(unsafe.Pointer(&v)) + return cgoBaseType(f, u.X) + } + + return f.pkg.types[arg].Type +} + +// typeOKForCgoCall reports whether the type of arg is OK to pass to a +// C function using cgo. This is not true for Go types with embedded +// pointers. m is used to avoid infinite recursion on recursive types. +func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool { + if t == nil || m[t] { + return true + } + m[t] = true + switch t := t.Underlying().(type) { + case *types.Chan, *types.Map, *types.Signature, *types.Slice: + return false + case *types.Pointer: + return typeOKForCgoCall(t.Elem(), m) + case *types.Array: + return typeOKForCgoCall(t.Elem(), m) + case *types.Struct: + for i := 0; i < t.NumFields(); i++ { + if !typeOKForCgoCall(t.Field(i).Type(), m) { + return false + } + } + } + return true +} diff --git a/go/analysis/passes/vet/composite.go b/go/analysis/passes/vet/composite.go new file mode 100644 index 00000000..93fd747d --- /dev/null +++ b/go/analysis/passes/vet/composite.go @@ -0,0 +1,88 @@ +// +build ignore + +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for unkeyed struct literals. + +package main + +import ( + "cmd/vet/internal/whitelist" + "flag" + "go/ast" + "go/types" + "strings" +) + +var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only") + +func init() { + register("composites", + "check that composite literals of types from imported packages use field-keyed elements", + checkUnkeyedLiteral, + compositeLit) +} + +// checkUnkeyedLiteral checks if a composite literal is a struct literal with +// unkeyed fields. +func checkUnkeyedLiteral(f *File, node ast.Node) { + cl := node.(*ast.CompositeLit) + + typ := f.pkg.types[cl].Type + if typ == nil { + // cannot determine composite literals' type, skip it + return + } + typeName := typ.String() + if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] { + // skip whitelisted types + return + } + under := typ.Underlying() + for { + ptr, ok := under.(*types.Pointer) + if !ok { + break + } + under = ptr.Elem().Underlying() + } + if _, ok := under.(*types.Struct); !ok { + // skip non-struct composite literals + return + } + if isLocalType(f, typ) { + // allow unkeyed locally defined composite literal + return + } + + // check if the CompositeLit contains an unkeyed field + allKeyValue := true + for _, e := range cl.Elts { + if _, ok := e.(*ast.KeyValueExpr); !ok { + allKeyValue = false + break + } + } + if allKeyValue { + // all the composite literal fields are keyed + return + } + + f.Badf(cl.Pos(), "%s composite literal uses unkeyed fields", typeName) +} + +func isLocalType(f *File, typ types.Type) bool { + switch x := typ.(type) { + case *types.Struct: + // struct literals are local types + return true + case *types.Pointer: + return isLocalType(f, x.Elem()) + case *types.Named: + // names in package foo are local to foo_test too + return strings.TrimSuffix(x.Obj().Pkg().Path(), "_test") == strings.TrimSuffix(f.pkg.path, "_test") + } + return false +} diff --git a/go/analysis/passes/vet/copylock.go b/go/analysis/passes/vet/copylock.go new file mode 100644 index 00000000..f4490457 --- /dev/null +++ b/go/analysis/passes/vet/copylock.go @@ -0,0 +1,268 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code to check that locks are not passed by value. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("copylocks", + "check that locks are not passed by value", + checkCopyLocks, + funcDecl, rangeStmt, funcLit, callExpr, assignStmt, genDecl, compositeLit, returnStmt) +} + +// checkCopyLocks checks whether node might +// inadvertently copy a lock. +func checkCopyLocks(f *File, node ast.Node) { + switch node := node.(type) { + case *ast.RangeStmt: + checkCopyLocksRange(f, node) + case *ast.FuncDecl: + checkCopyLocksFunc(f, node.Name.Name, node.Recv, node.Type) + case *ast.FuncLit: + checkCopyLocksFunc(f, "func", nil, node.Type) + case *ast.CallExpr: + checkCopyLocksCallExpr(f, node) + case *ast.AssignStmt: + checkCopyLocksAssign(f, node) + case *ast.GenDecl: + checkCopyLocksGenDecl(f, node) + case *ast.CompositeLit: + checkCopyLocksCompositeLit(f, node) + case *ast.ReturnStmt: + checkCopyLocksReturnStmt(f, node) + } +} + +// checkCopyLocksAssign checks whether an assignment +// copies a lock. +func checkCopyLocksAssign(f *File, as *ast.AssignStmt) { + for i, x := range as.Rhs { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "assignment copies lock value to %v: %v", f.gofmt(as.Lhs[i]), path) + } + } +} + +// checkCopyLocksGenDecl checks whether lock is copied +// in variable declaration. +func checkCopyLocksGenDecl(f *File, gd *ast.GenDecl) { + if gd.Tok != token.VAR { + return + } + for _, spec := range gd.Specs { + valueSpec := spec.(*ast.ValueSpec) + for i, x := range valueSpec.Values { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "variable declaration copies lock value to %v: %v", valueSpec.Names[i].Name, path) + } + } + } +} + +// checkCopyLocksCompositeLit detects lock copy inside a composite literal +func checkCopyLocksCompositeLit(f *File, cl *ast.CompositeLit) { + for _, x := range cl.Elts { + if node, ok := x.(*ast.KeyValueExpr); ok { + x = node.Value + } + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "literal copies lock value from %v: %v", f.gofmt(x), path) + } + } +} + +// checkCopyLocksReturnStmt detects lock copy in return statement +func checkCopyLocksReturnStmt(f *File, rs *ast.ReturnStmt) { + for _, x := range rs.Results { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "return copies lock value: %v", path) + } + } +} + +// checkCopyLocksCallExpr detects lock copy in the arguments to a function call +func checkCopyLocksCallExpr(f *File, ce *ast.CallExpr) { + var id *ast.Ident + switch fun := ce.Fun.(type) { + case *ast.Ident: + id = fun + case *ast.SelectorExpr: + id = fun.Sel + } + if fun, ok := f.pkg.uses[id].(*types.Builtin); ok { + switch fun.Name() { + case "new", "len", "cap", "Sizeof": + return + } + } + for _, x := range ce.Args { + if path := lockPathRhs(f, x); path != nil { + f.Badf(x.Pos(), "call of %s copies lock value: %v", f.gofmt(ce.Fun), path) + } + } +} + +// checkCopyLocksFunc checks whether a function might +// inadvertently copy a lock, by checking whether +// its receiver, parameters, or return values +// are locks. +func checkCopyLocksFunc(f *File, name string, recv *ast.FieldList, typ *ast.FuncType) { + if recv != nil && len(recv.List) > 0 { + expr := recv.List[0].Type + if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil { + f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path) + } + } + + if typ.Params != nil { + for _, field := range typ.Params.List { + expr := field.Type + if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil { + f.Badf(expr.Pos(), "%s passes lock by value: %v", name, path) + } + } + } + + // Don't check typ.Results. If T has a Lock field it's OK to write + // return T{} + // because that is returning the zero value. Leave result checking + // to the return statement. +} + +// checkCopyLocksRange checks whether a range statement +// might inadvertently copy a lock by checking whether +// any of the range variables are locks. +func checkCopyLocksRange(f *File, r *ast.RangeStmt) { + checkCopyLocksRangeVar(f, r.Tok, r.Key) + checkCopyLocksRangeVar(f, r.Tok, r.Value) +} + +func checkCopyLocksRangeVar(f *File, rtok token.Token, e ast.Expr) { + if e == nil { + return + } + id, isId := e.(*ast.Ident) + if isId && id.Name == "_" { + return + } + + var typ types.Type + if rtok == token.DEFINE { + if !isId { + return + } + obj := f.pkg.defs[id] + if obj == nil { + return + } + typ = obj.Type() + } else { + typ = f.pkg.types[e].Type + } + + if typ == nil { + return + } + if path := lockPath(f.pkg.typesPkg, typ); path != nil { + f.Badf(e.Pos(), "range var %s copies lock: %v", f.gofmt(e), path) + } +} + +type typePath []types.Type + +// String pretty-prints a typePath. +func (path typePath) String() string { + n := len(path) + var buf bytes.Buffer + for i := range path { + if i > 0 { + fmt.Fprint(&buf, " contains ") + } + // The human-readable path is in reverse order, outermost to innermost. + fmt.Fprint(&buf, path[n-i-1].String()) + } + return buf.String() +} + +func lockPathRhs(f *File, x ast.Expr) typePath { + if _, ok := x.(*ast.CompositeLit); ok { + return nil + } + if _, ok := x.(*ast.CallExpr); ok { + // A call may return a zero value. + return nil + } + if star, ok := x.(*ast.StarExpr); ok { + if _, ok := star.X.(*ast.CallExpr); ok { + // A call may return a pointer to a zero value. + return nil + } + } + return lockPath(f.pkg.typesPkg, f.pkg.types[x].Type) +} + +// lockPath returns a typePath describing the location of a lock value +// contained in typ. If there is no contained lock, it returns nil. +func lockPath(tpkg *types.Package, typ types.Type) typePath { + if typ == nil { + return nil + } + + for { + atyp, ok := typ.Underlying().(*types.Array) + if !ok { + break + } + typ = atyp.Elem() + } + + // We're only interested in the case in which the underlying + // type is a struct. (Interfaces and pointers are safe to copy.) + styp, ok := typ.Underlying().(*types.Struct) + if !ok { + return nil + } + + // We're looking for cases in which a pointer to this type + // is a sync.Locker, but a value is not. This differentiates + // embedded interfaces from embedded values. + if types.Implements(types.NewPointer(typ), lockerType) && !types.Implements(typ, lockerType) { + return []types.Type{typ} + } + + nfields := styp.NumFields() + for i := 0; i < nfields; i++ { + ftyp := styp.Field(i).Type() + subpath := lockPath(tpkg, ftyp) + if subpath != nil { + return append(subpath, typ) + } + } + + return nil +} + +var lockerType *types.Interface + +// Construct a sync.Locker interface type. +func init() { + nullary := types.NewSignature(nil, nil, nil, false) // func() + methods := []*types.Func{ + types.NewFunc(token.NoPos, nil, "Lock", nullary), + types.NewFunc(token.NoPos, nil, "Unlock", nullary), + } + lockerType = types.NewInterface(methods, nil).Complete() +} diff --git a/go/analysis/passes/vet/dead.go b/go/analysis/passes/vet/dead.go new file mode 100644 index 00000000..3eb53230 --- /dev/null +++ b/go/analysis/passes/vet/dead.go @@ -0,0 +1,110 @@ +// +build ignore + +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Simplified dead code detector. Used for skipping certain checks +// on unreachable code (for instance, shift checks on arch-specific code). + +package main + +import ( + "go/ast" + "go/constant" +) + +// updateDead puts unreachable "if" and "case" nodes into f.dead. +func (f *File) updateDead(node ast.Node) { + if f.dead[node] { + // The node is already marked as dead. + return + } + + switch stmt := node.(type) { + case *ast.IfStmt: + // "if" branch is dead if its condition evaluates + // to constant false. + v := f.pkg.types[stmt.Cond].Value + if v == nil { + return + } + if !constant.BoolVal(v) { + f.setDead(stmt.Body) + return + } + f.setDead(stmt.Else) + case *ast.SwitchStmt: + // Case clause with empty switch tag is dead if it evaluates + // to constant false. + if stmt.Tag == nil { + BodyLoopBool: + for _, stmt := range stmt.Body.List { + cc := stmt.(*ast.CaseClause) + if cc.List == nil { + // Skip default case. + continue + } + for _, expr := range cc.List { + v := f.pkg.types[expr].Value + if v == nil || v.Kind() != constant.Bool || constant.BoolVal(v) { + continue BodyLoopBool + } + } + f.setDead(cc) + } + return + } + + // Case clause is dead if its constant value doesn't match + // the constant value from the switch tag. + // TODO: This handles integer comparisons only. + v := f.pkg.types[stmt.Tag].Value + if v == nil || v.Kind() != constant.Int { + return + } + tagN, ok := constant.Uint64Val(v) + if !ok { + return + } + BodyLoopInt: + for _, x := range stmt.Body.List { + cc := x.(*ast.CaseClause) + if cc.List == nil { + // Skip default case. + continue + } + for _, expr := range cc.List { + v := f.pkg.types[expr].Value + if v == nil { + continue BodyLoopInt + } + n, ok := constant.Uint64Val(v) + if !ok || tagN == n { + continue BodyLoopInt + } + } + f.setDead(cc) + } + } +} + +// setDead marks the node and all the children as dead. +func (f *File) setDead(node ast.Node) { + dv := deadVisitor{ + f: f, + } + ast.Walk(dv, node) +} + +type deadVisitor struct { + f *File +} + +func (dv deadVisitor) Visit(node ast.Node) ast.Visitor { + if node == nil { + return nil + } + dv.f.dead[node] = true + return dv +} diff --git a/go/analysis/passes/vet/deadcode.go b/go/analysis/passes/vet/deadcode.go new file mode 100644 index 00000000..bfdbc3b0 --- /dev/null +++ b/go/analysis/passes/vet/deadcode.go @@ -0,0 +1,300 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for syntactically unreachable code. + +package main + +import ( + "go/ast" + "go/token" +) + +func init() { + register("unreachable", + "check for unreachable code", + checkUnreachable, + funcDecl, funcLit) +} + +type deadState struct { + f *File + hasBreak map[ast.Stmt]bool + hasGoto map[string]bool + labels map[string]ast.Stmt + breakTarget ast.Stmt + + reachable bool +} + +// checkUnreachable checks a function body for dead code. +// +// TODO(adonovan): use the new cfg package, which is more precise. +func checkUnreachable(f *File, node ast.Node) { + var body *ast.BlockStmt + switch n := node.(type) { + case *ast.FuncDecl: + body = n.Body + case *ast.FuncLit: + body = n.Body + } + if body == nil { + return + } + + d := &deadState{ + f: f, + hasBreak: make(map[ast.Stmt]bool), + hasGoto: make(map[string]bool), + labels: make(map[string]ast.Stmt), + } + + d.findLabels(body) + + d.reachable = true + d.findDead(body) +} + +// findLabels gathers information about the labels defined and used by stmt +// and about which statements break, whether a label is involved or not. +func (d *deadState) findLabels(stmt ast.Stmt) { + switch x := stmt.(type) { + default: + d.f.Warnf(x.Pos(), "internal error in findLabels: unexpected statement %T", x) + + case *ast.AssignStmt, + *ast.BadStmt, + *ast.DeclStmt, + *ast.DeferStmt, + *ast.EmptyStmt, + *ast.ExprStmt, + *ast.GoStmt, + *ast.IncDecStmt, + *ast.ReturnStmt, + *ast.SendStmt: + // no statements inside + + case *ast.BlockStmt: + for _, stmt := range x.List { + d.findLabels(stmt) + } + + case *ast.BranchStmt: + switch x.Tok { + case token.GOTO: + if x.Label != nil { + d.hasGoto[x.Label.Name] = true + } + + case token.BREAK: + stmt := d.breakTarget + if x.Label != nil { + stmt = d.labels[x.Label.Name] + } + if stmt != nil { + d.hasBreak[stmt] = true + } + } + + case *ast.IfStmt: + d.findLabels(x.Body) + if x.Else != nil { + d.findLabels(x.Else) + } + + case *ast.LabeledStmt: + d.labels[x.Label.Name] = x.Stmt + d.findLabels(x.Stmt) + + // These cases are all the same, but the x.Body only works + // when the specific type of x is known, so the cases cannot + // be merged. + case *ast.ForStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.RangeStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.SelectStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.SwitchStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.TypeSwitchStmt: + outer := d.breakTarget + d.breakTarget = x + d.findLabels(x.Body) + d.breakTarget = outer + + case *ast.CommClause: + for _, stmt := range x.Body { + d.findLabels(stmt) + } + + case *ast.CaseClause: + for _, stmt := range x.Body { + d.findLabels(stmt) + } + } +} + +// findDead walks the statement looking for dead code. +// If d.reachable is false on entry, stmt itself is dead. +// When findDead returns, d.reachable tells whether the +// statement following stmt is reachable. +func (d *deadState) findDead(stmt ast.Stmt) { + // Is this a labeled goto target? + // If so, assume it is reachable due to the goto. + // This is slightly conservative, in that we don't + // check that the goto is reachable, so + // L: goto L + // will not provoke a warning. + // But it's good enough. + if x, isLabel := stmt.(*ast.LabeledStmt); isLabel && d.hasGoto[x.Label.Name] { + d.reachable = true + } + + if !d.reachable { + switch stmt.(type) { + case *ast.EmptyStmt: + // do not warn about unreachable empty statements + default: + d.f.Bad(stmt.Pos(), "unreachable code") + d.reachable = true // silence error about next statement + } + } + + switch x := stmt.(type) { + default: + d.f.Warnf(x.Pos(), "internal error in findDead: unexpected statement %T", x) + + case *ast.AssignStmt, + *ast.BadStmt, + *ast.DeclStmt, + *ast.DeferStmt, + *ast.EmptyStmt, + *ast.GoStmt, + *ast.IncDecStmt, + *ast.SendStmt: + // no control flow + + case *ast.BlockStmt: + for _, stmt := range x.List { + d.findDead(stmt) + } + + case *ast.BranchStmt: + switch x.Tok { + case token.BREAK, token.GOTO, token.FALLTHROUGH: + d.reachable = false + case token.CONTINUE: + // NOTE: We accept "continue" statements as terminating. + // They are not necessary in the spec definition of terminating, + // because a continue statement cannot be the final statement + // before a return. But for the more general problem of syntactically + // identifying dead code, continue redirects control flow just + // like the other terminating statements. + d.reachable = false + } + + case *ast.ExprStmt: + // Call to panic? + call, ok := x.X.(*ast.CallExpr) + if ok { + name, ok := call.Fun.(*ast.Ident) + if ok && name.Name == "panic" && name.Obj == nil { + d.reachable = false + } + } + + case *ast.ForStmt: + d.findDead(x.Body) + d.reachable = x.Cond != nil || d.hasBreak[x] + + case *ast.IfStmt: + d.findDead(x.Body) + if x.Else != nil { + r := d.reachable + d.reachable = true + d.findDead(x.Else) + d.reachable = d.reachable || r + } else { + // might not have executed if statement + d.reachable = true + } + + case *ast.LabeledStmt: + d.findDead(x.Stmt) + + case *ast.RangeStmt: + d.findDead(x.Body) + d.reachable = true + + case *ast.ReturnStmt: + d.reachable = false + + case *ast.SelectStmt: + // NOTE: Unlike switch and type switch below, we don't care + // whether a select has a default, because a select without a + // default blocks until one of the cases can run. That's different + // from a switch without a default, which behaves like it has + // a default with an empty body. + anyReachable := false + for _, comm := range x.Body.List { + d.reachable = true + for _, stmt := range comm.(*ast.CommClause).Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] + + case *ast.SwitchStmt: + anyReachable := false + hasDefault := false + for _, cas := range x.Body.List { + cc := cas.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + d.reachable = true + for _, stmt := range cc.Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] || !hasDefault + + case *ast.TypeSwitchStmt: + anyReachable := false + hasDefault := false + for _, cas := range x.Body.List { + cc := cas.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + d.reachable = true + for _, stmt := range cc.Body { + d.findDead(stmt) + } + anyReachable = anyReachable || d.reachable + } + d.reachable = anyReachable || d.hasBreak[x] || !hasDefault + } +} diff --git a/go/analysis/passes/vet/doc.go b/go/analysis/passes/vet/doc.go new file mode 100644 index 00000000..0b970d08 --- /dev/null +++ b/go/analysis/passes/vet/doc.go @@ -0,0 +1,221 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* + +Vet examines Go source code and reports suspicious constructs, such as Printf +calls whose arguments do not align with the format string. Vet uses heuristics +that do not guarantee all reports are genuine problems, but it can find errors +not caught by the compilers. + +Vet is normally invoked using the go command by running "go vet": + + go vet +vets the package in the current directory. + + go vet package/path/name +vets the package whose path is provided. + +Use "go help packages" to see other ways of specifying which packages to vet. + +Vet's exit code is 2 for erroneous invocation of the tool, 1 if a +problem was reported, and 0 otherwise. Note that the tool does not +check every possible problem and depends on unreliable heuristics +so it should be used as guidance only, not as a firm indicator of +program correctness. + +By default the -all flag is set so all checks are performed. +If any flags are explicitly set to true, only those tests are run. Conversely, if +any flag is explicitly set to false, only those tests are disabled. Thus -printf=true +runs the printf check, -printf=false runs all checks except the printf check. + +By default vet uses the object files generated by 'go install some/pkg' to typecheck the code. +If the -source flag is provided, vet uses only source code. + +Available checks: + +Assembly declarations + +Flag: -asmdecl + +Mismatches between assembly files and Go function declarations. + +Useless assignments + +Flag: -assign + +Check for useless assignments. + +Atomic mistakes + +Flag: -atomic + +Common mistaken usages of the sync/atomic package. + +Boolean conditions + +Flag: -bool + +Mistakes involving boolean operators. + +Build tags + +Flag: -buildtags + +Badly formed or misplaced +build tags. + +Invalid uses of cgo + +Flag: -cgocall + +Detect some violations of the cgo pointer passing rules. + +Unkeyed composite literals + +Flag: -composites + +Composite struct literals that do not use the field-keyed syntax. + +Copying locks + +Flag: -copylocks + +Locks that are erroneously passed by value. + +HTTP responses used incorrectly + +Flag: -httpresponse + +Mistakes deferring a function call on an HTTP response before +checking whether the error returned with the response was nil. + +Failure to call the cancelation function returned by WithCancel + +Flag: -lostcancel + +The cancelation function returned by context.WithCancel, WithTimeout, +and WithDeadline must be called or the new context will remain live +until its parent context is cancelled. +(The background context is never cancelled.) + +Methods + +Flag: -methods + +Non-standard signatures for methods with familiar names, including: + Format GobEncode GobDecode MarshalJSON MarshalXML + Peek ReadByte ReadFrom ReadRune Scan Seek + UnmarshalJSON UnreadByte UnreadRune WriteByte + WriteTo + +Nil function comparison + +Flag: -nilfunc + +Comparisons between functions and nil. + +Printf family + +Flag: -printf + +Suspicious calls to fmt.Print, fmt.Printf, and related functions. +The check applies to known functions (for example, those in package fmt) +as well as any detected wrappers of known functions. + +The -printfuncs flag specifies a comma-separated list of names of +additional known formatting functions. Each name can be of the form +pkg.Name or pkg.Type.Name, where pkg is a complete import path, +or else can be a case-insensitive unqualified identifier like "errorf". +If a listed name ends in f, the function is assumed to be Printf-like, +taking a format string before the argument list. Otherwise it is +assumed to be Print-like, taking a list of arguments with no format string. + +Range loop variables + +Flag: -rangeloops + +Incorrect uses of range loop variables in closures. + +Shadowed variables + +Flag: -shadow=false (experimental; must be set explicitly) + +Variables that may have been unintentionally shadowed. + +Shifts + +Flag: -shift + +Shifts equal to or longer than the variable's length. + +Struct tags + +Flag: -structtags + +Struct tags that do not follow the format understood by reflect.StructTag.Get. +Well-known encoding struct tags (json, xml) used with unexported fields. + +Tests and documentation examples + +Flag: -tests + +Mistakes involving tests including functions with incorrect names or signatures +and example tests that document identifiers not in the package. + +Unreachable code + +Flag: -unreachable + +Unreachable code. + +Misuse of unsafe Pointers + +Flag: -unsafeptr + +Likely incorrect uses of unsafe.Pointer to convert integers to pointers. +A conversion from uintptr to unsafe.Pointer is invalid if it implies that +there is a uintptr-typed word in memory that holds a pointer value, +because that word will be invisible to stack copying and to the garbage +collector. + +Unused result of certain function calls + +Flag: -unusedresult + +Calls to well-known functions and methods that return a value that is +discarded. By default, this includes functions like fmt.Errorf and +fmt.Sprintf and methods like String and Error. The flags -unusedfuncs +and -unusedstringmethods control the set. + +Other flags + +These flags configure the behavior of vet: + + -all (default true) + Enable all non-experimental checks. + -v + Verbose mode + -printfuncs + A comma-separated list of print-like function names + to supplement the standard list. + For more information, see the discussion of the -printf flag. + -shadowstrict + Whether to be strict about shadowing; can be noisy. + +Using vet directly + +For testing and debugging vet can be run directly by invoking +"go tool vet" or just running the binary. Run this way, vet might not +have up to date information for imported packages. + + go tool vet source/directory/*.go +vets the files named, all of which must be in the same package. + + go tool vet source/directory +recursively descends the directory, vetting each package it finds. + +*/ +package main diff --git a/go/analysis/passes/vet/httpresponse.go b/go/analysis/passes/vet/httpresponse.go new file mode 100644 index 00000000..600e81c7 --- /dev/null +++ b/go/analysis/passes/vet/httpresponse.go @@ -0,0 +1,139 @@ +// +build ignore + +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the check for http.Response values being used before +// checking for errors. + +package main + +import ( + "go/ast" + "go/types" +) + +func init() { + register("httpresponse", + "check errors are checked before using an http Response", + checkHTTPResponse, callExpr) +} + +func checkHTTPResponse(f *File, node ast.Node) { + call := node.(*ast.CallExpr) + if !isHTTPFuncOrMethodOnClient(f, call) { + return // the function call is not related to this check. + } + + finder := &blockStmtFinder{node: call} + ast.Walk(finder, f.file) + stmts := finder.stmts() + if len(stmts) < 2 { + return // the call to the http function is the last statement of the block. + } + + asg, ok := stmts[0].(*ast.AssignStmt) + if !ok { + return // the first statement is not assignment. + } + resp := rootIdent(asg.Lhs[0]) + if resp == nil { + return // could not find the http.Response in the assignment. + } + + def, ok := stmts[1].(*ast.DeferStmt) + if !ok { + return // the following statement is not a defer. + } + root := rootIdent(def.Call.Fun) + if root == nil { + return // could not find the receiver of the defer call. + } + + if resp.Obj == root.Obj { + f.Badf(root.Pos(), "using %s before checking for errors", resp.Name) + } +} + +// isHTTPFuncOrMethodOnClient checks whether the given call expression is on +// either a function of the net/http package or a method of http.Client that +// returns (*http.Response, error). +func isHTTPFuncOrMethodOnClient(f *File, expr *ast.CallExpr) bool { + fun, _ := expr.Fun.(*ast.SelectorExpr) + sig, _ := f.pkg.types[fun].Type.(*types.Signature) + if sig == nil { + return false // the call is not on of the form x.f() + } + + res := sig.Results() + if res.Len() != 2 { + return false // the function called does not return two values. + } + if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !isNamedType(ptr.Elem(), "net/http", "Response") { + return false // the first return type is not *http.Response. + } + if !types.Identical(res.At(1).Type().Underlying(), errorType) { + return false // the second return type is not error + } + + typ := f.pkg.types[fun.X].Type + if typ == nil { + id, ok := fun.X.(*ast.Ident) + return ok && id.Name == "http" // function in net/http package. + } + + if isNamedType(typ, "net/http", "Client") { + return true // method on http.Client. + } + ptr, ok := typ.(*types.Pointer) + return ok && isNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client. +} + +// blockStmtFinder is an ast.Visitor that given any ast node can find the +// statement containing it and its succeeding statements in the same block. +type blockStmtFinder struct { + node ast.Node // target of search + stmt ast.Stmt // innermost statement enclosing argument to Visit + block *ast.BlockStmt // innermost block enclosing argument to Visit. +} + +// Visit finds f.node performing a search down the ast tree. +// It keeps the last block statement and statement seen for later use. +func (f *blockStmtFinder) Visit(node ast.Node) ast.Visitor { + if node == nil || f.node.Pos() < node.Pos() || f.node.End() > node.End() { + return nil // not here + } + switch n := node.(type) { + case *ast.BlockStmt: + f.block = n + case ast.Stmt: + f.stmt = n + } + if f.node.Pos() == node.Pos() && f.node.End() == node.End() { + return nil // found + } + return f // keep looking +} + +// stmts returns the statements of f.block starting from the one including f.node. +func (f *blockStmtFinder) stmts() []ast.Stmt { + for i, v := range f.block.List { + if f.stmt == v { + return f.block.List[i:] + } + } + return nil +} + +// rootIdent finds the root identifier x in a chain of selections x.y.z, or nil if not found. +func rootIdent(n ast.Node) *ast.Ident { + switch n := n.(type) { + case *ast.SelectorExpr: + return rootIdent(n.X) + case *ast.Ident: + return n + default: + return nil + } +} diff --git a/go/analysis/passes/vet/internal/whitelist/whitelist.go b/go/analysis/passes/vet/internal/whitelist/whitelist.go new file mode 100644 index 00000000..fdd65d37 --- /dev/null +++ b/go/analysis/passes/vet/internal/whitelist/whitelist.go @@ -0,0 +1,28 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package whitelist defines exceptions for the vet tool. +package whitelist + +// UnkeyedLiteral is a white list of types in the standard packages +// that are used with unkeyed literals we deem to be acceptable. +var UnkeyedLiteral = map[string]bool{ + // These image and image/color struct types are frozen. We will never add fields to them. + "image/color.Alpha16": true, + "image/color.Alpha": true, + "image/color.CMYK": true, + "image/color.Gray16": true, + "image/color.Gray": true, + "image/color.NRGBA64": true, + "image/color.NRGBA": true, + "image/color.NYCbCrA": true, + "image/color.RGBA64": true, + "image/color.RGBA": true, + "image/color.YCbCr": true, + "image.Point": true, + "image.Rectangle": true, + "image.Uniform": true, + + "unicode.Range16": true, +} diff --git a/go/analysis/passes/vet/lostcancel.go b/go/analysis/passes/vet/lostcancel.go new file mode 100644 index 00000000..f82db470 --- /dev/null +++ b/go/analysis/passes/vet/lostcancel.go @@ -0,0 +1,324 @@ +// +build ignore + +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "cmd/vet/internal/cfg" + "fmt" + "go/ast" + "go/types" + "strconv" +) + +func init() { + register("lostcancel", + "check for failure to call cancelation function returned by context.WithCancel", + checkLostCancel, + funcDecl, funcLit) +} + +const debugLostCancel = false + +var contextPackage = "context" + +// checkLostCancel reports a failure to the call the cancel function +// returned by context.WithCancel, either because the variable was +// assigned to the blank identifier, or because there exists a +// control-flow path from the call to a return statement and that path +// does not "use" the cancel function. Any reference to the variable +// counts as a use, even within a nested function literal. +// +// checkLostCancel analyzes a single named or literal function. +func checkLostCancel(f *File, node ast.Node) { + // Fast path: bypass check if file doesn't use context.WithCancel. + if !hasImport(f.file, contextPackage) { + return + } + + // Maps each cancel variable to its defining ValueSpec/AssignStmt. + cancelvars := make(map[*types.Var]ast.Node) + + // Find the set of cancel vars to analyze. + stack := make([]ast.Node, 0, 32) + ast.Inspect(node, func(n ast.Node) bool { + switch n.(type) { + case *ast.FuncLit: + if len(stack) > 0 { + return false // don't stray into nested functions + } + case nil: + stack = stack[:len(stack)-1] // pop + return true + } + stack = append(stack, n) // push + + // Look for [{AssignStmt,ValueSpec} CallExpr SelectorExpr]: + // + // ctx, cancel := context.WithCancel(...) + // ctx, cancel = context.WithCancel(...) + // var ctx, cancel = context.WithCancel(...) + // + if isContextWithCancel(f, n) && isCall(stack[len(stack)-2]) { + var id *ast.Ident // id of cancel var + stmt := stack[len(stack)-3] + switch stmt := stmt.(type) { + case *ast.ValueSpec: + if len(stmt.Names) > 1 { + id = stmt.Names[1] + } + case *ast.AssignStmt: + if len(stmt.Lhs) > 1 { + id, _ = stmt.Lhs[1].(*ast.Ident) + } + } + if id != nil { + if id.Name == "_" { + f.Badf(id.Pos(), "the cancel function returned by context.%s should be called, not discarded, to avoid a context leak", + n.(*ast.SelectorExpr).Sel.Name) + } else if v, ok := f.pkg.uses[id].(*types.Var); ok { + cancelvars[v] = stmt + } else if v, ok := f.pkg.defs[id].(*types.Var); ok { + cancelvars[v] = stmt + } + } + } + + return true + }) + + if len(cancelvars) == 0 { + return // no need to build CFG + } + + // Tell the CFG builder which functions never return. + info := &types.Info{Uses: f.pkg.uses, Selections: f.pkg.selectors} + mayReturn := func(call *ast.CallExpr) bool { + name := callName(info, call) + return !noReturnFuncs[name] + } + + // Build the CFG. + var g *cfg.CFG + var sig *types.Signature + switch node := node.(type) { + case *ast.FuncDecl: + obj := f.pkg.defs[node.Name] + if obj == nil { + return // type error (e.g. duplicate function declaration) + } + sig, _ = obj.Type().(*types.Signature) + g = cfg.New(node.Body, mayReturn) + case *ast.FuncLit: + sig, _ = f.pkg.types[node.Type].Type.(*types.Signature) + g = cfg.New(node.Body, mayReturn) + } + + // Print CFG. + if debugLostCancel { + fmt.Println(g.Format(f.fset)) + } + + // Examine the CFG for each variable in turn. + // (It would be more efficient to analyze all cancelvars in a + // single pass over the AST, but seldom is there more than one.) + for v, stmt := range cancelvars { + if ret := lostCancelPath(f, g, v, stmt, sig); ret != nil { + lineno := f.fset.Position(stmt.Pos()).Line + f.Badf(stmt.Pos(), "the %s function is not used on all paths (possible context leak)", v.Name()) + f.Badf(ret.Pos(), "this return statement may be reached without using the %s var defined on line %d", v.Name(), lineno) + } + } +} + +func isCall(n ast.Node) bool { _, ok := n.(*ast.CallExpr); return ok } + +func hasImport(f *ast.File, path string) bool { + for _, imp := range f.Imports { + v, _ := strconv.Unquote(imp.Path.Value) + if v == path { + return true + } + } + return false +} + +// isContextWithCancel reports whether n is one of the qualified identifiers +// context.With{Cancel,Timeout,Deadline}. +func isContextWithCancel(f *File, n ast.Node) bool { + if sel, ok := n.(*ast.SelectorExpr); ok { + switch sel.Sel.Name { + case "WithCancel", "WithTimeout", "WithDeadline": + if x, ok := sel.X.(*ast.Ident); ok { + if pkgname, ok := f.pkg.uses[x].(*types.PkgName); ok { + return pkgname.Imported().Path() == contextPackage + } + // Import failed, so we can't check package path. + // Just check the local package name (heuristic). + return x.Name == "context" + } + } + } + return false +} + +// lostCancelPath finds a path through the CFG, from stmt (which defines +// the 'cancel' variable v) to a return statement, that doesn't "use" v. +// If it finds one, it returns the return statement (which may be synthetic). +// sig is the function's type, if known. +func lostCancelPath(f *File, g *cfg.CFG, v *types.Var, stmt ast.Node, sig *types.Signature) *ast.ReturnStmt { + vIsNamedResult := sig != nil && tupleContains(sig.Results(), v) + + // uses reports whether stmts contain a "use" of variable v. + uses := func(f *File, v *types.Var, stmts []ast.Node) bool { + found := false + for _, stmt := range stmts { + ast.Inspect(stmt, func(n ast.Node) bool { + switch n := n.(type) { + case *ast.Ident: + if f.pkg.uses[n] == v { + found = true + } + case *ast.ReturnStmt: + // A naked return statement counts as a use + // of the named result variables. + if n.Results == nil && vIsNamedResult { + found = true + } + } + return !found + }) + } + return found + } + + // blockUses computes "uses" for each block, caching the result. + memo := make(map[*cfg.Block]bool) + blockUses := func(f *File, v *types.Var, b *cfg.Block) bool { + res, ok := memo[b] + if !ok { + res = uses(f, v, b.Nodes) + memo[b] = res + } + return res + } + + // Find the var's defining block in the CFG, + // plus the rest of the statements of that block. + var defblock *cfg.Block + var rest []ast.Node +outer: + for _, b := range g.Blocks { + for i, n := range b.Nodes { + if n == stmt { + defblock = b + rest = b.Nodes[i+1:] + break outer + } + } + } + if defblock == nil { + panic("internal error: can't find defining block for cancel var") + } + + // Is v "used" in the remainder of its defining block? + if uses(f, v, rest) { + return nil + } + + // Does the defining block return without using v? + if ret := defblock.Return(); ret != nil { + return ret + } + + // Search the CFG depth-first for a path, from defblock to a + // return block, in which v is never "used". + seen := make(map[*cfg.Block]bool) + var search func(blocks []*cfg.Block) *ast.ReturnStmt + search = func(blocks []*cfg.Block) *ast.ReturnStmt { + for _, b := range blocks { + if !seen[b] { + seen[b] = true + + // Prune the search if the block uses v. + if blockUses(f, v, b) { + continue + } + + // Found path to return statement? + if ret := b.Return(); ret != nil { + if debugLostCancel { + fmt.Printf("found path to return in block %s\n", b) + } + return ret // found + } + + // Recur + if ret := search(b.Succs); ret != nil { + if debugLostCancel { + fmt.Printf(" from block %s\n", b) + } + return ret + } + } + } + return nil + } + return search(defblock.Succs) +} + +func tupleContains(tuple *types.Tuple, v *types.Var) bool { + for i := 0; i < tuple.Len(); i++ { + if tuple.At(i) == v { + return true + } + } + return false +} + +var noReturnFuncs = map[string]bool{ + "(*testing.common).FailNow": true, + "(*testing.common).Fatal": true, + "(*testing.common).Fatalf": true, + "(*testing.common).Skip": true, + "(*testing.common).SkipNow": true, + "(*testing.common).Skipf": true, + "log.Fatal": true, + "log.Fatalf": true, + "log.Fatalln": true, + "os.Exit": true, + "panic": true, + "runtime.Goexit": true, +} + +// callName returns the canonical name of the builtin, method, or +// function called by call, if known. +func callName(info *types.Info, call *ast.CallExpr) string { + switch fun := call.Fun.(type) { + case *ast.Ident: + // builtin, e.g. "panic" + if obj, ok := info.Uses[fun].(*types.Builtin); ok { + return obj.Name() + } + case *ast.SelectorExpr: + if sel, ok := info.Selections[fun]; ok && sel.Kind() == types.MethodVal { + // method call, e.g. "(*testing.common).Fatal" + meth := sel.Obj() + return fmt.Sprintf("(%s).%s", + meth.Type().(*types.Signature).Recv().Type(), + meth.Name()) + } + if obj, ok := info.Uses[fun.Sel]; ok { + // qualified identifier, e.g. "os.Exit" + return fmt.Sprintf("%s.%s", + obj.Pkg().Path(), + obj.Name()) + } + } + + // function with no name, or defined in missing imported package + return "" +} diff --git a/go/analysis/passes/vet/main.go b/go/analysis/passes/vet/main.go new file mode 100644 index 00000000..60114c5c --- /dev/null +++ b/go/analysis/passes/vet/main.go @@ -0,0 +1,753 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Vet is a simple checker for static errors in Go source code. +// See doc.go for more information. + +package main + +import ( + "bytes" + "encoding/gob" + "encoding/json" + "flag" + "fmt" + "go/ast" + "go/build" + "go/importer" + "go/parser" + "go/printer" + "go/token" + "go/types" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + + "cmd/internal/objabi" +) + +// Important! If you add flags here, make sure to update cmd/go/internal/vet/vetflag.go. + +var ( + verbose = flag.Bool("v", false, "verbose") + source = flag.Bool("source", false, "import from source instead of compiled object files") + tags = flag.String("tags", "", "space-separated list of build tags to apply when parsing") + tagList = []string{} // exploded version of tags flag; set in main + + vcfg vetConfig + mustTypecheck bool +) + +var exitCode = 0 + +// "-all" flag enables all non-experimental checks +var all = triStateFlag("all", unset, "enable all non-experimental checks") + +// Flags to control which individual checks to perform. +var report = map[string]*triState{ + // Only unusual checks are written here. + // Most checks that operate during the AST walk are added by register. + "asmdecl": triStateFlag("asmdecl", unset, "check assembly against Go declarations"), + "buildtags": triStateFlag("buildtags", unset, "check that +build tags are valid"), +} + +// experimental records the flags enabling experimental features. These must be +// requested explicitly; they are not enabled by -all. +var experimental = map[string]bool{} + +// setTrueCount record how many flags are explicitly set to true. +var setTrueCount int + +// dirsRun and filesRun indicate whether the vet is applied to directory or +// file targets. The distinction affects which checks are run. +var dirsRun, filesRun bool + +// includesNonTest indicates whether the vet is applied to non-test targets. +// Certain checks are relevant only if they touch both test and non-test files. +var includesNonTest bool + +// A triState is a boolean that knows whether it has been set to either true or false. +// It is used to identify if a flag appears; the standard boolean flag cannot +// distinguish missing from unset. It also satisfies flag.Value. +type triState int + +const ( + unset triState = iota + setTrue + setFalse +) + +func triStateFlag(name string, value triState, usage string) *triState { + flag.Var(&value, name, usage) + return &value +} + +// triState implements flag.Value, flag.Getter, and flag.boolFlag. +// They work like boolean flags: we can say vet -printf as well as vet -printf=true +func (ts *triState) Get() interface{} { + return *ts == setTrue +} + +func (ts triState) isTrue() bool { + return ts == setTrue +} + +func (ts *triState) Set(value string) error { + b, err := strconv.ParseBool(value) + if err != nil { + return err + } + if b { + *ts = setTrue + setTrueCount++ + } else { + *ts = setFalse + } + return nil +} + +func (ts *triState) String() string { + switch *ts { + case unset: + return "true" // An unset flag will be set by -all, so defaults to true. + case setTrue: + return "true" + case setFalse: + return "false" + } + panic("not reached") +} + +func (ts triState) IsBoolFlag() bool { + return true +} + +// vet tells whether to report errors for the named check, a flag name. +func vet(name string) bool { + return report[name].isTrue() +} + +// setExit sets the value for os.Exit when it is called, later. It +// remembers the highest value. +func setExit(err int) { + if err > exitCode { + exitCode = err + } +} + +var ( + // Each of these vars has a corresponding case in (*File).Visit. + assignStmt *ast.AssignStmt + binaryExpr *ast.BinaryExpr + callExpr *ast.CallExpr + compositeLit *ast.CompositeLit + exprStmt *ast.ExprStmt + forStmt *ast.ForStmt + funcDecl *ast.FuncDecl + funcLit *ast.FuncLit + genDecl *ast.GenDecl + interfaceType *ast.InterfaceType + rangeStmt *ast.RangeStmt + returnStmt *ast.ReturnStmt + structType *ast.StructType + + // checkers is a two-level map. + // The outer level is keyed by a nil pointer, one of the AST vars above. + // The inner level is keyed by checker name. + checkers = make(map[ast.Node]map[string]func(*File, ast.Node)) + pkgCheckers = make(map[string]func(*Package)) + exporters = make(map[string]func() interface{}) +) + +// The exporters data as written to the vetx output file. +type vetxExport struct { + Name string + Data interface{} +} + +// Vet can provide its own "export information" +// about package A to future invocations of vet +// on packages importing A. If B imports A, +// then running "go vet B" actually invokes vet twice: +// first, it runs vet on A, in "vetx-only" mode, which +// skips most checks and only computes export data +// describing A. Then it runs vet on B, making A's vetx +// data available for consultation. The vet of B +// computes vetx data for B in addition to its +// usual vet checks. + +// register registers the named check function, +// to be called with AST nodes of the given types. +// The registered functions are not called in vetx-only mode. +func register(name, usage string, fn func(*File, ast.Node), types ...ast.Node) { + report[name] = triStateFlag(name, unset, usage) + for _, typ := range types { + m := checkers[typ] + if m == nil { + m = make(map[string]func(*File, ast.Node)) + checkers[typ] = m + } + m[name] = fn + } +} + +// registerPkgCheck registers a package-level checking function, +// to be invoked with the whole package being vetted +// before any of the per-node handlers. +// The registered function fn is called even in vetx-only mode +// (see comment above), so fn must take care not to report +// errors when vcfg.VetxOnly is true. +func registerPkgCheck(name string, fn func(*Package)) { + pkgCheckers[name] = fn +} + +// registerExport registers a function to return vetx export data +// that should be saved and provided to future invocations of vet +// when checking packages importing this one. +// The value returned by fn should be nil or else valid to encode using gob. +// Typically a registerExport call is paired with a call to gob.Register. +func registerExport(name string, fn func() interface{}) { + exporters[name] = fn +} + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of vet:\n") + fmt.Fprintf(os.Stderr, "\tvet [flags] directory...\n") + fmt.Fprintf(os.Stderr, "\tvet [flags] files... # Must be a single package\n") + fmt.Fprintf(os.Stderr, "By default, -all is set and all non-experimental checks are run.\n") + fmt.Fprintf(os.Stderr, "For more information run\n") + fmt.Fprintf(os.Stderr, "\tgo doc cmd/vet\n\n") + fmt.Fprintf(os.Stderr, "Flags:\n") + flag.PrintDefaults() + os.Exit(2) +} + +// File is a wrapper for the state of a file used in the parser. +// The parse tree walkers are all methods of this type. +type File struct { + pkg *Package + fset *token.FileSet + name string + content []byte + file *ast.File + b bytes.Buffer // for use by methods + + // Parsed package "foo" when checking package "foo_test" + basePkg *Package + + // The keys are the objects that are receivers of a "String() + // string" method. The value reports whether the method has a + // pointer receiver. + // This is used by the recursiveStringer method in print.go. + stringerPtrs map[*ast.Object]bool + + // Registered checkers to run. + checkers map[ast.Node][]func(*File, ast.Node) + + // Unreachable nodes; can be ignored in shift check. + dead map[ast.Node]bool +} + +func main() { + objabi.AddVersionFlag() + flag.Usage = Usage + flag.Parse() + + // If any flag is set, we run only those checks requested. + // If all flag is set true or if no flags are set true, set all the non-experimental ones + // not explicitly set (in effect, set the "-all" flag). + if setTrueCount == 0 || *all == setTrue { + for name, setting := range report { + if *setting == unset && !experimental[name] { + *setting = setTrue + } + } + } + + // Accept space-separated tags because that matches + // the go command's other subcommands. + // Accept commas because go tool vet traditionally has. + tagList = strings.Fields(strings.ReplaceAll(*tags, ",", " ")) + + initPrintFlags() + initUnusedFlags() + + if flag.NArg() == 0 { + Usage() + } + + // Special case for "go vet" passing an explicit configuration: + // single argument ending in vet.cfg. + // Once we have a more general mechanism for obtaining this + // information from build tools like the go command, + // vet should be changed to use it. This vet.cfg hack is an + // experiment to learn about what form that information should take. + if flag.NArg() == 1 && strings.HasSuffix(flag.Arg(0), "vet.cfg") { + doPackageCfg(flag.Arg(0)) + os.Exit(exitCode) + } + + for _, name := range flag.Args() { + // Is it a directory? + fi, err := os.Stat(name) + if err != nil { + warnf("error walking tree: %s", err) + continue + } + if fi.IsDir() { + dirsRun = true + } else { + filesRun = true + if !strings.HasSuffix(name, "_test.go") { + includesNonTest = true + } + } + } + if dirsRun && filesRun { + Usage() + } + if dirsRun { + for _, name := range flag.Args() { + walkDir(name) + } + os.Exit(exitCode) + } + if doPackage(flag.Args(), nil) == nil { + warnf("no files checked") + } + os.Exit(exitCode) +} + +// prefixDirectory places the directory name on the beginning of each name in the list. +func prefixDirectory(directory string, names []string) { + if directory != "." { + for i, name := range names { + names[i] = filepath.Join(directory, name) + } + } +} + +// vetConfig is the JSON config struct prepared by the Go command. +type vetConfig struct { + Compiler string + Dir string + ImportPath string + GoFiles []string + ImportMap map[string]string + PackageFile map[string]string + Standard map[string]bool + PackageVetx map[string]string // map from import path to vetx data file + VetxOnly bool // only compute vetx output; don't run ordinary checks + VetxOutput string // file where vetx output should be written + + SucceedOnTypecheckFailure bool + + imp types.Importer +} + +func (v *vetConfig) Import(path string) (*types.Package, error) { + if v.imp == nil { + v.imp = importer.For(v.Compiler, v.openPackageFile) + } + if path == "unsafe" { + return v.imp.Import("unsafe") + } + p := v.ImportMap[path] + if p == "" { + return nil, fmt.Errorf("unknown import path %q", path) + } + if v.PackageFile[p] == "" { + if v.Compiler == "gccgo" && v.Standard[path] { + // gccgo doesn't have sources for standard library packages, + // but the importer will do the right thing. + return v.imp.Import(path) + } + return nil, fmt.Errorf("unknown package file for import %q", path) + } + return v.imp.Import(p) +} + +func (v *vetConfig) openPackageFile(path string) (io.ReadCloser, error) { + file := v.PackageFile[path] + if file == "" { + if v.Compiler == "gccgo" && v.Standard[path] { + // The importer knows how to handle this. + return nil, nil + } + // Note that path here has been translated via v.ImportMap, + // unlike in the error in Import above. We prefer the error in + // Import, but it's worth diagnosing this one too, just in case. + return nil, fmt.Errorf("unknown package file for %q", path) + } + f, err := os.Open(file) + if err != nil { + return nil, err + } + return f, nil +} + +// doPackageCfg analyzes a single package described in a config file. +func doPackageCfg(cfgFile string) { + js, err := ioutil.ReadFile(cfgFile) + if err != nil { + errorf("%v", err) + } + if err := json.Unmarshal(js, &vcfg); err != nil { + errorf("parsing vet config %s: %v", cfgFile, err) + } + stdImporter = &vcfg + inittypes() + mustTypecheck = true + doPackage(vcfg.GoFiles, nil) + if vcfg.VetxOutput != "" { + out := make([]vetxExport, 0, len(exporters)) + for name, fn := range exporters { + out = append(out, vetxExport{ + Name: name, + Data: fn(), + }) + } + // Sort the data so that it is consistent across builds. + sort.Slice(out, func(i, j int) bool { + return out[i].Name < out[j].Name + }) + var buf bytes.Buffer + if err := gob.NewEncoder(&buf).Encode(out); err != nil { + errorf("encoding vet output: %v", err) + return + } + if err := ioutil.WriteFile(vcfg.VetxOutput, buf.Bytes(), 0666); err != nil { + errorf("saving vet output: %v", err) + return + } + } +} + +// doPackageDir analyzes the single package found in the directory, if there is one, +// plus a test package, if there is one. +func doPackageDir(directory string) { + context := build.Default + if len(context.BuildTags) != 0 { + warnf("build tags %s previously set", context.BuildTags) + } + context.BuildTags = append(tagList, context.BuildTags...) + + pkg, err := context.ImportDir(directory, 0) + if err != nil { + // If it's just that there are no go source files, that's fine. + if _, nogo := err.(*build.NoGoError); nogo { + return + } + // Non-fatal: we are doing a recursive walk and there may be other directories. + warnf("cannot process directory %s: %s", directory, err) + return + } + var names []string + names = append(names, pkg.GoFiles...) + names = append(names, pkg.CgoFiles...) + names = append(names, pkg.TestGoFiles...) // These are also in the "foo" package. + names = append(names, pkg.SFiles...) + prefixDirectory(directory, names) + basePkg := doPackage(names, nil) + // Is there also a "foo_test" package? If so, do that one as well. + if len(pkg.XTestGoFiles) > 0 { + names = pkg.XTestGoFiles + prefixDirectory(directory, names) + doPackage(names, basePkg) + } +} + +type Package struct { + path string + defs map[*ast.Ident]types.Object + uses map[*ast.Ident]types.Object + implicits map[ast.Node]types.Object + selectors map[*ast.SelectorExpr]*types.Selection + types map[ast.Expr]types.TypeAndValue + spans map[types.Object]Span + files []*File + typesPkg *types.Package +} + +// doPackage analyzes the single package constructed from the named files. +// It returns the parsed Package or nil if none of the files have been checked. +func doPackage(names []string, basePkg *Package) *Package { + var files []*File + var astFiles []*ast.File + fs := token.NewFileSet() + for _, name := range names { + data, err := ioutil.ReadFile(name) + if err != nil { + // Warn but continue to next package. + warnf("%s: %s", name, err) + return nil + } + var parsedFile *ast.File + if strings.HasSuffix(name, ".go") { + parsedFile, err = parser.ParseFile(fs, name, data, parser.ParseComments) + if err != nil { + warnf("%s: %s", name, err) + return nil + } + astFiles = append(astFiles, parsedFile) + } + file := &File{ + fset: fs, + content: data, + name: name, + file: parsedFile, + dead: make(map[ast.Node]bool), + } + files = append(files, file) + } + if len(astFiles) == 0 { + return nil + } + pkg := new(Package) + pkg.path = astFiles[0].Name.Name + pkg.files = files + // Type check the package. + errs := pkg.check(fs, astFiles) + if errs != nil { + if vcfg.SucceedOnTypecheckFailure { + os.Exit(0) + } + if *verbose || mustTypecheck { + for _, err := range errs { + fmt.Fprintf(os.Stderr, "%v\n", err) + } + if mustTypecheck { + // This message could be silenced, and we could just exit, + // but it might be helpful at least at first to make clear that the + // above errors are coming from vet and not the compiler + // (they often look like compiler errors, such as "declared but not used"). + errorf("typecheck failures") + } + } + } + + // Check. + for _, file := range files { + file.pkg = pkg + file.basePkg = basePkg + } + for name, fn := range pkgCheckers { + if vet(name) { + fn(pkg) + } + } + if vcfg.VetxOnly { + return pkg + } + + chk := make(map[ast.Node][]func(*File, ast.Node)) + for typ, set := range checkers { + for name, fn := range set { + if vet(name) { + chk[typ] = append(chk[typ], fn) + } + } + } + for _, file := range files { + checkBuildTag(file) + file.checkers = chk + if file.file != nil { + file.walkFile(file.name, file.file) + } + } + return pkg +} + +func visit(path string, f os.FileInfo, err error) error { + if err != nil { + warnf("walk error: %s", err) + return err + } + // One package per directory. Ignore the files themselves. + if !f.IsDir() { + return nil + } + doPackageDir(path) + return nil +} + +func (pkg *Package) hasFileWithSuffix(suffix string) bool { + for _, f := range pkg.files { + if strings.HasSuffix(f.name, suffix) { + return true + } + } + return false +} + +// walkDir recursively walks the tree looking for Go packages. +func walkDir(root string) { + filepath.Walk(root, visit) +} + +// errorf formats the error to standard error, adding program +// identification and a newline, and exits. +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + os.Exit(2) +} + +// warnf formats the error to standard error, adding program +// identification and a newline, but does not exit. +func warnf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + setExit(1) +} + +// Println is fmt.Println guarded by -v. +func Println(args ...interface{}) { + if !*verbose { + return + } + fmt.Println(args...) +} + +// Printf is fmt.Printf guarded by -v. +func Printf(format string, args ...interface{}) { + if !*verbose { + return + } + fmt.Printf(format+"\n", args...) +} + +// Bad reports an error and sets the exit code.. +func (f *File) Bad(pos token.Pos, args ...interface{}) { + f.Warn(pos, args...) + setExit(1) +} + +// Badf reports a formatted error and sets the exit code. +func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { + f.Warnf(pos, format, args...) + setExit(1) +} + +// loc returns a formatted representation of the position. +func (f *File) loc(pos token.Pos) string { + if pos == token.NoPos { + return "" + } + // Do not print columns. Because the pos often points to the start of an + // expression instead of the inner part with the actual error, the + // precision can mislead. + posn := f.fset.Position(pos) + return fmt.Sprintf("%s:%d", posn.Filename, posn.Line) +} + +// locPrefix returns a formatted representation of the position for use as a line prefix. +func (f *File) locPrefix(pos token.Pos) string { + if pos == token.NoPos { + return "" + } + return fmt.Sprintf("%s: ", f.loc(pos)) +} + +// Warn reports an error but does not set the exit code. +func (f *File) Warn(pos token.Pos, args ...interface{}) { + fmt.Fprintf(os.Stderr, "%s%s", f.locPrefix(pos), fmt.Sprintln(args...)) +} + +// Warnf reports a formatted error but does not set the exit code. +func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "%s%s\n", f.locPrefix(pos), fmt.Sprintf(format, args...)) +} + +// walkFile walks the file's tree. +func (f *File) walkFile(name string, file *ast.File) { + Println("Checking file", name) + ast.Walk(f, file) +} + +// Visit implements the ast.Visitor interface. +func (f *File) Visit(node ast.Node) ast.Visitor { + f.updateDead(node) + var key ast.Node + switch node.(type) { + case *ast.AssignStmt: + key = assignStmt + case *ast.BinaryExpr: + key = binaryExpr + case *ast.CallExpr: + key = callExpr + case *ast.CompositeLit: + key = compositeLit + case *ast.ExprStmt: + key = exprStmt + case *ast.ForStmt: + key = forStmt + case *ast.FuncDecl: + key = funcDecl + case *ast.FuncLit: + key = funcLit + case *ast.GenDecl: + key = genDecl + case *ast.InterfaceType: + key = interfaceType + case *ast.RangeStmt: + key = rangeStmt + case *ast.ReturnStmt: + key = returnStmt + case *ast.StructType: + key = structType + } + for _, fn := range f.checkers[key] { + fn(f, node) + } + return f +} + +// gofmt returns a string representation of the expression. +func (f *File) gofmt(x ast.Expr) string { + f.b.Reset() + printer.Fprint(&f.b, f.fset, x) + return f.b.String() +} + +// imported[path][key] is previously written export data. +var imported = make(map[string]map[string]interface{}) + +// readVetx reads export data written by a previous +// invocation of vet on an imported package (path). +// The key is the name passed to registerExport +// when the data was originally generated. +// readVetx returns nil if the data is unavailable. +func readVetx(path, key string) interface{} { + if path == "unsafe" || vcfg.ImportPath == "" { + return nil + } + m := imported[path] + if m == nil { + file := vcfg.PackageVetx[path] + if file == "" { + return nil + } + data, err := ioutil.ReadFile(file) + if err != nil { + return nil + } + var out []vetxExport + err = gob.NewDecoder(bytes.NewReader(data)).Decode(&out) + if err != nil { + return nil + } + m = make(map[string]interface{}) + for _, x := range out { + m[x.Name] = x.Data + } + imported[path] = m + } + return m[key] +} diff --git a/go/analysis/passes/vet/method.go b/go/analysis/passes/vet/method.go new file mode 100644 index 00000000..38efd9b0 --- /dev/null +++ b/go/analysis/passes/vet/method.go @@ -0,0 +1,181 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code to check canonical methods. + +package main + +import ( + "fmt" + "go/ast" + "go/printer" + "strings" +) + +func init() { + register("methods", + "check that canonically named methods are canonically defined", + checkCanonicalMethod, + funcDecl, interfaceType) +} + +type MethodSig struct { + args []string + results []string +} + +// canonicalMethods lists the input and output types for Go methods +// that are checked using dynamic interface checks. Because the +// checks are dynamic, such methods would not cause a compile error +// if they have the wrong signature: instead the dynamic check would +// fail, sometimes mysteriously. If a method is found with a name listed +// here but not the input/output types listed here, vet complains. +// +// A few of the canonical methods have very common names. +// For example, a type might implement a Scan method that +// has nothing to do with fmt.Scanner, but we still want to check +// the methods that are intended to implement fmt.Scanner. +// To do that, the arguments that have a = prefix are treated as +// signals that the canonical meaning is intended: if a Scan +// method doesn't have a fmt.ScanState as its first argument, +// we let it go. But if it does have a fmt.ScanState, then the +// rest has to match. +var canonicalMethods = map[string]MethodSig{ + // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict + "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter + "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder + "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder + "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler + "MarshalXML": {[]string{"*xml.Encoder", "xml.StartElement"}, []string{"error"}}, // xml.Marshaler + "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader + "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom + "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader + "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner + "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker + "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler + "UnmarshalXML": {[]string{"*xml.Decoder", "xml.StartElement"}, []string{"error"}}, // xml.Unmarshaler + "UnreadByte": {[]string{}, []string{"error"}}, + "UnreadRune": {[]string{}, []string{"error"}}, + "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer) + "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo +} + +func checkCanonicalMethod(f *File, node ast.Node) { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Recv != nil { + canonicalMethod(f, n.Name, n.Type) + } + case *ast.InterfaceType: + for _, field := range n.Methods.List { + for _, id := range field.Names { + canonicalMethod(f, id, field.Type.(*ast.FuncType)) + } + } + } +} + +func canonicalMethod(f *File, id *ast.Ident, t *ast.FuncType) { + // Expected input/output. + expect, ok := canonicalMethods[id.Name] + if !ok { + return + } + + // Actual input/output + args := typeFlatten(t.Params.List) + var results []ast.Expr + if t.Results != nil { + results = typeFlatten(t.Results.List) + } + + // Do the =s (if any) all match? + if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") { + return + } + + // Everything must match. + if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") { + expectFmt := id.Name + "(" + argjoin(expect.args) + ")" + if len(expect.results) == 1 { + expectFmt += " " + argjoin(expect.results) + } else if len(expect.results) > 1 { + expectFmt += " (" + argjoin(expect.results) + ")" + } + + f.b.Reset() + if err := printer.Fprint(&f.b, f.fset, t); err != nil { + fmt.Fprintf(&f.b, "<%s>", err) + } + actual := f.b.String() + actual = strings.TrimPrefix(actual, "func") + actual = id.Name + actual + + f.Badf(id.Pos(), "method %s should have signature %s", actual, expectFmt) + } +} + +func argjoin(x []string) string { + y := make([]string, len(x)) + for i, s := range x { + if s[0] == '=' { + s = s[1:] + } + y[i] = s + } + return strings.Join(y, ", ") +} + +// Turn parameter list into slice of types +// (in the ast, types are Exprs). +// Have to handle f(int, bool) and f(x, y, z int) +// so not a simple 1-to-1 conversion. +func typeFlatten(l []*ast.Field) []ast.Expr { + var t []ast.Expr + for _, f := range l { + if len(f.Names) == 0 { + t = append(t, f.Type) + continue + } + for range f.Names { + t = append(t, f.Type) + } + } + return t +} + +// Does each type in expect with the given prefix match the corresponding type in actual? +func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool { + for i, x := range expect { + if !strings.HasPrefix(x, prefix) { + continue + } + if i >= len(actual) { + return false + } + if !f.matchParamType(x, actual[i]) { + return false + } + } + if prefix == "" && len(actual) > len(expect) { + return false + } + return true +} + +// Does this one type match? +func (f *File) matchParamType(expect string, actual ast.Expr) bool { + expect = strings.TrimPrefix(expect, "=") + // Strip package name if we're in that package. + if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' { + expect = expect[n+1:] + } + + // Overkill but easy. + f.b.Reset() + printer.Fprint(&f.b, f.fset, actual) + return f.b.String() == expect +} diff --git a/go/analysis/passes/vet/nilfunc.go b/go/analysis/passes/vet/nilfunc.go new file mode 100644 index 00000000..4d5cfe39 --- /dev/null +++ b/go/analysis/passes/vet/nilfunc.go @@ -0,0 +1,69 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for useless function comparisons. +A useless comparison is one like f == nil as opposed to f() == nil. +*/ + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("nilfunc", + "check for comparisons between functions and nil", + checkNilFuncComparison, + binaryExpr) +} + +func checkNilFuncComparison(f *File, node ast.Node) { + e := node.(*ast.BinaryExpr) + + // Only want == or != comparisons. + if e.Op != token.EQL && e.Op != token.NEQ { + return + } + + // Only want comparisons with a nil identifier on one side. + var e2 ast.Expr + switch { + case f.isNil(e.X): + e2 = e.Y + case f.isNil(e.Y): + e2 = e.X + default: + return + } + + // Only want identifiers or selector expressions. + var obj types.Object + switch v := e2.(type) { + case *ast.Ident: + obj = f.pkg.uses[v] + case *ast.SelectorExpr: + obj = f.pkg.uses[v.Sel] + default: + return + } + + // Only want functions. + if _, ok := obj.(*types.Func); !ok { + return + } + + f.Badf(e.Pos(), "comparison of function %v %v nil is always %v", obj.Name(), e.Op, e.Op == token.NEQ) +} + +// isNil reports whether the provided expression is the built-in nil +// identifier. +func (f *File) isNil(e ast.Expr) bool { + return f.pkg.types[e].Type == types.Typ[types.UntypedNil] +} diff --git a/go/analysis/passes/vet/print.go b/go/analysis/passes/vet/print.go new file mode 100644 index 00000000..f980dcb5 --- /dev/null +++ b/go/analysis/passes/vet/print.go @@ -0,0 +1,1072 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the printf-checker. + +package main + +import ( + "bytes" + "encoding/gob" + "flag" + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" +) + +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") + +func init() { + register("printf", + "check printf-like invocations", + checkFmtPrintfCall, + funcDecl, callExpr) + registerPkgCheck("printf", findPrintfLike) + registerExport("printf", exportPrintfLike) + gob.Register([]printfExport(nil)) +} + +func initPrintFlags() { + if *printfuncs == "" { + return + } + for _, name := range strings.Split(*printfuncs, ",") { + if len(name) == 0 { + flag.Usage() + } + + // Backwards compatibility: skip optional first argument + // index after the colon. + if colon := strings.LastIndex(name, ":"); colon > 0 { + name = name[:colon] + } + + if !strings.Contains(name, ".") { + name = strings.ToLower(name) + } + isPrint[name] = true + } +} + +var localPrintfLike = make(map[string]int) + +type printfExport struct { + Name string + Kind int +} + +// printfImported maps from package name to the printf vet data +// exported by that package. +var printfImported = make(map[string]map[string]int) + +type printfWrapper struct { + name string + fn *ast.FuncDecl + format *ast.Field + args *ast.Field + callers []printfCaller + failed bool // if true, not a printf wrapper +} + +type printfCaller struct { + w *printfWrapper + call *ast.CallExpr +} + +// maybePrintfWrapper decides whether decl (a declared function) may be a wrapper +// around a fmt.Printf or fmt.Print function. If so it returns a printfWrapper +// function describing the declaration. Later processing will analyze the +// graph of potential printf wrappers to pick out the ones that are true wrappers. +// A function may be a Printf or Print wrapper if its last argument is ...interface{}. +// If the next-to-last argument is a string, then this may be a Printf wrapper. +// Otherwise it may be a Print wrapper. +func maybePrintfWrapper(decl ast.Decl) *printfWrapper { + // Look for functions with final argument type ...interface{}. + fn, ok := decl.(*ast.FuncDecl) + if !ok || fn.Body == nil { + return nil + } + name := fn.Name.Name + if fn.Recv != nil { + // For (*T).Name or T.name, use "T.name". + rcvr := fn.Recv.List[0].Type + if ptr, ok := rcvr.(*ast.StarExpr); ok { + rcvr = ptr.X + } + id, ok := rcvr.(*ast.Ident) + if !ok { + return nil + } + name = id.Name + "." + name + } + params := fn.Type.Params.List + if len(params) == 0 { + return nil + } + args := params[len(params)-1] + if len(args.Names) != 1 { + return nil + } + ddd, ok := args.Type.(*ast.Ellipsis) + if !ok { + return nil + } + iface, ok := ddd.Elt.(*ast.InterfaceType) + if !ok || len(iface.Methods.List) > 0 { + return nil + } + var format *ast.Field + if len(params) >= 2 { + p := params[len(params)-2] + if len(p.Names) == 1 { + if id, ok := p.Type.(*ast.Ident); ok && id.Name == "string" { + format = p + } + } + } + + return &printfWrapper{ + name: name, + fn: fn, + format: format, + args: args, + } +} + +// findPrintfLike scans the entire package to find printf-like functions. +func findPrintfLike(pkg *Package) { + if vcfg.ImportPath == "" { // no type or vetx information; don't bother + return + } + + // Gather potential wrappesr and call graph between them. + byName := make(map[string]*printfWrapper) + var wrappers []*printfWrapper + for _, file := range pkg.files { + if file.file == nil { + continue + } + for _, decl := range file.file.Decls { + w := maybePrintfWrapper(decl) + if w == nil { + continue + } + byName[w.name] = w + wrappers = append(wrappers, w) + } + } + + // Walk the graph to figure out which are really printf wrappers. + for _, w := range wrappers { + // Scan function for calls that could be to other printf-like functions. + ast.Inspect(w.fn.Body, func(n ast.Node) bool { + if w.failed { + return false + } + + // TODO: Relax these checks; issue 26555. + if assign, ok := n.(*ast.AssignStmt); ok { + for _, lhs := range assign.Lhs { + if match(lhs, w.format) || match(lhs, w.args) { + // Modifies the format + // string or args in + // some way, so not a + // simple wrapper. + w.failed = true + return false + } + } + } + if un, ok := n.(*ast.UnaryExpr); ok && un.Op == token.AND { + if match(un.X, w.format) || match(un.X, w.args) { + // Taking the address of the + // format string or args, + // so not a simple wrapper. + w.failed = true + return false + } + } + + call, ok := n.(*ast.CallExpr) + if !ok || len(call.Args) == 0 || !match(call.Args[len(call.Args)-1], w.args) { + return true + } + + pkgpath, name, kind := printfNameAndKind(pkg, call.Fun) + if kind != 0 { + checkPrintfFwd(pkg, w, call, kind) + return true + } + + // If the call is to another function in this package, + // maybe we will find out it is printf-like later. + // Remember this call for later checking. + if pkgpath == "" && byName[name] != nil { + callee := byName[name] + callee.callers = append(callee.callers, printfCaller{w, call}) + } + + return true + }) + } +} + +func match(arg ast.Expr, param *ast.Field) bool { + id, ok := arg.(*ast.Ident) + return ok && id.Obj != nil && id.Obj.Decl == param +} + +const ( + kindPrintf = 1 + kindPrint = 2 +) + +// printfLike reports whether a call to fn should be considered a call to a printf-like function. +// It returns 0 (indicating not a printf-like function), kindPrintf, or kindPrint. +func printfLike(pkg *Package, fn ast.Expr, byName map[string]*printfWrapper) int { + if id, ok := fn.(*ast.Ident); ok && id.Obj != nil { + if w := byName[id.Name]; w != nil && id.Obj.Decl == w.fn { + // Found call to function in same package. + return localPrintfLike[id.Name] + } + } + if sel, ok := fn.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == "fmt" && strings.Contains(sel.Sel.Name, "rint") { + if strings.HasSuffix(sel.Sel.Name, "f") { + return kindPrintf + } + return kindPrint + } + } + return 0 +} + +// checkPrintfFwd checks that a printf-forwarding wrapper is forwarding correctly. +// It diagnoses writing fmt.Printf(format, args) instead of fmt.Printf(format, args...). +func checkPrintfFwd(pkg *Package, w *printfWrapper, call *ast.CallExpr, kind int) { + matched := kind == kindPrint || + kind == kindPrintf && len(call.Args) >= 2 && match(call.Args[len(call.Args)-2], w.format) + if !matched { + return + } + + if !call.Ellipsis.IsValid() { + typ, ok := pkg.types[call.Fun].Type.(*types.Signature) + if !ok { + return + } + if len(call.Args) > typ.Params().Len() { + // If we're passing more arguments than what the + // print/printf function can take, adding an ellipsis + // would break the program. For example: + // + // func foo(arg1 string, arg2 ...interface{} { + // fmt.Printf("%s %v", arg1, arg2) + // } + return + } + if !vcfg.VetxOnly { + desc := "printf" + if kind == kindPrint { + desc = "print" + } + pkg.files[0].Badf(call.Pos(), "missing ... in args forwarded to %s-like function", desc) + } + return + } + name := w.name + if localPrintfLike[name] == 0 { + localPrintfLike[name] = kind + for _, caller := range w.callers { + checkPrintfFwd(pkg, caller.w, caller.call, kind) + } + } +} + +func exportPrintfLike() interface{} { + out := make([]printfExport, 0, len(localPrintfLike)) + for name, kind := range localPrintfLike { + out = append(out, printfExport{ + Name: name, + Kind: kind, + }) + } + sort.Slice(out, func(i, j int) bool { + return out[i].Name < out[j].Name + }) + return out +} + +// isPrint records the print functions. +// If a key ends in 'f' then it is assumed to be a formatted print. +var isPrint = map[string]bool{ + "fmt.Errorf": true, + "fmt.Fprint": true, + "fmt.Fprintf": true, + "fmt.Fprintln": true, + "fmt.Print": true, + "fmt.Printf": true, + "fmt.Println": true, + "fmt.Sprint": true, + "fmt.Sprintf": true, + "fmt.Sprintln": true, + + // testing.B, testing.T not auto-detected + // because the methods are picked up by embedding. + "testing.B.Error": true, + "testing.B.Errorf": true, + "testing.B.Fatal": true, + "testing.B.Fatalf": true, + "testing.B.Log": true, + "testing.B.Logf": true, + "testing.B.Skip": true, + "testing.B.Skipf": true, + "testing.T.Error": true, + "testing.T.Errorf": true, + "testing.T.Fatal": true, + "testing.T.Fatalf": true, + "testing.T.Log": true, + "testing.T.Logf": true, + "testing.T.Skip": true, + "testing.T.Skipf": true, + + // testing.TB is an interface, so can't detect wrapping. + "testing.TB.Error": true, + "testing.TB.Errorf": true, + "testing.TB.Fatal": true, + "testing.TB.Fatalf": true, + "testing.TB.Log": true, + "testing.TB.Logf": true, + "testing.TB.Skip": true, + "testing.TB.Skipf": true, +} + +// formatString returns the format string argument and its index within +// the given printf-like call expression. +// +// The last parameter before variadic arguments is assumed to be +// a format string. +// +// The first string literal or string constant is assumed to be a format string +// if the call's signature cannot be determined. +// +// If it cannot find any format string parameter, it returns ("", -1). +func formatString(f *File, call *ast.CallExpr) (format string, idx int) { + typ := f.pkg.types[call.Fun].Type + if typ != nil { + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return "", -1 + } + idx := sig.Params().Len() - 2 + if idx < 0 { + // Skip checking variadic functions without + // fixed arguments. + return "", -1 + } + s, ok := stringConstantArg(f, call, idx) + if !ok { + // The last argument before variadic args isn't a string. + return "", -1 + } + return s, idx + } + } + + // Cannot determine call's signature. Fall back to scanning for the first + // string constant in the call. + for idx := range call.Args { + if s, ok := stringConstantArg(f, call, idx); ok { + return s, idx + } + if f.pkg.types[call.Args[idx]].Type == types.Typ[types.String] { + // Skip checking a call with a non-constant format + // string argument, since its contents are unavailable + // for validation. + return "", -1 + } + } + return "", -1 +} + +// stringConstantArg returns call's string constant argument at the index idx. +// +// ("", false) is returned if call's argument at the index idx isn't a string +// constant. +func stringConstantArg(f *File, call *ast.CallExpr, idx int) (string, bool) { + if idx >= len(call.Args) { + return "", false + } + arg := call.Args[idx] + lit := f.pkg.types[arg].Value + if lit != nil && lit.Kind() == constant.String { + return constant.StringVal(lit), true + } + return "", false +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func checkFmtPrintfCall(f *File, node ast.Node) { + if f.pkg.typesPkg == nil { + // This check now requires type information. + return + } + + if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) { + // Remember we saw this. + if f.stringerPtrs == nil { + f.stringerPtrs = make(map[*ast.Object]bool) + } + if l := d.Recv.List; len(l) == 1 { + if n := l[0].Names; len(n) == 1 { + typ := f.pkg.types[l[0].Type] + _, ptrRecv := typ.Type.(*types.Pointer) + f.stringerPtrs[n[0].Obj] = ptrRecv + } + } + return + } + + call, ok := node.(*ast.CallExpr) + if !ok { + return + } + + // Construct name like pkg.Printf or pkg.Type.Printf for lookup. + _, name, kind := printfNameAndKind(f.pkg, call.Fun) + if kind == kindPrintf { + f.checkPrintf(call, name) + } + if kind == kindPrint { + f.checkPrint(call, name) + } +} + +func printfName(pkg *Package, called ast.Expr) (pkgpath, name string) { + switch x := called.(type) { + case *ast.Ident: + if fn, ok := pkg.uses[x].(*types.Func); ok { + if fn.Pkg() == nil || fn.Pkg() == pkg.typesPkg { + pkgpath = "" + } else { + pkgpath = fn.Pkg().Path() + } + return pkgpath, x.Name + } + + case *ast.SelectorExpr: + // Check for "fmt.Printf". + if id, ok := x.X.(*ast.Ident); ok { + if pkgName, ok := pkg.uses[id].(*types.PkgName); ok { + return pkgName.Imported().Path(), x.Sel.Name + } + } + + // Check for t.Logf where t is a *testing.T. + if sel := pkg.selectors[x]; sel != nil { + recv := sel.Recv() + if p, ok := recv.(*types.Pointer); ok { + recv = p.Elem() + } + if named, ok := recv.(*types.Named); ok { + obj := named.Obj() + if obj.Pkg() == nil || obj.Pkg() == pkg.typesPkg { + pkgpath = "" + } else { + pkgpath = obj.Pkg().Path() + } + return pkgpath, obj.Name() + "." + x.Sel.Name + } + } + } + return "", "" +} + +func printfNameAndKind(pkg *Package, called ast.Expr) (pkgpath, name string, kind int) { + pkgpath, name = printfName(pkg, called) + if name == "" { + return pkgpath, name, 0 + } + + if pkgpath == "" { + kind = localPrintfLike[name] + } else if m, ok := printfImported[pkgpath]; ok { + kind = m[name] + } else { + var m map[string]int + if out, ok := readVetx(pkgpath, "printf").([]printfExport); ok { + m = make(map[string]int) + for _, x := range out { + m[x.Name] = x.Kind + } + } + printfImported[pkgpath] = m + kind = m[name] + } + + if kind == 0 { + _, ok := isPrint[pkgpath+"."+name] + if !ok { + // Next look up just "printf", for use with -printfuncs. + short := name[strings.LastIndex(name, ".")+1:] + _, ok = isPrint[strings.ToLower(short)] + } + if ok { + if strings.HasSuffix(name, "f") { + kind = kindPrintf + } else { + kind = kindPrint + } + } + } + return pkgpath, name, kind +} + +// isStringer returns true if the provided declaration is a "String() string" +// method, an implementation of fmt.Stringer. +func isStringer(f *File, d *ast.FuncDecl) bool { + return d.Recv != nil && d.Name.Name == "String" && d.Type.Results != nil && + len(d.Type.Params.List) == 0 && len(d.Type.Results.List) == 1 && + f.pkg.types[d.Type.Results.List[0].Type].Type == types.Typ[types.String] +} + +// isFormatter reports whether t satisfies fmt.Formatter. +// Unlike fmt.Stringer, it's impossible to satisfy fmt.Formatter without importing fmt. +func (f *File) isFormatter(t types.Type) bool { + return formatterType != nil && types.Implements(t, formatterType) +} + +// formatState holds the parsed representation of a printf directive such as "%3.*[4]d". +// It is constructed by parsePrintfVerb. +type formatState struct { + verb rune // the format verb: 'd' for "%d" + format string // the full format directive from % through verb, "%.3d". + name string // Printf, Sprintf etc. + flags []byte // the list of # + etc. + argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call + firstArg int // Index of first argument after the format in the Printf call. + // Used only during parse. + file *File + call *ast.CallExpr + argNum int // Which argument we're expecting to format now. + hasIndex bool // Whether the argument is indexed. + indexPending bool // Whether we have an indexed argument that has not resolved. + nbytes int // number of bytes of the format string consumed. +} + +// checkPrintf checks a call to a formatted print routine such as Printf. +func (f *File) checkPrintf(call *ast.CallExpr, name string) { + format, idx := formatString(f, call) + if idx < 0 { + if *verbose { + f.Warn(call.Pos(), "can't check non-constant format in call to", name) + } + return + } + + firstArg := idx + 1 // Arguments are immediately after format string. + if !strings.Contains(format, "%") { + if len(call.Args) > firstArg { + f.Badf(call.Pos(), "%s call has arguments but no formatting directives", name) + } + return + } + // Hard part: check formats against args. + argNum := firstArg + maxArgNum := firstArg + anyIndex := false + for i, w := 0, 0; i < len(format); i += w { + w = 1 + if format[i] != '%' { + continue + } + state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum) + if state == nil { + return + } + w = len(state.format) + if !f.okPrintfArg(call, state) { // One error per format is enough. + return + } + if state.hasIndex { + anyIndex = true + } + if len(state.argNums) > 0 { + // Continue with the next sequential argument. + argNum = state.argNums[len(state.argNums)-1] + 1 + } + for _, n := range state.argNums { + if n >= maxArgNum { + maxArgNum = n + 1 + } + } + } + // Dotdotdot is hard. + if call.Ellipsis.IsValid() && maxArgNum >= len(call.Args)-1 { + return + } + // If any formats are indexed, extra arguments are ignored. + if anyIndex { + return + } + // There should be no leftover arguments. + if maxArgNum != len(call.Args) { + expect := maxArgNum - firstArg + numArgs := len(call.Args) - firstArg + f.Badf(call.Pos(), "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg")) + } +} + +// parseFlags accepts any printf flags. +func (s *formatState) parseFlags() { + for s.nbytes < len(s.format) { + switch c := s.format[s.nbytes]; c { + case '#', '0', '+', '-', ' ': + s.flags = append(s.flags, c) + s.nbytes++ + default: + return + } + } +} + +// scanNum advances through a decimal number if present. +func (s *formatState) scanNum() { + for ; s.nbytes < len(s.format); s.nbytes++ { + c := s.format[s.nbytes] + if c < '0' || '9' < c { + return + } + } +} + +// parseIndex scans an index expression. It returns false if there is a syntax error. +func (s *formatState) parseIndex() bool { + if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' { + return true + } + // Argument index present. + s.nbytes++ // skip '[' + start := s.nbytes + s.scanNum() + ok := true + if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' { + ok = false + s.nbytes = strings.Index(s.format, "]") + if s.nbytes < 0 { + s.file.Badf(s.call.Pos(), "%s format %s is missing closing ]", s.name, s.format) + return false + } + } + arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32) + if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) { + s.file.Badf(s.call.Pos(), "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes]) + return false + } + s.nbytes++ // skip ']' + arg := int(arg32) + arg += s.firstArg - 1 // We want to zero-index the actual arguments. + s.argNum = arg + s.hasIndex = true + s.indexPending = true + return true +} + +// parseNum scans a width or precision (or *). It returns false if there's a bad index expression. +func (s *formatState) parseNum() bool { + if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' { + if s.indexPending { // Absorb it. + s.indexPending = false + } + s.nbytes++ + s.argNums = append(s.argNums, s.argNum) + s.argNum++ + } else { + s.scanNum() + } + return true +} + +// parsePrecision scans for a precision. It returns false if there's a bad index expression. +func (s *formatState) parsePrecision() bool { + // If there's a period, there may be a precision. + if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' { + s.flags = append(s.flags, '.') // Treat precision as a flag. + s.nbytes++ + if !s.parseIndex() { + return false + } + if !s.parseNum() { + return false + } + } + return true +} + +// parsePrintfVerb looks the formatting directive that begins the format string +// and returns a formatState that encodes what the directive wants, without looking +// at the actual arguments present in the call. The result is nil if there is an error. +func (f *File) parsePrintfVerb(call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState { + state := &formatState{ + format: format, + name: name, + flags: make([]byte, 0, 5), + argNum: argNum, + argNums: make([]int, 0, 1), + nbytes: 1, // There's guaranteed to be a percent sign. + firstArg: firstArg, + file: f, + call: call, + } + // There may be flags. + state.parseFlags() + // There may be an index. + if !state.parseIndex() { + return nil + } + // There may be a width. + if !state.parseNum() { + return nil + } + // There may be a precision. + if !state.parsePrecision() { + return nil + } + // Now a verb, possibly prefixed by an index (which we may already have). + if !state.indexPending && !state.parseIndex() { + return nil + } + if state.nbytes == len(state.format) { + f.Badf(call.Pos(), "%s format %s is missing verb at end of string", name, state.format) + return nil + } + verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:]) + state.verb = verb + state.nbytes += w + if verb != '%' { + state.argNums = append(state.argNums, state.argNum) + } + state.format = state.format[:state.nbytes] + return state +} + +// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask. +type printfArgType int + +const ( + argBool printfArgType = 1 << iota + argInt + argRune + argString + argFloat + argComplex + argPointer + anyType printfArgType = ^0 +) + +type printVerb struct { + verb rune // User may provide verb through Formatter; could be a rune. + flags string // known flags are all ASCII + typ printfArgType +} + +// Common flag sets for printf verbs. +const ( + noFlag = "" + numFlag = " -+.0" + sharpNumFlag = " -+.0#" + allFlags = " -+.0#" +) + +// printVerbs identifies which flags are known to printf for each verb. +var printVerbs = []printVerb{ + // '-' is a width modifier, always valid. + // '.' is a precision for float, max width for strings. + // '+' is required sign for numbers, Go format for %v. + // '#' is alternate format for several verbs. + // ' ' is spacer for numbers + {'%', noFlag, 0}, + {'b', numFlag, argInt | argFloat | argComplex}, + {'c', "-", argRune | argInt}, + {'d', numFlag, argInt | argPointer}, + {'e', sharpNumFlag, argFloat | argComplex}, + {'E', sharpNumFlag, argFloat | argComplex}, + {'f', sharpNumFlag, argFloat | argComplex}, + {'F', sharpNumFlag, argFloat | argComplex}, + {'g', sharpNumFlag, argFloat | argComplex}, + {'G', sharpNumFlag, argFloat | argComplex}, + {'o', sharpNumFlag, argInt}, + {'p', "-#", argPointer}, + {'q', " -+.0#", argRune | argInt | argString}, + {'s', " -+.0", argString}, + {'t', "-", argBool}, + {'T', "-", anyType}, + {'U', "-#", argRune | argInt}, + {'v', allFlags, anyType}, + {'x', sharpNumFlag, argRune | argInt | argString | argPointer}, + {'X', sharpNumFlag, argRune | argInt | argString | argPointer}, +} + +// okPrintfArg compares the formatState to the arguments actually present, +// reporting any discrepancies it can discern. If the final argument is ellipsissed, +// there's little it can do for that. +func (f *File) okPrintfArg(call *ast.CallExpr, state *formatState) (ok bool) { + var v printVerb + found := false + // Linear scan is fast enough for a small list. + for _, v = range printVerbs { + if v.verb == state.verb { + found = true + break + } + } + + // Does current arg implement fmt.Formatter? + formatter := false + if state.argNum < len(call.Args) { + if tv, ok := f.pkg.types[call.Args[state.argNum]]; ok { + formatter = f.isFormatter(tv.Type) + } + } + + if !formatter { + if !found { + f.Badf(call.Pos(), "%s format %s has unknown verb %c", state.name, state.format, state.verb) + return false + } + for _, flag := range state.flags { + // TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11. + // See issues 23598 and 23605. + if flag == '0' { + continue + } + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "%s format %s has unrecognized flag %c", state.name, state.format, flag) + return false + } + } + } + // Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all + // but the final arg must be an integer. + trueArgs := 1 + if state.verb == '%' { + trueArgs = 0 + } + nargs := len(state.argNums) + for i := 0; i < nargs-trueArgs; i++ { + argNum := state.argNums[i] + if !f.argCanBeChecked(call, i, state) { + return + } + arg := call.Args[argNum] + if !f.matchArgType(argInt, nil, arg) { + f.Badf(call.Pos(), "%s format %s uses non-int %s as argument of *", state.name, state.format, f.gofmt(arg)) + return false + } + } + if state.verb == '%' || formatter { + return true + } + argNum := state.argNums[len(state.argNums)-1] + if !f.argCanBeChecked(call, len(state.argNums)-1, state) { + return false + } + arg := call.Args[argNum] + if f.isFunctionValue(arg) && state.verb != 'p' && state.verb != 'T' { + f.Badf(call.Pos(), "%s format %s arg %s is a func value, not called", state.name, state.format, f.gofmt(arg)) + return false + } + if !f.matchArgType(v.typ, nil, arg) { + typeString := "" + if typ := f.pkg.types[arg].Type; typ != nil { + typeString = typ.String() + } + f.Badf(call.Pos(), "%s format %s has arg %s of wrong type %s", state.name, state.format, f.gofmt(arg), typeString) + return false + } + if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) { + f.Badf(call.Pos(), "%s format %s with arg %s causes recursive String method call", state.name, state.format, f.gofmt(arg)) + return false + } + return true +} + +// recursiveStringer reports whether the provided argument is r or &r for the +// fmt.Stringer receiver identifier r. +func (f *File) recursiveStringer(e ast.Expr) bool { + if len(f.stringerPtrs) == 0 { + return false + } + ptr := false + var obj *ast.Object + switch e := e.(type) { + case *ast.Ident: + obj = e.Obj + case *ast.UnaryExpr: + if id, ok := e.X.(*ast.Ident); ok && e.Op == token.AND { + obj = id.Obj + ptr = true + } + } + + // It's unlikely to be a recursive stringer if it has a Format method. + if typ := f.pkg.types[e].Type; typ != nil { + if f.isFormatter(typ) { + return false + } + } + + // We compare the underlying Object, which checks that the identifier + // is the one we declared as the receiver for the String method in + // which this printf appears. + ptrRecv, exist := f.stringerPtrs[obj] + if !exist { + return false + } + // We also need to check that using &t when we declared String + // on (t *T) is ok; in such a case, the address is printed. + if ptr && ptrRecv { + return false + } + return true +} + +// isFunctionValue reports whether the expression is a function as opposed to a function call. +// It is almost always a mistake to print a function value. +func (f *File) isFunctionValue(e ast.Expr) bool { + if typ := f.pkg.types[e].Type; typ != nil { + _, ok := typ.(*types.Signature) + return ok + } + return false +} + +// argCanBeChecked reports whether the specified argument is statically present; +// it may be beyond the list of arguments or in a terminal slice... argument, which +// means we can't see it. +func (f *File) argCanBeChecked(call *ast.CallExpr, formatArg int, state *formatState) bool { + argNum := state.argNums[formatArg] + if argNum <= 0 { + // Shouldn't happen, so catch it with prejudice. + panic("negative arg num") + } + if argNum < len(call.Args)-1 { + return true // Always OK. + } + if call.Ellipsis.IsValid() { + return false // We just can't tell; there could be many more arguments. + } + if argNum < len(call.Args) { + return true + } + // There are bad indexes in the format or there are fewer arguments than the format needs. + // This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi". + arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed. + f.Badf(call.Pos(), "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg")) + return false +} + +// printFormatRE is the regexp we match and report as a possible format string +// in the first argument to unformatted prints like fmt.Print. +// We exclude the space flag, so that printing a string like "x % y" is not reported as a format. +var printFormatRE = regexp.MustCompile(`%` + flagsRE + numOptRE + `\.?` + numOptRE + indexOptRE + verbRE) + +const ( + flagsRE = `[+\-#]*` + indexOptRE = `(\[[0-9]+\])?` + numOptRE = `([0-9]+|` + indexOptRE + `\*)?` + verbRE = `[bcdefgopqstvxEFGTUX]` +) + +// checkPrint checks a call to an unformatted print routine such as Println. +func (f *File) checkPrint(call *ast.CallExpr, name string) { + firstArg := 0 + typ := f.pkg.types[call.Fun].Type + if typ == nil { + // Skip checking functions with unknown type. + return + } + if sig, ok := typ.(*types.Signature); ok { + if !sig.Variadic() { + // Skip checking non-variadic functions. + return + } + params := sig.Params() + firstArg = params.Len() - 1 + + typ := params.At(firstArg).Type() + typ = typ.(*types.Slice).Elem() + it, ok := typ.(*types.Interface) + if !ok || !it.Empty() { + // Skip variadic functions accepting non-interface{} args. + return + } + } + args := call.Args + if len(args) <= firstArg { + // Skip calls without variadic args. + return + } + args = args[firstArg:] + + if firstArg == 0 { + if sel, ok := call.Args[0].(*ast.SelectorExpr); ok { + if x, ok := sel.X.(*ast.Ident); ok { + if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") { + f.Badf(call.Pos(), "%s does not take io.Writer but has first arg %s", name, f.gofmt(call.Args[0])) + } + } + } + } + + arg := args[0] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + // Ignore trailing % character in lit.Value. + // The % in "abc 0.0%" couldn't be a formatting directive. + s := strings.TrimSuffix(lit.Value, `%"`) + if strings.Contains(s, "%") { + m := printFormatRE.FindStringSubmatch(s) + if m != nil { + f.Badf(call.Pos(), "%s call has possible formatting directive %s", name, m[0]) + } + } + } + if strings.HasSuffix(name, "ln") { + // The last item, if a string, should not have a newline. + arg = args[len(args)-1] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + str, _ := strconv.Unquote(lit.Value) + if strings.HasSuffix(str, "\n") { + f.Badf(call.Pos(), "%s arg list ends with redundant newline", name) + } + } + } + for _, arg := range args { + if f.isFunctionValue(arg) { + f.Badf(call.Pos(), "%s arg %s is a func value, not called", name, f.gofmt(arg)) + } + if f.recursiveStringer(arg) { + f.Badf(call.Pos(), "%s arg %s causes recursive call to String method", name, f.gofmt(arg)) + } + } +} + +// count(n, what) returns "1 what" or "N whats" +// (assuming the plural of what is whats). +func count(n int, what string) string { + if n == 1 { + return "1 " + what + } + return fmt.Sprintf("%d %ss", n, what) +} diff --git a/go/analysis/passes/vet/rangeloop.go b/go/analysis/passes/vet/rangeloop.go new file mode 100644 index 00000000..19642df2 --- /dev/null +++ b/go/analysis/passes/vet/rangeloop.go @@ -0,0 +1,107 @@ +// +build ignore + +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check range loop variables bound inside function +literals that are deferred or launched in new goroutines. We only check +instances where the defer or go statement is the last statement in the loop +body, as otherwise we would need whole program analysis. + +For example: + + for i, v := range s { + go func() { + println(i, v) // not what you might expect + }() + } + +See: https://golang.org/doc/go_faq.html#closures_and_goroutines +*/ + +package main + +import "go/ast" + +func init() { + register("rangeloops", + "check that loop variables are used correctly", + checkLoop, + rangeStmt, forStmt) +} + +// checkLoop walks the body of the provided loop statement, checking whether +// its index or value variables are used unsafely inside goroutines or deferred +// function literals. +func checkLoop(f *File, node ast.Node) { + // Find the variables updated by the loop statement. + var vars []*ast.Ident + addVar := func(expr ast.Expr) { + if id, ok := expr.(*ast.Ident); ok { + vars = append(vars, id) + } + } + var body *ast.BlockStmt + switch n := node.(type) { + case *ast.RangeStmt: + body = n.Body + addVar(n.Key) + addVar(n.Value) + case *ast.ForStmt: + body = n.Body + switch post := n.Post.(type) { + case *ast.AssignStmt: + // e.g. for p = head; p != nil; p = p.next + for _, lhs := range post.Lhs { + addVar(lhs) + } + case *ast.IncDecStmt: + // e.g. for i := 0; i < n; i++ + addVar(post.X) + } + } + if vars == nil { + return + } + + // Inspect a go or defer statement + // if it's the last one in the loop body. + // (We give up if there are following statements, + // because it's hard to prove go isn't followed by wait, + // or defer by return.) + if len(body.List) == 0 { + return + } + var last *ast.CallExpr + switch s := body.List[len(body.List)-1].(type) { + case *ast.GoStmt: + last = s.Call + case *ast.DeferStmt: + last = s.Call + default: + return + } + lit, ok := last.Fun.(*ast.FuncLit) + if !ok { + return + } + ast.Inspect(lit.Body, func(n ast.Node) bool { + id, ok := n.(*ast.Ident) + if !ok || id.Obj == nil { + return true + } + if f.pkg.types[id].Type == nil { + // Not referring to a variable (e.g. struct field name) + return true + } + for _, v := range vars { + if v.Obj == id.Obj { + f.Badf(id.Pos(), "loop variable %s captured by func literal", + id.Name) + } + } + return true + }) +} diff --git a/go/analysis/passes/vet/shadow.go b/go/analysis/passes/vet/shadow.go new file mode 100644 index 00000000..2679721e --- /dev/null +++ b/go/analysis/passes/vet/shadow.go @@ -0,0 +1,245 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for shadowed variables. +A shadowed variable is a variable declared in an inner scope +with the same name and type as a variable in an outer scope, +and where the outer variable is mentioned after the inner one +is declared. + +(This definition can be refined; the module generates too many +false positives and is not yet enabled by default.) + +For example: + + func BadRead(f *os.File, buf []byte) error { + var err error + for { + n, err := f.Read(buf) // shadows the function variable 'err' + if err != nil { + break // causes return of wrong value + } + foo(buf) + } + return err + } + +*/ + +package main + +import ( + "flag" + "go/ast" + "go/token" + "go/types" +) + +var strictShadowing = flag.Bool("shadowstrict", false, "whether to be strict about shadowing; can be noisy") + +func init() { + register("shadow", + "check for shadowed variables (experimental; must be set explicitly)", + checkShadow, + assignStmt, genDecl) + experimental["shadow"] = true +} + +func checkShadow(f *File, node ast.Node) { + switch n := node.(type) { + case *ast.AssignStmt: + checkShadowAssignment(f, n) + case *ast.GenDecl: + checkShadowDecl(f, n) + } +} + +// Span stores the minimum range of byte positions in the file in which a +// given variable (types.Object) is mentioned. It is lexically defined: it spans +// from the beginning of its first mention to the end of its last mention. +// A variable is considered shadowed (if *strictShadowing is off) only if the +// shadowing variable is declared within the span of the shadowed variable. +// In other words, if a variable is shadowed but not used after the shadowed +// variable is declared, it is inconsequential and not worth complaining about. +// This simple check dramatically reduces the nuisance rate for the shadowing +// check, at least until something cleverer comes along. +// +// One wrinkle: A "naked return" is a silent use of a variable that the Span +// will not capture, but the compilers catch naked returns of shadowed +// variables so we don't need to. +// +// Cases this gets wrong (TODO): +// - If a for loop's continuation statement mentions a variable redeclared in +// the block, we should complain about it but don't. +// - A variable declared inside a function literal can falsely be identified +// as shadowing a variable in the outer function. +// +type Span struct { + min token.Pos + max token.Pos +} + +// contains reports whether the position is inside the span. +func (s Span) contains(pos token.Pos) bool { + return s.min <= pos && pos < s.max +} + +// growSpan expands the span for the object to contain the source range [pos, end). +func (pkg *Package) growSpan(obj types.Object, pos, end token.Pos) { + if *strictShadowing { + return // No need + } + span, ok := pkg.spans[obj] + if ok { + if span.min > pos { + span.min = pos + } + if span.max < end { + span.max = end + } + } else { + span = Span{pos, end} + } + pkg.spans[obj] = span +} + +// checkShadowAssignment checks for shadowing in a short variable declaration. +func checkShadowAssignment(f *File, a *ast.AssignStmt) { + if a.Tok != token.DEFINE { + return + } + if f.idiomaticShortRedecl(a) { + return + } + for _, expr := range a.Lhs { + ident, ok := expr.(*ast.Ident) + if !ok { + f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier") + return + } + checkShadowing(f, ident) + } +} + +// idiomaticShortRedecl reports whether this short declaration can be ignored for +// the purposes of shadowing, that is, that any redeclarations it contains are deliberate. +func (f *File) idiomaticShortRedecl(a *ast.AssignStmt) bool { + // Don't complain about deliberate redeclarations of the form + // i := i + // Such constructs are idiomatic in range loops to create a new variable + // for each iteration. Another example is + // switch n := n.(type) + if len(a.Rhs) != len(a.Lhs) { + return false + } + // We know it's an assignment, so the LHS must be all identifiers. (We check anyway.) + for i, expr := range a.Lhs { + lhs, ok := expr.(*ast.Ident) + if !ok { + f.Badf(expr.Pos(), "invalid AST: short variable declaration of non-identifier") + return true // Don't do any more processing. + } + switch rhs := a.Rhs[i].(type) { + case *ast.Ident: + if lhs.Name != rhs.Name { + return false + } + case *ast.TypeAssertExpr: + if id, ok := rhs.X.(*ast.Ident); ok { + if lhs.Name != id.Name { + return false + } + } + default: + return false + } + } + return true +} + +// idiomaticRedecl reports whether this declaration spec can be ignored for +// the purposes of shadowing, that is, that any redeclarations it contains are deliberate. +func (f *File) idiomaticRedecl(d *ast.ValueSpec) bool { + // Don't complain about deliberate redeclarations of the form + // var i, j = i, j + if len(d.Names) != len(d.Values) { + return false + } + for i, lhs := range d.Names { + if rhs, ok := d.Values[i].(*ast.Ident); ok { + if lhs.Name != rhs.Name { + return false + } + } + } + return true +} + +// checkShadowDecl checks for shadowing in a general variable declaration. +func checkShadowDecl(f *File, d *ast.GenDecl) { + if d.Tok != token.VAR { + return + } + for _, spec := range d.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + f.Badf(spec.Pos(), "invalid AST: var GenDecl not ValueSpec") + return + } + // Don't complain about deliberate redeclarations of the form + // var i = i + if f.idiomaticRedecl(valueSpec) { + return + } + for _, ident := range valueSpec.Names { + checkShadowing(f, ident) + } + } +} + +// checkShadowing checks whether the identifier shadows an identifier in an outer scope. +func checkShadowing(f *File, ident *ast.Ident) { + if ident.Name == "_" { + // Can't shadow the blank identifier. + return + } + obj := f.pkg.defs[ident] + if obj == nil { + return + } + // obj.Parent.Parent is the surrounding scope. If we can find another declaration + // starting from there, we have a shadowed identifier. + _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos()) + if shadowed == nil { + return + } + // Don't complain if it's shadowing a universe-declared identifier; that's fine. + if shadowed.Parent() == types.Universe { + return + } + if *strictShadowing { + // The shadowed identifier must appear before this one to be an instance of shadowing. + if shadowed.Pos() > ident.Pos() { + return + } + } else { + // Don't complain if the span of validity of the shadowed identifier doesn't include + // the shadowing identifier. + span, ok := f.pkg.spans[shadowed] + if !ok { + f.Badf(shadowed.Pos(), "internal error: no range for %q", shadowed.Name()) + return + } + if !span.contains(ident.Pos()) { + return + } + } + // Don't complain if the types differ: that implies the programmer really wants two different things. + if types.Identical(obj.Type(), shadowed.Type()) { + f.Badf(ident.Pos(), "declaration of %q shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) + } +} diff --git a/go/analysis/passes/vet/shift.go b/go/analysis/passes/vet/shift.go new file mode 100644 index 00000000..5ddd0cb4 --- /dev/null +++ b/go/analysis/passes/vet/shift.go @@ -0,0 +1,100 @@ +// +build ignore + +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +This file contains the code to check for suspicious shifts. +*/ + +package main + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" +) + +func init() { + register("shift", + "check for useless shifts", + checkShift, + binaryExpr, assignStmt) +} + +func checkShift(f *File, node ast.Node) { + if f.dead[node] { + // Skip shift checks on unreachable nodes. + return + } + + switch node := node.(type) { + case *ast.BinaryExpr: + if node.Op == token.SHL || node.Op == token.SHR { + checkLongShift(f, node, node.X, node.Y) + } + case *ast.AssignStmt: + if len(node.Lhs) != 1 || len(node.Rhs) != 1 { + return + } + if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN { + checkLongShift(f, node, node.Lhs[0], node.Rhs[0]) + } + } +} + +// checkLongShift checks if shift or shift-assign operations shift by more than +// the length of the underlying variable. +func checkLongShift(f *File, node ast.Node, x, y ast.Expr) { + if f.pkg.types[x].Value != nil { + // Ignore shifts of constants. + // These are frequently used for bit-twiddling tricks + // like ^uint(0) >> 63 for 32/64 bit detection and compatibility. + return + } + + v := f.pkg.types[y].Value + if v == nil { + return + } + amt, ok := constant.Int64Val(v) + if !ok { + return + } + t := f.pkg.types[x].Type + if t == nil { + return + } + b, ok := t.Underlying().(*types.Basic) + if !ok { + return + } + var size int64 + switch b.Kind() { + case types.Uint8, types.Int8: + size = 8 + case types.Uint16, types.Int16: + size = 16 + case types.Uint32, types.Int32: + size = 32 + case types.Uint64, types.Int64: + size = 64 + case types.Int, types.Uint: + size = uintBitSize + case types.Uintptr: + size = uintptrBitSize + default: + return + } + if amt >= size { + ident := f.gofmt(x) + f.Badf(node.Pos(), "%s (%d bits) too small for shift of %d", ident, size, amt) + } +} + +var ( + uintBitSize = 8 * archSizes.Sizeof(types.Typ[types.Uint]) + uintptrBitSize = 8 * archSizes.Sizeof(types.Typ[types.Uintptr]) +) diff --git a/go/analysis/passes/vet/structtag.go b/go/analysis/passes/vet/structtag.go new file mode 100644 index 00000000..bdd5f311 --- /dev/null +++ b/go/analysis/passes/vet/structtag.go @@ -0,0 +1,243 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for canonical struct tags. + +package main + +import ( + "errors" + "go/ast" + "go/token" + "go/types" + "reflect" + "strconv" + "strings" +) + +func init() { + register("structtags", + "check that struct field tags have canonical format and apply to exported fields as needed", + checkStructFieldTags, + structType) +} + +// checkStructFieldTags checks all the field tags of a struct, including checking for duplicates. +func checkStructFieldTags(f *File, node ast.Node) { + astType := node.(*ast.StructType) + typ := f.pkg.types[astType].Type.(*types.Struct) + var seen map[[2]string]token.Pos + for i := 0; i < typ.NumFields(); i++ { + field := typ.Field(i) + tag := typ.Tag(i) + checkCanonicalFieldTag(f, astType, field, tag, &seen) + } +} + +var checkTagDups = []string{"json", "xml"} +var checkTagSpaces = map[string]bool{"json": true, "xml": true, "asn1": true} + +// checkCanonicalFieldTag checks a single struct field tag. +// top is the top-level struct type that is currently being checked. +func checkCanonicalFieldTag(f *File, top *ast.StructType, field *types.Var, tag string, seen *map[[2]string]token.Pos) { + for _, key := range checkTagDups { + checkTagDuplicates(f, tag, key, field, field, seen) + } + + if err := validateStructTag(tag); err != nil { + f.Badf(field.Pos(), "struct field tag %#q not compatible with reflect.StructTag.Get: %s", tag, err) + } + + // Check for use of json or xml tags with unexported fields. + + // Embedded struct. Nothing to do for now, but that + // may change, depending on what happens with issue 7363. + if field.Anonymous() { + return + } + + if field.Exported() { + return + } + + for _, enc := range [...]string{"json", "xml"} { + if reflect.StructTag(tag).Get(enc) != "" { + f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Name(), enc) + return + } + } +} + +// checkTagDuplicates checks a single struct field tag to see if any tags are +// duplicated. nearest is the field that's closest to the field being checked, +// while still being part of the top-level struct type. +func checkTagDuplicates(f *File, tag, key string, nearest, field *types.Var, seen *map[[2]string]token.Pos) { + val := reflect.StructTag(tag).Get(key) + if val == "-" { + // Ignored, even if the field is anonymous. + return + } + if val == "" || val[0] == ',' { + if field.Anonymous() { + typ, ok := field.Type().Underlying().(*types.Struct) + if !ok { + return + } + for i := 0; i < typ.NumFields(); i++ { + field := typ.Field(i) + if !field.Exported() { + continue + } + tag := typ.Tag(i) + checkTagDuplicates(f, tag, key, nearest, field, seen) + } + } + // Ignored if the field isn't anonymous. + return + } + if key == "xml" && field.Name() == "XMLName" { + // XMLName defines the XML element name of the struct being + // checked. That name cannot collide with element or attribute + // names defined on other fields of the struct. Vet does not have a + // check for untagged fields of type struct defining their own name + // by containing a field named XMLName; see issue 18256. + return + } + if i := strings.Index(val, ","); i >= 0 { + if key == "xml" { + // Use a separate namespace for XML attributes. + for _, opt := range strings.Split(val[i:], ",") { + if opt == "attr" { + key += " attribute" // Key is part of the error message. + break + } + } + } + val = val[:i] + } + if *seen == nil { + *seen = map[[2]string]token.Pos{} + } + if pos, ok := (*seen)[[2]string{key, val}]; ok { + f.Badf(nearest.Pos(), "struct field %s repeats %s tag %q also at %s", field.Name(), key, val, f.loc(pos)) + } else { + (*seen)[[2]string{key, val}] = field.Pos() + } +} + +var ( + errTagSyntax = errors.New("bad syntax for struct tag pair") + errTagKeySyntax = errors.New("bad syntax for struct tag key") + errTagValueSyntax = errors.New("bad syntax for struct tag value") + errTagValueSpace = errors.New("suspicious space in struct tag value") + errTagSpace = errors.New("key:\"value\" pairs not separated by spaces") +) + +// validateStructTag parses the struct tag and returns an error if it is not +// in the canonical format, which is a space-separated list of key:"value" +// settings. The value may contain spaces. +func validateStructTag(tag string) error { + // This code is based on the StructTag.Get code in package reflect. + + n := 0 + for ; tag != ""; n++ { + if n > 0 && tag != "" && tag[0] != ' ' { + // More restrictive than reflect, but catches likely mistakes + // like `x:"foo",y:"bar"`, which parses as `x:"foo" ,y:"bar"` with second key ",y". + return errTagSpace + } + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 { + return errTagKeySyntax + } + if i+1 >= len(tag) || tag[i] != ':' { + return errTagSyntax + } + if tag[i+1] != '"' { + return errTagValueSyntax + } + key := tag[:i] + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + return errTagValueSyntax + } + qvalue := tag[:i+1] + tag = tag[i+1:] + + value, err := strconv.Unquote(qvalue) + if err != nil { + return errTagValueSyntax + } + + if !checkTagSpaces[key] { + continue + } + + switch key { + case "xml": + // If the first or last character in the XML tag is a space, it is + // suspicious. + if strings.Trim(value, " ") != value { + return errTagValueSpace + } + + // If there are multiple spaces, they are suspicious. + if strings.Count(value, " ") > 1 { + return errTagValueSpace + } + + // If there is no comma, skip the rest of the checks. + comma := strings.IndexRune(value, ',') + if comma < 0 { + continue + } + + // If the character before a comma is a space, this is suspicious. + if comma > 0 && value[comma-1] == ' ' { + return errTagValueSpace + } + value = value[comma+1:] + case "json": + // JSON allows using spaces in the name, so skip it. + comma := strings.IndexRune(value, ',') + if comma < 0 { + continue + } + value = value[comma+1:] + } + + if strings.IndexByte(value, ' ') >= 0 { + return errTagValueSpace + } + } + return nil +} diff --git a/go/analysis/passes/vet/testdata/asm/asm.go b/go/analysis/passes/vet/testdata/asm/asm.go new file mode 100644 index 00000000..2237ddc3 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm.go @@ -0,0 +1,48 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// This file contains declarations to test the assembly in test_asm.s. + +package testdata + +type S struct { + i int32 + b bool + s string +} + +func arg1(x int8, y uint8) +func arg2(x int16, y uint16) +func arg4(x int32, y uint32) +func arg8(x int64, y uint64) +func argint(x int, y uint) +func argptr(x *byte, y *byte, c chan int, m map[int]int, f func()) +func argstring(x, y string) +func argslice(x, y []string) +func argiface(x interface{}, y interface { + m() +}) +func argcomplex(x complex64, y complex128) +func argstruct(x S, y struct{}) +func argarray(x [2]S) +func returnint() int +func returnbyte(x int) byte +func returnnamed(x byte) (r1 int, r2 int16, r3 string, r4 byte) +func returnintmissing() int +func leaf(x, y int) int + +func noprof(x int) +func dupok(x int) +func nosplit(x int) +func rodata(x int) +func noptr(x int) +func wrapper(x int) + +func f15271() (x uint32) +func f17584(x float32, y complex64) + +func noframe1(x int32) +func noframe2(x int32) diff --git a/go/analysis/passes/vet/testdata/asm/asm1.s b/go/analysis/passes/vet/testdata/asm/asm1.s new file mode 100644 index 00000000..cac6ed22 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm1.s @@ -0,0 +1,315 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64 +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), AX + // MOVB x+0(FP), AX // commented out instructions used to panic + MOVB y+1(FP), BX + MOVW x+0(FP), AX // ERROR "\[amd64\] arg1: invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int8 is 1-byte value" + MOVL y+1(FP), AX // ERROR "invalid MOVL of y\+1\(FP\); uint8 is 1-byte value" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int8 is 1-byte value" + MOVQ y+1(FP), AX // ERROR "invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + TESTB x+0(FP), AX + TESTB y+1(FP), BX + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int8 is 1-byte value" + TESTW y+1(FP), AX // ERROR "invalid TESTW of y\+1\(FP\); uint8 is 1-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int8 is 1-byte value" + TESTL y+1(FP), AX // ERROR "invalid TESTL of y\+1\(FP\); uint8 is 1-byte value" + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int8 is 1-byte value" + TESTQ y+1(FP), AX // ERROR "invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value" + TESTB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + TESTB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 8(SP), AX // ERROR "8\(SP\) should be x\+0\(FP\)" + MOVB 9(SP), AX // ERROR "9\(SP\) should be y\+1\(FP\)" + MOVB 10(SP), AX // ERROR "use of 10\(SP\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVW x+0(FP), AX + MOVW y+2(FP), BX + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int16 is 2-byte value" + MOVL y+2(FP), AX // ERROR "invalid MOVL of y\+2\(FP\); uint16 is 2-byte value" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int16 is 2-byte value" + MOVQ y+2(FP), AX // ERROR "invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value" + MOVW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int16 is 2-byte value" + TESTB y+2(FP), AX // ERROR "invalid TESTB of y\+2\(FP\); uint16 is 2-byte value" + TESTW x+0(FP), AX + TESTW y+2(FP), BX + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int16 is 2-byte value" + TESTL y+2(FP), AX // ERROR "invalid TESTL of y\+2\(FP\); uint16 is 2-byte value" + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int16 is 2-byte value" + TESTQ y+2(FP), AX // ERROR "invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value" + TESTW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + TESTW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int32 is 4-byte value" + MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint32 is 4-byte value" + MOVL x+0(FP), AX + MOVL y+4(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int32 is 4-byte value" + MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value" + MOVL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int32 is 4-byte value" + TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint32 is 4-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int32 is 4-byte value" + TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint32 is 4-byte value" + TESTL x+0(FP), AX + TESTL y+4(FP), AX + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int32 is 4-byte value" + TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value" + TESTL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + TESTL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value" + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int64 is 8-byte value" + MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint64 is 8-byte value" + MOVQ x+0(FP), AX + MOVQ y+8(FP), AX + MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int64 is 8-byte value" + TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint64 is 8-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int64 is 8-byte value" + TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint64 is 8-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int64 is 8-byte value" + TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint64 is 8-byte value" + TESTQ x+0(FP), AX + TESTQ y+8(FP), AX + TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 8-byte value" + MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint is 8-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int is 8-byte value" + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int is 8-byte value" + MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint is 8-byte value" + MOVQ x+0(FP), AX + MOVQ y+8(FP), AX + MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int is 8-byte value" + TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint is 8-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int is 8-byte value" + TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint is 8-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int is 8-byte value" + TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint is 8-byte value" + TESTQ x+0(FP), AX + TESTQ y+8(FP), AX + TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-40" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 8-byte value" + MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); \*byte is 8-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 8-byte value" + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); \*byte is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); \*byte is 8-byte value" + MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); \*byte is 8-byte value" + MOVQ x+0(FP), AX + MOVQ y+8(FP), AX + MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); \*byte is 8-byte value" + TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); \*byte is 8-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); \*byte is 8-byte value" + TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); \*byte is 8-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); \*byte is 8-byte value" + TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); \*byte is 8-byte value" + TESTQ x+0(FP), AX + TESTQ y+8(FP), AX + TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + MOVL c+16(FP), AX // ERROR "invalid MOVL of c\+16\(FP\); chan int is 8-byte value" + MOVL m+24(FP), AX // ERROR "invalid MOVL of m\+24\(FP\); map\[int\]int is 8-byte value" + MOVL f+32(FP), AX // ERROR "invalid MOVL of f\+32\(FP\); func\(\) is 8-byte value" + RET + +TEXT ·argstring(SB),0,$32 // ERROR "wrong argument size 0; expected \$\.\.\.-32" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); string base is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); string base is 8-byte value" + MOVQ x+0(FP), AX + MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 8-byte value" + MOVL x_base+0(FP), AX // ERROR "invalid MOVL of x_base\+0\(FP\); string base is 8-byte value" + MOVQ x_base+0(FP), AX + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+8(FP), AX // ERROR "invalid MOVW of x_len\+8\(FP\); string len is 8-byte value" + MOVL x_len+8(FP), AX // ERROR "invalid MOVL of x_len\+8\(FP\); string len is 8-byte value" + MOVQ x_len+8(FP), AX + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+16\(FP\)" + MOVQ y_len+8(FP), AX // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)" + RET + +TEXT ·argslice(SB),0,$48 // ERROR "wrong argument size 0; expected \$\.\.\.-48" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); slice base is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); slice base is 8-byte value" + MOVQ x+0(FP), AX + MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value" + MOVL x_base+0(FP), AX // ERROR "invalid MOVL of x_base\+0\(FP\); slice base is 8-byte value" + MOVQ x_base+0(FP), AX + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+8(FP), AX // ERROR "invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value" + MOVL x_len+8(FP), AX // ERROR "invalid MOVL of x_len\+8\(FP\); slice len is 8-byte value" + MOVQ x_len+8(FP), AX + MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVL x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVW x_cap+16(FP), AX // ERROR "invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVL x_cap+16(FP), AX // ERROR "invalid MOVL of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVQ x_cap+16(FP), AX + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+24\(FP\)" + MOVQ y_len+8(FP), AX // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)" + MOVQ y_cap+16(FP), AX // ERROR "invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-32 + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); interface type is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); interface type is 8-byte value" + MOVQ x+0(FP), AX + MOVW x_type+0(FP), AX // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value" + MOVL x_type+0(FP), AX // ERROR "invalid MOVL of x_type\+0\(FP\); interface type is 8-byte value" + MOVQ x_type+0(FP), AX + MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVL x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVW x_data+8(FP), AX // ERROR "invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value" + MOVL x_data+8(FP), AX // ERROR "invalid MOVL of x_data\+8\(FP\); interface data is 8-byte value" + MOVQ x_data+8(FP), AX + MOVW y+16(FP), AX // ERROR "invalid MOVW of y\+16\(FP\); interface itable is 8-byte value" + MOVL y+16(FP), AX // ERROR "invalid MOVL of y\+16\(FP\); interface itable is 8-byte value" + MOVQ y+16(FP), AX + MOVW y_itable+16(FP), AX // ERROR "invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVL y_itable+16(FP), AX // ERROR "invalid MOVL of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVQ y_itable+16(FP), AX + MOVQ y_type+16(FP), AX // ERROR "unknown variable y_type; offset 16 is y_itable\+16\(FP\)" + MOVW y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVL y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVQ y_data+16(FP), AX // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVW y_data+24(FP), AX // ERROR "invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value" + MOVL y_data+24(FP), AX // ERROR "invalid MOVL of y_data\+24\(FP\); interface data is 8-byte value" + MOVQ y_data+24(FP), AX + RET + +TEXT ·argcomplex(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24" + MOVSS x+0(FP), X0 // ERROR "invalid MOVSS of x\+0\(FP\); complex64 is 8-byte value containing x_real\+0\(FP\) and x_imag\+4\(FP\)" + MOVSD x+0(FP), X0 // ERROR "invalid MOVSD of x\+0\(FP\); complex64 is 8-byte value containing x_real\+0\(FP\) and x_imag\+4\(FP\)" + MOVSS x_real+0(FP), X0 + MOVSD x_real+0(FP), X0 // ERROR "invalid MOVSD of x_real\+0\(FP\); real\(complex64\) is 4-byte value" + MOVSS x_real+4(FP), X0 // ERROR "invalid offset x_real\+4\(FP\); expected x_real\+0\(FP\)" + MOVSS x_imag+4(FP), X0 + MOVSD x_imag+4(FP), X0 // ERROR "invalid MOVSD of x_imag\+4\(FP\); imag\(complex64\) is 4-byte value" + MOVSS x_imag+8(FP), X0 // ERROR "invalid offset x_imag\+8\(FP\); expected x_imag\+4\(FP\)" + MOVSD y+8(FP), X0 // ERROR "invalid MOVSD of y\+8\(FP\); complex128 is 16-byte value containing y_real\+8\(FP\) and y_imag\+16\(FP\)" + MOVSS y_real+8(FP), X0 // ERROR "invalid MOVSS of y_real\+8\(FP\); real\(complex128\) is 8-byte value" + MOVSD y_real+8(FP), X0 + MOVSS y_real+16(FP), X0 // ERROR "invalid offset y_real\+16\(FP\); expected y_real\+8\(FP\)" + MOVSS y_imag+16(FP), X0 // ERROR "invalid MOVSS of y_imag\+16\(FP\); imag\(complex128\) is 8-byte value" + MOVSD y_imag+16(FP), X0 + MOVSS y_imag+24(FP), X0 // ERROR "invalid offset y_imag\+24\(FP\); expected y_imag\+16\(FP\)" + RET + +TEXT ·argstruct(SB),0,$64 // ERROR "wrong argument size 0; expected \$\.\.\.-24" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); testdata.S is 24-byte value" + MOVQ x_i+0(FP), AX // ERROR "invalid MOVQ of x_i\+0\(FP\); int32 is 4-byte value" + MOVQ x_b+0(FP), AX // ERROR "invalid offset x_b\+0\(FP\); expected x_b\+4\(FP\)" + MOVQ x_s+8(FP), AX + MOVQ x_s_base+8(FP), AX + MOVQ x_s+16(FP), AX // ERROR "invalid offset x_s\+16\(FP\); expected x_s\+8\(FP\), x_s_base\+8\(FP\), or x_s_len\+16\(FP\)" + MOVQ x_s_len+16(FP), AX + RET + +TEXT ·argarray(SB),0,$64 // ERROR "wrong argument size 0; expected \$\.\.\.-48" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); \[2\]testdata.S is 48-byte value" + MOVQ x_0_i+0(FP), AX // ERROR "invalid MOVQ of x_0_i\+0\(FP\); int32 is 4-byte value" + MOVQ x_0_b+0(FP), AX // ERROR "invalid offset x_0_b\+0\(FP\); expected x_0_b\+4\(FP\)" + MOVQ x_0_s+8(FP), AX + MOVQ x_0_s_base+8(FP), AX + MOVQ x_0_s+16(FP), AX // ERROR "invalid offset x_0_s\+16\(FP\); expected x_0_s\+8\(FP\), x_0_s_base\+8\(FP\), or x_0_s_len\+16\(FP\)" + MOVQ x_0_s_len+16(FP), AX + MOVB foo+25(FP), AX // ERROR "unknown variable foo; offset 25 is x_1_i\+24\(FP\)" + MOVQ x_1_s+32(FP), AX + MOVQ x_1_s_base+32(FP), AX + MOVQ x_1_s+40(FP), AX // ERROR "invalid offset x_1_s\+40\(FP\); expected x_1_s\+32\(FP\), x_1_s_base\+32\(FP\), or x_1_s_len\+40\(FP\)" + MOVQ x_1_s_len+40(FP), AX + RET + +TEXT ·returnint(SB),0,$0-8 + MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 8-byte value" + MOVW AX, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 8-byte value" + MOVL AX, ret+0(FP) // ERROR "invalid MOVL of ret\+0\(FP\); int is 8-byte value" + MOVQ AX, ret+0(FP) + MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-9 + MOVQ x+0(FP), AX + MOVB AX, ret+8(FP) + MOVW AX, ret+8(FP) // ERROR "invalid MOVW of ret\+8\(FP\); byte is 1-byte value" + MOVL AX, ret+8(FP) // ERROR "invalid MOVL of ret\+8\(FP\); byte is 1-byte value" + MOVQ AX, ret+8(FP) // ERROR "invalid MOVQ of ret\+8\(FP\); byte is 1-byte value" + MOVB AX, ret+7(FP) // ERROR "invalid offset ret\+7\(FP\); expected ret\+8\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-41 + MOVB x+0(FP), AX + MOVQ AX, r1+8(FP) + MOVW AX, r2+16(FP) + MOVQ AX, r3+24(FP) + MOVQ AX, r3_base+24(FP) + MOVQ AX, r3_len+32(FP) + MOVB AX, r4+40(FP) + MOVL AX, r1+8(FP) // ERROR "invalid MOVL of r1\+8\(FP\); int is 8-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-8 + RET // ERROR "RET without writing to 8-byte ret\+0\(FP\)" + + +// issue 15271 +TEXT ·f15271(SB), NOSPLIT, $0-4 + // Stick 123 into the low 32 bits of X0. + MOVQ $123, AX + PINSRD $0, AX, X0 + + // Return them. + PEXTRD $0, X0, x+0(FP) + RET + +// issue 17584 +TEXT ·f17584(SB), NOSPLIT, $12 + MOVSS x+0(FP), X0 + MOVSS y_real+4(FP), X0 + MOVSS y_imag+8(FP), X0 + RET diff --git a/go/analysis/passes/vet/testdata/asm/asm2.s b/go/analysis/passes/vet/testdata/asm/asm2.s new file mode 100644 index 00000000..c33c02a7 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm2.s @@ -0,0 +1,257 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build 386 +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), AX + MOVB y+1(FP), BX + MOVW x+0(FP), AX // ERROR "\[386\] arg1: invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int8 is 1-byte value" + MOVL y+1(FP), AX // ERROR "invalid MOVL of y\+1\(FP\); uint8 is 1-byte value" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int8 is 1-byte value" + MOVQ y+1(FP), AX // ERROR "invalid MOVQ of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + TESTB x+0(FP), AX + TESTB y+1(FP), BX + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int8 is 1-byte value" + TESTW y+1(FP), AX // ERROR "invalid TESTW of y\+1\(FP\); uint8 is 1-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int8 is 1-byte value" + TESTL y+1(FP), AX // ERROR "invalid TESTL of y\+1\(FP\); uint8 is 1-byte value" + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int8 is 1-byte value" + TESTQ y+1(FP), AX // ERROR "invalid TESTQ of y\+1\(FP\); uint8 is 1-byte value" + TESTB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + TESTB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 4(SP), AX // ERROR "4\(SP\) should be x\+0\(FP\)" + MOVB 5(SP), AX // ERROR "5\(SP\) should be y\+1\(FP\)" + MOVB 6(SP), AX // ERROR "use of 6\(SP\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVW x+0(FP), AX + MOVW y+2(FP), BX + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int16 is 2-byte value" + MOVL y+2(FP), AX // ERROR "invalid MOVL of y\+2\(FP\); uint16 is 2-byte value" + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int16 is 2-byte value" + MOVQ y+2(FP), AX // ERROR "invalid MOVQ of y\+2\(FP\); uint16 is 2-byte value" + MOVW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int16 is 2-byte value" + TESTB y+2(FP), AX // ERROR "invalid TESTB of y\+2\(FP\); uint16 is 2-byte value" + TESTW x+0(FP), AX + TESTW y+2(FP), BX + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int16 is 2-byte value" + TESTL y+2(FP), AX // ERROR "invalid TESTL of y\+2\(FP\); uint16 is 2-byte value" + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int16 is 2-byte value" + TESTQ y+2(FP), AX // ERROR "invalid TESTQ of y\+2\(FP\); uint16 is 2-byte value" + TESTW x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + TESTW y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int32 is 4-byte value" + MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint32 is 4-byte value" + MOVL x+0(FP), AX + MOVL y+4(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int32 is 4-byte value" + MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint32 is 4-byte value" + MOVL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int32 is 4-byte value" + TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint32 is 4-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int32 is 4-byte value" + TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint32 is 4-byte value" + TESTL x+0(FP), AX + TESTL y+4(FP), AX + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int32 is 4-byte value" + TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint32 is 4-byte value" + TESTL x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + TESTL y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value" + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value" + MOVL x+0(FP), AX // ERROR "invalid MOVL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)" + MOVL x_lo+0(FP), AX + MOVL x_hi+4(FP), AX + MOVL y+8(FP), AX // ERROR "invalid MOVL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)" + MOVL y_lo+8(FP), AX + MOVL y_hi+12(FP), AX + MOVQ x+0(FP), AX + MOVQ y+8(FP), AX + MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int64 is 8-byte value" + TESTB y+8(FP), BX // ERROR "invalid TESTB of y\+8\(FP\); uint64 is 8-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int64 is 8-byte value" + TESTW y+8(FP), AX // ERROR "invalid TESTW of y\+8\(FP\); uint64 is 8-byte value" + TESTL x+0(FP), AX // ERROR "invalid TESTL of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)" + TESTL y+8(FP), AX // ERROR "invalid TESTL of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)" + TESTQ x+0(FP), AX + TESTQ y+8(FP), AX + TESTQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint is 4-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int is 4-byte value" + MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); uint is 4-byte value" + MOVL x+0(FP), AX + MOVL y+4(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); int is 4-byte value" + MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); uint is 4-byte value" + MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); int is 4-byte value" + TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); uint is 4-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); int is 4-byte value" + TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); uint is 4-byte value" + TESTL x+0(FP), AX + TESTL y+4(FP), AX + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); int is 4-byte value" + TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); uint is 4-byte value" + TESTQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-20" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); \*byte is 4-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 4-byte value" + MOVW y+4(FP), AX // ERROR "invalid MOVW of y\+4\(FP\); \*byte is 4-byte value" + MOVL x+0(FP), AX + MOVL y+4(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); \*byte is 4-byte value" + MOVQ y+4(FP), AX // ERROR "invalid MOVQ of y\+4\(FP\); \*byte is 4-byte value" + MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + TESTB x+0(FP), AX // ERROR "invalid TESTB of x\+0\(FP\); \*byte is 4-byte value" + TESTB y+4(FP), BX // ERROR "invalid TESTB of y\+4\(FP\); \*byte is 4-byte value" + TESTW x+0(FP), AX // ERROR "invalid TESTW of x\+0\(FP\); \*byte is 4-byte value" + TESTW y+4(FP), AX // ERROR "invalid TESTW of y\+4\(FP\); \*byte is 4-byte value" + TESTL x+0(FP), AX + TESTL y+4(FP), AX + TESTQ x+0(FP), AX // ERROR "invalid TESTQ of x\+0\(FP\); \*byte is 4-byte value" + TESTQ y+4(FP), AX // ERROR "invalid TESTQ of y\+4\(FP\); \*byte is 4-byte value" + TESTQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + TESTQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + MOVW c+8(FP), AX // ERROR "invalid MOVW of c\+8\(FP\); chan int is 4-byte value" + MOVW m+12(FP), AX // ERROR "invalid MOVW of m\+12\(FP\); map\[int\]int is 4-byte value" + MOVW f+16(FP), AX // ERROR "invalid MOVW of f\+16\(FP\); func\(\) is 4-byte value" + RET + +TEXT ·argstring(SB),0,$16 // ERROR "wrong argument size 0; expected \$\.\.\.-16" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); string base is 4-byte value" + MOVL x+0(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); string base is 4-byte value" + MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 4-byte value" + MOVL x_base+0(FP), AX + MOVQ x_base+0(FP), AX // ERROR "invalid MOVQ of x_base\+0\(FP\); string base is 4-byte value" + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+4(FP), AX // ERROR "invalid MOVW of x_len\+4\(FP\); string len is 4-byte value" + MOVL x_len+4(FP), AX + MOVQ x_len+4(FP), AX // ERROR "invalid MOVQ of x_len\+4\(FP\); string len is 4-byte value" + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+8\(FP\)" + MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)" + RET + +TEXT ·argslice(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); slice base is 4-byte value" + MOVL x+0(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); slice base is 4-byte value" + MOVW x_base+0(FP), AX // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 4-byte value" + MOVL x_base+0(FP), AX + MOVQ x_base+0(FP), AX // ERROR "invalid MOVQ of x_base\+0\(FP\); slice base is 4-byte value" + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVL x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+4(FP), AX // ERROR "invalid MOVW of x_len\+4\(FP\); slice len is 4-byte value" + MOVL x_len+4(FP), AX + MOVQ x_len+4(FP), AX // ERROR "invalid MOVQ of x_len\+4\(FP\); slice len is 4-byte value" + MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVL x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVW x_cap+8(FP), AX // ERROR "invalid MOVW of x_cap\+8\(FP\); slice cap is 4-byte value" + MOVL x_cap+8(FP), AX + MOVQ x_cap+8(FP), AX // ERROR "invalid MOVQ of x_cap\+8\(FP\); slice cap is 4-byte value" + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+12\(FP\)" + MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)" + MOVQ y_cap+8(FP), AX // ERROR "invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-16 + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); interface type is 4-byte value" + MOVL x+0(FP), AX + MOVQ x+0(FP), AX // ERROR "invalid MOVQ of x\+0\(FP\); interface type is 4-byte value" + MOVW x_type+0(FP), AX // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 4-byte value" + MOVL x_type+0(FP), AX + MOVQ x_type+0(FP), AX // ERROR "invalid MOVQ of x_type\+0\(FP\); interface type is 4-byte value" + MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVL x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVW x_data+4(FP), AX // ERROR "invalid MOVW of x_data\+4\(FP\); interface data is 4-byte value" + MOVL x_data+4(FP), AX + MOVQ x_data+4(FP), AX // ERROR "invalid MOVQ of x_data\+4\(FP\); interface data is 4-byte value" + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); interface itable is 4-byte value" + MOVL y+8(FP), AX + MOVQ y+8(FP), AX // ERROR "invalid MOVQ of y\+8\(FP\); interface itable is 4-byte value" + MOVW y_itable+8(FP), AX // ERROR "invalid MOVW of y_itable\+8\(FP\); interface itable is 4-byte value" + MOVL y_itable+8(FP), AX + MOVQ y_itable+8(FP), AX // ERROR "invalid MOVQ of y_itable\+8\(FP\); interface itable is 4-byte value" + MOVQ y_type+8(FP), AX // ERROR "unknown variable y_type; offset 8 is y_itable\+8\(FP\)" + MOVW y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVL y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVQ y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVW y_data+12(FP), AX // ERROR "invalid MOVW of y_data\+12\(FP\); interface data is 4-byte value" + MOVL y_data+12(FP), AX + MOVQ y_data+12(FP), AX // ERROR "invalid MOVQ of y_data\+12\(FP\); interface data is 4-byte value" + RET + +TEXT ·returnint(SB),0,$0-4 + MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 4-byte value" + MOVW AX, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 4-byte value" + MOVL AX, ret+0(FP) + MOVQ AX, ret+0(FP) // ERROR "invalid MOVQ of ret\+0\(FP\); int is 4-byte value" + MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-5 + MOVL x+0(FP), AX + MOVB AX, ret+4(FP) + MOVW AX, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value" + MOVL AX, ret+4(FP) // ERROR "invalid MOVL of ret\+4\(FP\); byte is 1-byte value" + MOVQ AX, ret+4(FP) // ERROR "invalid MOVQ of ret\+4\(FP\); byte is 1-byte value" + MOVB AX, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-21 + MOVB x+0(FP), AX + MOVL AX, r1+4(FP) + MOVW AX, r2+8(FP) + MOVL AX, r3+12(FP) + MOVL AX, r3_base+12(FP) + MOVL AX, r3_len+16(FP) + MOVB AX, r4+20(FP) + MOVQ AX, r1+4(FP) // ERROR "invalid MOVQ of r1\+4\(FP\); int is 4-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-4 + RET // ERROR "RET without writing to 4-byte ret\+0\(FP\)" diff --git a/go/analysis/passes/vet/testdata/asm/asm3.s b/go/analysis/passes/vet/testdata/asm/asm3.s new file mode 100644 index 00000000..83e53862 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm3.s @@ -0,0 +1,192 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build arm +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), AX + MOVB y+1(FP), BX + MOVH x+0(FP), AX // ERROR "\[arm\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value" + MOVH y+1(FP), AX // ERROR "invalid MOVH of y\+1\(FP\); uint8 is 1-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVW y+1(FP), AX // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), AX // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVB y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 8(R13), AX // ERROR "8\(R13\) should be x\+0\(FP\)" + MOVB 9(R13), AX // ERROR "9\(R13\) should be y\+1\(FP\)" + MOVB 10(R13), AX // ERROR "use of 10\(R13\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "arg2: invalid MOVB of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), AX // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVH x+0(FP), AX + MOVH y+2(FP), BX + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int16 is 2-byte value" + MOVW y+2(FP), AX // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value" + MOVH x+2(FP), AX // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVH y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value" + MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), AX + MOVW y+4(FP), AX + MOVW x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), BX // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value" + MOVH y+8(FP), AX // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), AX // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)" + MOVW x_lo+0(FP), AX + MOVW x_hi+4(FP), AX + MOVW y+8(FP), AX // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)" + MOVW y_lo+8(FP), AX + MOVW y_hi+12(FP), AX + MOVQ x+0(FP), AX + MOVQ y+8(FP), AX + MOVQ x+8(FP), AX // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); int is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); uint is 4-byte value" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); int is 4-byte value" + MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); uint is 4-byte value" + MOVW x+0(FP), AX + MOVW y+4(FP), AX + MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-20" + MOVB x+0(FP), AX // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 4-byte value" + MOVB y+4(FP), BX // ERROR "invalid MOVB of y\+4\(FP\); \*byte is 4-byte value" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 4-byte value" + MOVH y+4(FP), AX // ERROR "invalid MOVH of y\+4\(FP\); \*byte is 4-byte value" + MOVW x+0(FP), AX + MOVW y+4(FP), AX + MOVQ x+4(FP), AX // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVQ y+2(FP), AX // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + MOVH c+8(FP), AX // ERROR "invalid MOVH of c\+8\(FP\); chan int is 4-byte value" + MOVH m+12(FP), AX // ERROR "invalid MOVH of m\+12\(FP\); map\[int\]int is 4-byte value" + MOVH f+16(FP), AX // ERROR "invalid MOVH of f\+16\(FP\); func\(\) is 4-byte value" + RET + +TEXT ·argstring(SB),0,$16 // ERROR "wrong argument size 0; expected \$\.\.\.-16" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); string base is 4-byte value" + MOVW x+0(FP), AX + MOVH x_base+0(FP), AX // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 4-byte value" + MOVW x_base+0(FP), AX + MOVH x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVH x_len+4(FP), AX // ERROR "invalid MOVH of x_len\+4\(FP\); string len is 4-byte value" + MOVW x_len+4(FP), AX + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+8\(FP\)" + MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)" + RET + +TEXT ·argslice(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24" + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); slice base is 4-byte value" + MOVW x+0(FP), AX + MOVH x_base+0(FP), AX // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 4-byte value" + MOVW x_base+0(FP), AX + MOVH x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVQ x_len+0(FP), AX // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVH x_len+4(FP), AX // ERROR "invalid MOVH of x_len\+4\(FP\); slice len is 4-byte value" + MOVW x_len+4(FP), AX + MOVH x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVW x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVQ x_cap+0(FP), AX // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVH x_cap+8(FP), AX // ERROR "invalid MOVH of x_cap\+8\(FP\); slice cap is 4-byte value" + MOVW x_cap+8(FP), AX + MOVQ y+0(FP), AX // ERROR "invalid offset y\+0\(FP\); expected y\+12\(FP\)" + MOVQ y_len+4(FP), AX // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)" + MOVQ y_cap+8(FP), AX // ERROR "invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-16 + MOVH x+0(FP), AX // ERROR "invalid MOVH of x\+0\(FP\); interface type is 4-byte value" + MOVW x+0(FP), AX + MOVH x_type+0(FP), AX // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 4-byte value" + MOVW x_type+0(FP), AX + MOVQ x_itable+0(FP), AX // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVQ x_itable+1(FP), AX // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVH x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVW x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVQ x_data+0(FP), AX // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVH x_data+4(FP), AX // ERROR "invalid MOVH of x_data\+4\(FP\); interface data is 4-byte value" + MOVW x_data+4(FP), AX + MOVH y+8(FP), AX // ERROR "invalid MOVH of y\+8\(FP\); interface itable is 4-byte value" + MOVW y+8(FP), AX + MOVH y_itable+8(FP), AX // ERROR "invalid MOVH of y_itable\+8\(FP\); interface itable is 4-byte value" + MOVW y_itable+8(FP), AX + MOVQ y_type+8(FP), AX // ERROR "unknown variable y_type; offset 8 is y_itable\+8\(FP\)" + MOVH y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVW y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVQ y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVH y_data+12(FP), AX // ERROR "invalid MOVH of y_data\+12\(FP\); interface data is 4-byte value" + MOVW y_data+12(FP), AX + RET + +TEXT ·returnint(SB),0,$0-4 + MOVB AX, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 4-byte value" + MOVH AX, ret+0(FP) // ERROR "invalid MOVH of ret\+0\(FP\); int is 4-byte value" + MOVW AX, ret+0(FP) + MOVQ AX, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVQ AX, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-5 + MOVW x+0(FP), AX + MOVB AX, ret+4(FP) + MOVH AX, ret+4(FP) // ERROR "invalid MOVH of ret\+4\(FP\); byte is 1-byte value" + MOVW AX, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value" + MOVB AX, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-21 + MOVB x+0(FP), AX + MOVW AX, r1+4(FP) + MOVH AX, r2+8(FP) + MOVW AX, r3+12(FP) + MOVW AX, r3_base+12(FP) + MOVW AX, r3_len+16(FP) + MOVB AX, r4+20(FP) + MOVB AX, r1+4(FP) // ERROR "invalid MOVB of r1\+4\(FP\); int is 4-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-4 + RET // ERROR "RET without writing to 4-byte ret\+0\(FP\)" + +TEXT ·leaf(SB),0,$-4-12 + MOVW x+0(FP), AX + MOVW y+4(FP), AX + MOVW AX, ret+8(FP) + RET + +TEXT ·noframe1(SB),0,$0-4 + MOVW 0(R13), AX // Okay; our saved LR + MOVW 4(R13), AX // Okay; caller's saved LR + MOVW x+8(R13), AX // Okay; x argument + MOVW 12(R13), AX // ERROR "use of 12\(R13\) points beyond argument frame" + RET + +TEXT ·noframe2(SB),NOFRAME,$0-4 + MOVW 0(R13), AX // Okay; caller's saved LR + MOVW x+4(R13), AX // Okay; x argument + MOVW 8(R13), AX // ERROR "use of 8\(R13\) points beyond argument frame" + MOVW 12(R13), AX // ERROR "use of 12\(R13\) points beyond argument frame" + RET diff --git a/go/analysis/passes/vet/testdata/asm/asm4.s b/go/analysis/passes/vet/testdata/asm/asm4.s new file mode 100644 index 00000000..044b050b --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm4.s @@ -0,0 +1,26 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build amd64 +// +build vet_test + +// Test cases for symbolic NOSPLIT etc. on TEXT symbols. + +TEXT ·noprof(SB),NOPROF,$0-8 + RET + +TEXT ·dupok(SB),DUPOK,$0-8 + RET + +TEXT ·nosplit(SB),NOSPLIT,$0 + RET + +TEXT ·rodata(SB),RODATA,$0-8 + RET + +TEXT ·noptr(SB),NOPTR|NOSPLIT,$0 + RET + +TEXT ·wrapper(SB),WRAPPER,$0-8 + RET diff --git a/go/analysis/passes/vet/testdata/asm/asm5.s b/go/analysis/passes/vet/testdata/asm/asm5.s new file mode 100644 index 00000000..c6176e96 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm5.s @@ -0,0 +1,193 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build mips64 +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), R1 + MOVBU y+1(FP), R2 + MOVH x+0(FP), R1 // ERROR "\[mips64\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value" + MOVHU y+1(FP), R1 // ERROR "invalid MOVHU of y\+1\(FP\); uint8 is 1-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVWU y+1(FP), R1 // ERROR "invalid MOVWU of y\+1\(FP\); uint8 is 1-byte value" + MOVV x+0(FP), R1 // ERROR "invalid MOVV of x\+0\(FP\); int8 is 1-byte value" + MOVV y+1(FP), R1 // ERROR "invalid MOVV of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), R1 // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVBU y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 16(R29), R1 // ERROR "16\(R29\) should be x\+0\(FP\)" + MOVB 17(R29), R1 // ERROR "17\(R29\) should be y\+1\(FP\)" + MOVB 18(R29), R1 // ERROR "use of 18\(R29\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVBU x+0(FP), R1 // ERROR "arg2: invalid MOVBU of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), R1 // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVHU x+0(FP), R1 + MOVH y+2(FP), R2 + MOVWU x+0(FP), R1 // ERROR "invalid MOVWU of x\+0\(FP\); int16 is 2-byte value" + MOVW y+2(FP), R1 // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value" + MOVV x+0(FP), R1 // ERROR "invalid MOVV of x\+0\(FP\); int16 is 2-byte value" + MOVV y+2(FP), R1 // ERROR "invalid MOVV of y\+2\(FP\); uint16 is 2-byte value" + MOVHU x+2(FP), R1 // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVH y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), R2 // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value" + MOVH y+4(FP), R1 // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), R1 + MOVW y+4(FP), R1 + MOVV x+0(FP), R1 // ERROR "invalid MOVV of x\+0\(FP\); int32 is 4-byte value" + MOVV y+4(FP), R1 // ERROR "invalid MOVV of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+4(FP), R1 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value" + MOVV x+0(FP), R1 + MOVV y+8(FP), R1 + MOVV x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVV y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); uint is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); uint is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); uint is 8-byte value" + MOVV x+0(FP), R1 + MOVV y+8(FP), R1 + MOVV x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVV y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-40" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); \*byte is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); \*byte is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); \*byte is 8-byte value" + MOVV x+0(FP), R1 + MOVV y+8(FP), R1 + MOVV x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVV y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + MOVW c+16(FP), R1 // ERROR "invalid MOVW of c\+16\(FP\); chan int is 8-byte value" + MOVW m+24(FP), R1 // ERROR "invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value" + MOVW f+32(FP), R1 // ERROR "invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value" + RET + +TEXT ·argstring(SB),0,$32 // ERROR "wrong argument size 0; expected \$\.\.\.-32" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); string base is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); string base is 8-byte value" + MOVV x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 8-byte value" + MOVW x_base+0(FP), R1 // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 8-byte value" + MOVV x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVV x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R1 // ERROR "invalid MOVH of x_len\+8\(FP\); string len is 8-byte value" + MOVW x_len+8(FP), R1 // ERROR "invalid MOVW of x_len\+8\(FP\); string len is 8-byte value" + MOVV x_len+8(FP), R1 + MOVV y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+16\(FP\)" + MOVV y_len+8(FP), R1 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)" + RET + +TEXT ·argslice(SB),0,$48 // ERROR "wrong argument size 0; expected \$\.\.\.-48" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); slice base is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); slice base is 8-byte value" + MOVV x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value" + MOVW x_base+0(FP), R1 // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value" + MOVV x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVV x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R1 // ERROR "invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value" + MOVW x_len+8(FP), R1 // ERROR "invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value" + MOVV x_len+8(FP), R1 + MOVH x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVW x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVV x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVH x_cap+16(FP), R1 // ERROR "invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVW x_cap+16(FP), R1 // ERROR "invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVV x_cap+16(FP), R1 + MOVV y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+24\(FP\)" + MOVV y_len+8(FP), R1 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)" + MOVV y_cap+16(FP), R1 // ERROR "invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-32 + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); interface type is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); interface type is 8-byte value" + MOVV x+0(FP), R1 + MOVH x_type+0(FP), R1 // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value" + MOVW x_type+0(FP), R1 // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value" + MOVV x_type+0(FP), R1 + MOVV x_itable+0(FP), R1 // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVV x_itable+1(FP), R1 // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVH x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVW x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVV x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVH x_data+8(FP), R1 // ERROR "invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value" + MOVW x_data+8(FP), R1 // ERROR "invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value" + MOVV x_data+8(FP), R1 + MOVH y+16(FP), R1 // ERROR "invalid MOVH of y\+16\(FP\); interface itable is 8-byte value" + MOVW y+16(FP), R1 // ERROR "invalid MOVW of y\+16\(FP\); interface itable is 8-byte value" + MOVV y+16(FP), R1 + MOVH y_itable+16(FP), R1 // ERROR "invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVW y_itable+16(FP), R1 // ERROR "invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVV y_itable+16(FP), R1 + MOVV y_type+16(FP), R1 // ERROR "unknown variable y_type; offset 16 is y_itable\+16\(FP\)" + MOVH y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVW y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVV y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVH y_data+24(FP), R1 // ERROR "invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value" + MOVW y_data+24(FP), R1 // ERROR "invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value" + MOVV y_data+24(FP), R1 + RET + +TEXT ·returnint(SB),0,$0-8 + MOVB R1, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 8-byte value" + MOVH R1, ret+0(FP) // ERROR "invalid MOVH of ret\+0\(FP\); int is 8-byte value" + MOVW R1, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 8-byte value" + MOVV R1, ret+0(FP) + MOVV R1, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVV R1, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-9 + MOVV x+0(FP), R1 + MOVB R1, ret+8(FP) + MOVH R1, ret+8(FP) // ERROR "invalid MOVH of ret\+8\(FP\); byte is 1-byte value" + MOVW R1, ret+8(FP) // ERROR "invalid MOVW of ret\+8\(FP\); byte is 1-byte value" + MOVV R1, ret+8(FP) // ERROR "invalid MOVV of ret\+8\(FP\); byte is 1-byte value" + MOVB R1, ret+7(FP) // ERROR "invalid offset ret\+7\(FP\); expected ret\+8\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-41 + MOVB x+0(FP), R1 + MOVV R1, r1+8(FP) + MOVH R1, r2+16(FP) + MOVV R1, r3+24(FP) + MOVV R1, r3_base+24(FP) + MOVV R1, r3_len+32(FP) + MOVB R1, r4+40(FP) + MOVW R1, r1+8(FP) // ERROR "invalid MOVW of r1\+8\(FP\); int is 8-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-8 + RET // ERROR "RET without writing to 8-byte ret\+0\(FP\)" diff --git a/go/analysis/passes/vet/testdata/asm/asm6.s b/go/analysis/passes/vet/testdata/asm/asm6.s new file mode 100644 index 00000000..4e85ab3d --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm6.s @@ -0,0 +1,193 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build s390x +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), R1 + MOVBZ y+1(FP), R2 + MOVH x+0(FP), R1 // ERROR "\[s390x\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value" + MOVHZ y+1(FP), R1 // ERROR "invalid MOVHZ of y\+1\(FP\); uint8 is 1-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVWZ y+1(FP), R1 // ERROR "invalid MOVWZ of y\+1\(FP\); uint8 is 1-byte value" + MOVD x+0(FP), R1 // ERROR "invalid MOVD of x\+0\(FP\); int8 is 1-byte value" + MOVD y+1(FP), R1 // ERROR "invalid MOVD of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), R1 // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVBZ y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 16(R15), R1 // ERROR "16\(R15\) should be x\+0\(FP\)" + MOVB 17(R15), R1 // ERROR "17\(R15\) should be y\+1\(FP\)" + MOVB 18(R15), R1 // ERROR "use of 18\(R15\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVBZ x+0(FP), R1 // ERROR "arg2: invalid MOVBZ of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), R1 // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVHZ x+0(FP), R1 + MOVH y+2(FP), R2 + MOVWZ x+0(FP), R1 // ERROR "invalid MOVWZ of x\+0\(FP\); int16 is 2-byte value" + MOVW y+2(FP), R1 // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value" + MOVD x+0(FP), R1 // ERROR "invalid MOVD of x\+0\(FP\); int16 is 2-byte value" + MOVD y+2(FP), R1 // ERROR "invalid MOVD of y\+2\(FP\); uint16 is 2-byte value" + MOVHZ x+2(FP), R1 // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVH y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), R2 // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value" + MOVH y+4(FP), R1 // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), R1 + MOVW y+4(FP), R1 + MOVD x+0(FP), R1 // ERROR "invalid MOVD of x\+0\(FP\); int32 is 4-byte value" + MOVD y+4(FP), R1 // ERROR "invalid MOVD of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+4(FP), R1 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value" + MOVD x+0(FP), R1 + MOVD y+8(FP), R1 + MOVD x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); uint is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); uint is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); uint is 8-byte value" + MOVD x+0(FP), R1 + MOVD y+8(FP), R1 + MOVD x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-40" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); \*byte is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); \*byte is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 8-byte value" + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); \*byte is 8-byte value" + MOVD x+0(FP), R1 + MOVD y+8(FP), R1 + MOVD x+8(FP), R1 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + MOVW c+16(FP), R1 // ERROR "invalid MOVW of c\+16\(FP\); chan int is 8-byte value" + MOVW m+24(FP), R1 // ERROR "invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value" + MOVW f+32(FP), R1 // ERROR "invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value" + RET + +TEXT ·argstring(SB),0,$32 // ERROR "wrong argument size 0; expected \$\.\.\.-32" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); string base is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); string base is 8-byte value" + MOVD x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 8-byte value" + MOVW x_base+0(FP), R1 // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 8-byte value" + MOVD x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVD x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R1 // ERROR "invalid MOVH of x_len\+8\(FP\); string len is 8-byte value" + MOVW x_len+8(FP), R1 // ERROR "invalid MOVW of x_len\+8\(FP\); string len is 8-byte value" + MOVD x_len+8(FP), R1 + MOVD y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+16\(FP\)" + MOVD y_len+8(FP), R1 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)" + RET + +TEXT ·argslice(SB),0,$48 // ERROR "wrong argument size 0; expected \$\.\.\.-48" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); slice base is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); slice base is 8-byte value" + MOVD x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value" + MOVW x_base+0(FP), R1 // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value" + MOVD x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVD x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R1 // ERROR "invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value" + MOVW x_len+8(FP), R1 // ERROR "invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value" + MOVD x_len+8(FP), R1 + MOVH x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVW x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVD x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVH x_cap+16(FP), R1 // ERROR "invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVW x_cap+16(FP), R1 // ERROR "invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVD x_cap+16(FP), R1 + MOVD y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+24\(FP\)" + MOVD y_len+8(FP), R1 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)" + MOVD y_cap+16(FP), R1 // ERROR "invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-32 + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); interface type is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); interface type is 8-byte value" + MOVD x+0(FP), R1 + MOVH x_type+0(FP), R1 // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value" + MOVW x_type+0(FP), R1 // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value" + MOVD x_type+0(FP), R1 + MOVD x_itable+0(FP), R1 // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVD x_itable+1(FP), R1 // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVH x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVW x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVD x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVH x_data+8(FP), R1 // ERROR "invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value" + MOVW x_data+8(FP), R1 // ERROR "invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value" + MOVD x_data+8(FP), R1 + MOVH y+16(FP), R1 // ERROR "invalid MOVH of y\+16\(FP\); interface itable is 8-byte value" + MOVW y+16(FP), R1 // ERROR "invalid MOVW of y\+16\(FP\); interface itable is 8-byte value" + MOVD y+16(FP), R1 + MOVH y_itable+16(FP), R1 // ERROR "invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVW y_itable+16(FP), R1 // ERROR "invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVD y_itable+16(FP), R1 + MOVD y_type+16(FP), R1 // ERROR "unknown variable y_type; offset 16 is y_itable\+16\(FP\)" + MOVH y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVW y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVD y_data+16(FP), R1 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVH y_data+24(FP), R1 // ERROR "invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value" + MOVW y_data+24(FP), R1 // ERROR "invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value" + MOVD y_data+24(FP), R1 + RET + +TEXT ·returnint(SB),0,$0-8 + MOVB R1, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 8-byte value" + MOVH R1, ret+0(FP) // ERROR "invalid MOVH of ret\+0\(FP\); int is 8-byte value" + MOVW R1, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 8-byte value" + MOVD R1, ret+0(FP) + MOVD R1, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVD R1, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-9 + MOVD x+0(FP), R1 + MOVB R1, ret+8(FP) + MOVH R1, ret+8(FP) // ERROR "invalid MOVH of ret\+8\(FP\); byte is 1-byte value" + MOVW R1, ret+8(FP) // ERROR "invalid MOVW of ret\+8\(FP\); byte is 1-byte value" + MOVD R1, ret+8(FP) // ERROR "invalid MOVD of ret\+8\(FP\); byte is 1-byte value" + MOVB R1, ret+7(FP) // ERROR "invalid offset ret\+7\(FP\); expected ret\+8\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-41 + MOVB x+0(FP), R1 + MOVD R1, r1+8(FP) + MOVH R1, r2+16(FP) + MOVD R1, r3+24(FP) + MOVD R1, r3_base+24(FP) + MOVD R1, r3_len+32(FP) + MOVB R1, r4+40(FP) + MOVW R1, r1+8(FP) // ERROR "invalid MOVW of r1\+8\(FP\); int is 8-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-8 + RET // ERROR "RET without writing to 8-byte ret\+0\(FP\)" diff --git a/go/analysis/passes/vet/testdata/asm/asm7.s b/go/analysis/passes/vet/testdata/asm/asm7.s new file mode 100644 index 00000000..d5ff5460 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm/asm7.s @@ -0,0 +1,193 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ppc64 ppc64le +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), R3 + MOVBZ y+1(FP), R4 + MOVH x+0(FP), R3 // ERROR "\[(ppc64|ppc64le)\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value" + MOVHZ y+1(FP), R3 // ERROR "invalid MOVHZ of y\+1\(FP\); uint8 is 1-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVWZ y+1(FP), R3 // ERROR "invalid MOVWZ of y\+1\(FP\); uint8 is 1-byte value" + MOVD x+0(FP), R3 // ERROR "invalid MOVD of x\+0\(FP\); int8 is 1-byte value" + MOVD y+1(FP), R3 // ERROR "invalid MOVD of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), R3 // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVBZ y+2(FP), R3 // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 16(R1), R3 // ERROR "16\(R1\) should be x\+0\(FP\)" + MOVB 17(R1), R3 // ERROR "17\(R1\) should be y\+1\(FP\)" + MOVB 18(R1), R3 // ERROR "use of 18\(R1\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVBZ x+0(FP), R3 // ERROR "arg2: invalid MOVBZ of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), R3 // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVHZ x+0(FP), R3 + MOVH y+2(FP), R4 + MOVWZ x+0(FP), R3 // ERROR "invalid MOVWZ of x\+0\(FP\); int16 is 2-byte value" + MOVW y+2(FP), R3 // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value" + MOVD x+0(FP), R3 // ERROR "invalid MOVD of x\+0\(FP\); int16 is 2-byte value" + MOVD y+2(FP), R3 // ERROR "invalid MOVD of y\+2\(FP\); uint16 is 2-byte value" + MOVHZ x+2(FP), R3 // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVH y+0(FP), R3 // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), R3 // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), R4 // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value" + MOVH y+4(FP), R3 // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), R3 + MOVW y+4(FP), R3 + MOVD x+0(FP), R3 // ERROR "invalid MOVD of x\+0\(FP\); int32 is 4-byte value" + MOVD y+4(FP), R3 // ERROR "invalid MOVD of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+4(FP), R3 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R3 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R3 // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), R4 // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value" + MOVH y+8(FP), R3 // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value" + MOVW y+8(FP), R3 // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value" + MOVD x+0(FP), R3 + MOVD y+8(FP), R3 + MOVD x+8(FP), R3 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R3 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R3 // ERROR "invalid MOVB of x\+0\(FP\); int is 8-byte value" + MOVB y+8(FP), R4 // ERROR "invalid MOVB of y\+8\(FP\); uint is 8-byte value" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); int is 8-byte value" + MOVH y+8(FP), R3 // ERROR "invalid MOVH of y\+8\(FP\); uint is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); int is 8-byte value" + MOVW y+8(FP), R3 // ERROR "invalid MOVW of y\+8\(FP\); uint is 8-byte value" + MOVD x+0(FP), R3 + MOVD y+8(FP), R3 + MOVD x+8(FP), R3 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R3 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-40" + MOVB x+0(FP), R3 // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 8-byte value" + MOVB y+8(FP), R4 // ERROR "invalid MOVB of y\+8\(FP\); \*byte is 8-byte value" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 8-byte value" + MOVH y+8(FP), R3 // ERROR "invalid MOVH of y\+8\(FP\); \*byte is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); \*byte is 8-byte value" + MOVW y+8(FP), R3 // ERROR "invalid MOVW of y\+8\(FP\); \*byte is 8-byte value" + MOVD x+0(FP), R3 + MOVD y+8(FP), R3 + MOVD x+8(FP), R3 // ERROR "invalid offset x\+8\(FP\); expected x\+0\(FP\)" + MOVD y+2(FP), R3 // ERROR "invalid offset y\+2\(FP\); expected y\+8\(FP\)" + MOVW c+16(FP), R3 // ERROR "invalid MOVW of c\+16\(FP\); chan int is 8-byte value" + MOVW m+24(FP), R3 // ERROR "invalid MOVW of m\+24\(FP\); map\[int\]int is 8-byte value" + MOVW f+32(FP), R3 // ERROR "invalid MOVW of f\+32\(FP\); func\(\) is 8-byte value" + RET + +TEXT ·argstring(SB),0,$32 // ERROR "wrong argument size 0; expected \$\.\.\.-32" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); string base is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); string base is 8-byte value" + MOVD x+0(FP), R3 + MOVH x_base+0(FP), R3 // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 8-byte value" + MOVW x_base+0(FP), R3 // ERROR "invalid MOVW of x_base\+0\(FP\); string base is 8-byte value" + MOVD x_base+0(FP), R3 + MOVH x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVD x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R3 // ERROR "invalid MOVH of x_len\+8\(FP\); string len is 8-byte value" + MOVW x_len+8(FP), R3 // ERROR "invalid MOVW of x_len\+8\(FP\); string len is 8-byte value" + MOVD x_len+8(FP), R3 + MOVD y+0(FP), R3 // ERROR "invalid offset y\+0\(FP\); expected y\+16\(FP\)" + MOVD y_len+8(FP), R3 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+24\(FP\)" + RET + +TEXT ·argslice(SB),0,$48 // ERROR "wrong argument size 0; expected \$\.\.\.-48" + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); slice base is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); slice base is 8-byte value" + MOVD x+0(FP), R3 + MOVH x_base+0(FP), R3 // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 8-byte value" + MOVW x_base+0(FP), R3 // ERROR "invalid MOVW of x_base\+0\(FP\); slice base is 8-byte value" + MOVD x_base+0(FP), R3 + MOVH x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVW x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVD x_len+0(FP), R3 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+8\(FP\)" + MOVH x_len+8(FP), R3 // ERROR "invalid MOVH of x_len\+8\(FP\); slice len is 8-byte value" + MOVW x_len+8(FP), R3 // ERROR "invalid MOVW of x_len\+8\(FP\); slice len is 8-byte value" + MOVD x_len+8(FP), R3 + MOVH x_cap+0(FP), R3 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVW x_cap+0(FP), R3 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVD x_cap+0(FP), R3 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+16\(FP\)" + MOVH x_cap+16(FP), R3 // ERROR "invalid MOVH of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVW x_cap+16(FP), R3 // ERROR "invalid MOVW of x_cap\+16\(FP\); slice cap is 8-byte value" + MOVD x_cap+16(FP), R3 + MOVD y+0(FP), R3 // ERROR "invalid offset y\+0\(FP\); expected y\+24\(FP\)" + MOVD y_len+8(FP), R3 // ERROR "invalid offset y_len\+8\(FP\); expected y_len\+32\(FP\)" + MOVD y_cap+16(FP), R3 // ERROR "invalid offset y_cap\+16\(FP\); expected y_cap\+40\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-32 + MOVH x+0(FP), R3 // ERROR "invalid MOVH of x\+0\(FP\); interface type is 8-byte value" + MOVW x+0(FP), R3 // ERROR "invalid MOVW of x\+0\(FP\); interface type is 8-byte value" + MOVD x+0(FP), R3 + MOVH x_type+0(FP), R3 // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 8-byte value" + MOVW x_type+0(FP), R3 // ERROR "invalid MOVW of x_type\+0\(FP\); interface type is 8-byte value" + MOVD x_type+0(FP), R3 + MOVD x_itable+0(FP), R3 // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVD x_itable+1(FP), R3 // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVH x_data+0(FP), R3 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVW x_data+0(FP), R3 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVD x_data+0(FP), R3 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+8\(FP\)" + MOVH x_data+8(FP), R3 // ERROR "invalid MOVH of x_data\+8\(FP\); interface data is 8-byte value" + MOVW x_data+8(FP), R3 // ERROR "invalid MOVW of x_data\+8\(FP\); interface data is 8-byte value" + MOVD x_data+8(FP), R3 + MOVH y+16(FP), R3 // ERROR "invalid MOVH of y\+16\(FP\); interface itable is 8-byte value" + MOVW y+16(FP), R3 // ERROR "invalid MOVW of y\+16\(FP\); interface itable is 8-byte value" + MOVD y+16(FP), R3 + MOVH y_itable+16(FP), R3 // ERROR "invalid MOVH of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVW y_itable+16(FP), R3 // ERROR "invalid MOVW of y_itable\+16\(FP\); interface itable is 8-byte value" + MOVD y_itable+16(FP), R3 + MOVD y_type+16(FP), R3 // ERROR "unknown variable y_type; offset 16 is y_itable\+16\(FP\)" + MOVH y_data+16(FP), R3 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVW y_data+16(FP), R3 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVD y_data+16(FP), R3 // ERROR "invalid offset y_data\+16\(FP\); expected y_data\+24\(FP\)" + MOVH y_data+24(FP), R3 // ERROR "invalid MOVH of y_data\+24\(FP\); interface data is 8-byte value" + MOVW y_data+24(FP), R3 // ERROR "invalid MOVW of y_data\+24\(FP\); interface data is 8-byte value" + MOVD y_data+24(FP), R3 + RET + +TEXT ·returnint(SB),0,$0-8 + MOVB R3, ret+0(FP) // ERROR "invalid MOVB of ret\+0\(FP\); int is 8-byte value" + MOVH R3, ret+0(FP) // ERROR "invalid MOVH of ret\+0\(FP\); int is 8-byte value" + MOVW R3, ret+0(FP) // ERROR "invalid MOVW of ret\+0\(FP\); int is 8-byte value" + MOVD R3, ret+0(FP) + MOVD R3, ret+1(FP) // ERROR "invalid offset ret\+1\(FP\); expected ret\+0\(FP\)" + MOVD R3, r+0(FP) // ERROR "unknown variable r; offset 0 is ret\+0\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-9 + MOVD x+0(FP), R3 + MOVB R3, ret+8(FP) + MOVH R3, ret+8(FP) // ERROR "invalid MOVH of ret\+8\(FP\); byte is 1-byte value" + MOVW R3, ret+8(FP) // ERROR "invalid MOVW of ret\+8\(FP\); byte is 1-byte value" + MOVD R3, ret+8(FP) // ERROR "invalid MOVD of ret\+8\(FP\); byte is 1-byte value" + MOVB R3, ret+7(FP) // ERROR "invalid offset ret\+7\(FP\); expected ret\+8\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-41 + MOVB x+0(FP), R3 + MOVD R3, r1+8(FP) + MOVH R3, r2+16(FP) + MOVD R3, r3+24(FP) + MOVD R3, r3_base+24(FP) + MOVD R3, r3_len+32(FP) + MOVB R3, r4+40(FP) + MOVW R3, r1+8(FP) // ERROR "invalid MOVW of r1\+8\(FP\); int is 8-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-8 + RET // ERROR "RET without writing to 8-byte ret\+0\(FP\)" diff --git a/go/analysis/passes/vet/testdata/asm8.s b/go/analysis/passes/vet/testdata/asm8.s new file mode 100644 index 00000000..550d92a8 --- /dev/null +++ b/go/analysis/passes/vet/testdata/asm8.s @@ -0,0 +1,165 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build mipsle +// +build vet_test + +TEXT ·arg1(SB),0,$0-2 + MOVB x+0(FP), R1 + MOVBU y+1(FP), R2 + MOVH x+0(FP), R1 // ERROR "\[mipsle\] arg1: invalid MOVH of x\+0\(FP\); int8 is 1-byte value" + MOVHU y+1(FP), R1 // ERROR "invalid MOVHU of y\+1\(FP\); uint8 is 1-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int8 is 1-byte value" + MOVWU y+1(FP), R1 // ERROR "invalid MOVWU of y\+1\(FP\); uint8 is 1-byte value" + MOVW y+1(FP), R1 // ERROR "invalid MOVW of y\+1\(FP\); uint8 is 1-byte value" + MOVB x+1(FP), R1 // ERROR "invalid offset x\+1\(FP\); expected x\+0\(FP\)" + MOVBU y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+1\(FP\)" + MOVB 8(R29), R1 // ERROR "8\(R29\) should be x\+0\(FP\)" + MOVB 9(R29), R1 // ERROR "9\(R29\) should be y\+1\(FP\)" + MOVB 10(R29), R1 // ERROR "use of 10\(R29\) points beyond argument frame" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVBU x+0(FP), R1 // ERROR "arg2: invalid MOVBU of x\+0\(FP\); int16 is 2-byte value" + MOVB y+2(FP), R1 // ERROR "invalid MOVB of y\+2\(FP\); uint16 is 2-byte value" + MOVHU x+0(FP), R1 + MOVH y+2(FP), R2 + MOVWU x+0(FP), R1 // ERROR "invalid MOVWU of x\+0\(FP\); int16 is 2-byte value" + MOVW y+2(FP), R1 // ERROR "invalid MOVW of y\+2\(FP\); uint16 is 2-byte value" + MOVHU x+2(FP), R1 // ERROR "invalid offset x\+2\(FP\); expected x\+0\(FP\)" + MOVH y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+2\(FP\)" + RET + +TEXT ·arg4(SB),0,$0-2 // ERROR "arg4: wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int32 is 4-byte value" + MOVB y+4(FP), R2 // ERROR "invalid MOVB of y\+4\(FP\); uint32 is 4-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int32 is 4-byte value" + MOVH y+4(FP), R1 // ERROR "invalid MOVH of y\+4\(FP\); uint32 is 4-byte value" + MOVW x+0(FP), R1 + MOVW y+4(FP), R1 + MOVW x+4(FP), R1 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·arg8(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-16" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int64 is 8-byte value" + MOVB y+8(FP), R2 // ERROR "invalid MOVB of y\+8\(FP\); uint64 is 8-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int64 is 8-byte value" + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); uint64 is 8-byte value" + MOVW x+0(FP), R1 // ERROR "invalid MOVW of x\+0\(FP\); int64 is 8-byte value containing x_lo\+0\(FP\) and x_hi\+4\(FP\)" + MOVW x_lo+0(FP), R1 + MOVW x_hi+4(FP), R1 + MOVW y+8(FP), R1 // ERROR "invalid MOVW of y\+8\(FP\); uint64 is 8-byte value containing y_lo\+8\(FP\) and y_hi\+12\(FP\)" + MOVW y_lo+8(FP), R1 + MOVW y_hi+12(FP), R1 + RET + +TEXT ·argint(SB),0,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-8" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); int is 4-byte value" + MOVB y+4(FP), R2 // ERROR "invalid MOVB of y\+4\(FP\); uint is 4-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); int is 4-byte value" + MOVH y+4(FP), R1 // ERROR "invalid MOVH of y\+4\(FP\); uint is 4-byte value" + MOVW x+0(FP), R1 + MOVW y+4(FP), R1 + MOVW x+4(FP), R1 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + RET + +TEXT ·argptr(SB),7,$0-2 // ERROR "wrong argument size 2; expected \$\.\.\.-20" + MOVB x+0(FP), R1 // ERROR "invalid MOVB of x\+0\(FP\); \*byte is 4-byte value" + MOVB y+4(FP), R2 // ERROR "invalid MOVB of y\+4\(FP\); \*byte is 4-byte value" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); \*byte is 4-byte value" + MOVH y+4(FP), R1 // ERROR "invalid MOVH of y\+4\(FP\); \*byte is 4-byte value" + MOVW x+0(FP), R1 + MOVW y+4(FP), R1 + MOVW x+4(FP), R1 // ERROR "invalid offset x\+4\(FP\); expected x\+0\(FP\)" + MOVW y+2(FP), R1 // ERROR "invalid offset y\+2\(FP\); expected y\+4\(FP\)" + MOVH c+8(FP), R1 // ERROR "invalid MOVH of c\+8\(FP\); chan int is 4-byte value" + MOVH m+12(FP), R1 // ERROR "invalid MOVH of m\+12\(FP\); map\[int\]int is 4-byte value" + MOVH f+16(FP), R1 // ERROR "invalid MOVH of f\+16\(FP\); func\(\) is 4-byte value" + RET + +TEXT ·argstring(SB),0,$16 // ERROR "wrong argument size 0; expected \$\.\.\.-16" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); string base is 4-byte value" + MOVW x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); string base is 4-byte value" + MOVW x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVH x_len+4(FP), R1 // ERROR "invalid MOVH of x_len\+4\(FP\); string len is 4-byte value" + MOVW x_len+4(FP), R1 + MOVW y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+8\(FP\)" + MOVW y_len+4(FP), R1 // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+12\(FP\)" + RET + +TEXT ·argslice(SB),0,$24 // ERROR "wrong argument size 0; expected \$\.\.\.-24" + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); slice base is 4-byte value" + MOVW x+0(FP), R1 + MOVH x_base+0(FP), R1 // ERROR "invalid MOVH of x_base\+0\(FP\); slice base is 4-byte value" + MOVW x_base+0(FP), R1 + MOVH x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVW x_len+0(FP), R1 // ERROR "invalid offset x_len\+0\(FP\); expected x_len\+4\(FP\)" + MOVH x_len+4(FP), R1 // ERROR "invalid MOVH of x_len\+4\(FP\); slice len is 4-byte value" + MOVW x_len+4(FP), R1 + MOVH x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVW x_cap+0(FP), R1 // ERROR "invalid offset x_cap\+0\(FP\); expected x_cap\+8\(FP\)" + MOVH x_cap+8(FP), R1 // ERROR "invalid MOVH of x_cap\+8\(FP\); slice cap is 4-byte value" + MOVW x_cap+8(FP), R1 + MOVW y+0(FP), R1 // ERROR "invalid offset y\+0\(FP\); expected y\+12\(FP\)" + MOVW y_len+4(FP), R1 // ERROR "invalid offset y_len\+4\(FP\); expected y_len\+16\(FP\)" + MOVW y_cap+8(FP), R1 // ERROR "invalid offset y_cap\+8\(FP\); expected y_cap\+20\(FP\)" + RET + +TEXT ·argiface(SB),0,$0-16 + MOVH x+0(FP), R1 // ERROR "invalid MOVH of x\+0\(FP\); interface type is 4-byte value" + MOVW x+0(FP), R1 + MOVH x_type+0(FP), R1 // ERROR "invalid MOVH of x_type\+0\(FP\); interface type is 4-byte value" + MOVW x_type+0(FP), R1 + MOVQ x_itable+0(FP), R1 // ERROR "unknown variable x_itable; offset 0 is x_type\+0\(FP\)" + MOVQ x_itable+1(FP), R1 // ERROR "unknown variable x_itable; offset 1 is x_type\+0\(FP\)" + MOVH x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVW x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVQ x_data+0(FP), R1 // ERROR "invalid offset x_data\+0\(FP\); expected x_data\+4\(FP\)" + MOVH x_data+4(FP), R1 // ERROR "invalid MOVH of x_data\+4\(FP\); interface data is 4-byte value" + MOVW x_data+4(FP), R1 + MOVH y+8(FP), R1 // ERROR "invalid MOVH of y\+8\(FP\); interface itable is 4-byte value" + MOVW y+8(FP), R1 + MOVH y_itable+8(FP), R1 // ERROR "invalid MOVH of y_itable\+8\(FP\); interface itable is 4-byte value" + MOVW y_itable+8(FP), R1 + MOVW y_type+8(FP), AX // ERROR "unknown variable y_type; offset 8 is y_itable\+8\(FP\)" + MOVH y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVW y_data+8(FP), AX // ERROR "invalid offset y_data\+8\(FP\); expected y_data\+12\(FP\)" + MOVH y_data+12(FP), AX // ERROR "invalid MOVH of y_data\+12\(FP\); interface data is 4-byte value" + MOVW y_data+12(FP), AX + RET + +TEXT ·returnbyte(SB),0,$0-5 + MOVW x+0(FP), R1 + MOVB R1, ret+4(FP) + MOVH R1, ret+4(FP) // ERROR "invalid MOVH of ret\+4\(FP\); byte is 1-byte value" + MOVW R1, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value" + MOVB R1, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)" + RET + +TEXT ·returnbyte(SB),0,$0-5 + MOVW x+0(FP), R1 + MOVB R1, ret+4(FP) + MOVH R1, ret+4(FP) // ERROR "invalid MOVH of ret\+4\(FP\); byte is 1-byte value" + MOVW R1, ret+4(FP) // ERROR "invalid MOVW of ret\+4\(FP\); byte is 1-byte value" + MOVB R1, ret+3(FP) // ERROR "invalid offset ret\+3\(FP\); expected ret\+4\(FP\)" + RET + +TEXT ·returnnamed(SB),0,$0-21 + MOVB x+0(FP), AX + MOVW R1, r1+4(FP) + MOVH R1, r2+8(FP) + MOVW R1, r3+12(FP) + MOVW R1, r3_base+12(FP) + MOVW R1, r3_len+16(FP) + MOVB R1, r4+20(FP) + MOVB R1, r1+4(FP) // ERROR "invalid MOVB of r1\+4\(FP\); int is 4-byte value" + RET + +TEXT ·returnintmissing(SB),0,$0-4 + RET // ERROR "RET without writing to 4-byte ret\+0\(FP\)" diff --git a/go/analysis/passes/vet/testdata/assign.go b/go/analysis/passes/vet/testdata/assign.go new file mode 100644 index 00000000..6140ad4d --- /dev/null +++ b/go/analysis/passes/vet/testdata/assign.go @@ -0,0 +1,31 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the useless-assignment checker. + +package testdata + +import "math/rand" + +type ST struct { + x int + l []int +} + +func (s *ST) SetX(x int, ch chan int) { + // Accidental self-assignment; it should be "s.x = x" + x = x // ERROR "self-assignment of x to x" + // Another mistake + s.x = s.x // ERROR "self-assignment of s.x to s.x" + + s.l[0] = s.l[0] // ERROR "self-assignment of s.l.0. to s.l.0." + + // Bail on any potential side effects to avoid false positives + s.l[num()] = s.l[num()] + rng := rand.New(rand.NewSource(0)) + s.l[rng.Intn(len(s.l))] = s.l[rng.Intn(len(s.l))] + s.l[<-ch] = s.l[<-ch] +} + +func num() int { return 2 } diff --git a/go/analysis/passes/vet/testdata/atomic.go b/go/analysis/passes/vet/testdata/atomic.go new file mode 100644 index 00000000..69730b4e --- /dev/null +++ b/go/analysis/passes/vet/testdata/atomic.go @@ -0,0 +1,62 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the atomic checker. + +package testdata + +import ( + "sync/atomic" +) + +type Counter uint64 + +func AtomicTests() { + x := uint64(1) + x = atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value" + _, x = 10, atomic.AddUint64(&x, 1) // ERROR "direct assignment to atomic value" + x, _ = atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value" + + y := &x + *y = atomic.AddUint64(y, 1) // ERROR "direct assignment to atomic value" + + var su struct{ Counter uint64 } + su.Counter = atomic.AddUint64(&su.Counter, 1) // ERROR "direct assignment to atomic value" + z1 := atomic.AddUint64(&su.Counter, 1) + _ = z1 // Avoid err "z declared and not used" + + var sp struct{ Counter *uint64 } + *sp.Counter = atomic.AddUint64(sp.Counter, 1) // ERROR "direct assignment to atomic value" + z2 := atomic.AddUint64(sp.Counter, 1) + _ = z2 // Avoid err "z declared and not used" + + au := []uint64{10, 20} + au[0] = atomic.AddUint64(&au[0], 1) // ERROR "direct assignment to atomic value" + au[1] = atomic.AddUint64(&au[0], 1) + + ap := []*uint64{&au[0], &au[1]} + *ap[0] = atomic.AddUint64(ap[0], 1) // ERROR "direct assignment to atomic value" + *ap[1] = atomic.AddUint64(ap[0], 1) + + x = atomic.AddUint64() // Used to make vet crash; now silently ignored. + + { + // A variable declaration creates a new variable in the current scope. + x := atomic.AddUint64(&x, 1) // ERROR "declaration of .x. shadows declaration at atomic.go:16" + + // Re-declaration assigns a new value. + x, w := atomic.AddUint64(&x, 1), 10 // ERROR "direct assignment to atomic value" + _ = w + } +} + +type T struct{} + +func (T) AddUint64(addr *uint64, delta uint64) uint64 { return 0 } + +func NonAtomic() { + x := uint64(1) + var atomic T + x = atomic.AddUint64(&x, 1) // ok; not the imported pkg +} diff --git a/go/analysis/passes/vet/testdata/bool.go b/go/analysis/passes/vet/testdata/bool.go new file mode 100644 index 00000000..80c44d25 --- /dev/null +++ b/go/analysis/passes/vet/testdata/bool.go @@ -0,0 +1,131 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the bool checker. + +package testdata + +import "io" + +type T int + +func (t T) Foo() int { return int(t) } + +type FT func() int + +var S []int + +func RatherStupidConditions() { + var f, g func() int + if f() == 0 || f() == 0 { // OK f might have side effects + } + var t T + _ = t.Foo() == 2 || t.Foo() == 2 // OK Foo might have side effects + if v, w := f(), g(); v == w || v == w { // ERROR "redundant or: v == w || v == w" + } + _ = f == nil || f == nil // ERROR "redundant or: f == nil || f == nil" + + _ = i == byte(1) || i == byte(1) // ERROR "redundant or: i == byte(1) || i == byte(1)" + _ = i == T(2) || i == T(2) // ERROR "redundant or: i == T(2) || i == T(2)" + _ = FT(f) == nil || FT(f) == nil // ERROR "redundant or: FT(f) == nil || FT(f) == nil" + + _ = (func() int)(f) == nil || (func() int)(f) == nil // ERROR "redundant or: (func() int)(f) == nil || (func() int)(f) == nil" + _ = append(S, 3) == nil || append(S, 3) == nil // OK append has side effects + + var namedFuncVar FT + _ = namedFuncVar() == namedFuncVar() // OK still func calls + + var c chan int + _ = 0 == <-c || 0 == <-c // OK subsequent receives may yield different values + for i, j := <-c, <-c; i == j || i == j; i, j = <-c, <-c { // ERROR "redundant or: i == j || i == j" + } + + var i, j, k int + _ = i+1 == 1 || i+1 == 1 // ERROR "redundant or: i\+1 == 1 || i\+1 == 1" + _ = i == 1 || j+1 == i || i == 1 // ERROR "redundant or: i == 1 || i == 1" + + _ = i == 1 || i == 1 || f() == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || f() == 1 || i == 1 // OK f may alter i as a side effect + _ = f() == 1 || i == 1 || i == 1 // ERROR "redundant or: i == 1 || i == 1" + + // Test partition edge cases + _ = f() == 1 || i == 1 || i == 1 || j == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = f() == 1 || j == 1 || i == 1 || i == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || f() == 1 || i == 1 || i == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || i == 1 || f() == 1 || i == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || i == 1 || j == 1 || f() == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = j == 1 || i == 1 || i == 1 || f() == 1 // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || f() == 1 || f() == 1 || i == 1 + + _ = i == 1 || (i == 1 || i == 2) // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || (f() == 1 || i == 1) // OK f may alter i as a side effect + _ = i == 1 || (i == 1 || f() == 1) // ERROR "redundant or: i == 1 || i == 1" + _ = i == 1 || (i == 2 || (i == 1 || i == 3)) // ERROR "redundant or: i == 1 || i == 1" + + var a, b bool + _ = i == 1 || (a || (i == 1 || b)) // ERROR "redundant or: i == 1 || i == 1" + + // Check that all redundant ors are flagged + _ = j == 0 || + i == 1 || + f() == 1 || + j == 0 || // ERROR "redundant or: j == 0 || j == 0" + i == 1 || // ERROR "redundant or: i == 1 || i == 1" + i == 1 || // ERROR "redundant or: i == 1 || i == 1" + i == 1 || + j == 0 || + k == 0 + + _ = i == 1*2*3 || i == 1*2*3 // ERROR "redundant or: i == 1\*2\*3 || i == 1\*2\*3" + + // These test that redundant, suspect expressions do not trigger multiple errors. + _ = i != 0 || i != 0 // ERROR "redundant or: i != 0 || i != 0" + _ = i == 0 && i == 0 // ERROR "redundant and: i == 0 && i == 0" + + // and is dual to or; check the basics and + // let the or tests pull the rest of the weight. + _ = 0 != <-c && 0 != <-c // OK subsequent receives may yield different values + _ = f() != 0 && f() != 0 // OK f might have side effects + _ = f != nil && f != nil // ERROR "redundant and: f != nil && f != nil" + _ = i != 1 && i != 1 && f() != 1 // ERROR "redundant and: i != 1 && i != 1" + _ = i != 1 && f() != 1 && i != 1 // OK f may alter i as a side effect + _ = f() != 1 && i != 1 && i != 1 // ERROR "redundant and: i != 1 && i != 1" +} + +func RoyallySuspectConditions() { + var i, j int + + _ = i == 0 || i == 1 // OK + _ = i != 0 || i != 1 // ERROR "suspect or: i != 0 || i != 1" + _ = i != 0 || 1 != i // ERROR "suspect or: i != 0 || 1 != i" + _ = 0 != i || 1 != i // ERROR "suspect or: 0 != i || 1 != i" + _ = 0 != i || i != 1 // ERROR "suspect or: 0 != i || i != 1" + + _ = (0 != i) || i != 1 // ERROR "suspect or: 0 != i || i != 1" + + _ = i+3 != 7 || j+5 == 0 || i+3 != 9 // ERROR "suspect or: i\+3 != 7 || i\+3 != 9" + + _ = i != 0 || j == 0 || i != 1 // ERROR "suspect or: i != 0 || i != 1" + + _ = i != 0 || i != 1<<4 // ERROR "suspect or: i != 0 || i != 1<<4" + + _ = i != 0 || j != 0 + _ = 0 != i || 0 != j + + var s string + _ = s != "one" || s != "the other" // ERROR "suspect or: s != .one. || s != .the other." + + _ = "et" != "alii" || "et" != "cetera" // ERROR "suspect or: .et. != .alii. || .et. != .cetera." + _ = "me gustas" != "tu" || "le gustas" != "tu" // OK we could catch this case, but it's not worth the code + + var err error + _ = err != nil || err != io.EOF // TODO catch this case? + + // Sanity check and. + _ = i != 0 && i != 1 // OK + _ = i == 0 && i == 1 // ERROR "suspect and: i == 0 && i == 1" + _ = i == 0 && 1 == i // ERROR "suspect and: i == 0 && 1 == i" + _ = 0 == i && 1 == i // ERROR "suspect and: 0 == i && 1 == i" + _ = 0 == i && i == 1 // ERROR "suspect and: 0 == i && i == 1" +} diff --git a/go/analysis/passes/vet/testdata/buildtag/buildtag.go b/go/analysis/passes/vet/testdata/buildtag/buildtag.go new file mode 100644 index 00000000..c2fd6aaa --- /dev/null +++ b/go/analysis/passes/vet/testdata/buildtag/buildtag.go @@ -0,0 +1,18 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the buildtag checker. + +// +builder // ERROR "possible malformed \+build comment" +// +build !ignore + +package testdata + +// +build toolate // ERROR "build comment must appear before package clause and be followed by a blank line$" + +var _ = 3 + +var _ = ` +// +build notacomment +` diff --git a/go/analysis/passes/vet/testdata/buildtag/buildtag_bad.go b/go/analysis/passes/vet/testdata/buildtag/buildtag_bad.go new file mode 100644 index 00000000..fbe10cf7 --- /dev/null +++ b/go/analysis/passes/vet/testdata/buildtag/buildtag_bad.go @@ -0,0 +1,15 @@ +// This file contains misplaced or malformed build constraints. +// The Go tool will skip it, because the constraints are invalid. +// It serves only to test the tag checker during make test. + +// Mention +build // ERROR "possible malformed \+build comment" + +// +build !!bang // ERROR "invalid double negative in build constraint" +// +build @#$ // ERROR "invalid non-alphanumeric build constraint" + +// +build toolate // ERROR "build comment must appear before package clause and be followed by a blank line" +package bad + +// This is package 'bad' rather than 'main' so the erroneous build +// tag doesn't end up looking like a package doc for the vet command +// when examined by godoc. diff --git a/go/analysis/passes/vet/testdata/cgo/cgo.go b/go/analysis/passes/vet/testdata/cgo/cgo.go new file mode 100644 index 00000000..d0df7cf6 --- /dev/null +++ b/go/analysis/passes/vet/testdata/cgo/cgo.go @@ -0,0 +1,59 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the cgo checker. + +package testdata + +// void f(void *); +import "C" + +import "unsafe" + +func CgoTests() { + var c chan bool + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&c))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&c)) // ERROR "embedded pointer" + + var m map[string]string + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&m))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&m)) // ERROR "embedded pointer" + + var f func() + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&f))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&f)) // ERROR "embedded pointer" + + var s []int + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&s)) // ERROR "embedded pointer" + + var a [1][]int + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&a)) // ERROR "embedded pointer" + + var st struct{ f []int } + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st))) // ERROR "embedded pointer" + C.f(unsafe.Pointer(&st)) // ERROR "embedded pointer" + + // The following cases are OK. + var i int + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&i))) + C.f(unsafe.Pointer(&i)) + + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&s[0]))) + C.f(unsafe.Pointer(&s[0])) + + var a2 [1]int + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&a2))) + C.f(unsafe.Pointer(&a2)) + + var st2 struct{ i int } + C.f(*(*unsafe.Pointer)(unsafe.Pointer(&st2))) + C.f(unsafe.Pointer(&st2)) + + type cgoStruct struct{ p *cgoStruct } + C.f(unsafe.Pointer(&cgoStruct{})) + + C.CBytes([]byte("hello")) +} diff --git a/go/analysis/passes/vet/testdata/cgo/cgo2.go b/go/analysis/passes/vet/testdata/cgo/cgo2.go new file mode 100644 index 00000000..4f271168 --- /dev/null +++ b/go/analysis/passes/vet/testdata/cgo/cgo2.go @@ -0,0 +1,12 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test the cgo checker on a file that doesn't use cgo. + +package testdata + +var _ = C.f(*p(**p)) + +// Passing a pointer (via the slice), but C isn't cgo. +var _ = C.f([]int{3}) diff --git a/go/analysis/passes/vet/testdata/cgo/cgo3.go b/go/analysis/passes/vet/testdata/cgo/cgo3.go new file mode 100644 index 00000000..0b1518e1 --- /dev/null +++ b/go/analysis/passes/vet/testdata/cgo/cgo3.go @@ -0,0 +1,13 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Used by TestVetVerbose to test that vet -v doesn't fail because it +// can't find "C". + +package testdata + +import "C" + +func F() { +} diff --git a/go/analysis/passes/vet/testdata/cgo/cgo4.go b/go/analysis/passes/vet/testdata/cgo/cgo4.go new file mode 100644 index 00000000..67b54506 --- /dev/null +++ b/go/analysis/passes/vet/testdata/cgo/cgo4.go @@ -0,0 +1,15 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Test the cgo checker on a file that doesn't use cgo, but has an +// import named "C". + +package testdata + +import C "fmt" + +var _ = C.Println(*p(**p)) + +// Passing a pointer (via a slice), but C is fmt, not cgo. +var _ = C.Println([]int{3}) diff --git a/go/analysis/passes/vet/testdata/composite.go b/go/analysis/passes/vet/testdata/composite.go new file mode 100644 index 00000000..3fe3eac7 --- /dev/null +++ b/go/analysis/passes/vet/testdata/composite.go @@ -0,0 +1,120 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for untagged struct literals. + +package testdata + +import ( + "flag" + "go/scanner" + "image" + "unicode" + + "path/to/unknownpkg" +) + +var Okay1 = []string{ + "Name", + "Usage", + "DefValue", +} + +var Okay2 = map[string]bool{ + "Name": true, + "Usage": true, + "DefValue": true, +} + +var Okay3 = struct { + X string + Y string + Z string +}{ + "Name", + "Usage", + "DefValue", +} + +var Okay4 = []struct { + A int + B int +}{ + {1, 2}, + {3, 4}, +} + +type MyStruct struct { + X string + Y string + Z string +} + +var Okay5 = &MyStruct{ + "Name", + "Usage", + "DefValue", +} + +var Okay6 = []MyStruct{ + {"foo", "bar", "baz"}, + {"aa", "bb", "cc"}, +} + +var Okay7 = []*MyStruct{ + {"foo", "bar", "baz"}, + {"aa", "bb", "cc"}, +} + +// Testing is awkward because we need to reference things from a separate package +// to trigger the warnings. + +var goodStructLiteral = flag.Flag{ + Name: "Name", + Usage: "Usage", +} +var badStructLiteral = flag.Flag{ // ERROR "unkeyed fields" + "Name", + "Usage", + nil, // Value + "DefValue", +} + +// SpecialCase is a named slice of CaseRange to test issue 9171. +var goodNamedSliceLiteral = unicode.SpecialCase{ + {Lo: 1, Hi: 2}, + unicode.CaseRange{Lo: 1, Hi: 2}, +} +var badNamedSliceLiteral = unicode.SpecialCase{ + {1, 2}, // ERROR "unkeyed fields" + unicode.CaseRange{1, 2}, // ERROR "unkeyed fields" +} + +// ErrorList is a named slice, so no warnings should be emitted. +var goodScannerErrorList = scanner.ErrorList{ + &scanner.Error{Msg: "foobar"}, +} +var badScannerErrorList = scanner.ErrorList{ + &scanner.Error{"foobar"}, // ERROR "unkeyed fields" +} + +// Check whitelisted structs: if vet is run with --compositewhitelist=false, +// this line triggers an error. +var whitelistedPoint = image.Point{1, 2} + +// Do not check type from unknown package. +// See issue 15408. +var unknownPkgVar = unknownpkg.Foobar{"foo", "bar"} + +// A named pointer slice of CaseRange to test issue 23539. In +// particular, we're interested in how some slice elements omit their +// type. +var goodNamedPointerSliceLiteral = []*unicode.CaseRange{ + {Lo: 1, Hi: 2}, + &unicode.CaseRange{Lo: 1, Hi: 2}, +} +var badNamedPointerSliceLiteral = []*unicode.CaseRange{ + {1, 2}, // ERROR "unkeyed fields" + &unicode.CaseRange{1, 2}, // ERROR "unkeyed fields" +} diff --git a/go/analysis/passes/vet/testdata/copylock.go b/go/analysis/passes/vet/testdata/copylock.go new file mode 100644 index 00000000..e9902a27 --- /dev/null +++ b/go/analysis/passes/vet/testdata/copylock.go @@ -0,0 +1,188 @@ +package testdata + +import ( + "sync" + "sync/atomic" + "unsafe" + . "unsafe" + unsafe1 "unsafe" +) + +func OkFunc() { + var x *sync.Mutex + p := x + var y sync.Mutex + p = &y + + var z = sync.Mutex{} + w := sync.Mutex{} + + w = sync.Mutex{} + q := struct{ L sync.Mutex }{ + L: sync.Mutex{}, + } + + yy := []Tlock{ + Tlock{}, + Tlock{ + once: sync.Once{}, + }, + } + + nl := new(sync.Mutex) + mx := make([]sync.Mutex, 10) + xx := struct{ L *sync.Mutex }{ + L: new(sync.Mutex), + } +} + +type Tlock struct { + once sync.Once +} + +func BadFunc() { + var x *sync.Mutex + p := x + var y sync.Mutex + p = &y + *p = *x // ERROR "assignment copies lock value to \*p: sync.Mutex" + + var t Tlock + var tp *Tlock + tp = &t + *tp = t // ERROR "assignment copies lock value to \*tp: testdata.Tlock contains sync.Once contains sync.Mutex" + t = *tp // ERROR "assignment copies lock value to t: testdata.Tlock contains sync.Once contains sync.Mutex" + + y := *x // ERROR "assignment copies lock value to y: sync.Mutex" + var z = t // ERROR "variable declaration copies lock value to z: testdata.Tlock contains sync.Once contains sync.Mutex" + + w := struct{ L sync.Mutex }{ + L: *x, // ERROR "literal copies lock value from \*x: sync.Mutex" + } + var q = map[int]Tlock{ + 1: t, // ERROR "literal copies lock value from t: testdata.Tlock contains sync.Once contains sync.Mutex" + 2: *tp, // ERROR "literal copies lock value from \*tp: testdata.Tlock contains sync.Once contains sync.Mutex" + } + yy := []Tlock{ + t, // ERROR "literal copies lock value from t: testdata.Tlock contains sync.Once contains sync.Mutex" + *tp, // ERROR "literal copies lock value from \*tp: testdata.Tlock contains sync.Once contains sync.Mutex" + } + + // override 'new' keyword + new := func(interface{}) {} + new(t) // ERROR "call of new copies lock value: testdata.Tlock contains sync.Once contains sync.Mutex" + + // copy of array of locks + var muA [5]sync.Mutex + muB := muA // ERROR "assignment copies lock value to muB: sync.Mutex" + muA = muB // ERROR "assignment copies lock value to muA: sync.Mutex" + muSlice := muA[:] // OK + + // multidimensional array + var mmuA [5][5]sync.Mutex + mmuB := mmuA // ERROR "assignment copies lock value to mmuB: sync.Mutex" + mmuA = mmuB // ERROR "assignment copies lock value to mmuA: sync.Mutex" + mmuSlice := mmuA[:] // OK + + // slice copy is ok + var fmuA [5][][5]sync.Mutex + fmuB := fmuA // OK + fmuA = fmuB // OK + fmuSlice := fmuA[:] // OK +} + +func LenAndCapOnLockArrays() { + var a [5]sync.Mutex + aLen := len(a) // OK + aCap := cap(a) // OK + + // override 'len' and 'cap' keywords + + len := func(interface{}) {} + len(a) // ERROR "call of len copies lock value: sync.Mutex" + + cap := func(interface{}) {} + cap(a) // ERROR "call of cap copies lock value: sync.Mutex" +} + +func SizeofMutex() { + var mu sync.Mutex + unsafe.Sizeof(mu) // OK + unsafe1.Sizeof(mu) // OK + Sizeof(mu) // OK + unsafe := struct{ Sizeof func(interface{}) }{} + unsafe.Sizeof(mu) // ERROR "call of unsafe.Sizeof copies lock value: sync.Mutex" + Sizeof := func(interface{}) {} + Sizeof(mu) // ERROR "call of Sizeof copies lock value: sync.Mutex" +} + +// SyncTypesCheck checks copying of sync.* types except sync.Mutex +func SyncTypesCheck() { + // sync.RWMutex copying + var rwmuX sync.RWMutex + var rwmuXX = sync.RWMutex{} + rwmuX1 := new(sync.RWMutex) + rwmuY := rwmuX // ERROR "assignment copies lock value to rwmuY: sync.RWMutex" + rwmuY = rwmuX // ERROR "assignment copies lock value to rwmuY: sync.RWMutex" + var rwmuYY = rwmuX // ERROR "variable declaration copies lock value to rwmuYY: sync.RWMutex" + rwmuP := &rwmuX + rwmuZ := &sync.RWMutex{} + + // sync.Cond copying + var condX sync.Cond + var condXX = sync.Cond{} + condX1 := new(sync.Cond) + condY := condX // ERROR "assignment copies lock value to condY: sync.Cond contains sync.noCopy" + condY = condX // ERROR "assignment copies lock value to condY: sync.Cond contains sync.noCopy" + var condYY = condX // ERROR "variable declaration copies lock value to condYY: sync.Cond contains sync.noCopy" + condP := &condX + condZ := &sync.Cond{ + L: &sync.Mutex{}, + } + condZ = sync.NewCond(&sync.Mutex{}) + + // sync.WaitGroup copying + var wgX sync.WaitGroup + var wgXX = sync.WaitGroup{} + wgX1 := new(sync.WaitGroup) + wgY := wgX // ERROR "assignment copies lock value to wgY: sync.WaitGroup contains sync.noCopy" + wgY = wgX // ERROR "assignment copies lock value to wgY: sync.WaitGroup contains sync.noCopy" + var wgYY = wgX // ERROR "variable declaration copies lock value to wgYY: sync.WaitGroup contains sync.noCopy" + wgP := &wgX + wgZ := &sync.WaitGroup{} + + // sync.Pool copying + var poolX sync.Pool + var poolXX = sync.Pool{} + poolX1 := new(sync.Pool) + poolY := poolX // ERROR "assignment copies lock value to poolY: sync.Pool contains sync.noCopy" + poolY = poolX // ERROR "assignment copies lock value to poolY: sync.Pool contains sync.noCopy" + var poolYY = poolX // ERROR "variable declaration copies lock value to poolYY: sync.Pool contains sync.noCopy" + poolP := &poolX + poolZ := &sync.Pool{} + + // sync.Once copying + var onceX sync.Once + var onceXX = sync.Once{} + onceX1 := new(sync.Once) + onceY := onceX // ERROR "assignment copies lock value to onceY: sync.Once contains sync.Mutex" + onceY = onceX // ERROR "assignment copies lock value to onceY: sync.Once contains sync.Mutex" + var onceYY = onceX // ERROR "variable declaration copies lock value to onceYY: sync.Once contains sync.Mutex" + onceP := &onceX + onceZ := &sync.Once{} +} + +// AtomicTypesCheck checks copying of sync/atomic types +func AtomicTypesCheck() { + // atomic.Value copying + var vX atomic.Value + var vXX = atomic.Value{} + vX1 := new(atomic.Value) + // These are OK because the value has not been used yet. + // (And vet can't tell whether it has been used, so they're always OK.) + vY := vX + vY = vX + var vYY = vX + vP := &vX + vZ := &atomic.Value{} +} diff --git a/go/analysis/passes/vet/testdata/copylock_func.go b/go/analysis/passes/vet/testdata/copylock_func.go new file mode 100644 index 00000000..280747a3 --- /dev/null +++ b/go/analysis/passes/vet/testdata/copylock_func.go @@ -0,0 +1,136 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the copylock checker's +// function declaration analysis. + +package testdata + +import "sync" + +func OkFunc(*sync.Mutex) {} +func BadFunc(sync.Mutex) {} // ERROR "BadFunc passes lock by value: sync.Mutex" +func BadFunc2(sync.Map) {} // ERROR "BadFunc2 passes lock by value: sync.Map contains sync.Mutex" +func OkRet() *sync.Mutex {} +func BadRet() sync.Mutex {} // Don't warn about results + +var ( + OkClosure = func(*sync.Mutex) {} + BadClosure = func(sync.Mutex) {} // ERROR "func passes lock by value: sync.Mutex" + BadClosure2 = func(sync.Map) {} // ERROR "func passes lock by value: sync.Map contains sync.Mutex" +) + +type EmbeddedRWMutex struct { + sync.RWMutex +} + +func (*EmbeddedRWMutex) OkMeth() {} +func (EmbeddedRWMutex) BadMeth() {} // ERROR "BadMeth passes lock by value: testdata.EmbeddedRWMutex" +func OkFunc(e *EmbeddedRWMutex) {} +func BadFunc(EmbeddedRWMutex) {} // ERROR "BadFunc passes lock by value: testdata.EmbeddedRWMutex" +func OkRet() *EmbeddedRWMutex {} +func BadRet() EmbeddedRWMutex {} // Don't warn about results + +type FieldMutex struct { + s sync.Mutex +} + +func (*FieldMutex) OkMeth() {} +func (FieldMutex) BadMeth() {} // ERROR "BadMeth passes lock by value: testdata.FieldMutex contains sync.Mutex" +func OkFunc(*FieldMutex) {} +func BadFunc(FieldMutex, int) {} // ERROR "BadFunc passes lock by value: testdata.FieldMutex contains sync.Mutex" + +type L0 struct { + L1 +} + +type L1 struct { + l L2 +} + +type L2 struct { + sync.Mutex +} + +func (*L0) Ok() {} +func (L0) Bad() {} // ERROR "Bad passes lock by value: testdata.L0 contains testdata.L1 contains testdata.L2" + +type EmbeddedMutexPointer struct { + s *sync.Mutex // safe to copy this pointer +} + +func (*EmbeddedMutexPointer) Ok() {} +func (EmbeddedMutexPointer) AlsoOk() {} +func StillOk(EmbeddedMutexPointer) {} +func LookinGood() EmbeddedMutexPointer {} + +type EmbeddedLocker struct { + sync.Locker // safe to copy interface values +} + +func (*EmbeddedLocker) Ok() {} +func (EmbeddedLocker) AlsoOk() {} + +type CustomLock struct{} + +func (*CustomLock) Lock() {} +func (*CustomLock) Unlock() {} + +func Ok(*CustomLock) {} +func Bad(CustomLock) {} // ERROR "Bad passes lock by value: testdata.CustomLock" + +// Passing lock values into interface function arguments +func FuncCallInterfaceArg(f func(a int, b interface{})) { + var m sync.Mutex + var t struct{ lock sync.Mutex } + + f(1, "foo") + f(2, &t) + f(3, &sync.Mutex{}) + f(4, m) // ERROR "call of f copies lock value: sync.Mutex" + f(5, t) // ERROR "call of f copies lock value: struct.lock sync.Mutex. contains sync.Mutex" + var fntab []func(t) + fntab[0](t) // ERROR "call of fntab.0. copies lock value: struct.lock sync.Mutex. contains sync.Mutex" +} + +// Returning lock via interface value +func ReturnViaInterface(x int) (int, interface{}) { + var m sync.Mutex + var t struct{ lock sync.Mutex } + + switch x % 4 { + case 0: + return 0, "qwe" + case 1: + return 1, &sync.Mutex{} + case 2: + return 2, m // ERROR "return copies lock value: sync.Mutex" + default: + return 3, t // ERROR "return copies lock value: struct.lock sync.Mutex. contains sync.Mutex" + } +} + +// Some cases that we don't warn about. + +func AcceptedCases() { + x := EmbeddedRwMutex{} // composite literal on RHS is OK (#16227) + x = BadRet() // function call on RHS is OK (#16227) + x = *OKRet() // indirection of function call on RHS is OK (#16227) +} + +// TODO: Unfortunate cases + +// Non-ideal error message: +// Since we're looking for Lock methods, sync.Once's underlying +// sync.Mutex gets called out, but without any reference to the sync.Once. +type LocalOnce sync.Once + +func (LocalOnce) Bad() {} // ERROR "Bad passes lock by value: testdata.LocalOnce contains sync.Mutex" + +// False negative: +// LocalMutex doesn't have a Lock method. +// Nevertheless, it is probably a bad idea to pass it by value. +type LocalMutex sync.Mutex + +func (LocalMutex) Bad() {} // WANTED: An error here :( diff --git a/go/analysis/passes/vet/testdata/copylock_range.go b/go/analysis/passes/vet/testdata/copylock_range.go new file mode 100644 index 00000000..f1273812 --- /dev/null +++ b/go/analysis/passes/vet/testdata/copylock_range.go @@ -0,0 +1,67 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the copylock checker's +// range statement analysis. + +package testdata + +import "sync" + +func rangeMutex() { + var mu sync.Mutex + var i int + + var s []sync.Mutex + for range s { + } + for i = range s { + } + for i := range s { + } + for i, _ = range s { + } + for i, _ := range s { + } + for _, mu = range s { // ERROR "range var mu copies lock: sync.Mutex" + } + for _, m := range s { // ERROR "range var m copies lock: sync.Mutex" + } + for i, mu = range s { // ERROR "range var mu copies lock: sync.Mutex" + } + for i, m := range s { // ERROR "range var m copies lock: sync.Mutex" + } + + var a [3]sync.Mutex + for _, m := range a { // ERROR "range var m copies lock: sync.Mutex" + } + + var m map[sync.Mutex]sync.Mutex + for k := range m { // ERROR "range var k copies lock: sync.Mutex" + } + for mu, _ = range m { // ERROR "range var mu copies lock: sync.Mutex" + } + for k, _ := range m { // ERROR "range var k copies lock: sync.Mutex" + } + for _, mu = range m { // ERROR "range var mu copies lock: sync.Mutex" + } + for _, v := range m { // ERROR "range var v copies lock: sync.Mutex" + } + + var c chan sync.Mutex + for range c { + } + for mu = range c { // ERROR "range var mu copies lock: sync.Mutex" + } + for v := range c { // ERROR "range var v copies lock: sync.Mutex" + } + + // Test non-idents in range variables + var t struct { + i int + mu sync.Mutex + } + for t.i, t.mu = range s { // ERROR "range var t.mu copies lock: sync.Mutex" + } +} diff --git a/go/analysis/passes/vet/testdata/deadcode.go b/go/analysis/passes/vet/testdata/deadcode.go new file mode 100644 index 00000000..d1a7adee --- /dev/null +++ b/go/analysis/passes/vet/testdata/deadcode.go @@ -0,0 +1,2134 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// This file contains tests for the dead code checker. + +package testdata + +type T int + +var x interface{} +var c chan int + +func external() int // ok + +func _() int { +} + +func _() int { + print(1) +} + +func _() int { + print(1) + return 2 + println() // ERROR "unreachable code" +} + +func _() int { +L: + print(1) + goto L + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + panic(2) + println() // ERROR "unreachable code" +} + +// but only builtin panic +func _() int { + var panic = func(int) {} + print(1) + panic(2) + println() // ok +} + +func _() int { + { + print(1) + return 2 + println() // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + { + print(1) + return 2 + } + println() // ERROR "unreachable code" +} + +func _() int { +L: + { + print(1) + goto L + println() // ERROR "unreachable code" + } + println() // ok +} + +func _() int { +L: + { + print(1) + goto L + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + { + panic(2) + } +} + +func _() int { + print(1) + { + panic(2) + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + { + panic(2) + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + return 2 + { // ERROR "unreachable code" + } +} + +func _() int { +L: + print(1) + goto L + { // ERROR "unreachable code" + } +} + +func _() int { + print(1) + panic(2) + { // ERROR "unreachable code" + } +} + +func _() int { + { + print(1) + return 2 + { // ERROR "unreachable code" + } + } +} + +func _() int { +L: + { + print(1) + goto L + { // ERROR "unreachable code" + } + } +} + +func _() int { + print(1) + { + panic(2) + { // ERROR "unreachable code" + } + } +} + +func _() int { + { + print(1) + return 2 + } + { // ERROR "unreachable code" + } +} + +func _() int { +L: + { + print(1) + goto L + } + { // ERROR "unreachable code" + } +} + +func _() int { + print(1) + { + panic(2) + } + { // ERROR "unreachable code" + } +} + +func _() int { + print(1) + if x == nil { + panic(2) + } else { + panic(3) + } + println() // ERROR "unreachable code" +} + +func _() int { +L: + print(1) + if x == nil { + panic(2) + } else { + goto L + } + println() // ERROR "unreachable code" +} + +func _() int { +L: + print(1) + if x == nil { + panic(2) + } else if x == 1 { + return 0 + } else if x != 2 { + panic(3) + } else { + goto L + } + println() // ERROR "unreachable code" +} + +// if-else chain missing final else is not okay, even if the +// conditions cover every possible case. + +func _() int { + print(1) + if x == nil { + panic(2) + } else if x != nil { + panic(3) + } + println() // ok +} + +func _() int { + print(1) + if x == nil { + panic(2) + } + println() // ok +} + +func _() int { +L: + print(1) + if x == nil { + panic(2) + } else if x == 1 { + return 0 + } else if x != 1 { + panic(3) + } + println() // ok +} + +func _() int { + print(1) + for { + } + println() // ERROR "unreachable code" +} + +func _() int { + for { + for { + break + } + } + println() // ERROR "unreachable code" +} + +func _() int { + for { + for { + break + println() // ERROR "unreachable code" + } + } +} + +func _() int { + for { + for { + continue + println() // ERROR "unreachable code" + } + } +} + +func _() int { + for { + L: + for { + break L + } + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + for { + break + } + println() // ok +} + +func _() int { + for { + for { + } + break // ERROR "unreachable code" + } + println() // ok +} + +func _() int { +L: + for { + for { + break L + } + } + println() // ok +} + +func _() int { + print(1) + for x == nil { + } + println() // ok +} + +func _() int { + for x == nil { + for { + break + } + } + println() // ok +} + +func _() int { + for x == nil { + L: + for { + break L + } + } + println() // ok +} + +func _() int { + print(1) + for true { + } + println() // ok +} + +func _() int { + for true { + for { + break + } + } + println() // ok +} + +func _() int { + for true { + L: + for { + break L + } + } + println() // ok +} + +func _() int { + print(1) + select {} + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + select { + case <-c: + print(2) + for { + } + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + select { + case <-c: + print(2) + for { + } + } + println() // ERROR "unreachable code" +} + +func _() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + case c <- 1: + print(2) + goto L + println() // ERROR "unreachable code" + } +} + +func _() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + case c <- 1: + print(2) + goto L + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + default: + select {} + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + default: + select {} + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + select { + case <-c: + print(2) + } + println() // ok +} + +func _() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + goto L // ERROR "unreachable code" + case c <- 1: + print(2) + } + println() // ok +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + default: + print(2) + } + println() // ok +} + +func _() int { + print(1) + select { + default: + break + } + println() // ok +} + +func _() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + break // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + print(1) +L: + select { + case <-c: + print(2) + for { + break L + } + } + println() // ok +} + +func _() int { + print(1) +L: + select { + case <-c: + print(2) + panic("abc") + case c <- 1: + print(2) + break L + } + println() // ok +} + +func _() int { + print(1) + select { + case <-c: + print(1) + panic("abc") + default: + select {} + break // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + println() // ERROR "unreachable code" + default: + return 4 + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + default: + return 4 + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch x { + default: + return 4 + println() // ERROR "unreachable code" + case 1: + print(2) + panic(3) + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x { + default: + return 4 + case 1: + print(2) + panic(3) + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + default: + return 4 + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + default: + return 4 + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch { + } + println() // ok +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + case 2: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x { + case 2: + return 4 + case 1: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + case 2: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) +L: + switch x { + case 1: + print(2) + panic(3) + break L // ERROR "unreachable code" + default: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x { + default: + return 4 + break // ERROR "unreachable code" + case 1: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) +L: + switch x { + case 1: + print(2) + for { + break L + } + default: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + println() // ERROR "unreachable code" + default: + return 4 + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + default: + return 4 + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch x.(type) { + default: + return 4 + println() // ERROR "unreachable code" + case int: + print(2) + panic(3) + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x.(type) { + default: + return 4 + case int: + print(2) + panic(3) + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + default: + return 4 + println() // ERROR "unreachable code" + } +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + default: + return 4 + } + println() // ERROR "unreachable code" +} + +func _() int { + print(1) + switch { + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + case float64: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + case float64: + return 4 + case int: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + case float64: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) +L: + switch x.(type) { + case int: + print(2) + panic(3) + break L // ERROR "unreachable code" + default: + return 4 + } + println() // ok +} + +func _() int { + print(1) + switch x.(type) { + default: + return 4 + break // ERROR "unreachable code" + case int: + print(2) + panic(3) + } + println() // ok +} + +func _() int { + print(1) +L: + switch x.(type) { + case int: + print(2) + for { + break L + } + default: + return 4 + } + println() // ok +} + +// again, but without the leading print(1). +// testing that everything works when the terminating statement is first. + +func _() int { + println() // ok +} + +func _() int { + return 2 + println() // ERROR "unreachable code" +} + +func _() int { +L: + goto L + println() // ERROR "unreachable code" +} + +func _() int { + panic(2) + println() // ERROR "unreachable code" +} + +// but only builtin panic +func _() int { + var panic = func(int) {} + panic(2) + println() // ok +} + +func _() int { + { + return 2 + println() // ERROR "unreachable code" + } +} + +func _() int { + { + return 2 + } + println() // ERROR "unreachable code" +} + +func _() int { +L: + { + goto L + println() // ERROR "unreachable code" + } +} + +func _() int { +L: + { + goto L + } + println() // ERROR "unreachable code" +} + +func _() int { + { + panic(2) + println() // ERROR "unreachable code" + } +} + +func _() int { + { + panic(2) + } + println() // ERROR "unreachable code" +} + +func _() int { + return 2 + { // ERROR "unreachable code" + } + println() // ok +} + +func _() int { +L: + goto L + { // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + panic(2) + { // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + { + return 2 + { // ERROR "unreachable code" + } + } + println() // ok +} + +func _() int { +L: + { + goto L + { // ERROR "unreachable code" + } + } + println() // ok +} + +func _() int { + { + panic(2) + { // ERROR "unreachable code" + } + } + println() // ok +} + +func _() int { + { + return 2 + } + { // ERROR "unreachable code" + } + println() // ok +} + +func _() int { +L: + { + goto L + } + { // ERROR "unreachable code" + } + println() // ok +} + +func _() int { + { + panic(2) + } + { // ERROR "unreachable code" + } + println() // ok +} + +// again, with func literals + +var _ = func() int { +} + +var _ = func() int { + print(1) +} + +var _ = func() int { + print(1) + return 2 + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + print(1) + goto L + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + panic(2) + println() // ERROR "unreachable code" +} + +// but only builtin panic +var _ = func() int { + var panic = func(int) {} + print(1) + panic(2) + println() // ok +} + +var _ = func() int { + { + print(1) + return 2 + println() // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + { + print(1) + return 2 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + { + print(1) + goto L + println() // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { +L: + { + print(1) + goto L + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + { + panic(2) + } +} + +var _ = func() int { + print(1) + { + panic(2) + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + { + panic(2) + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + return 2 + { // ERROR "unreachable code" + } +} + +var _ = func() int { +L: + print(1) + goto L + { // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + panic(2) + { // ERROR "unreachable code" + } +} + +var _ = func() int { + { + print(1) + return 2 + { // ERROR "unreachable code" + } + } +} + +var _ = func() int { +L: + { + print(1) + goto L + { // ERROR "unreachable code" + } + } +} + +var _ = func() int { + print(1) + { + panic(2) + { // ERROR "unreachable code" + } + } +} + +var _ = func() int { + { + print(1) + return 2 + } + { // ERROR "unreachable code" + } +} + +var _ = func() int { +L: + { + print(1) + goto L + } + { // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + { + panic(2) + } + { // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + if x == nil { + panic(2) + } else { + panic(3) + } + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + print(1) + if x == nil { + panic(2) + } else { + goto L + } + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + print(1) + if x == nil { + panic(2) + } else if x == 1 { + return 0 + } else if x != 2 { + panic(3) + } else { + goto L + } + println() // ERROR "unreachable code" +} + +// if-else chain missing final else is not okay, even if the +// conditions cover every possible case. + +var _ = func() int { + print(1) + if x == nil { + panic(2) + } else if x != nil { + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) + if x == nil { + panic(2) + } + println() // ok +} + +var _ = func() int { +L: + print(1) + if x == nil { + panic(2) + } else if x == 1 { + return 0 + } else if x != 1 { + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) + for { + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + for { + for { + break + } + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + for { + for { + break + println() // ERROR "unreachable code" + } + } +} + +var _ = func() int { + for { + for { + continue + println() // ERROR "unreachable code" + } + } +} + +var _ = func() int { + for { + L: + for { + break L + } + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + for { + break + } + println() // ok +} + +var _ = func() int { + for { + for { + } + break // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { +L: + for { + for { + break L + } + } + println() // ok +} + +var _ = func() int { + print(1) + for x == nil { + } + println() // ok +} + +var _ = func() int { + for x == nil { + for { + break + } + } + println() // ok +} + +var _ = func() int { + for x == nil { + L: + for { + break L + } + } + println() // ok +} + +var _ = func() int { + print(1) + for true { + } + println() // ok +} + +var _ = func() int { + for true { + for { + break + } + } + println() // ok +} + +var _ = func() int { + for true { + L: + for { + break L + } + } + println() // ok +} + +var _ = func() int { + print(1) + select {} + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + for { + } + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + for { + } + } + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + case c <- 1: + print(2) + goto L + println() // ERROR "unreachable code" + } +} + +var _ = func() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + case c <- 1: + print(2) + goto L + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + println() // ERROR "unreachable code" + default: + select {} + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + default: + select {} + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + } + println() // ok +} + +var _ = func() int { +L: + print(1) + select { + case <-c: + print(2) + panic("abc") + goto L // ERROR "unreachable code" + case c <- 1: + print(2) + } + println() // ok +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + default: + print(2) + } + println() // ok +} + +var _ = func() int { + print(1) + select { + default: + break + } + println() // ok +} + +var _ = func() int { + print(1) + select { + case <-c: + print(2) + panic("abc") + break // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + print(1) +L: + select { + case <-c: + print(2) + for { + break L + } + } + println() // ok +} + +var _ = func() int { + print(1) +L: + select { + case <-c: + print(2) + panic("abc") + case c <- 1: + print(2) + break L + } + println() // ok +} + +var _ = func() int { + print(1) + select { + case <-c: + print(1) + panic("abc") + default: + select {} + break // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + println() // ERROR "unreachable code" + default: + return 4 + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + default: + return 4 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch x { + default: + return 4 + println() // ERROR "unreachable code" + case 1: + print(2) + panic(3) + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x { + default: + return 4 + case 1: + print(2) + panic(3) + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + default: + return 4 + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + default: + return 4 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch { + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + case 2: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + case 2: + return 4 + case 1: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + fallthrough + case 2: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + case 1: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) +L: + switch x { + case 1: + print(2) + panic(3) + break L // ERROR "unreachable code" + default: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x { + default: + return 4 + break // ERROR "unreachable code" + case 1: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) +L: + switch x { + case 1: + print(2) + for { + break L + } + default: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + println() // ERROR "unreachable code" + default: + return 4 + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + default: + return 4 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch x.(type) { + default: + return 4 + println() // ERROR "unreachable code" + case int: + print(2) + panic(3) + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x.(type) { + default: + return 4 + case int: + print(2) + panic(3) + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + default: + return 4 + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + default: + return 4 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + print(1) + switch { + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + case float64: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + case float64: + return 4 + case int: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + fallthrough + case float64: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + case int: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) +L: + switch x.(type) { + case int: + print(2) + panic(3) + break L // ERROR "unreachable code" + default: + return 4 + } + println() // ok +} + +var _ = func() int { + print(1) + switch x.(type) { + default: + return 4 + break // ERROR "unreachable code" + case int: + print(2) + panic(3) + } + println() // ok +} + +var _ = func() int { + print(1) +L: + switch x.(type) { + case int: + print(2) + for { + break L + } + default: + return 4 + } + println() // ok +} + +// again, but without the leading print(1). +// testing that everything works when the terminating statement is first. + +var _ = func() int { + println() // ok +} + +var _ = func() int { + return 2 + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + goto L + println() // ERROR "unreachable code" +} + +var _ = func() int { + panic(2) + println() // ERROR "unreachable code" +} + +// but only builtin panic +var _ = func() int { + var panic = func(int) {} + panic(2) + println() // ok +} + +var _ = func() int { + { + return 2 + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + { + return 2 + } + println() // ERROR "unreachable code" +} + +var _ = func() int { +L: + { + goto L + println() // ERROR "unreachable code" + } +} + +var _ = func() int { +L: + { + goto L + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + { + panic(2) + println() // ERROR "unreachable code" + } +} + +var _ = func() int { + { + panic(2) + } + println() // ERROR "unreachable code" +} + +var _ = func() int { + return 2 + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { +L: + goto L + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + panic(2) + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + { + return 2 + { // ERROR "unreachable code" + } + } + println() // ok +} + +var _ = func() int { +L: + { + goto L + { // ERROR "unreachable code" + } + } + println() // ok +} + +var _ = func() int { + { + panic(2) + { // ERROR "unreachable code" + } + } + println() // ok +} + +var _ = func() int { + { + return 2 + } + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { +L: + { + goto L + } + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() int { + { + panic(2) + } + { // ERROR "unreachable code" + } + println() // ok +} + +var _ = func() { + // goto without label used to panic + goto +} + +func _() int { + // Empty switch tag with non-bool case value used to panic. + switch { + case 1: + println() + } + println() +} diff --git a/go/analysis/passes/vet/testdata/divergent/buf.go b/go/analysis/passes/vet/testdata/divergent/buf.go new file mode 100644 index 00000000..0efe0f83 --- /dev/null +++ b/go/analysis/passes/vet/testdata/divergent/buf.go @@ -0,0 +1,17 @@ +// Test of examples with divergent packages. + +// Package buf ... +package buf + +// Buf is a ... +type Buf []byte + +// Append ... +func (*Buf) Append([]byte) {} + +func (Buf) Reset() {} + +func (Buf) Len() int { return 0 } + +// DefaultBuf is a ... +var DefaultBuf Buf diff --git a/go/analysis/passes/vet/testdata/divergent/buf_test.go b/go/analysis/passes/vet/testdata/divergent/buf_test.go new file mode 100644 index 00000000..b75d55ea --- /dev/null +++ b/go/analysis/passes/vet/testdata/divergent/buf_test.go @@ -0,0 +1,35 @@ +// Test of examples with divergent packages. + +package buf_test + +func Example() {} // OK because is package-level. + +func Example_suffix() {} // OK because refers to suffix annotation. + +func Example_BadSuffix() {} // ERROR "Example_BadSuffix has malformed example suffix: BadSuffix" + +func ExampleBuf() {} // OK because refers to known top-level type. + +func ExampleBuf_Append() {} // OK because refers to known method. + +func ExampleBuf_Clear() {} // ERROR "ExampleBuf_Clear refers to unknown field or method: Buf.Clear" + +func ExampleBuf_suffix() {} // OK because refers to suffix annotation. + +func ExampleBuf_Append_Bad() {} // ERROR "ExampleBuf_Append_Bad has malformed example suffix: Bad" + +func ExampleBuf_Append_suffix() {} // OK because refers to known method with valid suffix. + +func ExampleDefaultBuf() {} // OK because refers to top-level identifier. + +func ExampleBuf_Reset() bool { return true } // ERROR "ExampleBuf_Reset should return nothing" + +func ExampleBuf_Len(i int) {} // ERROR "ExampleBuf_Len should be niladic" + +// "Puffer" is German for "Buffer". + +func ExamplePuffer() {} // ERROR "ExamplePuffer refers to unknown identifier: Puffer" + +func ExamplePuffer_Append() {} // ERROR "ExamplePuffer_Append refers to unknown identifier: Puffer" + +func ExamplePuffer_suffix() {} // ERROR "ExamplePuffer_suffix refers to unknown identifier: Puffer" diff --git a/go/analysis/passes/vet/testdata/httpresponse.go b/go/analysis/passes/vet/testdata/httpresponse.go new file mode 100644 index 00000000..7302a64a --- /dev/null +++ b/go/analysis/passes/vet/testdata/httpresponse.go @@ -0,0 +1,85 @@ +package testdata + +import ( + "log" + "net/http" +) + +func goodHTTPGet() { + res, err := http.Get("http://foo.com") + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() +} + +func badHTTPGet() { + res, err := http.Get("http://foo.com") + defer res.Body.Close() // ERROR "using res before checking for errors" + if err != nil { + log.Fatal(err) + } +} + +func badHTTPHead() { + res, err := http.Head("http://foo.com") + defer res.Body.Close() // ERROR "using res before checking for errors" + if err != nil { + log.Fatal(err) + } +} + +func goodClientGet() { + client := http.DefaultClient + res, err := client.Get("http://foo.com") + if err != nil { + log.Fatal(err) + } + defer res.Body.Close() +} + +func badClientPtrGet() { + client := http.DefaultClient + resp, err := client.Get("http://foo.com") + defer resp.Body.Close() // ERROR "using resp before checking for errors" + if err != nil { + log.Fatal(err) + } +} + +func badClientGet() { + client := http.Client{} + resp, err := client.Get("http://foo.com") + defer resp.Body.Close() // ERROR "using resp before checking for errors" + if err != nil { + log.Fatal(err) + } +} + +func badClientPtrDo() { + client := http.DefaultClient + req, err := http.NewRequest("GET", "http://foo.com", nil) + if err != nil { + log.Fatal(err) + } + + resp, err := client.Do(req) + defer resp.Body.Close() // ERROR "using resp before checking for errors" + if err != nil { + log.Fatal(err) + } +} + +func badClientDo() { + var client http.Client + req, err := http.NewRequest("GET", "http://foo.com", nil) + if err != nil { + log.Fatal(err) + } + + resp, err := client.Do(req) + defer resp.Body.Close() // ERROR "using resp before checking for errors" + if err != nil { + log.Fatal(err) + } +} diff --git a/go/analysis/passes/vet/testdata/incomplete/examples_test.go b/go/analysis/passes/vet/testdata/incomplete/examples_test.go new file mode 100644 index 00000000..445502b3 --- /dev/null +++ b/go/analysis/passes/vet/testdata/incomplete/examples_test.go @@ -0,0 +1,33 @@ +// Test of examples. + +package testdata + +func Example() {} // OK because is package-level. + +func Example_suffix() // OK because refers to suffix annotation. + +func Example_BadSuffix() // OK because non-test package was excluded. No false positives wanted. + +func ExampleBuf() // OK because non-test package was excluded. No false positives wanted. + +func ExampleBuf_Append() {} // OK because non-test package was excluded. No false positives wanted. + +func ExampleBuf_Clear() {} // OK because non-test package was excluded. No false positives wanted. + +func ExampleBuf_suffix() {} // OK because refers to suffix annotation. + +func ExampleBuf_Append_Bad() {} // OK because non-test package was excluded. No false positives wanted. + +func ExampleBuf_Append_suffix() {} // OK because refers to known method with valid suffix. + +func ExampleBuf_Reset() bool { return true } // ERROR "ExampleBuf_Reset should return nothing" + +func ExampleBuf_Len(i int) {} // ERROR "ExampleBuf_Len should be niladic" + +// "Puffer" is German for "Buffer". + +func ExamplePuffer() // OK because non-test package was excluded. No false positives wanted. + +func ExamplePuffer_Append() // OK because non-test package was excluded. No false positives wanted. + +func ExamplePuffer_suffix() // OK because non-test package was excluded. No false positives wanted. diff --git a/go/analysis/passes/vet/testdata/lostcancel.go b/go/analysis/passes/vet/testdata/lostcancel.go new file mode 100644 index 00000000..b7549c00 --- /dev/null +++ b/go/analysis/passes/vet/testdata/lostcancel.go @@ -0,0 +1,155 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testdata + +import ( + "context" + "log" + "os" + "testing" +) + +// Check the three functions and assignment forms (var, :=, =) we look for. +// (Do these early: line numbers are fragile.) +func _() { + var ctx, cancel = context.WithCancel() // ERROR "the cancel function is not used on all paths \(possible context leak\)" +} // ERROR "this return statement may be reached without using the cancel var defined on line 17" + +func _() { + ctx, cancel2 := context.WithDeadline() // ERROR "the cancel2 function is not used..." +} // ERROR "may be reached without using the cancel2 var defined on line 21" + +func _() { + var ctx context.Context + var cancel3 func() + ctx, cancel3 = context.WithTimeout() // ERROR "function is not used..." +} // ERROR "this return statement may be reached without using the cancel3 var defined on line 27" + +func _() { + ctx, _ := context.WithCancel() // ERROR "the cancel function returned by context.WithCancel should be called, not discarded, to avoid a context leak" + ctx, _ = context.WithTimeout() // ERROR "the cancel function returned by context.WithTimeout should be called, not discarded, to avoid a context leak" + ctx, _ = context.WithDeadline() // ERROR "the cancel function returned by context.WithDeadline should be called, not discarded, to avoid a context leak" +} + +func _() { + ctx, cancel := context.WithCancel() + defer cancel() // ok +} + +func _() { + ctx, cancel := context.WithCancel() // ERROR "not used on all paths" + if condition { + cancel() + } + return // ERROR "this return statement may be reached without using the cancel var" +} + +func _() { + ctx, cancel := context.WithCancel() + if condition { + cancel() + } else { + // ok: infinite loop + for { + print(0) + } + } +} + +func _() { + ctx, cancel := context.WithCancel() // ERROR "not used on all paths" + if condition { + cancel() + } else { + for i := 0; i < 10; i++ { + print(0) + } + } +} // ERROR "this return statement may be reached without using the cancel var" + +func _() { + ctx, cancel := context.WithCancel() + // ok: used on all paths + switch someInt { + case 0: + new(testing.T).FailNow() + case 1: + log.Fatal() + case 2: + cancel() + case 3: + print("hi") + fallthrough + default: + os.Exit(1) + } +} + +func _() { + ctx, cancel := context.WithCancel() // ERROR "not used on all paths" + switch someInt { + case 0: + new(testing.T).FailNow() + case 1: + log.Fatal() + case 2: + cancel() + case 3: + print("hi") // falls through to implicit return + default: + os.Exit(1) + } +} // ERROR "this return statement may be reached without using the cancel var" + +func _(ch chan int) int { + ctx, cancel := context.WithCancel() // ERROR "not used on all paths" + select { + case <-ch: + new(testing.T).FailNow() + case y <- ch: + print("hi") // falls through to implicit return + case ch <- 1: + cancel() + default: + os.Exit(1) + } +} // ERROR "this return statement may be reached without using the cancel var" + +func _(ch chan int) int { + ctx, cancel := context.WithCancel() + // A blocking select must execute one of its cases. + select { + case <-ch: + panic() + } +} + +func _() { + go func() { + ctx, cancel := context.WithCancel() // ERROR "not used on all paths" + print(ctx) + }() // ERROR "may be reached without using the cancel var" +} + +var condition bool +var someInt int + +// Regression test for Go issue 16143. +func _() { + var x struct{ f func() } + x.f() +} + +// Regression test for Go issue 16230. +func _() (ctx context.Context, cancel func()) { + ctx, cancel = context.WithCancel() + return // a naked return counts as a load of the named result values +} + +// Same as above, but for literal function. +var _ = func() (ctx context.Context, cancel func()) { + ctx, cancel = context.WithCancel() + return +} diff --git a/go/analysis/passes/vet/testdata/method.go b/go/analysis/passes/vet/testdata/method.go new file mode 100644 index 00000000..52b500df --- /dev/null +++ b/go/analysis/passes/vet/testdata/method.go @@ -0,0 +1,22 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the canonical method checker. + +// This file contains the code to check canonical methods. + +package testdata + +import ( + "fmt" +) + +type MethodTest int + +func (t *MethodTest) Scan(x fmt.ScanState, c byte) { // ERROR "should have signature Scan" +} + +type MethodTestInterface interface { + ReadByte() byte // ERROR "should have signature ReadByte" +} diff --git a/go/analysis/passes/vet/testdata/nilfunc.go b/go/analysis/passes/vet/testdata/nilfunc.go new file mode 100644 index 00000000..2ce7bc8c --- /dev/null +++ b/go/analysis/passes/vet/testdata/nilfunc.go @@ -0,0 +1,35 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testdata + +func F() {} + +type T struct { + F func() +} + +func (T) M() {} + +var Fv = F + +func Comparison() { + var t T + var fn func() + if fn == nil || Fv == nil || t.F == nil { + // no error; these func vars or fields may be nil + } + if F == nil { // ERROR "comparison of function F == nil is always false" + panic("can't happen") + } + if t.M == nil { // ERROR "comparison of function M == nil is always false" + panic("can't happen") + } + if F != nil { // ERROR "comparison of function F != nil is always true" + if t.M != nil { // ERROR "comparison of function M != nil is always true" + return + } + } + panic("can't happen") +} diff --git a/go/analysis/passes/vet/testdata/print.go b/go/analysis/passes/vet/testdata/print.go new file mode 100644 index 00000000..88163b59 --- /dev/null +++ b/go/analysis/passes/vet/testdata/print.go @@ -0,0 +1,647 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the printf checker. + +package testdata + +import ( + "fmt" + logpkg "log" // renamed to make it harder to see + "math" + "os" + "testing" + "unsafe" // just for test case printing unsafe.Pointer + // For testing printf-like functions from external package. + // "github.com/foobar/externalprintf" +) + +func UnsafePointerPrintfTest() { + var up unsafe.Pointer + fmt.Printf("%p, %x %X", up, up, up) +} + +// Error methods that do not satisfy the Error interface and should be checked. +type errorTest1 int + +func (errorTest1) Error(...interface{}) string { + return "hi" +} + +type errorTest2 int // Analogous to testing's *T type. +func (errorTest2) Error(...interface{}) { +} + +type errorTest3 int + +func (errorTest3) Error() { // No return value. +} + +type errorTest4 int + +func (errorTest4) Error() int { // Different return type. + return 3 +} + +type errorTest5 int + +func (errorTest5) error() { // niladic; don't complain if no args (was bug) +} + +// This function never executes, but it serves as a simple test for the program. +// Test with make test. +func PrintfTests() { + var b bool + var i int + var r rune + var s string + var x float64 + var p *int + var imap map[int]int + var fslice []float64 + var c complex64 + // Some good format/argtypes + fmt.Printf("") + fmt.Printf("%b %b %b", 3, i, x) + fmt.Printf("%c %c %c %c", 3, i, 'x', r) + fmt.Printf("%d %d %d", 3, i, imap) + fmt.Printf("%e %e %e %e", 3e9, x, fslice, c) + fmt.Printf("%E %E %E %E", 3e9, x, fslice, c) + fmt.Printf("%f %f %f %f", 3e9, x, fslice, c) + fmt.Printf("%F %F %F %F", 3e9, x, fslice, c) + fmt.Printf("%g %g %g %g", 3e9, x, fslice, c) + fmt.Printf("%G %G %G %G", 3e9, x, fslice, c) + fmt.Printf("%b %b %b %b", 3e9, x, fslice, c) + fmt.Printf("%o %o", 3, i) + fmt.Printf("%p", p) + fmt.Printf("%q %q %q %q", 3, i, 'x', r) + fmt.Printf("%s %s %s", "hi", s, []byte{65}) + fmt.Printf("%t %t", true, b) + fmt.Printf("%T %T", 3, i) + fmt.Printf("%U %U", 3, i) + fmt.Printf("%v %v", 3, i) + fmt.Printf("%x %x %x %x", 3, i, "hi", s) + fmt.Printf("%X %X %X %X", 3, i, "hi", s) + fmt.Printf("%.*s %d %g", 3, "hi", 23, 2.3) + fmt.Printf("%s", &stringerv) + fmt.Printf("%v", &stringerv) + fmt.Printf("%T", &stringerv) + fmt.Printf("%s", &embeddedStringerv) + fmt.Printf("%v", &embeddedStringerv) + fmt.Printf("%T", &embeddedStringerv) + fmt.Printf("%v", notstringerv) + fmt.Printf("%T", notstringerv) + fmt.Printf("%q", stringerarrayv) + fmt.Printf("%v", stringerarrayv) + fmt.Printf("%s", stringerarrayv) + fmt.Printf("%v", notstringerarrayv) + fmt.Printf("%T", notstringerarrayv) + fmt.Printf("%d", new(fmt.Formatter)) + fmt.Printf("%*%", 2) // Ridiculous but allowed. + fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say. + + fmt.Printf("%g", 1+2i) + fmt.Printf("%#e %#E %#f %#F %#g %#G", 1.2, 1.2, 1.2, 1.2, 1.2, 1.2) // OK since Go 1.9 + // Some bad format/argTypes + fmt.Printf("%b", "hi") // ERROR "Printf format %b has arg \x22hi\x22 of wrong type string" + fmt.Printf("%t", c) // ERROR "Printf format %t has arg c of wrong type complex64" + fmt.Printf("%t", 1+2i) // ERROR "Printf format %t has arg 1 \+ 2i of wrong type complex128" + fmt.Printf("%c", 2.3) // ERROR "Printf format %c has arg 2.3 of wrong type float64" + fmt.Printf("%d", 2.3) // ERROR "Printf format %d has arg 2.3 of wrong type float64" + fmt.Printf("%e", "hi") // ERROR "Printf format %e has arg \x22hi\x22 of wrong type string" + fmt.Printf("%E", true) // ERROR "Printf format %E has arg true of wrong type bool" + fmt.Printf("%f", "hi") // ERROR "Printf format %f has arg \x22hi\x22 of wrong type string" + fmt.Printf("%F", 'x') // ERROR "Printf format %F has arg 'x' of wrong type rune" + fmt.Printf("%g", "hi") // ERROR "Printf format %g has arg \x22hi\x22 of wrong type string" + fmt.Printf("%g", imap) // ERROR "Printf format %g has arg imap of wrong type map\[int\]int" + fmt.Printf("%G", i) // ERROR "Printf format %G has arg i of wrong type int" + fmt.Printf("%o", x) // ERROR "Printf format %o has arg x of wrong type float64" + fmt.Printf("%p", nil) // ERROR "Printf format %p has arg nil of wrong type untyped nil" + fmt.Printf("%p", 23) // ERROR "Printf format %p has arg 23 of wrong type int" + fmt.Printf("%q", x) // ERROR "Printf format %q has arg x of wrong type float64" + fmt.Printf("%s", b) // ERROR "Printf format %s has arg b of wrong type bool" + fmt.Printf("%s", byte(65)) // ERROR "Printf format %s has arg byte\(65\) of wrong type byte" + fmt.Printf("%t", 23) // ERROR "Printf format %t has arg 23 of wrong type int" + fmt.Printf("%U", x) // ERROR "Printf format %U has arg x of wrong type float64" + fmt.Printf("%x", nil) // ERROR "Printf format %x has arg nil of wrong type untyped nil" + fmt.Printf("%X", 2.3) // ERROR "Printf format %X has arg 2.3 of wrong type float64" + fmt.Printf("%s", stringerv) // ERROR "Printf format %s has arg stringerv of wrong type testdata.ptrStringer" + fmt.Printf("%t", stringerv) // ERROR "Printf format %t has arg stringerv of wrong type testdata.ptrStringer" + fmt.Printf("%s", embeddedStringerv) // ERROR "Printf format %s has arg embeddedStringerv of wrong type testdata.embeddedStringer" + fmt.Printf("%t", embeddedStringerv) // ERROR "Printf format %t has arg embeddedStringerv of wrong type testdata.embeddedStringer" + fmt.Printf("%q", notstringerv) // ERROR "Printf format %q has arg notstringerv of wrong type testdata.notstringer" + fmt.Printf("%t", notstringerv) // ERROR "Printf format %t has arg notstringerv of wrong type testdata.notstringer" + fmt.Printf("%t", stringerarrayv) // ERROR "Printf format %t has arg stringerarrayv of wrong type testdata.stringerarray" + fmt.Printf("%t", notstringerarrayv) // ERROR "Printf format %t has arg notstringerarrayv of wrong type testdata.notstringerarray" + fmt.Printf("%q", notstringerarrayv) // ERROR "Printf format %q has arg notstringerarrayv of wrong type testdata.notstringerarray" + fmt.Printf("%d", BoolFormatter(true)) // ERROR "Printf format %d has arg BoolFormatter\(true\) of wrong type testdata.BoolFormatter" + fmt.Printf("%z", FormatterVal(true)) // correct (the type is responsible for formatting) + fmt.Printf("%d", FormatterVal(true)) // correct (the type is responsible for formatting) + fmt.Printf("%s", nonemptyinterface) // correct (the type is responsible for formatting) + fmt.Printf("%.*s %d %6g", 3, "hi", 23, 'x') // ERROR "Printf format %6g has arg 'x' of wrong type rune" + fmt.Println() // not an error + fmt.Println("%s", "hi") // ERROR "Println call has possible formatting directive %s" + fmt.Println("%v", "hi") // ERROR "Println call has possible formatting directive %v" + fmt.Println("%T", "hi") // ERROR "Println call has possible formatting directive %T" + fmt.Println("0.0%") // correct (trailing % couldn't be a formatting directive) + fmt.Printf("%s", "hi", 3) // ERROR "Printf call needs 1 arg but has 2 args" + _ = fmt.Sprintf("%"+("s"), "hi", 3) // ERROR "Sprintf call needs 1 arg but has 2 args" + fmt.Printf("%s%%%d", "hi", 3) // correct + fmt.Printf("%08s", "woo") // correct + fmt.Printf("% 8s", "woo") // correct + fmt.Printf("%.*d", 3, 3) // correct + fmt.Printf("%.*d x", 3, 3, 3, 3) // ERROR "Printf call needs 2 args but has 4 args" + fmt.Printf("%.*d x", "hi", 3) // ERROR "Printf format %.*d uses non-int \x22hi\x22 as argument of \*" + fmt.Printf("%.*d x", i, 3) // correct + fmt.Printf("%.*d x", s, 3) // ERROR "Printf format %.\*d uses non-int s as argument of \*" + fmt.Printf("%*% x", 0.22) // ERROR "Printf format %\*% uses non-int 0.22 as argument of \*" + fmt.Printf("%q %q", multi()...) // ok + fmt.Printf("%#q", `blah`) // ok + // printf("now is the time", "buddy") // no error "printf call has arguments but no formatting directives" + Printf("now is the time", "buddy") // ERROR "Printf call has arguments but no formatting directives" + Printf("hi") // ok + const format = "%s %s\n" + Printf(format, "hi", "there") + Printf(format, "hi") // ERROR "Printf format %s reads arg #2, but call has 1 arg$" + Printf("%s %d %.3v %q", "str", 4) // ERROR "Printf format %.3v reads arg #3, but call has 2 args" + f := new(ptrStringer) + f.Warn(0, "%s", "hello", 3) // ERROR "Warn call has possible formatting directive %s" + f.Warnf(0, "%s", "hello", 3) // ERROR "Warnf call needs 1 arg but has 2 args" + f.Warnf(0, "%r", "hello") // ERROR "Warnf format %r has unknown verb r" + f.Warnf(0, "%#s", "hello") // ERROR "Warnf format %#s has unrecognized flag #" + f.Warn2(0, "%s", "hello", 3) // ERROR "Warn2 call has possible formatting directive %s" + f.Warnf2(0, "%s", "hello", 3) // ERROR "Warnf2 call needs 1 arg but has 2 args" + f.Warnf2(0, "%r", "hello") // ERROR "Warnf2 format %r has unknown verb r" + f.Warnf2(0, "%#s", "hello") // ERROR "Warnf2 format %#s has unrecognized flag #" + f.Wrap(0, "%s", "hello", 3) // ERROR "Wrap call has possible formatting directive %s" + f.Wrapf(0, "%s", "hello", 3) // ERROR "Wrapf call needs 1 arg but has 2 args" + f.Wrapf(0, "%r", "hello") // ERROR "Wrapf format %r has unknown verb r" + f.Wrapf(0, "%#s", "hello") // ERROR "Wrapf format %#s has unrecognized flag #" + f.Wrap2(0, "%s", "hello", 3) // ERROR "Wrap2 call has possible formatting directive %s" + f.Wrapf2(0, "%s", "hello", 3) // ERROR "Wrapf2 call needs 1 arg but has 2 args" + f.Wrapf2(0, "%r", "hello") // ERROR "Wrapf2 format %r has unknown verb r" + f.Wrapf2(0, "%#s", "hello") // ERROR "Wrapf2 format %#s has unrecognized flag #" + fmt.Printf("%#s", FormatterVal(true)) // correct (the type is responsible for formatting) + Printf("d%", 2) // ERROR "Printf format % is missing verb at end of string" + Printf("%d", percentDV) + Printf("%d", &percentDV) + Printf("%d", notPercentDV) // ERROR "Printf format %d has arg notPercentDV of wrong type testdata.notPercentDStruct" + Printf("%d", ¬PercentDV) // ERROR "Printf format %d has arg ¬PercentDV of wrong type \*testdata.notPercentDStruct" + Printf("%p", ¬PercentDV) // Works regardless: we print it as a pointer. + Printf("%q", &percentDV) // ERROR "Printf format %q has arg &percentDV of wrong type \*testdata.percentDStruct" + Printf("%s", percentSV) + Printf("%s", &percentSV) + // Good argument reorderings. + Printf("%[1]d", 3) + Printf("%[1]*d", 3, 1) + Printf("%[2]*[1]d", 1, 3) + Printf("%[2]*.[1]*[3]d", 2, 3, 4) + fmt.Fprintf(os.Stderr, "%[2]*.[1]*[3]d", 2, 3, 4) // Use Fprintf to make sure we count arguments correctly. + // Bad argument reorderings. + Printf("%[xd", 3) // ERROR "Printf format %\[xd is missing closing \]" + Printf("%[x]d x", 3) // ERROR "Printf format has invalid argument index \[x\]" + Printf("%[3]*s x", "hi", 2) // ERROR "Printf format has invalid argument index \[3\]" + _ = fmt.Sprintf("%[3]d x", 2) // ERROR "Sprintf format has invalid argument index \[3\]" + Printf("%[2]*.[1]*[3]d x", 2, "hi", 4) // ERROR "Printf format %\[2]\*\.\[1\]\*\[3\]d uses non-int \x22hi\x22 as argument of \*" + Printf("%[0]s x", "arg1") // ERROR "Printf format has invalid argument index \[0\]" + Printf("%[0]d x", 1) // ERROR "Printf format has invalid argument index \[0\]" + // Something that satisfies the error interface. + var e error + fmt.Println(e.Error()) // ok + // Something that looks like an error interface but isn't, such as the (*T).Error method + // in the testing package. + var et1 *testing.T + et1.Error() // ok + et1.Error("hi") // ok + et1.Error("%d", 3) // ERROR "Error call has possible formatting directive %d" + var et3 errorTest3 + et3.Error() // ok, not an error method. + var et4 errorTest4 + et4.Error() // ok, not an error method. + var et5 errorTest5 + et5.error() // ok, not an error method. + // Interfaces can be used with any verb. + var iface interface { + ToTheMadness() bool // Method ToTheMadness usually returns false + } + fmt.Printf("%f", iface) // ok: fmt treats interfaces as transparent and iface may well have a float concrete type + // Can't print a function. + Printf("%d", someFunction) // ERROR "Printf format %d arg someFunction is a func value, not called" + Printf("%v", someFunction) // ERROR "Printf format %v arg someFunction is a func value, not called" + Println(someFunction) // ERROR "Println arg someFunction is a func value, not called" + Printf("%p", someFunction) // ok: maybe someone wants to see the pointer + Printf("%T", someFunction) // ok: maybe someone wants to see the type + // Bug: used to recur forever. + Printf("%p %x", recursiveStructV, recursiveStructV.next) + Printf("%p %x", recursiveStruct1V, recursiveStruct1V.next) + Printf("%p %x", recursiveSliceV, recursiveSliceV) + Printf("%p %x", recursiveMapV, recursiveMapV) + // Special handling for Log. + math.Log(3) // OK + var t *testing.T + t.Log("%d", 3) // ERROR "Log call has possible formatting directive %d" + t.Logf("%d", 3) + t.Logf("%d", "hi") // ERROR "Logf format %d has arg \x22hi\x22 of wrong type string" + + Errorf(1, "%d", 3) // OK + Errorf(1, "%d", "hi") // ERROR "Errorf format %d has arg \x22hi\x22 of wrong type string" + + // Multiple string arguments before variadic args + errorf("WARNING", "foobar") // OK + errorf("INFO", "s=%s, n=%d", "foo", 1) // OK + errorf("ERROR", "%d") // no error "errorf format %d reads arg #1, but call has 0 args" + + // Printf from external package + // externalprintf.Printf("%d", 42) // OK + // externalprintf.Printf("foobar") // OK + // level := 123 + // externalprintf.Logf(level, "%d", 42) // OK + // externalprintf.Errorf(level, level, "foo %q bar", "foobar") // OK + // externalprintf.Logf(level, "%d") // no error "Logf format %d reads arg #1, but call has 0 args" + // var formatStr = "%s %s" + // externalprintf.Sprintf(formatStr, "a", "b") // OK + // externalprintf.Logf(level, formatStr, "a", "b") // OK + + // user-defined Println-like functions + ss := &someStruct{} + ss.Log(someFunction, "foo") // OK + ss.Error(someFunction, someFunction) // OK + ss.Println() // OK + ss.Println(1.234, "foo") // OK + ss.Println(1, someFunction) // no error "Println arg someFunction is a func value, not called" + ss.log(someFunction) // OK + ss.log(someFunction, "bar", 1.33) // OK + ss.log(someFunction, someFunction) // no error "log arg someFunction is a func value, not called" + + // indexed arguments + Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4) // OK + Printf("%d %[0]d %d %[2]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[0\]" + Printf("%d %[3]d %d %[-2]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[-2\]" + Printf("%d %[3]d %d %[2234234234234]d x", 1, 2, 3, 4) // ERROR "Printf format has invalid argument index \[2234234234234\]" + Printf("%d %[3]d %-10d %[2]d x", 1, 2, 3) // ERROR "Printf format %-10d reads arg #4, but call has 3 args" + Printf("%[1][3]d x", 1, 2) // ERROR "Printf format %\[1\]\[ has unknown verb \[" + Printf("%[1]d x", 1, 2) // OK + Printf("%d %[3]d %d %[2]d x", 1, 2, 3, 4, 5) // OK + + // wrote Println but meant Fprintln + Printf("%p\n", os.Stdout) // OK + Println(os.Stdout, "hello") // ERROR "Println does not take io.Writer but has first arg os.Stdout" + + Printf(someString(), "hello") // OK + + // Printf wrappers in package log should be detected automatically + logpkg.Fatal("%d", 1) // ERROR "Fatal call has possible formatting directive %d" + logpkg.Fatalf("%d", "x") // ERROR "Fatalf format %d has arg \x22x\x22 of wrong type string" + logpkg.Fatalln("%d", 1) // ERROR "Fatalln call has possible formatting directive %d" + logpkg.Panic("%d", 1) // ERROR "Panic call has possible formatting directive %d" + logpkg.Panicf("%d", "x") // ERROR "Panicf format %d has arg \x22x\x22 of wrong type string" + logpkg.Panicln("%d", 1) // ERROR "Panicln call has possible formatting directive %d" + logpkg.Print("%d", 1) // ERROR "Print call has possible formatting directive %d" + logpkg.Printf("%d", "x") // ERROR "Printf format %d has arg \x22x\x22 of wrong type string" + logpkg.Println("%d", 1) // ERROR "Println call has possible formatting directive %d" + + // Methods too. + var l *logpkg.Logger + l.Fatal("%d", 1) // ERROR "Fatal call has possible formatting directive %d" + l.Fatalf("%d", "x") // ERROR "Fatalf format %d has arg \x22x\x22 of wrong type string" + l.Fatalln("%d", 1) // ERROR "Fatalln call has possible formatting directive %d" + l.Panic("%d", 1) // ERROR "Panic call has possible formatting directive %d" + l.Panicf("%d", "x") // ERROR "Panicf format %d has arg \x22x\x22 of wrong type string" + l.Panicln("%d", 1) // ERROR "Panicln call has possible formatting directive %d" + l.Print("%d", 1) // ERROR "Print call has possible formatting directive %d" + l.Printf("%d", "x") // ERROR "Printf format %d has arg \x22x\x22 of wrong type string" + l.Println("%d", 1) // ERROR "Println call has possible formatting directive %d" + + // Issue 26486 + dbg("", 1) // no error "call has arguments but no formatting directive" +} + +func someString() string { return "X" } + +type someStruct struct{} + +// Log is non-variadic user-define Println-like function. +// Calls to this func must be skipped when checking +// for Println-like arguments. +func (ss *someStruct) Log(f func(), s string) {} + +// Error is variadic user-define Println-like function. +// Calls to this func mustn't be checked for Println-like arguments, +// since variadic arguments type isn't interface{}. +func (ss *someStruct) Error(args ...func()) {} + +// Println is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) Println(args ...interface{}) {} + +// log is variadic user-defined Println-like function. +// Calls to this func must be checked for Println-like arguments. +func (ss *someStruct) log(f func(), args ...interface{}) {} + +// A function we use as a function value; it has no other purpose. +func someFunction() {} + +// Printf is used by the test so we must declare it. +func Printf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +// Println is used by the test so we must declare it. +func Println(args ...interface{}) { + fmt.Println(args...) +} + +// printf is used by the test so we must declare it. +func printf(format string, args ...interface{}) { + fmt.Printf(format, args...) +} + +// Errorf is used by the test for a case in which the first parameter +// is not a format string. +func Errorf(i int, format string, args ...interface{}) { + _ = fmt.Errorf(format, args...) +} + +// errorf is used by the test for a case in which the function accepts multiple +// string parameters before variadic arguments +func errorf(level, format string, args ...interface{}) { + _ = fmt.Errorf(format, args...) +} + +// multi is used by the test. +func multi() []interface{} { + panic("don't call - testing only") +} + +type stringer int + +func (stringer) String() string { return "string" } + +type ptrStringer float64 + +var stringerv ptrStringer + +func (*ptrStringer) String() string { + return "string" +} + +func (p *ptrStringer) Warn2(x int, args ...interface{}) string { + return p.Warn(x, args...) +} + +func (p *ptrStringer) Warnf2(x int, format string, args ...interface{}) string { + return p.Warnf(x, format, args...) +} + +func (*ptrStringer) Warn(x int, args ...interface{}) string { + return "warn" +} + +func (*ptrStringer) Warnf(x int, format string, args ...interface{}) string { + return "warnf" +} + +func (p *ptrStringer) Wrap2(x int, args ...interface{}) string { + return p.Wrap(x, args...) +} + +func (p *ptrStringer) Wrapf2(x int, format string, args ...interface{}) string { + return p.Wrapf(x, format, args...) +} + +func (*ptrStringer) Wrap(x int, args ...interface{}) string { + return fmt.Sprint(args...) +} + +func (*ptrStringer) Wrapf(x int, format string, args ...interface{}) string { + return fmt.Sprintf(format, args...) +} + +func (*ptrStringer) BadWrap(x int, args ...interface{}) string { + return fmt.Sprint(args) // ERROR "missing ... in args forwarded to print-like function" +} + +func (*ptrStringer) BadWrapf(x int, format string, args ...interface{}) string { + return fmt.Sprintf(format, args) // ERROR "missing ... in args forwarded to printf-like function" +} + +func (*ptrStringer) WrapfFalsePositive(x int, arg1 string, arg2 ...interface{}) string { + return fmt.Sprintf("%s %v", arg1, arg2) +} + +type embeddedStringer struct { + foo string + ptrStringer + bar int +} + +var embeddedStringerv embeddedStringer + +type notstringer struct { + f float64 +} + +var notstringerv notstringer + +type stringerarray [4]float64 + +func (stringerarray) String() string { + return "string" +} + +var stringerarrayv stringerarray + +type notstringerarray [4]float64 + +var notstringerarrayv notstringerarray + +var nonemptyinterface = interface { + f() +}(nil) + +// A data type we can print with "%d". +type percentDStruct struct { + a int + b []byte + c *float64 +} + +var percentDV percentDStruct + +// A data type we cannot print correctly with "%d". +type notPercentDStruct struct { + a int + b []byte + c bool +} + +var notPercentDV notPercentDStruct + +// A data type we can print with "%s". +type percentSStruct struct { + a string + b []byte + C stringerarray +} + +var percentSV percentSStruct + +type recursiveStringer int + +func (s recursiveStringer) String() string { + _ = fmt.Sprintf("%d", s) + _ = fmt.Sprintf("%#v", s) + _ = fmt.Sprintf("%v", s) // ERROR "Sprintf format %v with arg s causes recursive String method call" + _ = fmt.Sprintf("%v", &s) // ERROR "Sprintf format %v with arg &s causes recursive String method call" + _ = fmt.Sprintf("%T", s) // ok; does not recursively call String + return fmt.Sprintln(s) // ERROR "Sprintln arg s causes recursive call to String method" +} + +type recursivePtrStringer int + +func (p *recursivePtrStringer) String() string { + _ = fmt.Sprintf("%v", *p) + _ = fmt.Sprint(&p) // ok; prints address + return fmt.Sprintln(p) // ERROR "Sprintln arg p causes recursive call to String method" +} + +type BoolFormatter bool + +func (*BoolFormatter) Format(fmt.State, rune) { +} + +// Formatter with value receiver +type FormatterVal bool + +func (FormatterVal) Format(fmt.State, rune) { +} + +type RecursiveSlice []RecursiveSlice + +var recursiveSliceV = &RecursiveSlice{} + +type RecursiveMap map[int]RecursiveMap + +var recursiveMapV = make(RecursiveMap) + +type RecursiveStruct struct { + next *RecursiveStruct +} + +var recursiveStructV = &RecursiveStruct{} + +type RecursiveStruct1 struct { + next *RecursiveStruct2 +} + +type RecursiveStruct2 struct { + next *RecursiveStruct1 +} + +var recursiveStruct1V = &RecursiveStruct1{} + +type unexportedInterface struct { + f interface{} +} + +// Issue 17798: unexported ptrStringer cannot be formatted. +type unexportedStringer struct { + t ptrStringer +} +type unexportedStringerOtherFields struct { + s string + t ptrStringer + S string +} + +// Issue 17798: unexported error cannot be formatted. +type unexportedError struct { + e error +} +type unexportedErrorOtherFields struct { + s string + e error + S string +} + +type errorer struct{} + +func (e errorer) Error() string { return "errorer" } + +type unexportedCustomError struct { + e errorer +} + +type errorInterface interface { + error + ExtraMethod() +} + +type unexportedErrorInterface struct { + e errorInterface +} + +func UnexportedStringerOrError() { + fmt.Printf("%s", unexportedInterface{"foo"}) // ok; prints {foo} + fmt.Printf("%s", unexportedInterface{3}) // ok; we can't see the problem + + us := unexportedStringer{} + fmt.Printf("%s", us) // ERROR "Printf format %s has arg us of wrong type testdata.unexportedStringer" + fmt.Printf("%s", &us) // ERROR "Printf format %s has arg &us of wrong type [*]testdata.unexportedStringer" + + usf := unexportedStringerOtherFields{ + s: "foo", + S: "bar", + } + fmt.Printf("%s", usf) // ERROR "Printf format %s has arg usf of wrong type testdata.unexportedStringerOtherFields" + fmt.Printf("%s", &usf) // ERROR "Printf format %s has arg &usf of wrong type [*]testdata.unexportedStringerOtherFields" + + ue := unexportedError{ + e: &errorer{}, + } + fmt.Printf("%s", ue) // ERROR "Printf format %s has arg ue of wrong type testdata.unexportedError" + fmt.Printf("%s", &ue) // ERROR "Printf format %s has arg &ue of wrong type [*]testdata.unexportedError" + + uef := unexportedErrorOtherFields{ + s: "foo", + e: &errorer{}, + S: "bar", + } + fmt.Printf("%s", uef) // ERROR "Printf format %s has arg uef of wrong type testdata.unexportedErrorOtherFields" + fmt.Printf("%s", &uef) // ERROR "Printf format %s has arg &uef of wrong type [*]testdata.unexportedErrorOtherFields" + + uce := unexportedCustomError{ + e: errorer{}, + } + fmt.Printf("%s", uce) // ERROR "Printf format %s has arg uce of wrong type testdata.unexportedCustomError" + + uei := unexportedErrorInterface{} + fmt.Printf("%s", uei) // ERROR "Printf format %s has arg uei of wrong type testdata.unexportedErrorInterface" + fmt.Println("foo\n", "bar") // not an error + + fmt.Println("foo\n") // ERROR "Println arg list ends with redundant newline" + fmt.Println("foo\\n") // not an error + fmt.Println(`foo\n`) // not an error + + intSlice := []int{3, 4} + fmt.Printf("%s", intSlice) // ERROR "Printf format %s has arg intSlice of wrong type \[\]int" + nonStringerArray := [1]unexportedStringer{{}} + fmt.Printf("%s", nonStringerArray) // ERROR "Printf format %s has arg nonStringerArray of wrong type \[1\]testdata.unexportedStringer" + fmt.Printf("%s", []stringer{3, 4}) // not an error + fmt.Printf("%s", [2]stringer{3, 4}) // not an error +} + +// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11. +// See issues 23598 and 23605. +func DisableErrorForFlag0() { + fmt.Printf("%0t", true) +} + +// Issue 26486. +func dbg(format string, args ...interface{}) { + if format == "" { + format = "%v" + } + fmt.Printf(format, args...) +} diff --git a/go/analysis/passes/vet/testdata/rangeloop.go b/go/analysis/passes/vet/testdata/rangeloop.go new file mode 100644 index 00000000..cd3b4cbc --- /dev/null +++ b/go/analysis/passes/vet/testdata/rangeloop.go @@ -0,0 +1,90 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the rangeloop checker. + +package testdata + +func RangeLoopTests() { + var s []int + for i, v := range s { + go func() { + println(i) // ERROR "loop variable i captured by func literal" + println(v) // ERROR "loop variable v captured by func literal" + }() + } + for i, v := range s { + defer func() { + println(i) // ERROR "loop variable i captured by func literal" + println(v) // ERROR "loop variable v captured by func literal" + }() + } + for i := range s { + go func() { + println(i) // ERROR "loop variable i captured by func literal" + }() + } + for _, v := range s { + go func() { + println(v) // ERROR "loop variable v captured by func literal" + }() + } + for i, v := range s { + go func() { + println(i, v) + }() + println("unfortunately, we don't catch the error above because of this statement") + } + for i, v := range s { + go func(i, v int) { + println(i, v) + }(i, v) + } + for i, v := range s { + i, v := i, v + go func() { + println(i, v) + }() + } + // If the key of the range statement is not an identifier + // the code should not panic (it used to). + var x [2]int + var f int + for x[0], f = range s { + go func() { + _ = f // ERROR "loop variable f captured by func literal" + }() + } + type T struct { + v int + } + for _, v := range s { + go func() { + _ = T{v: 1} + _ = []int{v: 1} // ERROR "loop variable v captured by func literal" + }() + } + + // ordinary for-loops + for i := 0; i < 10; i++ { + go func() { + print(i) // ERROR "loop variable i captured by func literal" + }() + } + for i, j := 0, 1; i < 100; i, j = j, i+j { + go func() { + print(j) // ERROR "loop variable j captured by func literal" + }() + } + type cons struct { + car int + cdr *cons + } + var head *cons + for p := head; p != nil; p = p.next { + go func() { + print(p.car) // ERROR "loop variable p captured by func literal" + }() + } +} diff --git a/go/analysis/passes/vet/testdata/shadow.go b/go/analysis/passes/vet/testdata/shadow.go new file mode 100644 index 00000000..d10fde2b --- /dev/null +++ b/go/analysis/passes/vet/testdata/shadow.go @@ -0,0 +1,91 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the shadowed variable checker. +// Some of these errors are caught by the compiler (shadowed return parameters for example) +// but are nonetheless useful tests. + +package testdata + +import "os" + +func ShadowRead(f *os.File, buf []byte) (err error) { + var x int + if f != nil { + err := 3 // OK - different type. + _ = err + } + if f != nil { + _, err := f.Read(buf) // ERROR "declaration of .err. shadows declaration at shadow.go:13" + if err != nil { + return err + } + i := 3 // OK + _ = i + } + if f != nil { + x := one() // ERROR "declaration of .x. shadows declaration at shadow.go:14" + var _, err = f.Read(buf) // ERROR "declaration of .err. shadows declaration at shadow.go:13" + if x == 1 && err != nil { + return err + } + } + for i := 0; i < 10; i++ { + i := i // OK: obviously intentional idiomatic redeclaration + go func() { + println(i) + }() + } + var shadowTemp interface{} + switch shadowTemp := shadowTemp.(type) { // OK: obviously intentional idiomatic redeclaration + case int: + println("OK") + _ = shadowTemp + } + if shadowTemp := shadowTemp; true { // OK: obviously intentional idiomatic redeclaration + var f *os.File // OK because f is not mentioned later in the function. + // The declaration of x is a shadow because x is mentioned below. + var x int // ERROR "declaration of .x. shadows declaration at shadow.go:14" + _, _, _ = x, f, shadowTemp + } + // Use a couple of variables to trigger shadowing errors. + _, _ = err, x + return +} + +func one() int { + return 1 +} + +// Must not complain with an internal error for the +// implicitly declared type switch variable v. +func issue26725(x interface{}) int { + switch v := x.(type) { + case int, int32: + if v, ok := x.(int); ok { + return v + } + case int64: + return int(v) + } + return 0 +} + +// Verify that implicitly declared variables from +// type switches are considered in shadowing analysis. +func shadowTypeSwitch(a interface{}) { + switch t := a.(type) { + case int: + { + t := 0 // ERROR "declaration of .t. shadows declaration at shadow.go:78" + _ = t + } + _ = t + case uint: + { + t := uint(0) // OK because t is not mentioned later in this function + _ = t + } + } +} diff --git a/go/analysis/passes/vet/testdata/shift.go b/go/analysis/passes/vet/testdata/shift.go new file mode 100644 index 00000000..73cbaf88 --- /dev/null +++ b/go/analysis/passes/vet/testdata/shift.go @@ -0,0 +1,162 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the suspicious shift checker. + +package testdata + +import ( + "fmt" + "unsafe" +) + +func ShiftTest() { + var i8 int8 + _ = i8 << 7 + _ = (i8 + 1) << 8 // ERROR ".i8 . 1. .8 bits. too small for shift of 8" + _ = i8 << (7 + 1) // ERROR "i8 .8 bits. too small for shift of 8" + _ = i8 >> 8 // ERROR "i8 .8 bits. too small for shift of 8" + i8 <<= 8 // ERROR "i8 .8 bits. too small for shift of 8" + i8 >>= 8 // ERROR "i8 .8 bits. too small for shift of 8" + var i16 int16 + _ = i16 << 15 + _ = i16 << 16 // ERROR "i16 .16 bits. too small for shift of 16" + _ = i16 >> 16 // ERROR "i16 .16 bits. too small for shift of 16" + i16 <<= 16 // ERROR "i16 .16 bits. too small for shift of 16" + i16 >>= 16 // ERROR "i16 .16 bits. too small for shift of 16" + var i32 int32 + _ = i32 << 31 + _ = i32 << 32 // ERROR "i32 .32 bits. too small for shift of 32" + _ = i32 >> 32 // ERROR "i32 .32 bits. too small for shift of 32" + i32 <<= 32 // ERROR "i32 .32 bits. too small for shift of 32" + i32 >>= 32 // ERROR "i32 .32 bits. too small for shift of 32" + var i64 int64 + _ = i64 << 63 + _ = i64 << 64 // ERROR "i64 .64 bits. too small for shift of 64" + _ = i64 >> 64 // ERROR "i64 .64 bits. too small for shift of 64" + i64 <<= 64 // ERROR "i64 .64 bits. too small for shift of 64" + i64 >>= 64 // ERROR "i64 .64 bits. too small for shift of 64" + var u8 uint8 + _ = u8 << 7 + _ = u8 << 8 // ERROR "u8 .8 bits. too small for shift of 8" + _ = u8 >> 8 // ERROR "u8 .8 bits. too small for shift of 8" + u8 <<= 8 // ERROR "u8 .8 bits. too small for shift of 8" + u8 >>= 8 // ERROR "u8 .8 bits. too small for shift of 8" + var u16 uint16 + _ = u16 << 15 + _ = u16 << 16 // ERROR "u16 .16 bits. too small for shift of 16" + _ = u16 >> 16 // ERROR "u16 .16 bits. too small for shift of 16" + u16 <<= 16 // ERROR "u16 .16 bits. too small for shift of 16" + u16 >>= 16 // ERROR "u16 .16 bits. too small for shift of 16" + var u32 uint32 + _ = u32 << 31 + _ = u32 << 32 // ERROR "u32 .32 bits. too small for shift of 32" + _ = u32 >> 32 // ERROR "u32 .32 bits. too small for shift of 32" + u32 <<= 32 // ERROR "u32 .32 bits. too small for shift of 32" + u32 >>= 32 // ERROR "u32 .32 bits. too small for shift of 32" + var u64 uint64 + _ = u64 << 63 + _ = u64 << 64 // ERROR "u64 .64 bits. too small for shift of 64" + _ = u64 >> 64 // ERROR "u64 .64 bits. too small for shift of 64" + u64 <<= 64 // ERROR "u64 .64 bits. too small for shift of 64" + u64 >>= 64 // ERROR "u64 .64 bits. too small for shift of 64" + _ = u64 << u64 // Non-constant shifts should succeed. + + var i int + _ = i << 31 + const in = 8 * unsafe.Sizeof(i) + _ = i << in // ERROR "too small for shift" + _ = i >> in // ERROR "too small for shift" + i <<= in // ERROR "too small for shift" + i >>= in // ERROR "too small for shift" + const ix = 8*unsafe.Sizeof(i) - 1 + _ = i << ix + _ = i >> ix + i <<= ix + i >>= ix + + var u uint + _ = u << 31 + const un = 8 * unsafe.Sizeof(u) + _ = u << un // ERROR "too small for shift" + _ = u >> un // ERROR "too small for shift" + u <<= un // ERROR "too small for shift" + u >>= un // ERROR "too small for shift" + const ux = 8*unsafe.Sizeof(u) - 1 + _ = u << ux + _ = u >> ux + u <<= ux + u >>= ux + + var p uintptr + _ = p << 31 + const pn = 8 * unsafe.Sizeof(p) + _ = p << pn // ERROR "too small for shift" + _ = p >> pn // ERROR "too small for shift" + p <<= pn // ERROR "too small for shift" + p >>= pn // ERROR "too small for shift" + const px = 8*unsafe.Sizeof(p) - 1 + _ = p << px + _ = p >> px + p <<= px + p >>= px + + const oneIf64Bit = ^uint(0) >> 63 // allow large shifts of constants; they are used for 32/64 bit compatibility tricks + + var h uintptr + h = h<<8 | (h >> (8 * (unsafe.Sizeof(h) - 1))) + h <<= 8 * unsafe.Sizeof(h) // ERROR "too small for shift" + h >>= 7 * unsafe.Alignof(h) + h >>= 8 * unsafe.Alignof(h) // ERROR "too small for shift" +} + +func ShiftDeadCode() { + var i int + const iBits = 8 * unsafe.Sizeof(i) + + if iBits <= 32 { + if iBits == 16 { + _ = i >> 8 + } else { + _ = i >> 16 + } + } else { + _ = i >> 32 + } + + if iBits >= 64 { + _ = i << 32 + if iBits == 128 { + _ = i << 64 + } + } else { + _ = i << 16 + } + + if iBits == 64 { + _ = i << 32 + } + + switch iBits { + case 128, 64: + _ = i << 32 + default: + _ = i << 16 + } + + switch { + case iBits < 32: + _ = i << 16 + case iBits > 64: + _ = i << 64 + default: + _ = i << 64 // ERROR "too small for shift" + } + + // Make sure other vet checks work in dead code. + if iBits == 1024 { + _ = i << 512 // OK + fmt.Printf("foo %s bar", 123) // ERROR "Printf" + } +} diff --git a/go/analysis/passes/vet/testdata/structtag.go b/go/analysis/passes/vet/testdata/structtag.go new file mode 100644 index 00000000..ad55c4ab --- /dev/null +++ b/go/analysis/passes/vet/testdata/structtag.go @@ -0,0 +1,113 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for canonical struct tags. + +package testdata + +import "encoding/xml" + +type StructTagTest struct { + A int "hello" // ERROR "`hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair" + B int "\tx:\"y\"" // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag key" + C int "x:\"y\"\tx:\"y\"" // ERROR "not compatible with reflect.StructTag.Get" + D int "x:`y`" // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" + E int "ct\brl:\"char\"" // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag pair" + F int `:"emptykey"` // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag key" + G int `x:"noEndQuote` // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" + H int `x:"trunc\x0"` // ERROR "not compatible with reflect.StructTag.Get: bad syntax for struct tag value" + I int `x:"foo",y:"bar"` // ERROR "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces" + J int `x:"foo"y:"bar"` // ERROR "not compatible with reflect.StructTag.Get: key:.value. pairs not separated by spaces" + OK0 int `x:"y" u:"v" w:""` + OK1 int `x:"y:z" u:"v" w:""` // note multiple colons. + OK2 int "k0:\"values contain spaces\" k1:\"literal\ttabs\" k2:\"and\\tescaped\\tabs\"" + OK3 int `under_scores:"and" CAPS:"ARE_OK"` +} + +type UnexportedEncodingTagTest struct { + x int `json:"xx"` // ERROR "struct field x has json tag but is not exported" + y int `xml:"yy"` // ERROR "struct field y has xml tag but is not exported" + z int + A int `json:"aa" xml:"bb"` +} + +type unexp struct{} + +type JSONEmbeddedField struct { + UnexportedEncodingTagTest `is:"embedded"` + unexp `is:"embedded,notexported" json:"unexp"` // OK for now, see issue 7363 +} + +type AnonymousJSON struct{} +type AnonymousXML struct{} + +type AnonymousJSONField struct { + DuplicateAnonJSON int `json:"a"` + + A int "hello" // ERROR "`hello` not compatible with reflect.StructTag.Get: bad syntax for struct tag pair" +} + +type DuplicateJSONFields struct { + JSON int `json:"a"` + DuplicateJSON int `json:"a"` // ERROR "struct field DuplicateJSON repeats json tag .a. also at structtag.go:52" + IgnoredJSON int `json:"-"` + OtherIgnoredJSON int `json:"-"` + OmitJSON int `json:",omitempty"` + OtherOmitJSON int `json:",omitempty"` + DuplicateOmitJSON int `json:"a,omitempty"` // ERROR "struct field DuplicateOmitJSON repeats json tag .a. also at structtag.go:52" + NonJSON int `foo:"a"` + DuplicateNonJSON int `foo:"a"` + Embedded struct { + DuplicateJSON int `json:"a"` // OK because its not in the same struct type + } + AnonymousJSON `json:"a"` // ERROR "struct field AnonymousJSON repeats json tag .a. also at structtag.go:52" + + AnonymousJSONField // ERROR "struct field DuplicateAnonJSON repeats json tag .a. also at structtag.go:52" + + XML int `xml:"a"` + DuplicateXML int `xml:"a"` // ERROR "struct field DuplicateXML repeats xml tag .a. also at structtag.go:68" + IgnoredXML int `xml:"-"` + OtherIgnoredXML int `xml:"-"` + OmitXML int `xml:",omitempty"` + OtherOmitXML int `xml:",omitempty"` + DuplicateOmitXML int `xml:"a,omitempty"` // ERROR "struct field DuplicateOmitXML repeats xml tag .a. also at structtag.go:68" + NonXML int `foo:"a"` + DuplicateNonXML int `foo:"a"` + Embedded2 struct { + DuplicateXML int `xml:"a"` // OK because its not in the same struct type + } + AnonymousXML `xml:"a"` // ERROR "struct field AnonymousXML repeats xml tag .a. also at structtag.go:68" + Attribute struct { + XMLName xml.Name `xml:"b"` + NoDup int `xml:"b"` // OK because XMLName above affects enclosing struct. + Attr int `xml:"b,attr"` // OK because 0 is valid. + DupAttr int `xml:"b,attr"` // ERROR "struct field DupAttr repeats xml attribute tag .b. also at structtag.go:84" + DupOmitAttr int `xml:"b,omitempty,attr"` // ERROR "struct field DupOmitAttr repeats xml attribute tag .b. also at structtag.go:84" + + AnonymousXML `xml:"b,attr"` // ERROR "struct field AnonymousXML repeats xml attribute tag .b. also at structtag.go:84" + } + + AnonymousJSONField `json:"not_anon"` // ok; fields aren't embedded in JSON + AnonymousJSONField `json:"-"` // ok; entire field is ignored in JSON +} + +type UnexpectedSpacetest struct { + A int `json:"a,omitempty"` + B int `json:"b, omitempty"` // ERROR "suspicious space in struct tag value" + C int `json:"c ,omitempty"` + D int `json:"d,omitempty, string"` // ERROR "suspicious space in struct tag value" + E int `xml:"e local"` + F int `xml:"f "` // ERROR "suspicious space in struct tag value" + G int `xml:" g"` // ERROR "suspicious space in struct tag value" + H int `xml:"h ,omitempty"` // ERROR "suspicious space in struct tag value" + I int `xml:"i, omitempty"` // ERROR "suspicious space in struct tag value" + J int `xml:"j local ,omitempty"` // ERROR "suspicious space in struct tag value" + K int `xml:"k local, omitempty"` // ERROR "suspicious space in struct tag value" + L int `xml:" l local,omitempty"` // ERROR "suspicious space in struct tag value" + M int `xml:"m local,omitempty"` // ERROR "suspicious space in struct tag value" + N int `xml:" "` // ERROR "suspicious space in struct tag value" + O int `xml:""` + P int `xml:","` + Q int `foo:" doesn't care "` +} diff --git a/go/analysis/passes/vet/testdata/tagtest/file1.go b/go/analysis/passes/vet/testdata/tagtest/file1.go new file mode 100644 index 00000000..22a1509a --- /dev/null +++ b/go/analysis/passes/vet/testdata/tagtest/file1.go @@ -0,0 +1,10 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build testtag + +package main + +func main() { +} diff --git a/go/analysis/passes/vet/testdata/tagtest/file2.go b/go/analysis/passes/vet/testdata/tagtest/file2.go new file mode 100644 index 00000000..ba7dd91b --- /dev/null +++ b/go/analysis/passes/vet/testdata/tagtest/file2.go @@ -0,0 +1,10 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !testtag + +package main + +func ignore() { +} diff --git a/go/analysis/passes/vet/testdata/testingpkg/tests.go b/go/analysis/passes/vet/testdata/testingpkg/tests.go new file mode 100644 index 00000000..69d29d3c --- /dev/null +++ b/go/analysis/passes/vet/testdata/testingpkg/tests.go @@ -0,0 +1 @@ +package testdata diff --git a/go/analysis/passes/vet/testdata/testingpkg/tests_test.go b/go/analysis/passes/vet/testdata/testingpkg/tests_test.go new file mode 100644 index 00000000..f5bbc392 --- /dev/null +++ b/go/analysis/passes/vet/testdata/testingpkg/tests_test.go @@ -0,0 +1,74 @@ +package testdata + +import ( + "testing" +) + +// Buf is a ... +type Buf []byte + +// Append ... +func (*Buf) Append([]byte) {} + +func (Buf) Reset() {} + +func (Buf) Len() int { return 0 } + +// DefaultBuf is a ... +var DefaultBuf Buf + +func Example() {} // OK because is package-level. + +func Example_goodSuffix() // OK because refers to suffix annotation. + +func Example_BadSuffix() // ERROR "Example_BadSuffix has malformed example suffix: BadSuffix" + +func ExampleBuf() // OK because refers to known top-level type. + +func ExampleBuf_Append() {} // OK because refers to known method. + +func ExampleBuf_Clear() {} // ERROR "ExampleBuf_Clear refers to unknown field or method: Buf.Clear" + +func ExampleBuf_suffix() {} // OK because refers to suffix annotation. + +func ExampleBuf_Append_Bad() {} // ERROR "ExampleBuf_Append_Bad has malformed example suffix: Bad" + +func ExampleBuf_Append_suffix() {} // OK because refers to known method with valid suffix. + +func ExampleDefaultBuf() {} // OK because refers to top-level identifier. + +func ExampleBuf_Reset() bool { return true } // ERROR "ExampleBuf_Reset should return nothing" + +func ExampleBuf_Len(i int) {} // ERROR "ExampleBuf_Len should be niladic" + +// "Puffer" is German for "Buffer". + +func ExamplePuffer() // ERROR "ExamplePuffer refers to unknown identifier: Puffer" + +func ExamplePuffer_Append() // ERROR "ExamplePuffer_Append refers to unknown identifier: Puffer" + +func ExamplePuffer_suffix() // ERROR "ExamplePuffer_suffix refers to unknown identifier: Puffer" + +func nonTest() {} // OK because it doesn't start with "Test". + +func (Buf) TesthasReceiver() {} // OK because it has a receiver. + +func TestOKSuffix(*testing.T) {} // OK because first char after "Test" is Uppercase. + +func TestÜnicodeWorks(*testing.T) {} // OK because the first char after "Test" is Uppercase. + +func TestbadSuffix(*testing.T) {} // ERROR "first letter after 'Test' must not be lowercase" + +func TestemptyImportBadSuffix(*T) {} // ERROR "first letter after 'Test' must not be lowercase" + +func Test(*testing.T) {} // OK "Test" on its own is considered a test. + +func Testify() {} // OK because it takes no parameters. + +func TesttooManyParams(*testing.T, string) {} // OK because it takes too many parameters. + +func TesttooManyNames(a, b *testing.T) {} // OK because it takes too many names. + +func TestnoTParam(string) {} // OK because it doesn't take a *testing.T + +func BenchmarkbadSuffix(*testing.B) {} // ERROR "first letter after 'Benchmark' must not be lowercase" diff --git a/go/analysis/passes/vet/testdata/unsafeptr.go b/go/analysis/passes/vet/testdata/unsafeptr.go new file mode 100644 index 00000000..ce852009 --- /dev/null +++ b/go/analysis/passes/vet/testdata/unsafeptr.go @@ -0,0 +1,63 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package testdata + +import ( + "reflect" + "unsafe" +) + +func f() { + var x unsafe.Pointer + var y uintptr + x = unsafe.Pointer(y) // ERROR "possible misuse of unsafe.Pointer" + y = uintptr(x) + + // only allowed pointer arithmetic is ptr +/-/&^ num. + // num+ptr is technically okay but still flagged: write ptr+num instead. + x = unsafe.Pointer(uintptr(x) + 1) + x = unsafe.Pointer(1 + uintptr(x)) // ERROR "possible misuse of unsafe.Pointer" + x = unsafe.Pointer(uintptr(x) + uintptr(x)) // ERROR "possible misuse of unsafe.Pointer" + x = unsafe.Pointer(uintptr(x) - 1) + x = unsafe.Pointer(1 - uintptr(x)) // ERROR "possible misuse of unsafe.Pointer" + x = unsafe.Pointer(uintptr(x) &^ 3) + x = unsafe.Pointer(1 &^ uintptr(x)) // ERROR "possible misuse of unsafe.Pointer" + + // certain uses of reflect are okay + var v reflect.Value + x = unsafe.Pointer(v.Pointer()) + x = unsafe.Pointer(v.UnsafeAddr()) + var s1 *reflect.StringHeader + x = unsafe.Pointer(s1.Data) + var s2 *reflect.SliceHeader + x = unsafe.Pointer(s2.Data) + var s3 reflect.StringHeader + x = unsafe.Pointer(s3.Data) // ERROR "possible misuse of unsafe.Pointer" + var s4 reflect.SliceHeader + x = unsafe.Pointer(s4.Data) // ERROR "possible misuse of unsafe.Pointer" + + // but only in reflect + var vv V + x = unsafe.Pointer(vv.Pointer()) // ERROR "possible misuse of unsafe.Pointer" + x = unsafe.Pointer(vv.UnsafeAddr()) // ERROR "possible misuse of unsafe.Pointer" + var ss1 *StringHeader + x = unsafe.Pointer(ss1.Data) // ERROR "possible misuse of unsafe.Pointer" + var ss2 *SliceHeader + x = unsafe.Pointer(ss2.Data) // ERROR "possible misuse of unsafe.Pointer" + +} + +type V interface { + Pointer() uintptr + UnsafeAddr() uintptr +} + +type StringHeader struct { + Data uintptr +} + +type SliceHeader struct { + Data uintptr +} diff --git a/go/analysis/passes/vet/testdata/unused.go b/go/analysis/passes/vet/testdata/unused.go new file mode 100644 index 00000000..d50f6594 --- /dev/null +++ b/go/analysis/passes/vet/testdata/unused.go @@ -0,0 +1,29 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains tests for the unusedresult checker. + +package testdata + +import ( + "bytes" + "errors" + "fmt" +) + +func _() { + fmt.Errorf("") // ERROR "result of fmt.Errorf call not used" + _ = fmt.Errorf("") + + errors.New("") // ERROR "result of errors.New call not used" + + err := errors.New("") + err.Error() // ERROR "result of \(error\).Error call not used" + + var buf bytes.Buffer + buf.String() // ERROR "result of \(bytes.Buffer\).String call not used" + + fmt.Sprint("") // ERROR "result of fmt.Sprint call not used" + fmt.Sprintf("") // ERROR "result of fmt.Sprintf call not used" +} diff --git a/go/analysis/passes/vet/tests.go b/go/analysis/passes/vet/tests.go new file mode 100644 index 00000000..131d3e28 --- /dev/null +++ b/go/analysis/passes/vet/tests.go @@ -0,0 +1,189 @@ +// +build ignore + +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "go/types" + "strings" + "unicode" + "unicode/utf8" +) + +func init() { + register("tests", + "check for common mistaken usages of tests/documentation examples", + checkTestFunctions, + funcDecl) +} + +func isExampleSuffix(s string) bool { + r, size := utf8.DecodeRuneInString(s) + return size > 0 && unicode.IsLower(r) +} + +func isTestSuffix(name string) bool { + if len(name) == 0 { + // "Test" is ok. + return true + } + r, _ := utf8.DecodeRuneInString(name) + return !unicode.IsLower(r) +} + +func isTestParam(typ ast.Expr, wantType string) bool { + ptr, ok := typ.(*ast.StarExpr) + if !ok { + // Not a pointer. + return false + } + // No easy way of making sure it's a *testing.T or *testing.B: + // ensure the name of the type matches. + if name, ok := ptr.X.(*ast.Ident); ok { + return name.Name == wantType + } + if sel, ok := ptr.X.(*ast.SelectorExpr); ok { + return sel.Sel.Name == wantType + } + return false +} + +func lookup(name string, scopes []*types.Scope) types.Object { + for _, scope := range scopes { + if o := scope.Lookup(name); o != nil { + return o + } + } + return nil +} + +func extendedScope(f *File) []*types.Scope { + scopes := []*types.Scope{f.pkg.typesPkg.Scope()} + if f.basePkg != nil { + scopes = append(scopes, f.basePkg.typesPkg.Scope()) + } else { + // If basePkg is not specified (e.g. when checking a single file) try to + // find it among imports. + pkgName := f.pkg.typesPkg.Name() + if strings.HasSuffix(pkgName, "_test") { + basePkgName := strings.TrimSuffix(pkgName, "_test") + for _, p := range f.pkg.typesPkg.Imports() { + if p.Name() == basePkgName { + scopes = append(scopes, p.Scope()) + break + } + } + } + } + return scopes +} + +func checkExample(fn *ast.FuncDecl, f *File, report reporter) { + fnName := fn.Name.Name + if params := fn.Type.Params; len(params.List) != 0 { + report("%s should be niladic", fnName) + } + if results := fn.Type.Results; results != nil && len(results.List) != 0 { + report("%s should return nothing", fnName) + } + + if filesRun && !includesNonTest { + // The coherence checks between a test and the package it tests + // will report false positives if no non-test files have + // been provided. + return + } + + if fnName == "Example" { + // Nothing more to do. + return + } + + var ( + exName = strings.TrimPrefix(fnName, "Example") + elems = strings.SplitN(exName, "_", 3) + ident = elems[0] + obj = lookup(ident, extendedScope(f)) + ) + if ident != "" && obj == nil { + // Check ExampleFoo and ExampleBadFoo. + report("%s refers to unknown identifier: %s", fnName, ident) + // Abort since obj is absent and no subsequent checks can be performed. + return + } + if len(elems) < 2 { + // Nothing more to do. + return + } + + if ident == "" { + // Check Example_suffix and Example_BadSuffix. + if residual := strings.TrimPrefix(exName, "_"); !isExampleSuffix(residual) { + report("%s has malformed example suffix: %s", fnName, residual) + } + return + } + + mmbr := elems[1] + if !isExampleSuffix(mmbr) { + // Check ExampleFoo_Method and ExampleFoo_BadMethod. + if obj, _, _ := types.LookupFieldOrMethod(obj.Type(), true, obj.Pkg(), mmbr); obj == nil { + report("%s refers to unknown field or method: %s.%s", fnName, ident, mmbr) + } + } + if len(elems) == 3 && !isExampleSuffix(elems[2]) { + // Check ExampleFoo_Method_suffix and ExampleFoo_Method_Badsuffix. + report("%s has malformed example suffix: %s", fnName, elems[2]) + } +} + +func checkTest(fn *ast.FuncDecl, prefix string, report reporter) { + // Want functions with 0 results and 1 parameter. + if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 || + fn.Type.Params == nil || + len(fn.Type.Params.List) != 1 || + len(fn.Type.Params.List[0].Names) > 1 { + return + } + + // The param must look like a *testing.T or *testing.B. + if !isTestParam(fn.Type.Params.List[0].Type, prefix[:1]) { + return + } + + if !isTestSuffix(fn.Name.Name[len(prefix):]) { + report("%s has malformed name: first letter after '%s' must not be lowercase", fn.Name.Name, prefix) + } +} + +type reporter func(format string, args ...interface{}) + +// checkTestFunctions walks Test, Benchmark and Example functions checking +// malformed names, wrong signatures and examples documenting nonexistent +// identifiers. +func checkTestFunctions(f *File, node ast.Node) { + if !strings.HasSuffix(f.name, "_test.go") { + return + } + + fn, ok := node.(*ast.FuncDecl) + if !ok || fn.Recv != nil { + // Ignore non-functions or functions with receivers. + return + } + + report := func(format string, args ...interface{}) { f.Badf(node.Pos(), format, args...) } + + switch { + case strings.HasPrefix(fn.Name.Name, "Example"): + checkExample(fn, f, report) + case strings.HasPrefix(fn.Name.Name, "Test"): + checkTest(fn, "Test", report) + case strings.HasPrefix(fn.Name.Name, "Benchmark"): + checkTest(fn, "Benchmark", report) + } +} diff --git a/go/analysis/passes/vet/types.go b/go/analysis/passes/vet/types.go new file mode 100644 index 00000000..0b02250f --- /dev/null +++ b/go/analysis/passes/vet/types.go @@ -0,0 +1,335 @@ +// +build ignore + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the pieces of the tool that use typechecking from the go/types package. + +package main + +import ( + "go/ast" + "go/build" + "go/importer" + "go/token" + "go/types" +) + +// stdImporter is the importer we use to import packages. +// It is shared so that all packages are imported by the same importer. +var stdImporter types.Importer + +var ( + errorType *types.Interface + stringerType *types.Interface // possibly nil + formatterType *types.Interface // possibly nil +) + +func inittypes() { + errorType = types.Universe.Lookup("error").Type().Underlying().(*types.Interface) + + if typ := importType("fmt", "Stringer"); typ != nil { + stringerType = typ.Underlying().(*types.Interface) + } + if typ := importType("fmt", "Formatter"); typ != nil { + formatterType = typ.Underlying().(*types.Interface) + } +} + +// isNamedType reports whether t is the named type path.name. +func isNamedType(t types.Type, path, name string) bool { + n, ok := t.(*types.Named) + if !ok { + return false + } + obj := n.Obj() + return obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == path +} + +// importType returns the type denoted by the qualified identifier +// path.name, and adds the respective package to the imports map +// as a side effect. In case of an error, importType returns nil. +func importType(path, name string) types.Type { + pkg, err := stdImporter.Import(path) + if err != nil { + // This can happen if the package at path hasn't been compiled yet. + warnf("import failed: %v", err) + return nil + } + if obj, ok := pkg.Scope().Lookup(name).(*types.TypeName); ok { + return obj.Type() + } + warnf("invalid type name %q", name) + return nil +} + +func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) []error { + if stdImporter == nil { + if *source { + stdImporter = importer.For("source", nil) + } else { + stdImporter = importer.Default() + } + inittypes() + } + pkg.defs = make(map[*ast.Ident]types.Object) + pkg.uses = make(map[*ast.Ident]types.Object) + pkg.implicits = make(map[ast.Node]types.Object) + pkg.selectors = make(map[*ast.SelectorExpr]*types.Selection) + pkg.spans = make(map[types.Object]Span) + pkg.types = make(map[ast.Expr]types.TypeAndValue) + + var allErrors []error + config := types.Config{ + // We use the same importer for all imports to ensure that + // everybody sees identical packages for the given paths. + Importer: stdImporter, + // By providing a Config with our own error function, it will continue + // past the first error. We collect them all for printing later. + Error: func(e error) { + allErrors = append(allErrors, e) + }, + + Sizes: archSizes, + } + info := &types.Info{ + Selections: pkg.selectors, + Types: pkg.types, + Defs: pkg.defs, + Uses: pkg.uses, + Implicits: pkg.implicits, + } + typesPkg, err := config.Check(pkg.path, fs, astFiles, info) + if len(allErrors) == 0 && err != nil { + allErrors = append(allErrors, err) + } + pkg.typesPkg = typesPkg + // update spans + for id, obj := range pkg.defs { + // Ignore identifiers that don't denote objects + // (package names, symbolic variables such as t + // in t := x.(type) of type switch headers). + if obj != nil { + pkg.growSpan(obj, id.Pos(), id.End()) + } + } + for id, obj := range pkg.uses { + pkg.growSpan(obj, id.Pos(), id.End()) + } + for node, obj := range pkg.implicits { + // A type switch with a short variable declaration + // such as t := x.(type) doesn't declare the symbolic + // variable (t in the example) at the switch header; + // instead a new variable t (with specific type) is + // declared implicitly for each case. Such variables + // are found in the types.Info.Implicits (not Defs) + // map. Add them here, assuming they are declared at + // the type cases' colon ":". + if cc, ok := node.(*ast.CaseClause); ok { + pkg.growSpan(obj, cc.Colon, cc.Colon) + } + } + return allErrors +} + +// matchArgType reports an error if printf verb t is not appropriate +// for operand arg. +// +// typ is used only for recursive calls; external callers must supply nil. +// +// (Recursion arises from the compound types {map,chan,slice} which +// may be printed with %d etc. if that is appropriate for their element +// types.) +func (f *File) matchArgType(t printfArgType, typ types.Type, arg ast.Expr) bool { + return f.matchArgTypeInternal(t, typ, arg, make(map[types.Type]bool)) +} + +// matchArgTypeInternal is the internal version of matchArgType. It carries a map +// remembering what types are in progress so we don't recur when faced with recursive +// types or mutually recursive types. +func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { + // %v, %T accept any argument type. + if t == anyType { + return true + } + if typ == nil { + // external call + typ = f.pkg.types[arg].Type + if typ == nil { + return true // probably a type check problem + } + } + // If the type implements fmt.Formatter, we have nothing to check. + if f.isFormatter(typ) { + return true + } + // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? + if t&argString != 0 && isConvertibleToString(typ) { + return true + } + + typ = typ.Underlying() + if inProgress[typ] { + // We're already looking at this type. The call that started it will take care of it. + return true + } + inProgress[typ] = true + + switch typ := typ.(type) { + case *types.Signature: + return t&argPointer != 0 + + case *types.Map: + // Recur: map[int]int matches %d. + return t&argPointer != 0 || + (f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)) + + case *types.Chan: + return t&argPointer != 0 + + case *types.Array: + // Same as slice. + if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { + return true // %s matches []byte + } + // Recur: []int matches %d. + return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress) + + case *types.Slice: + // Same as array. + if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { + return true // %s matches []byte + } + // Recur: []int matches %d. But watch out for + // type T []T + // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. + return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress) + + case *types.Pointer: + // Ugly, but dealing with an edge case: a known pointer to an invalid type, + // probably something from a failed import. + if typ.Elem().String() == "invalid type" { + if *verbose { + f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg)) + } + return true // special case + } + // If it's actually a pointer with %p, it prints as one. + if t == argPointer { + return true + } + // If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct. + if str, ok := typ.Elem().Underlying().(*types.Struct); ok { + return f.matchStructArgType(t, str, arg, inProgress) + } + // Check whether the rest can print pointers. + return t&argPointer != 0 + + case *types.Struct: + return f.matchStructArgType(t, typ, arg, inProgress) + + case *types.Interface: + // There's little we can do. + // Whether any particular verb is valid depends on the argument. + // The user may have reasonable prior knowledge of the contents of the interface. + return true + + case *types.Basic: + switch typ.Kind() { + case types.UntypedBool, + types.Bool: + return t&argBool != 0 + + case types.UntypedInt, + types.Int, + types.Int8, + types.Int16, + types.Int32, + types.Int64, + types.Uint, + types.Uint8, + types.Uint16, + types.Uint32, + types.Uint64, + types.Uintptr: + return t&argInt != 0 + + case types.UntypedFloat, + types.Float32, + types.Float64: + return t&argFloat != 0 + + case types.UntypedComplex, + types.Complex64, + types.Complex128: + return t&argComplex != 0 + + case types.UntypedString, + types.String: + return t&argString != 0 + + case types.UnsafePointer: + return t&(argPointer|argInt) != 0 + + case types.UntypedRune: + return t&(argInt|argRune) != 0 + + case types.UntypedNil: + return false + + case types.Invalid: + if *verbose { + f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg)) + } + return true // Probably a type check problem. + } + panic("unreachable") + } + + return false +} + +func isConvertibleToString(typ types.Type) bool { + if bt, ok := typ.(*types.Basic); ok && bt.Kind() == types.UntypedNil { + // We explicitly don't want untyped nil, which is + // convertible to both of the interfaces below, as it + // would just panic anyway. + return false + } + if types.ConvertibleTo(typ, errorType) { + return true // via .Error() + } + if stringerType != nil && types.ConvertibleTo(typ, stringerType) { + return true // via .String() + } + return false +} + +// hasBasicType reports whether x's type is a types.Basic with the given kind. +func (f *File) hasBasicType(x ast.Expr, kind types.BasicKind) bool { + t := f.pkg.types[x].Type + if t != nil { + t = t.Underlying() + } + b, ok := t.(*types.Basic) + return ok && b.Kind() == kind +} + +// matchStructArgType reports whether all the elements of the struct match the expected +// type. For instance, with "%d" all the elements must be printable with the "%d" format. +func (f *File) matchStructArgType(t printfArgType, typ *types.Struct, arg ast.Expr, inProgress map[types.Type]bool) bool { + for i := 0; i < typ.NumFields(); i++ { + typf := typ.Field(i) + if !f.matchArgTypeInternal(t, typf.Type(), arg, inProgress) { + return false + } + if t&argString != 0 && !typf.Exported() && isConvertibleToString(typf.Type()) { + // Issue #17798: unexported Stringer or error cannot be properly fomatted. + return false + } + } + return true +} + +var archSizes = types.SizesFor("gc", build.Default.GOARCH) diff --git a/go/analysis/passes/vet/unsafeptr.go b/go/analysis/passes/vet/unsafeptr.go new file mode 100644 index 00000000..1bf32599 --- /dev/null +++ b/go/analysis/passes/vet/unsafeptr.go @@ -0,0 +1,99 @@ +// +build ignore + +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Check for invalid uintptr -> unsafe.Pointer conversions. + +package main + +import ( + "go/ast" + "go/token" + "go/types" +) + +func init() { + register("unsafeptr", + "check for misuse of unsafe.Pointer", + checkUnsafePointer, + callExpr) +} + +func checkUnsafePointer(f *File, node ast.Node) { + x := node.(*ast.CallExpr) + if len(x.Args) != 1 { + return + } + if f.hasBasicType(x.Fun, types.UnsafePointer) && f.hasBasicType(x.Args[0], types.Uintptr) && !f.isSafeUintptr(x.Args[0]) { + f.Badf(x.Pos(), "possible misuse of unsafe.Pointer") + } +} + +// isSafeUintptr reports whether x - already known to be a uintptr - +// is safe to convert to unsafe.Pointer. It is safe if x is itself derived +// directly from an unsafe.Pointer via conversion and pointer arithmetic +// or if x is the result of reflect.Value.Pointer or reflect.Value.UnsafeAddr +// or obtained from the Data field of a *reflect.SliceHeader or *reflect.StringHeader. +func (f *File) isSafeUintptr(x ast.Expr) bool { + switch x := x.(type) { + case *ast.ParenExpr: + return f.isSafeUintptr(x.X) + + case *ast.SelectorExpr: + switch x.Sel.Name { + case "Data": + // reflect.SliceHeader and reflect.StringHeader are okay, + // but only if they are pointing at a real slice or string. + // It's not okay to do: + // var x SliceHeader + // x.Data = uintptr(unsafe.Pointer(...)) + // ... use x ... + // p := unsafe.Pointer(x.Data) + // because in the middle the garbage collector doesn't + // see x.Data as a pointer and so x.Data may be dangling + // by the time we get to the conversion at the end. + // For now approximate by saying that *Header is okay + // but Header is not. + pt, ok := f.pkg.types[x.X].Type.(*types.Pointer) + if ok { + t, ok := pt.Elem().(*types.Named) + if ok && t.Obj().Pkg().Path() == "reflect" { + switch t.Obj().Name() { + case "StringHeader", "SliceHeader": + return true + } + } + } + } + + case *ast.CallExpr: + switch len(x.Args) { + case 0: + // maybe call to reflect.Value.Pointer or reflect.Value.UnsafeAddr. + sel, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + break + } + switch sel.Sel.Name { + case "Pointer", "UnsafeAddr": + t, ok := f.pkg.types[sel.X].Type.(*types.Named) + if ok && t.Obj().Pkg().Path() == "reflect" && t.Obj().Name() == "Value" { + return true + } + } + + case 1: + // maybe conversion of uintptr to unsafe.Pointer + return f.hasBasicType(x.Fun, types.Uintptr) && f.hasBasicType(x.Args[0], types.UnsafePointer) + } + + case *ast.BinaryExpr: + switch x.Op { + case token.ADD, token.SUB, token.AND_NOT: + return f.isSafeUintptr(x.X) && !f.isSafeUintptr(x.Y) + } + } + return false +} diff --git a/go/analysis/passes/vet/unused.go b/go/analysis/passes/vet/unused.go new file mode 100644 index 00000000..913af9c3 --- /dev/null +++ b/go/analysis/passes/vet/unused.go @@ -0,0 +1,95 @@ +// +build ignore + +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file defines the check for unused results of calls to certain +// pure functions. + +package main + +import ( + "flag" + "go/ast" + "go/token" + "go/types" + "strings" +) + +var unusedFuncsFlag = flag.String("unusedfuncs", + "errors.New,fmt.Errorf,fmt.Sprintf,fmt.Sprint,sort.Reverse", + "comma-separated list of functions whose results must be used") + +var unusedStringMethodsFlag = flag.String("unusedstringmethods", + "Error,String", + "comma-separated list of names of methods of type func() string whose results must be used") + +func init() { + register("unusedresult", + "check for unused result of calls to functions in -unusedfuncs list and methods in -unusedstringmethods list", + checkUnusedResult, + exprStmt) +} + +// func() string +var sigNoArgsStringResult = types.NewSignature(nil, nil, + types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])), + false) + +var unusedFuncs = make(map[string]bool) +var unusedStringMethods = make(map[string]bool) + +func initUnusedFlags() { + commaSplit := func(s string, m map[string]bool) { + if s != "" { + for _, name := range strings.Split(s, ",") { + if len(name) == 0 { + flag.Usage() + } + m[name] = true + } + } + } + commaSplit(*unusedFuncsFlag, unusedFuncs) + commaSplit(*unusedStringMethodsFlag, unusedStringMethods) +} + +func checkUnusedResult(f *File, n ast.Node) { + call, ok := unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) + if !ok { + return // not a call statement + } + fun := unparen(call.Fun) + + if f.pkg.types[fun].IsType() { + return // a conversion, not a call + } + + selector, ok := fun.(*ast.SelectorExpr) + if !ok { + return // neither a method call nor a qualified ident + } + + sel, ok := f.pkg.selectors[selector] + if ok && sel.Kind() == types.MethodVal { + // method (e.g. foo.String()) + obj := sel.Obj().(*types.Func) + sig := sel.Type().(*types.Signature) + if types.Identical(sig, sigNoArgsStringResult) { + if unusedStringMethods[obj.Name()] { + f.Badf(call.Lparen, "result of (%s).%s call not used", + sig.Recv().Type(), obj.Name()) + } + } + } else if !ok { + // package-qualified function (e.g. fmt.Errorf) + obj := f.pkg.uses[selector.Sel] + if obj, ok := obj.(*types.Func); ok { + qname := obj.Pkg().Path() + "." + obj.Name() + if unusedFuncs[qname] { + f.Badf(call.Lparen, "result of %v call not used", qname) + } + } + } +} diff --git a/go/analysis/passes/vet/vet_test.go b/go/analysis/passes/vet/vet_test.go new file mode 100644 index 00000000..0d0c439b --- /dev/null +++ b/go/analysis/passes/vet/vet_test.go @@ -0,0 +1,441 @@ +// +build ignore + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main_test + +import ( + "bytes" + "errors" + "fmt" + "internal/testenv" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "testing" +) + +const ( + dataDir = "testdata" + binary = "./testvet.exe" +) + +// We implement TestMain so remove the test binary when all is done. +func TestMain(m *testing.M) { + result := m.Run() + os.Remove(binary) + os.Exit(result) +} + +var ( + buildMu sync.Mutex // guards following + built = false // We have built the binary. + failed = false // We have failed to build the binary, don't try again. +) + +func Build(t *testing.T) { + buildMu.Lock() + defer buildMu.Unlock() + if built { + return + } + if failed { + t.Skip("cannot run on this environment") + } + testenv.MustHaveGoBuild(t) + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", binary) + output, err := cmd.CombinedOutput() + if err != nil { + failed = true + fmt.Fprintf(os.Stderr, "%s\n", output) + t.Fatal(err) + } + built = true +} + +func Vet(t *testing.T, files []string) { + flags := []string{ + "-printfuncs=Warn:1,Warnf:1", + "-all", + "-shadow", + } + cmd := exec.Command(binary, append(flags, files...)...) + errchk(cmd, files, t) +} + +// TestVet is equivalent to running this: +// go build -o ./testvet +// errorCheck the output of ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s +// rm ./testvet +// + +// TestVet tests self-contained files in testdata/*.go. +// +// If a file contains assembly or has inter-dependencies, it should be +// in its own test, like TestVetAsm, TestDivergentPackagesExamples, +// etc below. +func TestVet(t *testing.T) { + Build(t) + t.Parallel() + + gos, err := filepath.Glob(filepath.Join(dataDir, "*.go")) + if err != nil { + t.Fatal(err) + } + wide := runtime.GOMAXPROCS(0) + if wide > len(gos) { + wide = len(gos) + } + batch := make([][]string, wide) + for i, file := range gos { + // The print.go test is run by TestVetPrint. + if strings.HasSuffix(file, "print.go") { + continue + } + batch[i%wide] = append(batch[i%wide], file) + } + for i, files := range batch { + if len(files) == 0 { + continue + } + files := files + t.Run(fmt.Sprint(i), func(t *testing.T) { + t.Parallel() + t.Logf("files: %q", files) + Vet(t, files) + }) + } +} + +func TestVetPrint(t *testing.T) { + Build(t) + file := filepath.Join("testdata", "print.go") + cmd := exec.Command( + "go", "vet", "-vettool="+binary, + "-printf", + "-printfuncs=Warn:1,Warnf:1", + file, + ) + errchk(cmd, []string{file}, t) +} + +func TestVetAsm(t *testing.T) { + Build(t) + + asmDir := filepath.Join(dataDir, "asm") + gos, err := filepath.Glob(filepath.Join(asmDir, "*.go")) + if err != nil { + t.Fatal(err) + } + asms, err := filepath.Glob(filepath.Join(asmDir, "*.s")) + if err != nil { + t.Fatal(err) + } + + t.Parallel() + Vet(t, append(gos, asms...)) +} + +func TestVetDirs(t *testing.T) { + t.Parallel() + Build(t) + for _, dir := range []string{ + "testingpkg", + "divergent", + "buildtag", + "incomplete", // incomplete examples + "cgo", + } { + dir := dir + t.Run(dir, func(t *testing.T) { + t.Parallel() + gos, err := filepath.Glob(filepath.Join("testdata", dir, "*.go")) + if err != nil { + t.Fatal(err) + } + Vet(t, gos) + }) + } +} + +func errchk(c *exec.Cmd, files []string, t *testing.T) { + output, err := c.CombinedOutput() + if _, ok := err.(*exec.ExitError); !ok { + t.Logf("vet output:\n%s", output) + t.Fatal(err) + } + fullshort := make([]string, 0, len(files)*2) + for _, f := range files { + fullshort = append(fullshort, f, filepath.Base(f)) + } + err = errorCheck(string(output), false, fullshort...) + if err != nil { + t.Errorf("error check failed: %s", err) + } +} + +// TestTags verifies that the -tags argument controls which files to check. +func TestTags(t *testing.T) { + t.Parallel() + Build(t) + for _, tag := range []string{"testtag", "x testtag y", "x,testtag,y"} { + tag := tag + t.Run(tag, func(t *testing.T) { + t.Parallel() + t.Logf("-tags=%s", tag) + args := []string{ + "-tags=" + tag, + "-v", // We're going to look at the files it examines. + "testdata/tagtest", + } + cmd := exec.Command(binary, args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + // file1 has testtag and file2 has !testtag. + if !bytes.Contains(output, []byte(filepath.Join("tagtest", "file1.go"))) { + t.Error("file1 was excluded, should be included") + } + if bytes.Contains(output, []byte(filepath.Join("tagtest", "file2.go"))) { + t.Error("file2 was included, should be excluded") + } + }) + } +} + +// Issue #21188. +func TestVetVerbose(t *testing.T) { + t.Parallel() + Build(t) + cmd := exec.Command(binary, "-v", "-all", "testdata/cgo/cgo3.go") + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Error(err) + } +} + +// All declarations below were adapted from test/run.go. + +// errorCheck matches errors in outStr against comments in source files. +// For each line of the source files which should generate an error, +// there should be a comment of the form // ERROR "regexp". +// If outStr has an error for a line which has no such comment, +// this function will report an error. +// Likewise if outStr does not have an error for a line which has a comment, +// or if the error message does not match the . +// The syntax is Perl but its best to stick to egrep. +// +// Sources files are supplied as fullshort slice. +// It consists of pairs: full path to source file and it's base name. +func errorCheck(outStr string, wantAuto bool, fullshort ...string) (err error) { + var errs []error + out := splitOutput(outStr, wantAuto) + // Cut directory name. + for i := range out { + for j := 0; j < len(fullshort); j += 2 { + full, short := fullshort[j], fullshort[j+1] + out[i] = strings.ReplaceAll(out[i], full, short) + } + } + + var want []wantedError + for j := 0; j < len(fullshort); j += 2 { + full, short := fullshort[j], fullshort[j+1] + want = append(want, wantedErrors(full, short)...) + } + for _, we := range want { + var errmsgs []string + if we.auto { + errmsgs, out = partitionStrings("", out) + } else { + errmsgs, out = partitionStrings(we.prefix, out) + } + if len(errmsgs) == 0 { + errs = append(errs, fmt.Errorf("%s:%d: missing error %q", we.file, we.lineNum, we.reStr)) + continue + } + matched := false + n := len(out) + for _, errmsg := range errmsgs { + // Assume errmsg says "file:line: foo". + // Cut leading "file:line: " to avoid accidental matching of file name instead of message. + text := errmsg + if i := strings.Index(text, " "); i >= 0 { + text = text[i+1:] + } + if we.re.MatchString(text) { + matched = true + } else { + out = append(out, errmsg) + } + } + if !matched { + errs = append(errs, fmt.Errorf("%s:%d: no match for %#q in:\n\t%s", we.file, we.lineNum, we.reStr, strings.Join(out[n:], "\n\t"))) + continue + } + } + + if len(out) > 0 { + errs = append(errs, fmt.Errorf("Unmatched Errors:")) + for _, errLine := range out { + errs = append(errs, fmt.Errorf("%s", errLine)) + } + } + + if len(errs) == 0 { + return nil + } + if len(errs) == 1 { + return errs[0] + } + var buf bytes.Buffer + fmt.Fprintf(&buf, "\n") + for _, err := range errs { + fmt.Fprintf(&buf, "%s\n", err.Error()) + } + return errors.New(buf.String()) +} + +func splitOutput(out string, wantAuto bool) []string { + // gc error messages continue onto additional lines with leading tabs. + // Split the output at the beginning of each line that doesn't begin with a tab. + // lines are impossible to match so those are filtered out. + var res []string + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSuffix(line, "\r") // normalize Windows output + if strings.HasPrefix(line, "\t") { + res[len(res)-1] += "\n" + line + } else if strings.HasPrefix(line, "go tool") || strings.HasPrefix(line, "#") || !wantAuto && strings.HasPrefix(line, "") { + continue + } else if strings.TrimSpace(line) != "" { + res = append(res, line) + } + } + return res +} + +// matchPrefix reports whether s starts with file name prefix followed by a :, +// and possibly preceded by a directory name. +func matchPrefix(s, prefix string) bool { + i := strings.Index(s, ":") + if i < 0 { + return false + } + j := strings.LastIndex(s[:i], "/") + s = s[j+1:] + if len(s) <= len(prefix) || s[:len(prefix)] != prefix { + return false + } + if s[len(prefix)] == ':' { + return true + } + return false +} + +func partitionStrings(prefix string, strs []string) (matched, unmatched []string) { + for _, s := range strs { + if matchPrefix(s, prefix) { + matched = append(matched, s) + } else { + unmatched = append(unmatched, s) + } + } + return +} + +type wantedError struct { + reStr string + re *regexp.Regexp + lineNum int + auto bool // match line + file string + prefix string +} + +var ( + errRx = regexp.MustCompile(`// (?:GC_)?ERROR (.*)`) + errAutoRx = regexp.MustCompile(`// (?:GC_)?ERRORAUTO (.*)`) + errQuotesRx = regexp.MustCompile(`"([^"]*)"`) + lineRx = regexp.MustCompile(`LINE(([+-])([0-9]+))?`) +) + +// wantedErrors parses expected errors from comments in a file. +func wantedErrors(file, short string) (errs []wantedError) { + cache := make(map[string]*regexp.Regexp) + + src, err := ioutil.ReadFile(file) + if err != nil { + log.Fatal(err) + } + for i, line := range strings.Split(string(src), "\n") { + lineNum := i + 1 + if strings.Contains(line, "////") { + // double comment disables ERROR + continue + } + var auto bool + m := errAutoRx.FindStringSubmatch(line) + if m != nil { + auto = true + } else { + m = errRx.FindStringSubmatch(line) + } + if m == nil { + continue + } + all := m[1] + mm := errQuotesRx.FindAllStringSubmatch(all, -1) + if mm == nil { + log.Fatalf("%s:%d: invalid errchk line: %s", file, lineNum, line) + } + for _, m := range mm { + replacedOnce := false + rx := lineRx.ReplaceAllStringFunc(m[1], func(m string) string { + if replacedOnce { + return m + } + replacedOnce = true + n := lineNum + if strings.HasPrefix(m, "LINE+") { + delta, _ := strconv.Atoi(m[5:]) + n += delta + } else if strings.HasPrefix(m, "LINE-") { + delta, _ := strconv.Atoi(m[5:]) + n -= delta + } + return fmt.Sprintf("%s:%d", short, n) + }) + re := cache[rx] + if re == nil { + var err error + re, err = regexp.Compile(rx) + if err != nil { + log.Fatalf("%s:%d: invalid regexp \"%#q\" in ERROR line: %v", file, lineNum, rx, err) + } + cache[rx] = re + } + prefix := fmt.Sprintf("%s:%d", short, lineNum) + errs = append(errs, wantedError{ + reStr: rx, + re: re, + prefix: prefix, + auto: auto, + lineNum: lineNum, + file: short, + }) + } + } + + return +}