From 1b8b2c1551a1f375e5141eec1645e69906d337b3 Mon Sep 17 00:00:00 2001 From: Robert Griesemer <gri@golang.org> Date: Thu, 4 Jun 2015 12:54:58 -0700 Subject: [PATCH] cmd/vet: move cmd/vet into std repo cp -r x/tools/cmd/vet cmd/vet without any changes. The next change will adjust the source to use std repo go/types and friends. This may (temporarily) break the build; the next commit (immediately following) will fix it. We do it in two commits so that we can see the manual changes. Change-Id: Ic45dab7066f13923e21f8c61200c8c3fd447b171 Reviewed-on: https://go-review.googlesource.com/10694 Reviewed-by: Rob Pike <r@golang.org> --- src/cmd/vet/asmdecl.go | 662 ++++++++ src/cmd/vet/assign.go | 49 + src/cmd/vet/atomic.go | 66 + src/cmd/vet/bool.go | 186 +++ src/cmd/vet/buildtag.go | 91 + src/cmd/vet/composite.go | 125 ++ src/cmd/vet/copylock.go | 155 ++ src/cmd/vet/deadcode.go | 296 ++++ src/cmd/vet/doc.go | 190 +++ src/cmd/vet/main.go | 488 ++++++ src/cmd/vet/method.go | 182 ++ src/cmd/vet/nilfunc.go | 68 + src/cmd/vet/print.go | 587 +++++++ src/cmd/vet/rangeloop.go | 70 + src/cmd/vet/shadow.go | 245 +++ src/cmd/vet/shift.go | 83 + src/cmd/vet/structtag.go | 122 ++ src/cmd/vet/testdata/asm.go | 33 + src/cmd/vet/testdata/asm1.s | 254 +++ src/cmd/vet/testdata/asm2.s | 257 +++ src/cmd/vet/testdata/asm3.s | 178 ++ src/cmd/vet/testdata/asm4.s | 26 + src/cmd/vet/testdata/assign.go | 18 + src/cmd/vet/testdata/atomic.go | 43 + src/cmd/vet/testdata/bool.go | 113 ++ src/cmd/vet/testdata/buildtag.go | 14 + src/cmd/vet/testdata/buildtag_bad.go | 15 + src/cmd/vet/testdata/composite.go | 63 + src/cmd/vet/testdata/copylock_func.go | 90 + src/cmd/vet/testdata/copylock_range.go | 67 + src/cmd/vet/testdata/deadcode.go | 2125 ++++++++++++++++++++++++ src/cmd/vet/testdata/method.go | 22 + src/cmd/vet/testdata/nilfunc.go | 35 + src/cmd/vet/testdata/print.go | 342 ++++ src/cmd/vet/testdata/rangeloop.go | 59 + src/cmd/vet/testdata/shadow.go | 54 + src/cmd/vet/testdata/shift.go | 78 + src/cmd/vet/testdata/structtag.go | 36 + src/cmd/vet/testdata/tagtest/file1.go | 10 + src/cmd/vet/testdata/tagtest/file2.go | 10 + src/cmd/vet/testdata/unsafeptr.go | 61 + src/cmd/vet/testdata/unused.go | 29 + src/cmd/vet/types.go | 366 ++++ src/cmd/vet/unsafeptr.go | 98 ++ src/cmd/vet/unused.go | 94 ++ src/cmd/vet/vet_test.go | 102 ++ src/cmd/vet/whitelist/whitelist.go | 53 + 47 files changed, 8410 insertions(+) create mode 100644 src/cmd/vet/asmdecl.go create mode 100644 src/cmd/vet/assign.go create mode 100644 src/cmd/vet/atomic.go create mode 100644 src/cmd/vet/bool.go create mode 100644 src/cmd/vet/buildtag.go create mode 100644 src/cmd/vet/composite.go create mode 100644 src/cmd/vet/copylock.go create mode 100644 src/cmd/vet/deadcode.go create mode 100644 src/cmd/vet/doc.go create mode 100644 src/cmd/vet/main.go create mode 100644 src/cmd/vet/method.go create mode 100644 src/cmd/vet/nilfunc.go create mode 100644 src/cmd/vet/print.go create mode 100644 src/cmd/vet/rangeloop.go create mode 100644 src/cmd/vet/shadow.go create mode 100644 src/cmd/vet/shift.go create mode 100644 src/cmd/vet/structtag.go create mode 100644 src/cmd/vet/testdata/asm.go create mode 100644 src/cmd/vet/testdata/asm1.s create mode 100644 src/cmd/vet/testdata/asm2.s create mode 100644 src/cmd/vet/testdata/asm3.s create mode 100644 src/cmd/vet/testdata/asm4.s create mode 100644 src/cmd/vet/testdata/assign.go create mode 100644 src/cmd/vet/testdata/atomic.go create mode 100644 src/cmd/vet/testdata/bool.go create mode 100644 src/cmd/vet/testdata/buildtag.go create mode 100644 src/cmd/vet/testdata/buildtag_bad.go create mode 100644 src/cmd/vet/testdata/composite.go create mode 100644 src/cmd/vet/testdata/copylock_func.go create mode 100644 src/cmd/vet/testdata/copylock_range.go create mode 100644 src/cmd/vet/testdata/deadcode.go create mode 100644 src/cmd/vet/testdata/method.go create mode 100644 src/cmd/vet/testdata/nilfunc.go create mode 100644 src/cmd/vet/testdata/print.go create mode 100644 src/cmd/vet/testdata/rangeloop.go create mode 100644 src/cmd/vet/testdata/shadow.go create mode 100644 src/cmd/vet/testdata/shift.go create mode 100644 src/cmd/vet/testdata/structtag.go create mode 100644 src/cmd/vet/testdata/tagtest/file1.go create mode 100644 src/cmd/vet/testdata/tagtest/file2.go create mode 100644 src/cmd/vet/testdata/unsafeptr.go create mode 100644 src/cmd/vet/testdata/unused.go create mode 100644 src/cmd/vet/types.go create mode 100644 src/cmd/vet/unsafeptr.go create mode 100644 src/cmd/vet/unused.go create mode 100644 src/cmd/vet/vet_test.go create mode 100644 src/cmd/vet/whitelist/whitelist.go diff --git a/src/cmd/vet/asmdecl.go b/src/cmd/vet/asmdecl.go new file mode 100644 index 0000000000..6bdfdbf3be --- /dev/null +++ b/src/cmd/vet/asmdecl.go @@ -0,0 +1,662 @@ +// 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/token" + "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 + asmInterface + asmEmptyInterface +) + +// An asmArch describes assembly parameters for an architecture +type asmArch struct { + name string + ptrSize int + intSize int + maxAlign int + bigEndian bool + stack string + lr bool +} + +// 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{"386", 4, 4, 4, false, "SP", false} + asmArchArm = asmArch{"arm", 4, 4, 4, false, "R13", true} + asmArchArm64 = asmArch{"arm64", 8, 8, 8, false, "RSP", true} + asmArchAmd64 = asmArch{"amd64", 8, 8, 8, false, "SP", false} + asmArchAmd64p32 = asmArch{"amd64p32", 4, 4, 8, false, "SP", false} + asmArchPower64 = asmArch{"power64", 8, 8, 8, true, "R1", true} + asmArchPower64LE = asmArch{"power64le", 8, 8, 8, false, "R1", true} + + arches = []*asmArch{ + &asmArch386, + &asmArchArm, + &asmArchArm64, + &asmArchAmd64, + &asmArchAmd64p32, + &asmArchPower64, + &asmArchPower64LE, + } +) + +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*(.*))?`) + power64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) +) + +func asmCheck(pkg *Package) { + if !vet("asmdecl") { + 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 { + Fields: + for _, fld := range strings.Fields(m[1]) { + for _, a := range arches { + if a.name == fld { + arch = a.name + archDef = a + break Fields + } + } + } + } + } + + if m := asmTEXT.FindStringSubmatch(line); m != nil { + flushRet() + if arch == "" { + f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) + continue Files + } + fnName = m[1] + fn = knownFunc[m[1]][arch] + if fn != nil { + size, _ := strconv.Atoi(m[4]) + if size != fn.size && (m[2] != "7" && !strings.Contains(m[2], "NOSPLIT") || size != 0) { + badf("wrong argument size %d; expected $...-%d", size, fn.size) + } + } + localSize, _ = strconv.Atoi(m[3]) + localSize += archDef.intSize + if archDef.lr { + // Account for caller's saved LR + localSize += archDef.intSize + } + argSize, _ = strconv.Atoi(m[4]) + 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() + } +} + +// 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 + failed bool + ) + + addVar := func(outer string, v asmVar) { + 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 + } + } + + addParams := func(list []*ast.Field) { + for i, fld := range list { + // Determine alignment, size, and kind of type in declaration. + var align, size int + var kind asmKind + names := fld.Names + typ := f.gofmt(fld.Type) + switch t := fld.Type.(type) { + default: + switch typ { + default: + f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ) + failed = true + return + case "int8", "uint8", "byte", "bool": + size = 1 + case "int16", "uint16": + size = 2 + case "int32", "uint32", "float32": + size = 4 + case "int64", "uint64", "float64": + align = arch.maxAlign + size = 8 + case "int", "uint": + size = arch.intSize + case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer": + size = arch.ptrSize + case "string", "ErrorString": + size = arch.ptrSize * 2 + align = arch.ptrSize + kind = asmString + } + case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr: + size = arch.ptrSize + case *ast.InterfaceType: + align = arch.ptrSize + size = 2 * arch.ptrSize + if len(t.Methods.List) > 0 { + kind = asmInterface + } else { + kind = asmEmptyInterface + } + case *ast.ArrayType: + if t.Len == nil { + size = arch.ptrSize + 2*arch.intSize + align = arch.ptrSize + kind = asmSlice + break + } + f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) + failed = true + case *ast.StructType: + f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) + failed = true + } + if align == 0 { + align = size + } + if kind == 0 { + kind = asmKind(size) + } + offset += -offset & (align - 1) + + // Create variable for each name being declared with this type. + if len(names) == 0 { + name := "unnamed" + if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 { + // Assume assembly will refer to single unnamed result as r. + name = "ret" + } + names = []*ast.Ident{{Name: name}} + } + for _, id := range names { + name := id.Name + addVar("", asmVar{ + name: name, + kind: kind, + typ: typ, + off: offset, + size: size, + }) + switch kind { + case 8: + if arch.ptrSize == 4 { + w1, w2 := "lo", "hi" + if arch.bigEndian { + w1, w2 = w2, w1 + } + addVar(name, asmVar{ + name: name + "_" + w1, + kind: 4, + typ: "half " + typ, + off: offset, + size: 4, + }) + addVar(name, asmVar{ + name: name + "_" + w2, + kind: 4, + typ: "half " + typ, + off: offset + 4, + size: 4, + }) + } + + case asmEmptyInterface: + addVar(name, asmVar{ + name: name + "_type", + kind: asmKind(arch.ptrSize), + typ: "interface type", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_data", + kind: asmKind(arch.ptrSize), + typ: "interface data", + off: offset + arch.ptrSize, + size: arch.ptrSize, + }) + + case asmInterface: + addVar(name, asmVar{ + name: name + "_itable", + kind: asmKind(arch.ptrSize), + typ: "interface itable", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_data", + kind: asmKind(arch.ptrSize), + typ: "interface data", + off: offset + arch.ptrSize, + size: arch.ptrSize, + }) + + case asmSlice: + addVar(name, asmVar{ + name: name + "_base", + kind: asmKind(arch.ptrSize), + typ: "slice base", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_len", + kind: asmKind(arch.intSize), + typ: "slice len", + off: offset + arch.ptrSize, + size: arch.intSize, + }) + addVar(name, asmVar{ + name: name + "_cap", + kind: asmKind(arch.intSize), + typ: "slice cap", + off: offset + arch.ptrSize + arch.intSize, + size: arch.intSize, + }) + + case asmString: + addVar(name, asmVar{ + name: name + "_base", + kind: asmKind(arch.ptrSize), + typ: "string base", + off: offset, + size: arch.ptrSize, + }) + addVar(name, asmVar{ + name: name + "_len", + kind: asmKind(arch.intSize), + typ: "string len", + off: offset + arch.ptrSize, + size: arch.intSize, + }) + } + 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) + if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { + offset += -offset & (arch.maxAlign - 1) + addParams(decl.Type.Results.List) + } + fn.size = offset + m[arch.name] = fn + } + + if failed { + return nil + } + 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, "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 "power64", "power64le": + // Strip standard suffixes to reveal size letter. + m := power64Suff.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 + } + } + } + } + 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 + vt := v.typ + switch vk { + case asmInterface, asmEmptyInterface, asmString, asmSlice: + // allow reference to first word (pointer) + vk = v.inner[0].kind + 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, vk, inner.String()) + } +} diff --git a/src/cmd/vet/assign.go b/src/cmd/vet/assign.go new file mode 100644 index 0000000000..54c1ae1fdc --- /dev/null +++ b/src/cmd/vet/assign.go @@ -0,0 +1,49 @@ +// 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 "<expr> = <expr>". +// 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 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/src/cmd/vet/atomic.go b/src/cmd/vet/atomic.go new file mode 100644 index 0000000000..c084f13ab3 --- /dev/null +++ b/src/cmd/vet/atomic.go @@ -0,0 +1,66 @@ +// 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" +) + +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 + } + + for i, right := range n.Rhs { + call, ok := right.(*ast.CallExpr) + if !ok { + continue + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + pkg, ok := sel.X.(*ast.Ident) + if !ok || pkg.Name != "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/src/cmd/vet/bool.go b/src/cmd/vet/bool.go new file mode 100644 index 0000000000..07c2a93dff --- /dev/null +++ b/src/cmd/vet/bool.go @@ -0,0 +1,186 @@ +// 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(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(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(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(e ast.Expr) bool { + safe := true + ast.Inspect(e, func(node ast.Node) bool { + switch n := node.(type) { + // Using CallExpr here will catch conversions + // as well as function and method invocations. + // We'll live with the false negatives for now. + case *ast.CallExpr: + 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/src/cmd/vet/buildtag.go b/src/cmd/vet/buildtag.go new file mode 100644 index 0000000000..2d86edf734 --- /dev/null +++ b/src/cmd/vet/buildtag.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. + +package main + +import ( + "bytes" + "fmt" + "os" + "strings" + "unicode" +) + +var ( + nl = []byte("\n") + slashSlash = []byte("//") + plusBuild = []byte("+build") +) + +// checkBuildTag checks that build tags are in the correct location and well-formed. +func checkBuildTag(name string, data []byte) { + if !vet("buildtags") { + return + } + lines := bytes.SplitAfter(data, nl) + + // 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 + } + 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. + fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1) + continue + } + if i >= cutoff { + fmt.Fprintf(os.Stderr, "%s:%d: +build comment must appear before package clause and be followed by a blank line\n", name, i+1) + setExit(1) + continue + } + // Check arguments. + Args: + for _, arg := range fields[1:] { + for _, elem := range strings.Split(string(arg), ",") { + if strings.HasPrefix(elem, "!!") { + fmt.Fprintf(os.Stderr, "%s:%d: invalid double negative in build constraint: %s\n", name, i+1, arg) + setExit(1) + break Args + } + if strings.HasPrefix(elem, "!") { + elem = elem[1:] + } + for _, c := range elem { + if !unicode.IsLetter(c) && !unicode.IsDigit(c) && c != '_' && c != '.' { + fmt.Fprintf(os.Stderr, "%s:%d: invalid non-alphanumeric build constraint: %s\n", name, i+1, arg) + setExit(1) + break Args + } + } + } + } + continue + } + // Comment with +build but not at beginning. + if bytes.Contains(line, plusBuild) && i < cutoff { + fmt.Fprintf(os.Stderr, "%s:%d: possible malformed +build comment\n", name, i+1) + continue + } + } +} diff --git a/src/cmd/vet/composite.go b/src/cmd/vet/composite.go new file mode 100644 index 0000000000..0c3f916558 --- /dev/null +++ b/src/cmd/vet/composite.go @@ -0,0 +1,125 @@ +// 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 ( + "flag" + "go/ast" + "strings" + + "golang.org/x/tools/cmd/vet/whitelist" +) + +var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only") + +func init() { + register("composites", + "check that composite literals used 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) { + c := node.(*ast.CompositeLit) + typ := c.Type + for { + if typ1, ok := c.Type.(*ast.ParenExpr); ok { + typ = typ1 + continue + } + break + } + + switch typ.(type) { + case *ast.ArrayType: + return + case *ast.MapType: + return + case *ast.StructType: + return // a literal struct type does not need to use keys + case *ast.Ident: + // A simple type name like t or T does not need keys either, + // since it is almost certainly declared in the current package. + // (The exception is names being used via import . "pkg", but + // those are already breaking the Go 1 compatibility promise, + // so not reporting potential additional breakage seems okay.) + return + } + + // Otherwise the type is a selector like pkg.Name. + // We only care if pkg.Name is a struct, not if it's a map, array, or slice. + isStruct, typeString := f.pkg.isStruct(c) + if !isStruct { + return + } + + if typeString == "" { // isStruct doesn't know + typeString = f.gofmt(typ) + } + + // It's a struct, or we can't tell it's not a struct because we don't have types. + + // Check if the CompositeLit contains an unkeyed field. + allKeyValue := true + for _, e := range c.Elts { + if _, ok := e.(*ast.KeyValueExpr); !ok { + allKeyValue = false + break + } + } + if allKeyValue { + return + } + + // Check that the CompositeLit's type has the form pkg.Typ. + s, ok := c.Type.(*ast.SelectorExpr) + if !ok { + return + } + pkg, ok := s.X.(*ast.Ident) + if !ok { + return + } + + // Convert the package name to an import path, and compare to a whitelist. + path := pkgPath(f, pkg.Name) + if path == "" { + f.Badf(c.Pos(), "unresolvable package for %s.%s literal", pkg.Name, s.Sel.Name) + return + } + typeName := path + "." + s.Sel.Name + if *compositeWhiteList && whitelist.UnkeyedLiteral[typeName] { + return + } + + f.Bad(c.Pos(), typeString+" composite literal uses unkeyed fields") +} + +// pkgPath returns the import path "image/png" for the package name "png". +// +// This is based purely on syntax and convention, and not on the imported +// package's contents. It will be incorrect if a package name differs from the +// leaf element of the import path, or if the package was a dot import. +func pkgPath(f *File, pkgName string) (path string) { + for _, x := range f.file.Imports { + s := strings.Trim(x.Path.Value, `"`) + if x.Name != nil { + // Catch `import pkgName "foo/bar"`. + if x.Name.Name == pkgName { + return s + } + } else { + // Catch `import "pkgName"` or `import "foo/bar/pkgName"`. + if s == pkgName || strings.HasSuffix(s, "/"+pkgName) { + return s + } + } + } + return "" +} diff --git a/src/cmd/vet/copylock.go b/src/cmd/vet/copylock.go new file mode 100644 index 0000000000..e8a6820fce --- /dev/null +++ b/src/cmd/vet/copylock.go @@ -0,0 +1,155 @@ +// 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" + + "golang.org/x/tools/go/types" +) + +func init() { + register("copylocks", + "check that locks are not passed by value", + checkCopyLocks, + funcDecl, rangeStmt) +} + +// 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) + } +} + +// 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, d *ast.FuncDecl) { + if d.Recv != nil && len(d.Recv.List) > 0 { + expr := d.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", d.Name.Name, path) + } + } + + if d.Type.Params != nil { + for _, field := range d.Type.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", d.Name.Name, path) + } + } + } + + if d.Type.Results != nil { + for _, field := range d.Type.Results.List { + expr := field.Type + if path := lockPath(f.pkg.typesPkg, f.pkg.types[expr].Type); path != nil { + f.Badf(expr.Pos(), "%s returns Lock by value: %v", d.Name.Name, path) + } + } + } +} + +// 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() +} + +// 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 + } + + // 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 reference to this type + // can be locked, but a value cannot. This differentiates + // embedded interfaces from embedded values. + if plock := types.NewMethodSet(types.NewPointer(typ)).Lookup(tpkg, "Lock"); plock != nil { + if lock := types.NewMethodSet(typ).Lookup(tpkg, "Lock"); lock == nil { + 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 +} diff --git a/src/cmd/vet/deadcode.go b/src/cmd/vet/deadcode.go new file mode 100644 index 0000000000..3b306c2104 --- /dev/null +++ b/src/cmd/vet/deadcode.go @@ -0,0 +1,296 @@ +// 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. +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/src/cmd/vet/doc.go b/src/cmd/vet/doc.go new file mode 100644 index 0000000000..a19de3fac3 --- /dev/null +++ b/src/cmd/vet/doc.go @@ -0,0 +1,190 @@ +// 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. + +It can be invoked three ways: + +By package, from the go tool: + go vet package/path/name +vets the package whose path is provided. + +By files: + go tool vet source/directory/*.go +vets the files named, all of which must be in the same package. + +By directory: + go tool vet source/directory +recursively descends the directory, vetting each package it finds. + +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 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. + +Available checks: + +Printf family + +Flag: -printf + +Suspicious calls to functions in the Printf family, including any functions +with these names, disregarding case: + Print Printf Println + Fprint Fprintf Fprintln + Sprint Sprintf Sprintln + Error Errorf + Fatal Fatalf + Log Logf + Panic Panicf Panicln +If the function name ends with an 'f', the function is assumed to take +a format descriptor string in the manner of fmt.Printf. If not, vet +complains about arguments that look like format descriptor strings. + +It also checks for errors such as using a Writer as the first argument of +Printf. + +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 + +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. + +Unkeyed composite literals + +Flag: -composites + +Composite struct literals that do not use the field-keyed syntax. + +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. + +Copying locks + +Flag: -copylocks + +Locks that are erroneously passed by value. + +Nil function comparison + +Flag: -nilfunc + +Comparisons between functions and nil. + +Range loop variables + +Flag: -rangeloops + +Incorrect uses of range loop variables in closures. + +Unreachable code + +Flag: -unreachable + +Unreachable code. + +Shadowed variables + +Flag: -shadow=false (experimental; must be set explicitly) + +Variables that may have been unintentionally shadowed. + +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. + +Shifts + +Flag: -shift + +Shifts equal to or longer than the variable's length. + +Other flags + +These flags configure the behavior of vet: + + -all (default true) + Check everything; disabled if any explicit check is requested. + -v + Verbose mode + -printfuncs + A comma-separated list of print-like functions to supplement + the standard list. Each entry is in the form Name:N where N + is the zero-based argument position of the first argument + involved in the print: either the format or the first print + argument for non-formatted prints. For example, + if you have Warn and Warnf functions that take an + io.Writer as their first argument, like Fprintf, + -printfuncs=Warn:1,Warnf:1 + -shadowstrict + Whether to be strict about shadowing; can be noisy. + -test + For testing only: sets -all and -shadow. +*/ +package main // import "golang.org/x/tools/cmd/vet" diff --git a/src/cmd/vet/main.go b/src/cmd/vet/main.go new file mode 100644 index 0000000000..e4b68770b0 --- /dev/null +++ b/src/cmd/vet/main.go @@ -0,0 +1,488 @@ +// 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" + "flag" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/printer" + "go/token" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + + _ "golang.org/x/tools/go/gcimporter" + "golang.org/x/tools/go/types" +) + +var ( + verbose = flag.Bool("v", false, "verbose") + testFlag = flag.Bool("test", false, "for testing only: sets -all and -shadow") + tags = flag.String("tags", "", "comma-separated list of build tags to apply when parsing") + tagList = []string{} // exploded version of tags flag; set in main +) + +var exitCode = 0 + +// "all" is here only for the appearance of backwards compatibility. +// It has no effect; the triState flags do the work. +var all = flag.Bool("all", true, "check everything; disabled if any explicit check is requested") + +// 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 + +// 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 "unset" + 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 { + if *testFlag { + return true + } + 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 + field *ast.Field + funcDecl *ast.FuncDecl + funcLit *ast.FuncLit + genDecl *ast.GenDecl + interfaceType *ast.InterfaceType + rangeStmt *ast.RangeStmt + + // 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)) +) + +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 + } +} + +// Usage is a replacement usage function for the flags package. +func Usage() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + 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, "For more information run\n") + fmt.Fprintf(os.Stderr, "\tgodoc golang.org/x/tools/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 + + // The objects that are receivers of a "String() string" method. + // This is used by the recursiveStringer method in print.go. + stringers map[*ast.Object]bool + + // Registered checkers to run. + checkers map[ast.Node][]func(*File, ast.Node) +} + +func main() { + flag.Usage = Usage + flag.Parse() + + // If any flag is set, we run only those checks requested. + // If no flags are set true, set all the non-experimental ones not explicitly set (in effect, set the "-all" flag). + if setTrueCount == 0 { + for name, setting := range report { + if *setting == unset && !experimental[name] { + *setting = setTrue + } + } + } + + tagList = strings.Split(*tags, ",") + + initPrintFlags() + initUnusedFlags() + + if flag.NArg() == 0 { + Usage() + } + dirs := false + files := false + 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() { + dirs = true + } else { + files = true + } + } + if dirs && files { + Usage() + } + if dirs { + for _, name := range flag.Args() { + walkDir(name) + } + os.Exit(exitCode) + } + if !doPackage(".", flag.Args()) { + 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) + } + } +} + +// 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) + doPackage(directory, names) + // 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(directory, names) + } +} + +type Package struct { + path string + defs map[*ast.Ident]types.Object + uses map[*ast.Ident]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 whether any files were checked. +func doPackage(directory string, names []string) bool { + 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 false + } + checkBuildTag(name, data) + var parsedFile *ast.File + if strings.HasSuffix(name, ".go") { + parsedFile, err = parser.ParseFile(fs, name, data, 0) + if err != nil { + warnf("%s: %s", name, err) + return false + } + astFiles = append(astFiles, parsedFile) + } + files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) + } + if len(astFiles) == 0 { + return false + } + pkg := new(Package) + pkg.path = astFiles[0].Name.Name + pkg.files = files + // Type check the package. + err := pkg.check(fs, astFiles) + if err != nil && *verbose { + warnf("%s", err) + } + + // Check. + 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 { + file.pkg = pkg + file.checkers = chk + if file.file != nil { + file.walkFile(file.name, file.file) + } + } + asmCheck(pkg) + return true +} + +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) +} + +// Warn reports an error but does not set the exit code. +func (f *File) Warn(pos token.Pos, args ...interface{}) { + fmt.Fprint(os.Stderr, f.loc(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, f.loc(pos)+format+"\n", 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 { + 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.Field: + key = field + 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 + } + 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() +} diff --git a/src/cmd/vet/method.go b/src/cmd/vet/method.go new file mode 100644 index 0000000000..00949df437 --- /dev/null +++ b/src/cmd/vet/method.go @@ -0,0 +1,182 @@ +// 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 + "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader) + "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 { + if strings.HasPrefix(expect, "=") { + expect = expect[1:] + } + // 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/src/cmd/vet/nilfunc.go b/src/cmd/vet/nilfunc.go new file mode 100644 index 0000000000..fa1bac7e64 --- /dev/null +++ b/src/cmd/vet/nilfunc.go @@ -0,0 +1,68 @@ +// 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" + + "golang.org/x/tools/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/src/cmd/vet/print.go b/src/cmd/vet/print.go new file mode 100644 index 0000000000..b20d935ef4 --- /dev/null +++ b/src/cmd/vet/print.go @@ -0,0 +1,587 @@ +// 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" + "flag" + "go/ast" + "go/token" + "strconv" + "strings" + "unicode/utf8" + + "golang.org/x/tools/go/exact" + "golang.org/x/tools/go/types" +) + +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) +} + +func initPrintFlags() { + if *printfuncs == "" { + return + } + for _, name := range strings.Split(*printfuncs, ",") { + if len(name) == 0 { + flag.Usage() + } + skip := 0 + if colon := strings.LastIndex(name, ":"); colon > 0 { + var err error + skip, err = strconv.Atoi(name[colon+1:]) + if err != nil { + errorf(`illegal format for "Func:N" argument %q; %s`, name, err) + } + name = name[:colon] + } + name = strings.ToLower(name) + if name[len(name)-1] == 'f' { + printfList[name] = skip + } else { + printList[name] = skip + } + } +} + +// printfList records the formatted-print functions. The value is the location +// of the format parameter. Names are lower-cased so the lookup is +// case insensitive. +var printfList = map[string]int{ + "errorf": 0, + "fatalf": 0, + "fprintf": 1, + "logf": 0, + "panicf": 0, + "printf": 0, + "sprintf": 0, +} + +// printList records the unformatted-print functions. The value is the location +// of the first parameter to be printed. Names are lower-cased so the lookup is +// case insensitive. +var printList = map[string]int{ + "error": 0, + "fatal": 0, + "fprint": 1, "fprintln": 1, + "log": 0, + "panic": 0, "panicln": 0, + "print": 0, "println": 0, + "sprint": 0, "sprintln": 0, +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func checkFmtPrintfCall(f *File, node ast.Node) { + if d, ok := node.(*ast.FuncDecl); ok && isStringer(f, d) { + // Remember we saw this. + if f.stringers == nil { + f.stringers = make(map[*ast.Object]bool) + } + if l := d.Recv.List; len(l) == 1 { + if n := l[0].Names; len(n) == 1 { + f.stringers[n[0].Obj] = true + } + } + return + } + + call, ok := node.(*ast.CallExpr) + if !ok { + return + } + var Name string + switch x := call.Fun.(type) { + case *ast.Ident: + Name = x.Name + case *ast.SelectorExpr: + Name = x.Sel.Name + default: + return + } + + name := strings.ToLower(Name) + if skip, ok := printfList[name]; ok { + f.checkPrintf(call, Name, skip) + return + } + if skip, ok := printList[name]; ok { + f.checkPrint(call, Name, skip) + return + } +} + +// 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] +} + +// 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 + indexed bool // whether an indexing expression appears: %[1]d. + 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. + 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. +// call.Args[formatIndex] is (well, should be) the format argument. +func (f *File) checkPrintf(call *ast.CallExpr, name string, formatIndex int) { + if formatIndex >= len(call.Args) { + f.Bad(call.Pos(), "too few arguments in call to", name) + return + } + lit := f.pkg.types[call.Args[formatIndex]].Value + if lit == nil { + if *verbose { + f.Warn(call.Pos(), "can't check non-constant format in call to", name) + } + return + } + if lit.Kind() != exact.String { + f.Badf(call.Pos(), "constant %v not a string in call to %s", lit, name) + return + } + format := exact.StringVal(lit) + firstArg := formatIndex + 1 // Arguments are immediately after format string. + if !strings.Contains(format, "%") { + if len(call.Args) > firstArg { + f.Badf(call.Pos(), "no formatting directive in %s call", name) + } + return + } + // Hard part: check formats against args. + argNum := firstArg + indexed := false + for i, w := 0, 0; i < len(format); i += w { + w = 1 + if format[i] == '%' { + state := f.parsePrintfVerb(call, name, format[i:], firstArg, argNum) + if state == nil { + return + } + w = len(state.format) + if state.indexed { + indexed = true + } + if !f.okPrintfArg(call, state) { // One error per format is enough. + return + } + if len(state.argNums) > 0 { + // Continue with the next sequential argument. + argNum = state.argNums[len(state.argNums)-1] + 1 + } + } + } + // Dotdotdot is hard. + if call.Ellipsis.IsValid() && argNum >= len(call.Args)-1 { + return + } + // If the arguments were direct indexed, we assume the programmer knows what's up. + // Otherwise, there should be no leftover arguments. + if !indexed && argNum != len(call.Args) { + expect := argNum - firstArg + numArgs := len(call.Args) - firstArg + f.Badf(call.Pos(), "wrong number of args for format in %s call: %d needed but %d args", name, expect, numArgs) + } +} + +// 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.indexed = true + s.nbytes++ // skip '[' + start := s.nbytes + s.scanNum() + if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' { + s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index") + return false + } + arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32) + if err != nil { + s.file.Badf(s.call.Pos(), "illegal syntax for printf argument index: %s", err) + return false + } + s.nbytes++ // skip ']' + arg := int(arg32) + arg += s.firstArg - 1 // We want to zero-index the actual arguments. + s.argNum = arg + 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. + indexed: false, + firstArg: firstArg, + file: f, + call: call, + } + // There may be flags. + state.parseFlags() + indexPending := false + // 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 !indexPending && !state.parseIndex() { + return nil + } + if state.nbytes == len(state.format) { + f.Badf(call.Pos(), "missing verb at end of format string in %s call", name) + 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. +// TODO: A type that implements Formatter may do what it wants, and vet +// will complain incorrectly. +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}, + {'e', numFlag, argFloat | argComplex}, + {'E', numFlag, argFloat | argComplex}, + {'f', numFlag, argFloat | argComplex}, + {'F', numFlag, argFloat | argComplex}, + {'g', numFlag, argFloat | argComplex}, + {'G', numFlag, 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}, + {'X', sharpNumFlag, argRune | argInt | argString}, +} + +// 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 + } + } + if !found { + f.Badf(call.Pos(), "unrecognized printf verb %q", state.verb) + return false + } + for _, flag := range state.flags { + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", state.verb, 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, true, state) { + return + } + arg := call.Args[argNum] + if !f.matchArgType(argInt, nil, arg) { + f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(arg)) + return false + } + } + if state.verb == '%' { + return true + } + argNum := state.argNums[len(state.argNums)-1] + if !f.argCanBeChecked(call, len(state.argNums)-1, false, state) { + return false + } + arg := call.Args[argNum] + if !f.matchArgType(v.typ, nil, arg) { + typeString := "" + if typ := f.pkg.types[arg].Type; typ != nil { + typeString = typ.String() + } + f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), state.verb, typeString) + return false + } + if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) && f.recursiveStringer(arg) { + f.Badf(call.Pos(), "arg %s for printf causes recursive call to String method", 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.stringers) == 0 { + return 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 + } + } + + // It's unlikely to be a recursive stringer if it has a Format method. + if typ := f.pkg.types[e].Type; typ != nil { + // Not a perfect match; see issue 6259. + if f.hasMethod(typ, "Format") { + 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. + return f.stringers[obj] +} + +// 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, isStar bool, state *formatState) bool { + argNum := state.argNums[formatArg] + if argNum < 0 { + // Shouldn't happen, so catch it with prejudice. + panic("negative arg num") + } + if argNum == 0 { + f.Badf(call.Pos(), `index value [0] for %s("%s"); indexes start at 1`, state.name, state.format) + return false + } + 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(), `missing argument for %s("%s"): format reads arg %d, have only %d args`, state.name, state.format, arg, len(call.Args)-state.firstArg) + return false +} + +// checkPrint checks a call to an unformatted print routine such as Println. +// call.Args[firstArg] is the first argument to be printed. +func (f *File) checkPrint(call *ast.CallExpr, name string, firstArg int) { + isLn := strings.HasSuffix(name, "ln") + isF := strings.HasPrefix(name, "F") + args := call.Args + if name == "Log" && len(args) > 0 { + // Special case: Don't complain about math.Log or cmplx.Log. + // Not strictly necessary because the only complaint likely is for Log("%d") + // but it feels wrong to check that math.Log is a good print function. + if sel, ok := args[0].(*ast.SelectorExpr); ok { + if x, ok := sel.X.(*ast.Ident); ok { + if x.Name == "math" || x.Name == "cmplx" { + return + } + } + } + } + // check for Println(os.Stderr, ...) + if firstArg == 0 && !isF && len(args) > 0 { + if sel, ok := 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(), "first argument to %s is %s.%s", name, x.Name, sel.Sel.Name) + } + } + } + } + if len(args) <= firstArg { + // If we have a call to a method called Error that satisfies the Error interface, + // then it's ok. Otherwise it's something like (*T).Error from the testing package + // and we need to check it. + if name == "Error" && f.isErrorMethodCall(call) { + return + } + // If it's an Error call now, it's probably for printing errors. + if !isLn { + // Check the signature to be sure: there are niladic functions called "error". + if firstArg != 0 || f.numArgsInSignature(call) != firstArg { + f.Badf(call.Pos(), "no args in %s call", name) + } + } + return + } + arg := args[firstArg] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.Contains(lit.Value, "%") { + f.Badf(call.Pos(), "possible formatting directive in %s call", name) + } + } + if isLn { + // The last item, if a string, should not have a newline. + arg = args[len(call.Args)-1] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.HasSuffix(lit.Value, `\n"`) { + f.Badf(call.Pos(), "%s call ends with newline", name) + } + } + } + for _, arg := range args { + if f.recursiveStringer(arg) { + f.Badf(call.Pos(), "arg %s for print causes recursive call to String method", f.gofmt(arg)) + } + } +} diff --git a/src/cmd/vet/rangeloop.go b/src/cmd/vet/rangeloop.go new file mode 100644 index 0000000000..96e2ca8062 --- /dev/null +++ b/src/cmd/vet/rangeloop.go @@ -0,0 +1,70 @@ +// 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: http://golang.org/doc/go_faq.html#closures_and_goroutines +*/ + +package main + +import "go/ast" + +func init() { + register("rangeloops", + "check that range loop variables are used correctly", + checkRangeLoop, + rangeStmt) +} + +// checkRangeLoop walks the body of the provided range statement, checking if +// its index or value variables are used unsafely inside goroutines or deferred +// function literals. +func checkRangeLoop(f *File, node ast.Node) { + n := node.(*ast.RangeStmt) + key, _ := n.Key.(*ast.Ident) + val, _ := n.Value.(*ast.Ident) + if key == nil && val == nil { + return + } + sl := n.Body.List + if len(sl) == 0 { + return + } + var last *ast.CallExpr + switch s := sl[len(sl)-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 key != nil && id.Obj == key.Obj || val != nil && id.Obj == val.Obj { + f.Bad(id.Pos(), "range variable", id.Name, "captured by func literal") + } + return true + }) +} diff --git a/src/cmd/vet/shadow.go b/src/cmd/vet/shadow.go new file mode 100644 index 0000000000..34e5db9091 --- /dev/null +++ b/src/cmd/vet/shadow.go @@ -0,0 +1,245 @@ +// 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" + + "golang.org/x/tools/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 instance represented +// by the identifier. +func (pkg *Package) growSpan(ident *ast.Ident, obj types.Object) { + if *strictShadowing { + return // No need + } + pos := ident.Pos() + end := ident.End() + 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 + } + } + } + } + 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()) + 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(ident.Pos(), "internal error: no range for %s", ident.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 %s shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) + } +} diff --git a/src/cmd/vet/shift.go b/src/cmd/vet/shift.go new file mode 100644 index 0000000000..2385c23fdb --- /dev/null +++ b/src/cmd/vet/shift.go @@ -0,0 +1,83 @@ +// 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/token" + + "golang.org/x/tools/go/exact" + "golang.org/x/tools/go/types" +) + +func init() { + register("shift", + "check for useless shifts", + checkShift, + binaryExpr, assignStmt) +} + +func checkShift(f *File, node ast.Node) { + 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) { + v := f.pkg.types[y].Value + if v == nil { + return + } + amt, ok := exact.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 + var msg string + 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, types.Uintptr: + // These types may be as small as 32 bits, but no smaller. + size = 32 + msg = "might be " + default: + return + } + if amt >= size { + ident := f.gofmt(x) + f.Badf(node.Pos(), "%s %stoo small for shift of %d", ident, msg, amt) + } +} diff --git a/src/cmd/vet/structtag.go b/src/cmd/vet/structtag.go new file mode 100644 index 0000000000..e8164a46f9 --- /dev/null +++ b/src/cmd/vet/structtag.go @@ -0,0 +1,122 @@ +// 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" + "reflect" + "strconv" +) + +func init() { + register("structtags", + "check that struct field tags have canonical format and apply to exported fields as needed", + checkCanonicalFieldTag, + field) +} + +// checkCanonicalFieldTag checks a struct field tag. +func checkCanonicalFieldTag(f *File, node ast.Node) { + field := node.(*ast.Field) + if field.Tag == nil { + return + } + + tag, err := strconv.Unquote(field.Tag.Value) + if err != nil { + f.Badf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) + return + } + + if err := validateStructTag(tag); err != nil { + f.Badf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get: %s", field.Tag.Value, 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 len(field.Names) == 0 { + return + } + + if field.Names[0].IsExported() { + return + } + + st := reflect.StructTag(tag) + for _, enc := range [...]string{"json", "xml"} { + if st.Get(enc) != "" { + f.Badf(field.Pos(), "struct field %s has %s tag but is not exported", field.Names[0].Name, enc) + return + } + } +} + +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") +) + +// 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. + + for tag != "" { + // 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 + } + 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 := string(tag[:i+1]) + tag = tag[i+1:] + + if _, err := strconv.Unquote(qvalue); err != nil { + return errTagValueSyntax + } + } + return nil +} diff --git a/src/cmd/vet/testdata/asm.go b/src/cmd/vet/testdata/asm.go new file mode 100644 index 0000000000..9a3d5315ad --- /dev/null +++ b/src/cmd/vet/testdata/asm.go @@ -0,0 +1,33 @@ +// 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 + +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 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) diff --git a/src/cmd/vet/testdata/asm1.s b/src/cmd/vet/testdata/asm1.s new file mode 100644 index 0000000000..62f423cd8b --- /dev/null +++ b/src/cmd/vet/testdata/asm1.s @@ -0,0 +1,254 @@ +// 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 ·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\)" diff --git a/src/cmd/vet/testdata/asm2.s b/src/cmd/vet/testdata/asm2.s new file mode 100644 index 0000000000..c33c02a70b --- /dev/null +++ b/src/cmd/vet/testdata/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/src/cmd/vet/testdata/asm3.s b/src/cmd/vet/testdata/asm3.s new file mode 100644 index 0000000000..3d69356a0f --- /dev/null +++ b/src/cmd/vet/testdata/asm3.s @@ -0,0 +1,178 @@ +// 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 diff --git a/src/cmd/vet/testdata/asm4.s b/src/cmd/vet/testdata/asm4.s new file mode 100644 index 0000000000..044b050b6b --- /dev/null +++ b/src/cmd/vet/testdata/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/src/cmd/vet/testdata/assign.go b/src/cmd/vet/testdata/assign.go new file mode 100644 index 0000000000..32ba8683c1 --- /dev/null +++ b/src/cmd/vet/testdata/assign.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 useless-assignment checker. + +package testdata + +type ST struct { + x int +} + +func (s *ST) SetX(x 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" +} diff --git a/src/cmd/vet/testdata/atomic.go b/src/cmd/vet/testdata/atomic.go new file mode 100644 index 0000000000..1ba261d941 --- /dev/null +++ b/src/cmd/vet/testdata/atomic.go @@ -0,0 +1,43 @@ +// 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. +} diff --git a/src/cmd/vet/testdata/bool.go b/src/cmd/vet/testdata/bool.go new file mode 100644 index 0000000000..af6cc011dd --- /dev/null +++ b/src/cmd/vet/testdata/bool.go @@ -0,0 +1,113 @@ +// 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" + +func RatherStupidConditions() { + var f, g func() int + if f() == 0 || f() == 0 { // OK f 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) // TODO conversions are treated as if they may have side effects + + 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/src/cmd/vet/testdata/buildtag.go b/src/cmd/vet/testdata/buildtag.go new file mode 100644 index 0000000000..eb36fd3259 --- /dev/null +++ b/src/cmd/vet/testdata/buildtag.go @@ -0,0 +1,14 @@ +// 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 diff --git a/src/cmd/vet/testdata/buildtag_bad.go b/src/cmd/vet/testdata/buildtag_bad.go new file mode 100644 index 0000000000..fbe10cf748 --- /dev/null +++ b/src/cmd/vet/testdata/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/src/cmd/vet/testdata/composite.go b/src/cmd/vet/testdata/composite.go new file mode 100644 index 0000000000..69e7d7ccb0 --- /dev/null +++ b/src/cmd/vet/testdata/composite.go @@ -0,0 +1,63 @@ +// 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 untagged struct literal checker. + +// This file contains the test for untagged struct literals. + +package testdata + +import ( + "flag" + "go/scanner" +) + +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", +} + +type MyStruct struct { + X string + Y string + Z string +} + +var Okay4 = MyStruct{ + "Name", + "Usage", + "DefValue", +} + +// Testing is awkward because we need to reference things from a separate package +// to trigger the warnings. + +var BadStructLiteralUsedInTests = flag.Flag{ // ERROR "unkeyed fields" + "Name", + "Usage", + nil, // Value + "DefValue", +} + +// Used to test the check for slices and arrays: If that test is disabled and +// vet is run with --compositewhitelist=false, this line triggers an error. +// Clumsy but sufficient. +var scannerErrorListTest = scanner.ErrorList{nil, nil} diff --git a/src/cmd/vet/testdata/copylock_func.go b/src/cmd/vet/testdata/copylock_func.go new file mode 100644 index 0000000000..108c044209 --- /dev/null +++ b/src/cmd/vet/testdata/copylock_func.go @@ -0,0 +1,90 @@ +// 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 OkRet() *sync.Mutex {} +func BadRet() sync.Mutex {} // ERROR "BadRet returns Lock by value: 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 {} // ERROR "BadRet returns Lock by value: testdata.EmbeddedRWMutex" + +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" + +// 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/src/cmd/vet/testdata/copylock_range.go b/src/cmd/vet/testdata/copylock_range.go new file mode 100644 index 0000000000..f95b0252b6 --- /dev/null +++ b/src/cmd/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/src/cmd/vet/testdata/deadcode.go b/src/cmd/vet/testdata/deadcode.go new file mode 100644 index 0000000000..5370bc32f6 --- /dev/null +++ b/src/cmd/vet/testdata/deadcode.go @@ -0,0 +1,2125 @@ +// 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 +} diff --git a/src/cmd/vet/testdata/method.go b/src/cmd/vet/testdata/method.go new file mode 100644 index 0000000000..52b500df27 --- /dev/null +++ b/src/cmd/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/src/cmd/vet/testdata/nilfunc.go b/src/cmd/vet/testdata/nilfunc.go new file mode 100644 index 0000000000..2ce7bc8ca8 --- /dev/null +++ b/src/cmd/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/src/cmd/vet/testdata/print.go b/src/cmd/vet/testdata/print.go new file mode 100644 index 0000000000..3390a31f2c --- /dev/null +++ b/src/cmd/vet/testdata/print.go @@ -0,0 +1,342 @@ +// 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" + "math" + "os" + "unsafe" // just for test case printing unsafe.Pointer +) + +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", p, nil) + 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("%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(Formatter)) + fmt.Printf("%*%", 2) // Ridiculous but allowed. + fmt.Printf("%s", interface{}(nil)) // Nothing useful we can say. + + fmt.Printf("%g", 1+2i) + // Some bad format/argTypes + fmt.Printf("%b", "hi") // ERROR "arg .hi. for printf verb %b of wrong type" + fmt.Printf("%t", c) // ERROR "arg c for printf verb %t of wrong type" + fmt.Printf("%t", 1+2i) // ERROR "arg 1 \+ 2i for printf verb %t of wrong type" + fmt.Printf("%c", 2.3) // ERROR "arg 2.3 for printf verb %c of wrong type" + fmt.Printf("%d", 2.3) // ERROR "arg 2.3 for printf verb %d of wrong type" + fmt.Printf("%e", "hi") // ERROR "arg .hi. for printf verb %e of wrong type" + fmt.Printf("%E", true) // ERROR "arg true for printf verb %E of wrong type" + fmt.Printf("%f", "hi") // ERROR "arg .hi. for printf verb %f of wrong type" + fmt.Printf("%F", 'x') // ERROR "arg 'x' for printf verb %F of wrong type" + fmt.Printf("%g", "hi") // ERROR "arg .hi. for printf verb %g of wrong type" + fmt.Printf("%g", imap) // ERROR "arg imap for printf verb %g of wrong type" + fmt.Printf("%G", i) // ERROR "arg i for printf verb %G of wrong type" + fmt.Printf("%o", x) // ERROR "arg x for printf verb %o of wrong type" + fmt.Printf("%p", 23) // ERROR "arg 23 for printf verb %p of wrong type" + fmt.Printf("%q", x) // ERROR "arg x for printf verb %q of wrong type" + fmt.Printf("%s", b) // ERROR "arg b for printf verb %s of wrong type" + fmt.Printf("%s", byte(65)) // ERROR "arg byte\(65\) for printf verb %s of wrong type" + fmt.Printf("%t", 23) // ERROR "arg 23 for printf verb %t of wrong type" + fmt.Printf("%U", x) // ERROR "arg x for printf verb %U of wrong type" + fmt.Printf("%x", nil) // ERROR "arg nil for printf verb %x of wrong type" + fmt.Printf("%X", 2.3) // ERROR "arg 2.3 for printf verb %X of wrong type" + fmt.Printf("%s", stringerv) // ERROR "arg stringerv for printf verb %s of wrong type" + fmt.Printf("%t", stringerv) // ERROR "arg stringerv for printf verb %t of wrong type" + fmt.Printf("%q", notstringerv) // ERROR "arg notstringerv for printf verb %q of wrong type" + fmt.Printf("%t", notstringerv) // ERROR "arg notstringerv for printf verb %t of wrong type" + fmt.Printf("%t", stringerarrayv) // ERROR "arg stringerarrayv for printf verb %t of wrong type" + fmt.Printf("%t", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %t of wrong type" + fmt.Printf("%q", notstringerarrayv) // ERROR "arg notstringerarrayv for printf verb %q of wrong type" + fmt.Printf("%d", Formatter(true)) // correct (the type is responsible for formatting) + fmt.Printf("%s", nonemptyinterface) // correct (the dynamic type of nonemptyinterface may be a stringer) + fmt.Printf("%.*s %d %g", 3, "hi", 23, 'x') // ERROR "arg 'x' for printf verb %g of wrong type" + fmt.Println() // not an error + fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call" + fmt.Printf("%s", "hi", 3) // ERROR "wrong number of args for format in Printf call" + _ = fmt.Sprintf("%"+("s"), "hi", 3) // ERROR "wrong number of args for format in Sprintf call" + 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", 3, 3, 3, 3) // ERROR "wrong number of args for format in Printf call.*4 args" + fmt.Printf("%.*d", "hi", 3) // ERROR "arg .hi. for \* in printf format not of type int" + fmt.Printf("%.*d", i, 3) // correct + fmt.Printf("%.*d", s, 3) // ERROR "arg s for \* in printf format not of type int" + fmt.Printf("%*%", 0.22) // ERROR "arg 0.22 for \* in printf format not of type int" + fmt.Printf("%q %q", multi()...) // ok + fmt.Printf("%#q", `blah`) // ok + printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("hi") // ok + const format = "%s %s\n" + Printf(format, "hi", "there") + Printf(format, "hi") // ERROR "missing argument for Printf..%s..: format reads arg 2, have only 1" + Printf("%s %d %.3v %q", "str", 4) // ERROR "missing argument for Printf..%.3v..: format reads arg 3, have only 2" + f := new(stringer) + f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call" + f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args for format in Warnf call" + f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb" + f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag" + Printf("d%", 2) // ERROR "missing verb at end of format string in Printf call" + Printf("%d", percentDV) + Printf("%d", &percentDV) + Printf("%d", notPercentDV) // ERROR "arg notPercentDV for printf verb %d of wrong type" + Printf("%d", ¬PercentDV) // ERROR "arg ¬PercentDV for printf verb %d of wrong type" + Printf("%p", ¬PercentDV) // Works regardless: we print it as a pointer. + 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 "illegal syntax for printf argument index" + Printf("%[x]d", 3) // ERROR "illegal syntax for printf argument index" + Printf("%[3]*s", "hi", 2) // ERROR "missing argument for Printf.* reads arg 3, have only 2" + _ = fmt.Sprintf("%[3]d", 2) // ERROR "missing argument for Sprintf.* reads arg 3, have only 1" + Printf("%[2]*.[1]*[3]d", 2, "hi", 4) // ERROR "arg .hi. for \* in printf format not of type int" + Printf("%[0]s", "arg1") // ERROR "index value \[0\] for Printf.*; indexes start at 1" + Printf("%[0]d", 1) // ERROR "index value \[0\] for Printf.*; indexes start at 1" + // 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 errorTest1 + fmt.Println(et1.Error()) // ERROR "no args in Error call" + fmt.Println(et1.Error("hi")) // ok + fmt.Println(et1.Error("%d", 3)) // ERROR "possible formatting directive in Error call" + var et2 errorTest2 + et2.Error() // ERROR "no args in Error call" + et2.Error("hi") // ok, not an error method. + et2.Error("%d", 3) // ERROR "possible formatting directive in Error call" + 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. + // 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 + Log(3) // OK + Log("%d", 3) // ERROR "possible formatting directive in Log call" + Logf("%d", 3) + Logf("%d", "hi") // ERROR "arg .hi. for printf verb %d of wrong type: untyped string" + +} + +// Printf is used by the test so we must declare it. +func Printf(format string, args ...interface{}) { + panic("don't call - testing only") +} + +// printf is used by the test so we must declare it. +func printf(format string, args ...interface{}) { + panic("don't call - testing only") +} + +// multi is used by the test. +func multi() []interface{} { + panic("don't call - testing only") +} + +type stringer float64 + +var stringerv stringer + +func (*stringer) String() string { + return "string" +} + +func (*stringer) Warn(int, ...interface{}) string { + return "warn" +} + +func (*stringer) Warnf(int, string, ...interface{}) string { + return "warnf" +} + +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 "arg s for printf causes recursive call to String method" + _ = fmt.Sprintf("%v", &s) // ERROR "arg &s for printf causes recursive call to String method" + _ = fmt.Sprintf("%T", s) // ok; does not recursively call String + return fmt.Sprintln(s) // ERROR "arg s for print causes recursive call to String method" +} + +type recursivePtrStringer int + +func (p *recursivePtrStringer) String() string { + _ = fmt.Sprintf("%v", *p) + return fmt.Sprintln(p) // ERROR "arg p for print causes recursive call to String method" +} + +type Formatter bool + +func (*Formatter) 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 *Recursive2Struct +} + +type RecursiveStruct2 struct { + next *Recursive1Struct +} + +var recursiveStruct1V = &RecursiveStruct1{} + +// Fix for issue 7149: Missing return type on String method caused fault. +func (int) String() { + return "" +} diff --git a/src/cmd/vet/testdata/rangeloop.go b/src/cmd/vet/testdata/rangeloop.go new file mode 100644 index 0000000000..37b5940ddd --- /dev/null +++ b/src/cmd/vet/testdata/rangeloop.go @@ -0,0 +1,59 @@ +// 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 "range variable i captured by func literal" + println(v) // ERROR "range variable v captured by func literal" + }() + } + for i, v := range s { + defer func() { + println(i) // ERROR "range variable i captured by func literal" + println(v) // ERROR "range variable v captured by func literal" + }() + } + for i := range s { + go func() { + println(i) // ERROR "range variable i captured by func literal" + }() + } + for _, v := range s { + go func() { + println(v) // ERROR "range 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 "range variable f captured by func literal" + }() + } +} diff --git a/src/cmd/vet/testdata/shadow.go b/src/cmd/vet/testdata/shadow.go new file mode 100644 index 0000000000..34a680681b --- /dev/null +++ b/src/cmd/vet/testdata/shadow.go @@ -0,0 +1,54 @@ +// 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 testdata/shadow.go:13" + if err != nil { + return err + } + i := 3 // OK + _ = i + } + if f != nil { + var _, err = f.Read(buf) // ERROR "declaration of err shadows declaration at testdata/shadow.go:13" + if 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 testdata/shadow.go:14" + _, _, _ = x, f, shadowTemp + } + // Use a couple of variables to trigger shadowing errors. + _, _ = err, x + return +} diff --git a/src/cmd/vet/testdata/shift.go b/src/cmd/vet/testdata/shift.go new file mode 100644 index 0000000000..6624f09cc1 --- /dev/null +++ b/src/cmd/vet/testdata/shift.go @@ -0,0 +1,78 @@ +// 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 + +func ShiftTest() { + var i8 int8 + _ = i8 << 7 + _ = (i8 + 1) << 8 // ERROR "\(i8 \+ 1\) too small for shift of 8" + _ = i8 << (7 + 1) // ERROR "i8 too small for shift of 8" + _ = i8 >> 8 // ERROR "i8 too small for shift of 8" + i8 <<= 8 // ERROR "i8 too small for shift of 8" + i8 >>= 8 // ERROR "i8 too small for shift of 8" + var i16 int16 + _ = i16 << 15 + _ = i16 << 16 // ERROR "i16 too small for shift of 16" + _ = i16 >> 16 // ERROR "i16 too small for shift of 16" + i16 <<= 16 // ERROR "i16 too small for shift of 16" + i16 >>= 16 // ERROR "i16 too small for shift of 16" + var i32 int32 + _ = i32 << 31 + _ = i32 << 32 // ERROR "i32 too small for shift of 32" + _ = i32 >> 32 // ERROR "i32 too small for shift of 32" + i32 <<= 32 // ERROR "i32 too small for shift of 32" + i32 >>= 32 // ERROR "i32 too small for shift of 32" + var i64 int64 + _ = i64 << 63 + _ = i64 << 64 // ERROR "i64 too small for shift of 64" + _ = i64 >> 64 // ERROR "i64 too small for shift of 64" + i64 <<= 64 // ERROR "i64 too small for shift of 64" + i64 >>= 64 // ERROR "i64 too small for shift of 64" + var u8 uint8 + _ = u8 << 7 + _ = u8 << 8 // ERROR "u8 too small for shift of 8" + _ = u8 >> 8 // ERROR "u8 too small for shift of 8" + u8 <<= 8 // ERROR "u8 too small for shift of 8" + u8 >>= 8 // ERROR "u8 too small for shift of 8" + var u16 uint16 + _ = u16 << 15 + _ = u16 << 16 // ERROR "u16 too small for shift of 16" + _ = u16 >> 16 // ERROR "u16 too small for shift of 16" + u16 <<= 16 // ERROR "u16 too small for shift of 16" + u16 >>= 16 // ERROR "u16 too small for shift of 16" + var u32 uint32 + _ = u32 << 31 + _ = u32 << 32 // ERROR "u32 too small for shift of 32" + _ = u32 >> 32 // ERROR "u32 too small for shift of 32" + u32 <<= 32 // ERROR "u32 too small for shift of 32" + u32 >>= 32 // ERROR "u32 too small for shift of 32" + var u64 uint64 + _ = u64 << 63 + _ = u64 << 64 // ERROR "u64 too small for shift of 64" + _ = u64 >> 64 // ERROR "u64 too small for shift of 64" + u64 <<= 64 // ERROR "u64 too small for shift of 64" + u64 >>= 64 // ERROR "u64 too small for shift of 64" + _ = u64 << u64 // Non-constant shifts should succeed. + var i int + _ = i << 31 + _ = i << 32 // ERROR "i might be too small for shift of 32" + _ = i >> 32 // ERROR "i might be too small for shift of 32" + i <<= 32 // ERROR "i might be too small for shift of 32" + i >>= 32 // ERROR "i might be too small for shift of 32" + var u uint + _ = u << 31 + _ = u << 32 // ERROR "u might be too small for shift of 32" + _ = u >> 32 // ERROR "u might be too small for shift of 32" + u <<= 32 // ERROR "u might be too small for shift of 32" + u >>= 32 // ERROR "u might be too small for shift of 32" + var p uintptr + _ = p << 31 + _ = p << 32 // ERROR "p might be too small for shift of 32" + _ = p >> 32 // ERROR "p might be too small for shift of 32" + p <<= 32 // ERROR "p might be too small for shift of 32" + p >>= 32 // ERROR "p might be too small for shift of 32" +} diff --git a/src/cmd/vet/testdata/structtag.go b/src/cmd/vet/testdata/structtag.go new file mode 100644 index 0000000000..6878f5642d --- /dev/null +++ b/src/cmd/vet/testdata/structtag.go @@ -0,0 +1,36 @@ +// 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 + +type StructTagTest struct { + A int "hello" // ERROR "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" + 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 +} diff --git a/src/cmd/vet/testdata/tagtest/file1.go b/src/cmd/vet/testdata/tagtest/file1.go new file mode 100644 index 0000000000..22a1509acc --- /dev/null +++ b/src/cmd/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/src/cmd/vet/testdata/tagtest/file2.go b/src/cmd/vet/testdata/tagtest/file2.go new file mode 100644 index 0000000000..ba7dd91bbd --- /dev/null +++ b/src/cmd/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/src/cmd/vet/testdata/unsafeptr.go b/src/cmd/vet/testdata/unsafeptr.go new file mode 100644 index 0000000000..8f64030b85 --- /dev/null +++ b/src/cmd/vet/testdata/unsafeptr.go @@ -0,0 +1,61 @@ +// 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" + + // 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/src/cmd/vet/testdata/unused.go b/src/cmd/vet/testdata/unused.go new file mode 100644 index 0000000000..d50f6594d9 --- /dev/null +++ b/src/cmd/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/src/cmd/vet/types.go b/src/cmd/vet/types.go new file mode 100644 index 0000000000..89e9989d9a --- /dev/null +++ b/src/cmd/vet/types.go @@ -0,0 +1,366 @@ +// 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/token" + + "golang.org/x/tools/go/types" +) + +// imports is the canonical map of imported packages we need for typechecking. +// It is created during initialization. +var imports = make(map[string]*types.Package) + +var ( + stringerMethodType = types.New("func() string") + errorType = types.New("error").Underlying().(*types.Interface) + stringerType = types.New("interface{ String() string }").(*types.Interface) + formatterType *types.Interface +) + +func init() { + typ := importType("fmt", "Formatter") + if typ != nil { + formatterType = typ.Underlying().(*types.Interface) + } +} + +// importType returns the type denoted by the qualified identifier +// path.name, and adds the respective package to the imports map +// as a side effect. +func importType(path, name string) types.Type { + pkg, err := types.DefaultImport(imports, path) + if err != nil { + // This can happen if fmt hasn't been compiled yet. + // Since nothing uses formatterType anyway, don't complain. + //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 { + pkg.defs = make(map[*ast.Ident]types.Object) + pkg.uses = make(map[*ast.Ident]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) + config := types.Config{ + // We provide the same packages map for all imports to ensure + // that everybody sees identical packages for the given paths. + Packages: imports, + // By providing a Config with our own error function, it will continue + // past the first error. There is no need for that function to do anything. + Error: func(error) {}, + } + info := &types.Info{ + Selections: pkg.selectors, + Types: pkg.types, + Defs: pkg.defs, + Uses: pkg.uses, + } + typesPkg, err := config.Check(pkg.path, fs, astFiles, info) + pkg.typesPkg = typesPkg + // update spans + for id, obj := range pkg.defs { + pkg.growSpan(id, obj) + } + for id, obj := range pkg.uses { + pkg.growSpan(id, obj) + } + return err +} + +// isStruct reports whether the composite literal c is a struct. +// If it is not (probably a struct), it returns a printable form of the type. +func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) { + // Check that the CompositeLit's type is a slice or array (which needs no field keys), if possible. + typ := pkg.types[c].Type + // If it's a named type, pull out the underlying type. If it's not, the Underlying + // method returns the type itself. + actual := typ + if actual != nil { + actual = actual.Underlying() + } + if actual == nil { + // No type information available. Assume true, so we do the check. + return true, "" + } + switch actual.(type) { + case *types.Struct: + return true, typ.String() + default: + return false, "" + } +} + +// 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. + // But (see issue 6259) that's not easy to verify, so instead we see + // if its method set contains a Format function. We could do better, + // even now, but we don't need to be 100% accurate. Wait for 6259 to + // be fixed instead. TODO. + if f.hasMethod(typ, "Format") { + return true + } + // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? + if t&argString != 0 { + if types.AssertableTo(errorType, typ) || types.AssertableTo(stringerType, 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().Underlying(), 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) + } + // The rest can print with %p as pointers, or as integers with %x etc. + return t&(argInt|argPointer) != 0 + + case *types.Struct: + return f.matchStructArgType(t, typ, arg, inProgress) + + case *types.Interface: + // If the static type of the argument is empty interface, there's little we can do. + // Example: + // func f(x interface{}) { fmt.Printf("%s", x) } + // Whether x is valid for %s depends on the type of the argument to f. One day + // we will be able to do better. For now, we assume that empty interface is OK + // but non-empty interfaces, with Stringer and Error handled above, are errors. + return typ.NumMethods() == 0 + + 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 t&argPointer != 0 // TODO? + + 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 +} + +// 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++ { + if !f.matchArgTypeInternal(t, typ.Field(i).Type(), arg, inProgress) { + return false + } + } + return true +} + +// numArgsInSignature tells how many formal arguments the function type +// being called has. +func (f *File) numArgsInSignature(call *ast.CallExpr) int { + // Check the type of the function or method declaration + typ := f.pkg.types[call.Fun].Type + if typ == nil { + return 0 + } + // The type must be a signature, but be sure for safety. + sig, ok := typ.(*types.Signature) + if !ok { + return 0 + } + return sig.Params().Len() +} + +// isErrorMethodCall reports whether the call is of a method with signature +// func Error() string +// where "string" is the universe's string type. We know the method is called "Error". +func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { + typ := f.pkg.types[call].Type + if typ != nil { + // We know it's called "Error", so just check the function signature. + return types.Identical(f.pkg.types[call.Fun].Type, stringerMethodType) + } + // Without types, we can still check by hand. + // Is it a selector expression? Otherwise it's a function call, not a method call. + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + // The package is type-checked, so if there are no arguments, we're done. + if len(call.Args) > 0 { + return false + } + // Check the type of the method declaration + typ = f.pkg.types[sel].Type + if typ == nil { + return false + } + // The type must be a signature, but be sure for safety. + sig, ok := typ.(*types.Signature) + if !ok { + return false + } + // There must be a receiver for it to be a method call. Otherwise it is + // a function, not something that satisfies the error interface. + if sig.Recv() == nil { + return false + } + // There must be no arguments. Already verified by type checking, but be thorough. + if sig.Params().Len() > 0 { + return false + } + // Finally the real questions. + // There must be one result. + if sig.Results().Len() != 1 { + return false + } + // It must have return type "string" from the universe. + return sig.Results().At(0).Type() == types.Typ[types.String] +} + +// hasMethod reports whether the type contains a method with the given name. +// It is part of the workaround for Formatters and should be deleted when +// that workaround is no longer necessary. +// TODO: This could be better once issue 6259 is fixed. +func (f *File) hasMethod(typ types.Type, name string) bool { + // assume we have an addressable variable of type typ + obj, _, _ := types.LookupFieldOrMethod(typ, true, f.pkg.typesPkg, name) + _, ok := obj.(*types.Func) + return ok +} diff --git a/src/cmd/vet/unsafeptr.go b/src/cmd/vet/unsafeptr.go new file mode 100644 index 0000000000..ca15f72578 --- /dev/null +++ b/src/cmd/vet/unsafeptr.go @@ -0,0 +1,98 @@ +// 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" + + "golang.org/x/tools/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: + return f.isSafeUintptr(x.X) && !f.isSafeUintptr(x.Y) + } + } + return false +} diff --git a/src/cmd/vet/unused.go b/src/cmd/vet/unused.go new file mode 100644 index 0000000000..db988fe1de --- /dev/null +++ b/src/cmd/vet/unused.go @@ -0,0 +1,94 @@ +// 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" + "strings" + + "golang.org/x/tools/go/types" +) + +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, 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/src/cmd/vet/vet_test.go b/src/cmd/vet/vet_test.go new file mode 100644 index 0000000000..33e54ae900 --- /dev/null +++ b/src/cmd/vet/vet_test.go @@ -0,0 +1,102 @@ +// 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" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" +) + +const ( + dataDir = "testdata" + binary = "testvet" +) + +// Run this shell script, but do it in Go so it can be run by "go test". +// go build -o testvet +// $(GOROOT)/test/errchk ./testvet -shadow -printfuncs='Warn:1,Warnf:1' testdata/*.go testdata/*.s +// rm testvet +// +func TestVet(t *testing.T) { + // Plan 9 and Windows systems can't be guaranteed to have Perl and so can't run errchk. + switch runtime.GOOS { + case "plan9", "windows": + t.Skip("skipping test; no Perl on %q", runtime.GOOS) + } + + // go build + cmd := exec.Command("go", "build", "-o", binary) + run(cmd, t) + + // defer removal of vet + defer os.Remove(binary) + + // errchk ./testvet + gos, err := filepath.Glob(filepath.Join(dataDir, "*.go")) + if err != nil { + t.Fatal(err) + } + asms, err := filepath.Glob(filepath.Join(dataDir, "*.s")) + if err != nil { + t.Fatal(err) + } + files := append(gos, asms...) + errchk := filepath.Join(runtime.GOROOT(), "test", "errchk") + flags := []string{ + "./" + binary, + "-printfuncs=Warn:1,Warnf:1", + "-test", // TODO: Delete once -shadow is part of -all. + } + cmd = exec.Command(errchk, append(flags, files...)...) + if !run(cmd, t) { + t.Fatal("vet command failed") + } +} + +func run(c *exec.Cmd, t *testing.T) bool { + output, err := c.CombinedOutput() + os.Stderr.Write(output) + if err != nil { + t.Fatal(err) + } + // Errchk delights by not returning non-zero status if it finds errors, so we look at the output. + // It prints "BUG" if there is a failure. + if !c.ProcessState.Success() { + return false + } + return !bytes.Contains(output, []byte("BUG")) +} + +// TestTags verifies that the -tags argument controls which files to check. +func TestTags(t *testing.T) { + // go build + cmd := exec.Command("go", "build", "-o", binary) + run(cmd, t) + + // defer removal of vet + defer os.Remove(binary) + + args := []string{ + "-tags=testtag", + "-v", // We're going to look at the files it examines. + "testdata/tagtest", + } + cmd = exec.Command(filepath.Join(".", binary), args...) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + // file1 has testtag and file2 has !testtag. + if !bytes.Contains(output, []byte("tagtest/file1.go")) { + t.Error("file1 was excluded, should be included") + } + if bytes.Contains(output, []byte("tagtest/file2.go")) { + t.Error("file2 was included, should be excluded") + } +} diff --git a/src/cmd/vet/whitelist/whitelist.go b/src/cmd/vet/whitelist/whitelist.go new file mode 100644 index 0000000000..d6f0dce821 --- /dev/null +++ b/src/cmd/vet/whitelist/whitelist.go @@ -0,0 +1,53 @@ +// 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 // import "golang.org/x/tools/cmd/vet/whitelist" + +// UnkeyedLiteral are types that are actually slices, but +// syntactically, we cannot tell whether the Typ in pkg.Typ{1, 2, 3} +// is a slice or a struct, so we whitelist all the standard package +// library's exported slice types. +var UnkeyedLiteral = map[string]bool{ + /* + find $GOROOT/src -type f | grep -v _test.go | xargs grep '^type.*\[\]' | \ + grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/,,' | \ + sed 's, ,.,' | sed 's, .*,,' | grep -v '\.[a-z]' | \ + sort | awk '{ print "\"" $0 "\": true," }' + */ + "crypto/x509/pkix.RDNSequence": true, + "crypto/x509/pkix.RelativeDistinguishedNameSET": true, + "database/sql.RawBytes": true, + "debug/macho.LoadBytes": true, + "encoding/asn1.ObjectIdentifier": true, + "encoding/asn1.RawContent": true, + "encoding/json.RawMessage": true, + "encoding/xml.CharData": true, + "encoding/xml.Comment": true, + "encoding/xml.Directive": true, + "go/scanner.ErrorList": true, + "image/color.Palette": true, + "net.HardwareAddr": true, + "net.IP": true, + "net.IPMask": true, + "sort.Float64Slice": true, + "sort.IntSlice": true, + "sort.StringSlice": true, + "unicode.SpecialCase": true, + + // 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.RGBA64": true, + "image/color.RGBA": true, + "image/color.YCbCr": true, + "image.Point": true, + "image.Rectangle": true, + "image.Uniform": true, +} -- 2.30.9