diff --git a/cmd/vet/Makefile b/cmd/vet/Makefile new file mode 100644 index 0000000000..7b3a6da4ec --- /dev/null +++ b/cmd/vet/Makefile @@ -0,0 +1,14 @@ +# 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. + +# Assumes go/types is installed +test testshort: + go build -tags 'vet_test gotypes' + $(GOROOT)/test/errchk ./vet -printfuncs='Warn:1,Warnf:1' test_*.go test_*.s + +test_notypes: + go build -tags 'vet_test' + # Only those tests that do not depend on types. + # Excluded: test_print.go + $(GOROOT)/test/errchk ./vet -printfuncs='Warn:1,Warnf:1' test_asm.go test_assign.go test_atomic.go test_buildtag.go test_buildtag_bad.go test_deadcode.go test_method.go test_rangeloop.go test_structtag.go test_taglit.go test_*.s diff --git a/cmd/vet/asmdecl.go b/cmd/vet/asmdecl.go new file mode 100644 index 0000000000..c23514c28f --- /dev/null +++ b/cmd/vet/asmdecl.go @@ -0,0 +1,533 @@ +// 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 + bigEndian 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, false} + asmArchArm = asmArch{"arm", 4, 4, false} + asmArchAmd64 = asmArch{"amd64", 8, 8, false} + + arches = []*asmArch{ + &asmArch386, + &asmArchArm, + &asmArchAmd64, + } +) + +var ( + re = regexp.MustCompile + asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) + asmTEXT = re(`\bTEXT\b.*·([^\(]+)\(SB\)(?:\s*,\s*([0-9]+))?(?:\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\))`) + asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) +) + +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) + } + } + } + } + + var fn *asmFunc + 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 + for _, a := range arches { + if strings.HasSuffix(f.name, "_"+a.name+".s") { + arch = a.name + break + } + } + + lines := strings.SplitAfter(string(f.content), "\n") + for lineno, line := range lines { + lineno++ + + warnf := func(format string, args ...interface{}) { + f.Warnf(token.NoPos, "%s:%d: [%s] %s", f.name, lineno, arch, 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 + break Fields + } + } + } + } + } + + if m := asmTEXT.FindStringSubmatch(line); m != nil { + if arch == "" { + f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) + return + } + fn = knownFunc[m[1]][arch] + if fn != nil { + size, _ := strconv.Atoi(m[4]) + if size != fn.size && (m[2] != "7" || size != 0) { + warnf("wrong argument size %d; expected $...-%d", size, fn.size) + } + } + continue + } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { + // function, but not visible from Go (didn't match asmTEXT), so stop checking + fn = nil + continue + } + + if asmDATA.FindStringSubmatch(line) != nil { + fn = nil + } + if fn == nil { + continue + } + + for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { + warnf("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]) + } + v := fn.vars[name] + if v == nil { + // Allow argframe+0(FP). + if name == "argframe" && off == 0 { + continue + } + v = fn.varByOffset[off] + if v != nil { + warnf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) + } else { + warnf("unknown variable %s", name) + } + continue + } + asmCheckVar(warnf, fn, line, m[0], off, v) + } + } + } +} + +// 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.ptrSize + size = 8 + case "int", "uint": + size = arch.intSize + case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer": + size = arch.ptrSize + case "string": + 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.ptrSize - 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(warnf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { + m := asmOpcode.FindStringSubmatch(line) + if m == nil { + warnf("cannot find assembly opcode") + } + + // 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 + default: + if fn.arch.name == "386" || fn.arch.name == "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 + } + } + } + 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) + } + warnf("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) + } + } + warnf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String()) + } +} diff --git a/cmd/vet/assign.go b/cmd/vet/assign.go new file mode 100644 index 0000000000..a11f0f875f --- /dev/null +++ b/cmd/vet/assign.go @@ -0,0 +1,44 @@ +// 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" +) + +// TODO: should also check for assignments to struct fields inside methods +// that are on T instead of *T. + +// checkAssignStmt checks for assignments of the form " = ". +// These are almost always useless, and even when they aren't they are usually a mistake. +func (f *File) checkAssignStmt(stmt *ast.AssignStmt) { + if !vet("assign") { + return + } + 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.Warnf(stmt.Pos(), "self-assignment of %s to %s", re, le) + } + } +} diff --git a/cmd/vet/atomic.go b/cmd/vet/atomic.go new file mode 100644 index 0000000000..4ab256f649 --- /dev/null +++ b/cmd/vet/atomic.go @@ -0,0 +1,59 @@ +// 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" +) + +// checkAtomicAssignment walks the assignment statement checking for common +// mistaken usage of atomic package, such as: x = atomic.AddUint64(&x, 1) +func (f *File) checkAtomicAssignment(n *ast.AssignStmt) { + if !vet("atomic") { + return + } + + 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) { + 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.Warn(left.Pos(), "direct assignment to atomic value") + } +} diff --git a/cmd/vet/buildtag.go b/cmd/vet/buildtag.go new file mode 100644 index 0000000000..0ab13cb8a0 --- /dev/null +++ b/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 appears too late in file\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/cmd/vet/deadcode.go b/cmd/vet/deadcode.go new file mode 100644 index 0000000000..f90fc14a49 --- /dev/null +++ b/cmd/vet/deadcode.go @@ -0,0 +1,280 @@ +// 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" +) + +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 (f *File) checkUnreachable(body *ast.BlockStmt) { + if !vet("unreachable") || 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: + 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.Warnf(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/cmd/vet/doc.go b/cmd/vet/doc.go new file mode 100644 index 0000000000..eb1e436f0b --- /dev/null +++ b/cmd/vet/doc.go @@ -0,0 +1,76 @@ +// 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. + +Its 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, but if explicit flags are provided, only +those identified by the flags are performed. + +Available checks: + +1. Printf family, flag -printf + +Suspicious calls to functions in the Printf family, including any functions +with these names: + Print Printf Println + Fprint Fprintf Fprintln + Sprint Sprintf Sprintln + Error Errorf + Fatal Fatalf + 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. + +2. 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 + +3. Struct tags, flag -structtags + +Struct tags that do not follow the format understood by reflect.StructTag.Get. + +4. Untagged composite literals, flag -composites + +Composite struct literals that do not use the type-tagged syntax. + + +Usage: + + go tool vet [flag] [file.go ...] + go tool vet [flag] [directory ...] # Scan all .go files under directory, recursively + +The other flags are: + -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 + +*/ +package main diff --git a/cmd/vet/main.go b/cmd/vet/main.go new file mode 100644 index 0000000000..b3d12d09f9 --- /dev/null +++ b/cmd/vet/main.go @@ -0,0 +1,422 @@ +// 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" +) + +var verbose = flag.Bool("v", false, "verbose") +var exitCode = 0 + +// Flags to control which checks to perform. "all" is set to true here, and disabled later if +// a flag is set explicitly. +var report = map[string]*bool{ + "all": flag.Bool("all", true, "check everything; disabled if any explicit check is requested"), + "asmdecl": flag.Bool("asmdecl", false, "check assembly against Go declarations"), + "assign": flag.Bool("assign", false, "check for useless assignments"), + "atomic": flag.Bool("atomic", false, "check for common mistaken usages of the sync/atomic package"), + "buildtags": flag.Bool("buildtags", false, "check that +build tags are valid"), + "composites": flag.Bool("composites", false, "check that composite literals used type-tagged elements"), + "methods": flag.Bool("methods", false, "check that canonically named methods are canonically defined"), + "printf": flag.Bool("printf", false, "check printf-like invocations"), + "rangeloops": flag.Bool("rangeloops", false, "check that range loop variables are used correctly"), + "structtags": flag.Bool("structtags", false, "check that struct field tags have canonical format"), + "unreachable": flag.Bool("unreachable", false, "check for unreachable code"), +} + +// vet tells whether to report errors for the named check, a flag name. +func vet(name string) bool { + return *report["all"] || *report[name] +} + +// 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 + } +} + +// 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") + 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 +} + +func main() { + flag.Usage = Usage + flag.Parse() + + // If a check is named explicitly, turn off the 'all' flag. + for name, ptr := range report { + if name != "all" && *ptr { + *report["all"] = false + break + } + } + + if *printfuncs != "" { + 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 + } + } + } + + 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) + } + return + } + doPackage(flag.Args()) + 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) { + pkg, err := build.Default.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(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(names) + } +} + +type Package struct { + types map[ast.Expr]Type + values map[ast.Expr]ExactValue + files []*File +} + +// doPackage analyzes the single package constructed from the named files. +func doPackage(names []string) { + var files []*File + var astFiles []*ast.File + fs := token.NewFileSet() + for _, name := range names { + f, err := os.Open(name) + if err != nil { + // Warn but continue to next package. + warnf("%s: %s", name, err) + return + } + defer f.Close() + data, err := ioutil.ReadAll(f) + if err != nil { + warnf("%s: %s", name, err) + return + } + checkBuildTag(name, data) + var parsedFile *ast.File + if strings.HasSuffix(name, ".go") { + parsedFile, err = parser.ParseFile(fs, name, bytes.NewReader(data), 0) + if err != nil { + warnf("%s: %s", name, err) + return + } + astFiles = append(astFiles, parsedFile) + } + files = append(files, &File{fset: fs, content: data, name: name, file: parsedFile}) + } + pkg := new(Package) + pkg.files = files + // Type check the package. + err := pkg.check(fs, astFiles) + if err != nil && *verbose { + warnf("%s", err) + } + for _, file := range files { + file.pkg = pkg + if file.file != nil { + file.walkFile(file.name, file.file) + } + } + asmCheck(pkg) +} + +func visit(path string, f os.FileInfo, err error) error { + if err != nil { + warnf("walk error: %s", err) + return err + } + // One package per directory. Ignore the files themselves. + if !f.IsDir() { + return nil + } + doPackageDir(path) + return nil +} + +func (pkg *Package) hasFileWithSuffix(suffix string) bool { + for _, f := range pkg.files { + if strings.HasSuffix(f.name, suffix) { + return true + } + } + return false +} + +// walkDir recursively walks the tree looking for Go packages. +func walkDir(root string) { + filepath.Walk(root, visit) +} + +// errorf formats the error to standard error, adding program +// identification and a newline, and exits. +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + os.Exit(2) +} + +// warnf formats the error to standard error, adding program +// identification and a newline, but does not exit. +func warnf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, "vet: "+format+"\n", args...) + setExit(1) +} + +// Println is fmt.Println guarded by -v. +func Println(args ...interface{}) { + if !*verbose { + return + } + fmt.Println(args...) +} + +// Printf is fmt.Printf guarded by -v. +func Printf(format string, args ...interface{}) { + if !*verbose { + return + } + fmt.Printf(format+"\n", args...) +} + +// Bad reports an error and sets the exit code.. +func (f *File) Bad(pos token.Pos, args ...interface{}) { + f.Warn(pos, args...) + setExit(1) +} + +// Badf reports a formatted error and sets the exit code. +func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { + f.Warnf(pos, format, args...) + setExit(1) +} + +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 { + switch n := node.(type) { + case *ast.AssignStmt: + f.walkAssignStmt(n) + case *ast.CallExpr: + f.walkCallExpr(n) + case *ast.CompositeLit: + f.walkCompositeLit(n) + case *ast.Field: + f.walkFieldTag(n) + case *ast.FuncDecl: + f.walkFuncDecl(n) + case *ast.FuncLit: + f.walkFuncLit(n) + case *ast.InterfaceType: + f.walkInterfaceType(n) + case *ast.RangeStmt: + f.walkRangeStmt(n) + } + return f +} + +// walkAssignStmt walks an assignment statement +func (f *File) walkAssignStmt(stmt *ast.AssignStmt) { + f.checkAssignStmt(stmt) + f.checkAtomicAssignment(stmt) +} + +// walkCall walks a call expression. +func (f *File) walkCall(call *ast.CallExpr, name string) { + f.checkFmtPrintfCall(call, name) +} + +// walkCallExpr walks a call expression. +func (f *File) walkCallExpr(call *ast.CallExpr) { + switch x := call.Fun.(type) { + case *ast.Ident: + f.walkCall(call, x.Name) + case *ast.SelectorExpr: + f.walkCall(call, x.Sel.Name) + } +} + +// walkCompositeLit walks a composite literal. +func (f *File) walkCompositeLit(c *ast.CompositeLit) { + f.checkUntaggedLiteral(c) +} + +// walkFieldTag walks a struct field tag. +func (f *File) walkFieldTag(field *ast.Field) { + if field.Tag == nil { + return + } + f.checkCanonicalFieldTag(field) +} + +// walkMethod walks the method's signature. +func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) { + f.checkCanonicalMethod(id, t) +} + +// walkFuncDecl walks a function declaration. +func (f *File) walkFuncDecl(d *ast.FuncDecl) { + f.checkUnreachable(d.Body) + if d.Recv != nil { + f.walkMethod(d.Name, d.Type) + } +} + +// walkFuncLit walks a function literal. +func (f *File) walkFuncLit(x *ast.FuncLit) { + f.checkUnreachable(x.Body) +} + +// walkInterfaceType walks the method signatures of an interface. +func (f *File) walkInterfaceType(t *ast.InterfaceType) { + for _, field := range t.Methods.List { + for _, id := range field.Names { + f.walkMethod(id, field.Type.(*ast.FuncType)) + } + } +} + +// walkRangeStmt walks a range statement. +func (f *File) walkRangeStmt(n *ast.RangeStmt) { + checkRangeLoop(f, n) +} + +// 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/cmd/vet/method.go b/cmd/vet/method.go new file mode 100644 index 0000000000..8064235f46 --- /dev/null +++ b/cmd/vet/method.go @@ -0,0 +1,162 @@ +// 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" +) + +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{}, []string{"[]byte", "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 + "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 (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) { + if !vet("methods") { + return + } + // 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/cmd/vet/print.go b/cmd/vet/print.go new file mode 100644 index 0000000000..debfbf0bfb --- /dev/null +++ b/cmd/vet/print.go @@ -0,0 +1,351 @@ +// 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 ( + "flag" + "go/ast" + "go/token" + "strconv" + "strings" + "unicode/utf8" +) + +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") + +// 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, + "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, + "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 (f *File) checkFmtPrintfCall(call *ast.CallExpr, Name string) { + if !vet("printf") { + 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 + } +} + +// literal returns the literal value represented by the expression, or nil if it is not a literal. +func (f *File) literal(value ast.Expr) *ast.BasicLit { + switch v := value.(type) { + case *ast.BasicLit: + return v + case *ast.ParenExpr: + return f.literal(v.X) + case *ast.BinaryExpr: + if v.Op != token.ADD { + break + } + litX := f.literal(v.X) + litY := f.literal(v.Y) + if litX != nil && litY != nil { + lit := *litX + x, errX := strconv.Unquote(litX.Value) + y, errY := strconv.Unquote(litY.Value) + if errX == nil && errY == nil { + return &ast.BasicLit{ + ValuePos: lit.ValuePos, + Kind: lit.Kind, + Value: strconv.Quote(x + y), + } + } + } + case *ast.Ident: + // See if it's a constant or initial value (we can't tell the difference). + if v.Obj == nil || v.Obj.Decl == nil { + return nil + } + valueSpec, ok := v.Obj.Decl.(*ast.ValueSpec) + if ok && len(valueSpec.Names) == len(valueSpec.Values) { + // Find the index in the list of names + var i int + for i = 0; i < len(valueSpec.Names); i++ { + if valueSpec.Names[i].Name == v.Name { + if lit, ok := valueSpec.Values[i].(*ast.BasicLit); ok { + return lit + } + return nil + } + } + } + } + return nil +} + +// 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) { + return + } + lit := f.literal(call.Args[formatIndex]) + if lit == nil { + if *verbose { + f.Warn(call.Pos(), "can't check non-literal format in call to", name) + } + return + } + if lit.Kind != token.STRING { + f.Badf(call.Pos(), "literal %v not a string in call to", lit.Value, name) + } + format, err := strconv.Unquote(lit.Value) + if err != nil { + // Shouldn't happen if parser returned no errors, but be safe. + f.Badf(call.Pos(), "invalid quoted string literal") + } + 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 + for i, w := 0, 0; i < len(format); i += w { + w = 1 + if format[i] == '%' { + verb, flags, nbytes, nargs := f.parsePrintfVerb(call, format[i:]) + w = nbytes + if verb == '%' { // "%%" does nothing interesting. + continue + } + // If we've run out of args, print after loop will pick that up. + if argNum+nargs <= len(call.Args) { + f.checkPrintfArg(call, verb, flags, argNum, nargs) + } + argNum += nargs + } + } + // TODO: Dotdotdot is hard. + if call.Ellipsis.IsValid() && argNum != len(call.Args) { + return + } + if 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) + } +} + +// parsePrintfVerb returns the verb that begins the format string, along with its flags, +// the number of bytes to advance the format to step past the verb, and number of +// arguments it consumes. +func (f *File) parsePrintfVerb(call *ast.CallExpr, format string) (verb rune, flags []byte, nbytes, nargs int) { + // There's guaranteed a percent sign. + flags = make([]byte, 0, 5) + nbytes = 1 + end := len(format) + // There may be flags. +FlagLoop: + for nbytes < end { + switch format[nbytes] { + case '#', '0', '+', '-', ' ': + flags = append(flags, format[nbytes]) + nbytes++ + default: + break FlagLoop + } + } + getNum := func() { + if nbytes < end && format[nbytes] == '*' { + nbytes++ + nargs++ + } else { + for nbytes < end && '0' <= format[nbytes] && format[nbytes] <= '9' { + nbytes++ + } + } + } + // There may be a width. + getNum() + // If there's a period, there may be a precision. + if nbytes < end && format[nbytes] == '.' { + flags = append(flags, '.') // Treat precision as a flag. + nbytes++ + getNum() + } + // Now a verb. + c, w := utf8.DecodeRuneInString(format[nbytes:]) + nbytes += w + verb = c + if c != '%' { + nargs++ + } + return +} + +// 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 + argPointer + anyType printfArgType = ^0 +) + +type printVerb struct { + verb rune + flags string // known flags are all ASCII + typ printfArgType +} + +// Common flag sets for printf verbs. +const ( + 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 + {'b', numFlag, argInt | argFloat}, + {'c', "-", argRune | argInt}, + {'d', numFlag, argInt}, + {'e', numFlag, argFloat}, + {'E', numFlag, argFloat}, + {'f', numFlag, argFloat}, + {'F', numFlag, argFloat}, + {'g', numFlag, argFloat}, + {'G', numFlag, argFloat}, + {'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}, +} + +const printfVerbs = "bcdeEfFgGopqstTvxUX" + +func (f *File) checkPrintfArg(call *ast.CallExpr, verb rune, flags []byte, argNum, nargs int) { + // Linear scan is fast enough for a small list. + for _, v := range printVerbs { + if v.verb == verb { + for _, flag := range flags { + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag) + return + } + } + // Verb is good. If nargs>1, we have something like %.*s and all but the final + // arg must be integer. + for i := 0; i < nargs-1; i++ { + if !f.matchArgType(argInt, call.Args[argNum+i]) { + f.Badf(call.Pos(), "arg %s for * in printf format not of type int", f.gofmt(call.Args[argNum+i])) + } + } + for _, v := range printVerbs { + if v.verb == verb { + arg := call.Args[argNum+nargs-1] + if !f.matchArgType(v.typ, arg) { + typeString := "" + if typ := f.pkg.types[arg]; typ != nil { + typeString = typ.String() + } + f.Badf(call.Pos(), "arg %s for printf verb %%%c of wrong type: %s", f.gofmt(arg), verb, typeString) + } + break + } + } + return + } + } + f.Badf(call.Pos(), "unrecognized printf verb %q", verb) +} + +// 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 + // 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) + } + } + } +} diff --git a/cmd/vet/rangeloop.go b/cmd/vet/rangeloop.go new file mode 100644 index 0000000000..ecc5954272 --- /dev/null +++ b/cmd/vet/rangeloop.go @@ -0,0 +1,65 @@ +// 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" + +// 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, n *ast.RangeStmt) { + if !vet("rangeloops") { + return + } + 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.Warn(id.Pos(), "range variable", id.Name, "enclosed by function") + } + return true + }) +} diff --git a/cmd/vet/structtag.go b/cmd/vet/structtag.go new file mode 100644 index 0000000000..d835788368 --- /dev/null +++ b/cmd/vet/structtag.go @@ -0,0 +1,37 @@ +// 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 ( + "go/ast" + "reflect" + "strconv" +) + +// checkField checks a struct field tag. +func (f *File) checkCanonicalFieldTag(field *ast.Field) { + if !vet("structtags") { + return + } + 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 + } + + // Check tag for validity by appending + // new key:value to end and checking that + // the tag parsing code can find it. + if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" { + f.Badf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get", field.Tag.Value) + return + } +} diff --git a/cmd/vet/taglit.go b/cmd/vet/taglit.go new file mode 100644 index 0000000000..bcad2fe0a2 --- /dev/null +++ b/cmd/vet/taglit.go @@ -0,0 +1,164 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for untagged struct literals. + +package main + +import ( + "flag" + "go/ast" + "strings" +) + +var compositeWhiteList = flag.Bool("compositewhitelist", true, "use composite white list; for testing only") + +// checkUntaggedLiteral checks if a composite literal is a struct literal with +// untagged fields. +func (f *File) checkUntaggedLiteral(c *ast.CompositeLit) { + if !vet("composites") { + return + } + + 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 tags + case *ast.Ident: + // A simple type name like t or T does not need tags 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 untagged 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 && untaggedLiteralWhitelist[typeName] { + return + } + + f.Warn(c.Pos(), typeString+" composite literal uses untagged 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 "" +} + +var untaggedLiteralWhitelist = map[string]bool{ + /* + These types are actually slices. 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. + + find $GOROOT/src/pkg -type f | grep -v _test.go | xargs grep '^type.*\[\]' | \ + grep -v ' map\[' | sed 's,/[^/]*go.type,,' | sed 's,.*src/pkg/,,' | \ + 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.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, +} diff --git a/cmd/vet/test_asm.go b/cmd/vet/test_asm.go new file mode 100644 index 0000000000..098bdd15c6 --- /dev/null +++ b/cmd/vet/test_asm.go @@ -0,0 +1,24 @@ +// 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 main + +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) diff --git a/cmd/vet/test_asm1.s b/cmd/vet/test_asm1.s new file mode 100644 index 0000000000..8cd9eeab6a --- /dev/null +++ b/cmd/vet/test_asm1.s @@ -0,0 +1,247 @@ +// 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 y+1(FP), BX + MOVW x+0(FP), AX // ERROR "\[amd64\] 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\)" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "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 "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 diff --git a/cmd/vet/test_asm2.s b/cmd/vet/test_asm2.s new file mode 100644 index 0000000000..d8679c574e --- /dev/null +++ b/cmd/vet/test_asm2.s @@ -0,0 +1,251 @@ +// 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\] 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\)" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "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 "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 diff --git a/cmd/vet/test_asm3.s b/cmd/vet/test_asm3.s new file mode 100644 index 0000000000..bf98805a21 --- /dev/null +++ b/cmd/vet/test_asm3.s @@ -0,0 +1,166 @@ +// 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\] 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\)" + RET + +TEXT ·arg2(SB),0,$0-4 + MOVB x+0(FP), AX // ERROR "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 "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 diff --git a/cmd/vet/test_assign.go b/cmd/vet/test_assign.go new file mode 100644 index 0000000000..8e0f45e532 --- /dev/null +++ b/cmd/vet/test_assign.go @@ -0,0 +1,20 @@ +// 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. + +// +build vet_test + +package main + +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/cmd/vet/test_atomic.go b/cmd/vet/test_atomic.go new file mode 100644 index 0000000000..9231e9dc0d --- /dev/null +++ b/cmd/vet/test_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. + +// +build vet_test + +// This file contains tests for the atomic checker. + +package main + +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) +} diff --git a/cmd/vet/test_buildtag.go b/cmd/vet/test_buildtag.go new file mode 100644 index 0000000000..d7174ade21 --- /dev/null +++ b/cmd/vet/test_buildtag.go @@ -0,0 +1,15 @@ +// 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. + +// +build vet_test +// +builder // ERROR "possible malformed \+build comment" +// +build !ignore + +package main + +// +build toolate // ERROR "build comment appears too late in file" + +var _ = 3 diff --git a/cmd/vet/test_buildtag_bad.go b/cmd/vet/test_buildtag_bad.go new file mode 100644 index 0000000000..0a0a39bd1f --- /dev/null +++ b/cmd/vet/test_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 appears too late in file" +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/cmd/vet/test_deadcode.go b/cmd/vet/test_deadcode.go new file mode 100644 index 0000000000..d08e57782f --- /dev/null +++ b/cmd/vet/test_deadcode.go @@ -0,0 +1,2121 @@ +// 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 vet_test +// +build ignore + +// This file contains tests for the dead code checker. + +package main + +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 +} diff --git a/cmd/vet/test_method.go b/cmd/vet/test_method.go new file mode 100644 index 0000000000..41de62bb1d --- /dev/null +++ b/cmd/vet/test_method.go @@ -0,0 +1,24 @@ +// 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. + +// +build vet_test + +// This file contains the code to check canonical methods. + +package main + +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/cmd/vet/test_print.go b/cmd/vet/test_print.go new file mode 100644 index 0000000000..8b41e6c69b --- /dev/null +++ b/cmd/vet/test_print.go @@ -0,0 +1,153 @@ +// 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 vet_test + +// This file contains tests for the printf checker. + +package main + +import ( + "fmt" + "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 + // Some good format/argtypes + fmt.Printf("") + fmt.Printf("%b %b", 3, i) + fmt.Printf("%c %c %c %c", 3, i, 'x', r) + fmt.Printf("%d %d", 3, i) + fmt.Printf("%e %e", 3e9, x) + fmt.Printf("%E %E", 3e9, x) + fmt.Printf("%f %f", 3e9, x) + fmt.Printf("%F %F", 3e9, x) + fmt.Printf("%g %g", 3e9, x) + fmt.Printf("%G %G", 3e9, x) + 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", "hi", s) + 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) + // Some bad format/argTypes + fmt.Printf("%b", "hi") // ERROR "arg .hi. for printf verb %b 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", 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("%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 %d %g", 3, "hi", 23, 'x') // ERROR "arg 'x' for printf verb %g of wrong type" + // TODO + 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.Printf("%"+("s"), "hi", 3) // ERROR "wrong number of args for format in Printf 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) // ERROR "wrong number of args for format in Printf call" + 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("%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 "wrong number of args for format in Printf call" + f := new(File) + 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" + // 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. +} + +// printf is used by the test. +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") +} diff --git a/cmd/vet/test_rangeloop.go b/cmd/vet/test_rangeloop.go new file mode 100644 index 0000000000..941fd72aaa --- /dev/null +++ b/cmd/vet/test_rangeloop.go @@ -0,0 +1,61 @@ +// 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. + +// +build vet_test + +package main + +func RangeLoopTests() { + var s []int + for i, v := range s { + go func() { + println(i) // ERROR "range variable i enclosed by function" + println(v) // ERROR "range variable v enclosed by function" + }() + } + for i, v := range s { + defer func() { + println(i) // ERROR "range variable i enclosed by function" + println(v) // ERROR "range variable v enclosed by function" + }() + } + for i := range s { + go func() { + println(i) // ERROR "range variable i enclosed by function" + }() + } + for _, v := range s { + go func() { + println(v) // ERROR "range variable v enclosed by function" + }() + } + 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 enclosed by function" + }() + } +} diff --git a/cmd/vet/test_structtag.go b/cmd/vet/test_structtag.go new file mode 100644 index 0000000000..08cf737fd8 --- /dev/null +++ b/cmd/vet/test_structtag.go @@ -0,0 +1,15 @@ +// 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 structtag checker. + +// +build vet_test + +// This file contains the test for canonical struct tags. + +package main + +type StructTagTest struct { + X int "hello" // ERROR "not compatible with reflect.StructTag.Get" +} diff --git a/cmd/vet/test_taglit.go b/cmd/vet/test_taglit.go new file mode 100644 index 0000000000..f34062f18e --- /dev/null +++ b/cmd/vet/test_taglit.go @@ -0,0 +1,65 @@ +// 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. + +// +build vet_test + +// This file contains the test for untagged struct literals. + +package main + +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 "untagged 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/cmd/vet/types.go b/cmd/vet/types.go new file mode 100644 index 0000000000..f3f0f0a989 --- /dev/null +++ b/cmd/vet/types.go @@ -0,0 +1,185 @@ +// 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 gotypes + +// This file contains the pieces of the tool that require the go/types package. +// To compile this file, you must first run +// $ go get code.google.com/p/go.tools/go/types + +package main + +import ( + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" + "code.google.com/p/go.tools/go/types" +) + +// Type is equivalent to types.Type. Repeating it here allows us to avoid +// having main depend on the go/types package. +type Type interface { + String() string +} + +// ExactValue is equivalent to exact.Value. Repeating it here allows us to +// avoid having main depend on the go/exact package. +type ExactValue interface { + Kind() exact.Kind + String() string +} + +func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { + pkg.types = make(map[ast.Expr]Type) + pkg.values = make(map[ast.Expr]ExactValue) + exprFn := func(x ast.Expr, typ types.Type, val exact.Value) { + pkg.types[x] = typ + if val != nil { + pkg.values[x] = val + } + } + // By providing the Context with our own error function, it will continue + // past the first error. There is no need for that function to do anything. + context := types.Context{ + Expr: exprFn, + Error: func(error) {}, + } + _, err := context.Check(fs, astFiles) + 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 tag), if possible. + typ := pkg.types[c] + // If it's a named type, pull out the underlying type. + actual := typ + if namedType, ok := typ.(*types.NamedType); ok { + actual = namedType.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, "" + } +} + +func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool { + // TODO: for now, we can only test builtin types and untyped constants. + typ := f.pkg.types[arg] + if typ == nil { + return true + } + basic, ok := typ.(*types.Basic) + if !ok { + return true + } + switch basic.Kind { + case types.Bool: + return t&argBool != 0 + case types.Int, types.Int8, types.Int16, types.Int32, types.Int64: + fallthrough + case types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr: + return t&argInt != 0 + case types.Float32, types.Float64, types.Complex64, types.Complex128: + return t&argFloat != 0 + case types.String: + return t&argString != 0 + case types.UnsafePointer: + return t&(argPointer|argInt) != 0 + case types.UntypedBool: + return t&argBool != 0 + case types.UntypedComplex: + return t&argFloat != 0 + case types.UntypedFloat: + // If it's integral, we can use an int format. + switch f.pkg.values[arg].Kind() { + case exact.Int: + return t&(argInt|argFloat) != 0 + } + return t&argFloat != 0 + case types.UntypedInt: + return t&argInt != 0 + case types.UntypedRune: + return t&(argInt|argRune) != 0 + case types.UntypedString: + return t&argString != 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", arg) + } + return true // Probably a type check problem. + } + return false +} + +// 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] + 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 len(sig.Params) +} + +// 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 { + // 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] + 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 len(sig.Params) > 0 { + return false + } + // Finally the real questions. + // There must be one result. + if len(sig.Results) != 1 { + return false + } + // It must have return type "string" from the universe. + result := sig.Results[0].Type + if types.IsIdentical(result, types.Typ[types.String]) { + return true + } + return false +} diff --git a/cmd/vet/typestub.go b/cmd/vet/typestub.go new file mode 100644 index 0000000000..74a3b13e26 --- /dev/null +++ b/cmd/vet/typestub.go @@ -0,0 +1,50 @@ +// 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 !gotypes + +// This file contains stubs for the pieces of the tool that require the go/types package, +// to be used if go/types is not available. + +package main + +import ( + "go/ast" + "go/token" +) + +// Type is equivalent to go/types.Type. Repeating it here allows us to avoid +// having main depend on the go/types package. +type Type interface { + String() string +} + +// ExactValue is a stub for exact.Value. Stubbing it here allows us to +// avoid having main depend on the go/exact package. +type ExactValue interface { +} + +func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) error { + return nil +} + +func (pkg *Package) isStruct(c *ast.CompositeLit) (bool, string) { + return true, "" // Assume true, so we do the check. +} + +func (f *File) matchArgType(t printfArgType, arg ast.Expr) bool { + return true // We can't tell without types. +} + +func (f *File) numArgsInSignature(call *ast.CallExpr) int { + return 0 // We don't know. +} + +func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { + // Is it a selector expression? Otherwise it's a function call, not a method call. + if _, ok := call.Fun.(*ast.SelectorExpr); !ok { + return false + } + return true // Best guess we can make without types. +} diff --git a/go/exact/exact.go b/go/exact/exact.go new file mode 100644 index 0000000000..7bc1ffc365 --- /dev/null +++ b/go/exact/exact.go @@ -0,0 +1,748 @@ +// 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 exact implements mathematically exact values +// and operations for all Go basic types. +// +package exact + +import ( + "fmt" + "go/token" + "math/big" + "strconv" +) + +// Kind specifies the kind of value represented by a Value. +type Kind int + +// Implementation note: Kinds must be enumerated in +// order of increasing "complexity" (used by match). + +const ( + // unknown values + Unknown Kind = iota + + // non-numeric values + Nil + Bool + String + + // numeric values + Int + Float + Complex +) + +// A Value represents a mathematically precise value of a given Kind. +type Value interface { + // Kind returns the value kind; it is always the smallest + // kind in which the value can be represented exactly. + Kind() Kind + + // String returns a human-readable form of the value. + String() string + + // Prevent external implementations. + implementsValue() +} + +// TODO(gri) Handle unknown values more consistently. Some operations +// accept unknowns, some don't. + +// ---------------------------------------------------------------------------- +// Implementations + +type ( + unknownVal struct{} + nilVal struct{} + boolVal bool + stringVal string + int64Val int64 + intVal struct{ val *big.Int } + floatVal struct{ val *big.Rat } + complexVal struct{ re, im *big.Rat } +) + +func (unknownVal) Kind() Kind { return Unknown } +func (nilVal) Kind() Kind { return Nil } +func (boolVal) Kind() Kind { return Bool } +func (stringVal) Kind() Kind { return String } +func (int64Val) Kind() Kind { return Int } +func (intVal) Kind() Kind { return Int } +func (floatVal) Kind() Kind { return Float } +func (complexVal) Kind() Kind { return Complex } + +func (unknownVal) String() string { return "unknown" } +func (nilVal) String() string { return "nil" } +func (x boolVal) String() string { return fmt.Sprintf("%v", bool(x)) } +func (x stringVal) String() string { return strconv.Quote(string(x)) } +func (x int64Val) String() string { return strconv.FormatInt(int64(x), 10) } +func (x intVal) String() string { return x.val.String() } +func (x floatVal) String() string { return x.val.String() } +func (x complexVal) String() string { return fmt.Sprintf("(%s + %si)", x.re, x.im) } + +func (unknownVal) implementsValue() {} +func (nilVal) implementsValue() {} +func (boolVal) implementsValue() {} +func (stringVal) implementsValue() {} +func (int64Val) implementsValue() {} +func (intVal) implementsValue() {} +func (floatVal) implementsValue() {} +func (complexVal) implementsValue() {} + +// int64 bounds +var ( + minInt64 = big.NewInt(-1 << 63) + maxInt64 = big.NewInt(1<<63 - 1) +) + +func normInt(x *big.Int) Value { + if minInt64.Cmp(x) <= 0 && x.Cmp(maxInt64) <= 0 { + return int64Val(x.Int64()) + } + return intVal{x} +} + +func normFloat(x *big.Rat) Value { + if x.IsInt() { + return normInt(x.Num()) + } + return floatVal{x} +} + +func normComplex(re, im *big.Rat) Value { + if im.Sign() == 0 { + return normFloat(re) + } + return complexVal{re, im} +} + +// ---------------------------------------------------------------------------- +// Factories + +// MakeUnknown returns the Unknown value. +func MakeUnknown() Value { return unknownVal{} } + +// MakeNil returns the Nil value. +func MakeNil() Value { return nilVal{} } + +// MakeBool returns the Bool value for x. +func MakeBool(b bool) Value { return boolVal(b) } + +// MakeString returns the String value for x. +func MakeString(s string) Value { return stringVal(s) } + +// MakeInt64 returns the Int value for x. +func MakeInt64(x int64) Value { return int64Val(x) } + +// MakeUint64 returns the Int value for x. +func MakeUint64(x uint64) Value { return normInt(new(big.Int).SetUint64(x)) } + +// MakeFloat64 returns the numeric value for x. +// If x is not finite, the result is unknown. +func MakeFloat64(x float64) Value { + f := new(big.Rat).SetFloat64(x) + if f != nil { + return normFloat(f) + } + return unknownVal{} +} + +// MakeFromLiteral returns the corresponding literal value. +// If the literal has illegal format, the result is nil. +func MakeFromLiteral(lit string, tok token.Token) Value { + switch tok { + case token.INT: + if x, err := strconv.ParseInt(lit, 0, 64); err == nil { + return int64Val(x) + } + if x, ok := new(big.Int).SetString(lit, 0); ok { + return intVal{x} + } + + case token.FLOAT: + if x, ok := new(big.Rat).SetString(lit); ok { + return normFloat(x) + } + + case token.IMAG: + if n := len(lit); n > 0 && lit[n-1] == 'i' { + if im, ok := new(big.Rat).SetString(lit[0 : n-1]); ok { + return normComplex(big.NewRat(0, 1), im) + } + } + + case token.CHAR: + if n := len(lit); n >= 2 { + if code, _, _, err := strconv.UnquoteChar(lit[1:n-1], '\''); err == nil { + return int64Val(code) + } + } + + case token.STRING: + if s, err := strconv.Unquote(lit); err == nil { + return stringVal(s) + } + } + + // TODO(gri) should we instead a) return unknown, or b) an error? + return nil +} + +// ---------------------------------------------------------------------------- +// Accessors + +// BoolVal returns the Go boolean value of x, which must be a Bool. +func BoolVal(x Value) bool { return bool(x.(boolVal)) } + +// StringVal returns the Go string value of x, which must be a String. +func StringVal(x Value) string { return string(x.(stringVal)) } + +// Int64Val returns the Go int64 value of x and whether the result is exact; +// x must be an Int. If the result is not exact, its value is undefined. +func Int64Val(x Value) (int64, bool) { + switch x := x.(type) { + case int64Val: + return int64(x), true + case intVal: + return x.val.Int64(), x.val.BitLen() <= 63 + } + panic(fmt.Sprintf("invalid Int64Val(%v)", x)) +} + +// Uint64Val returns the Go uint64 value of x and whether the result is exact; +// x must be an Int. If the result is not exact, its value is undefined. +func Uint64Val(x Value) (uint64, bool) { + switch x := x.(type) { + case int64Val: + return uint64(x), x >= 0 + case intVal: + return x.val.Uint64(), x.val.Sign() >= 0 && x.val.BitLen() <= 64 + } + panic(fmt.Sprintf("invalid Uint64Val(%v)", x)) +} + +// Float64Val returns the nearest Go float64 value of x and whether the result is exact; +// x must be numeric but not Complex. +func Float64Val(x Value) (float64, bool) { + switch x := x.(type) { + case int64Val: + f := float64(int64(x)) + return f, int64Val(f) == x + case intVal: + return new(big.Rat).SetFrac(x.val, int1).Float64() + case floatVal: + return x.val.Float64() + } + panic(fmt.Sprintf("invalid Float64Val(%v)", x)) +} + +// BitLen() returns the number of bits required to represent +// the absolute value x in binary representation; x must be an Int. +func BitLen(x Value) int { + switch x := x.(type) { + case int64Val: + return new(big.Int).SetInt64(int64(x)).BitLen() + case intVal: + return x.val.BitLen() + } + panic(fmt.Sprintf("invalid BitLen(%v)", x)) +} + +// Sign returns -1, 0, or 1 depending on whether +// x < 0, x == 0, or x > 0. For complex values z, +// the sign is 0 if z == 0, otherwise it is != 0. +// For unknown values, the sign is always 1 (this +// helps avoiding "division by zero errors"). For +// all other values, Sign panics. +// +func Sign(x Value) int { + switch x := x.(type) { + case unknownVal: + return 1 + case int64Val: + switch { + case x < 0: + return -1 + case x > 0: + return 1 + } + return 0 + case intVal: + return x.val.Sign() + case floatVal: + return x.val.Sign() + case complexVal: + return x.re.Sign() | x.im.Sign() + } + panic(fmt.Sprintf("invalid Sign(%v)", x)) +} + +// ---------------------------------------------------------------------------- +// Support for assembling/disassembling complex numbers + +// MakeImag returns the numeric value x*i (possibly 0); +// x must be numeric but not Complex. +// If x is unknown, the result is unknown. +func MakeImag(x Value) Value { + var im *big.Rat + switch x := x.(type) { + case unknownVal: + return x + case int64Val: + im = big.NewRat(int64(x), 1) + case intVal: + im = new(big.Rat).SetFrac(x.val, int1) + case floatVal: + im = x.val + default: + panic(fmt.Sprintf("invalid MakeImag(%v)", x)) + } + return normComplex(rat0, im) +} + +// Real returns the real part of x, which must be a numeric value. +// If x is unknown, the result is unknown. +func Real(x Value) Value { + if z, ok := x.(complexVal); ok { + return normFloat(z.re) + } + return x +} + +// Imag returns the imaginary part of x, which must be a numeric value. +// If x is unknown, the result is 0. +func Imag(x Value) Value { + if z, ok := x.(complexVal); ok { + return normFloat(z.im) + } + return int64Val(0) +} + +// ---------------------------------------------------------------------------- +// Operations + +// is32bit reports whether x can be represented using 32 bits. +func is32bit(x int64) bool { + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// is63bit reports whether x can be represented using 63 bits. +func is63bit(x int64) bool { + const s = 63 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 +} + +// UnaryOp returns the result of the unary expression op y. +// The operation must be defined for the operand. +// If size >= 0 it specifies the ^ (xor) result size in bytes. +// +func UnaryOp(op token.Token, y Value, size int) Value { + switch op { + case token.ADD: + switch y.(type) { + case unknownVal, int64Val, intVal, floatVal, complexVal: + return y + } + + case token.SUB: + switch y := y.(type) { + case int64Val: + if z := -y; z != y { + return z // no overflow + } + return normInt(new(big.Int).Neg(big.NewInt(int64(y)))) + case intVal: + return normInt(new(big.Int).Neg(y.val)) + case floatVal: + return normFloat(new(big.Rat).Neg(y.val)) + case complexVal: + return normComplex(new(big.Rat).Neg(y.re), new(big.Rat).Neg(y.im)) + } + + case token.XOR: + var z big.Int + switch y := y.(type) { + case int64Val: + z.Not(big.NewInt(int64(y))) + case intVal: + z.Not(y.val) + default: + goto Error + } + // For unsigned types, the result will be negative and + // thus "too large": We must limit the result size to + // the type's size. + if size >= 0 { + s := uint(size) * 8 + z.AndNot(&z, new(big.Int).Lsh(big.NewInt(-1), s)) // z &^= (-1)< ord(y) { + y, x = match(y, x) + return x, y + } + // ord(x) <= ord(y) + + switch x := x.(type) { + case unknownVal, nilVal, boolVal, stringVal, complexVal: + return x, y + + case int64Val: + switch y := y.(type) { + case int64Val: + return x, y + case intVal: + return intVal{big.NewInt(int64(x))}, y + case floatVal: + return floatVal{big.NewRat(int64(x), 1)}, y + case complexVal: + return complexVal{big.NewRat(int64(x), 1), rat0}, y + } + + case intVal: + switch y := y.(type) { + case intVal: + return x, y + case floatVal: + return floatVal{new(big.Rat).SetFrac(x.val, int1)}, y + case complexVal: + return complexVal{new(big.Rat).SetFrac(x.val, int1), rat0}, y + } + + case floatVal: + switch y := y.(type) { + case floatVal: + return x, y + case complexVal: + return complexVal{x.val, rat0}, y + } + } + + panic("unreachable") +} + +// BinaryOp returns the result of the binary expression x op y. +// The operation must be defined for the operands. +// To force integer division of Int operands, use op == token.QUO_ASSIGN +// instead of token.QUO; the result is guaranteed to be Int in this case. +// Division by zero leads to a run-time panic. +// +func BinaryOp(x Value, op token.Token, y Value) Value { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return x + + case boolVal: + y := y.(boolVal) + switch op { + case token.LAND: + return x && y + case token.LOR: + return x || y + } + + case int64Val: + a := int64(x) + b := int64(y.(int64Val)) + var c int64 + switch op { + case token.ADD: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Add(big.NewInt(a), big.NewInt(b))) + } + c = a + b + case token.SUB: + if !is63bit(a) || !is63bit(b) { + return normInt(new(big.Int).Sub(big.NewInt(a), big.NewInt(b))) + } + c = a - b + case token.MUL: + if !is32bit(a) || !is32bit(b) { + return normInt(new(big.Int).Mul(big.NewInt(a), big.NewInt(b))) + } + c = a * b + case token.QUO: + return normFloat(new(big.Rat).SetFrac(big.NewInt(a), big.NewInt(b))) + case token.QUO_ASSIGN: // force integer division + c = a / b + case token.REM: + c = a % b + case token.AND: + c = a & b + case token.OR: + c = a | b + case token.XOR: + c = a ^ b + case token.AND_NOT: + c = a &^ b + default: + goto Error + } + return int64Val(c) + + case intVal: + a := x.val + b := y.(intVal).val + var c big.Int + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + return normFloat(new(big.Rat).SetFrac(a, b)) + case token.QUO_ASSIGN: // force integer division + c.Quo(a, b) + case token.REM: + c.Rem(a, b) + case token.AND: + c.And(a, b) + case token.OR: + c.Or(a, b) + case token.XOR: + c.Xor(a, b) + case token.AND_NOT: + c.AndNot(a, b) + default: + goto Error + } + return normInt(&c) + + case floatVal: + a := x.val + b := y.(floatVal).val + var c big.Rat + switch op { + case token.ADD: + c.Add(a, b) + case token.SUB: + c.Sub(a, b) + case token.MUL: + c.Mul(a, b) + case token.QUO: + c.Quo(a, b) + default: + goto Error + } + return normFloat(&c) + + case complexVal: + y := y.(complexVal) + a, b := x.re, x.im + c, d := y.re, y.im + var re, im big.Rat + switch op { + case token.ADD: + // (a+c) + i(b+d) + re.Add(a, c) + im.Add(b, d) + case token.SUB: + // (a-c) + i(b-d) + re.Sub(a, c) + im.Sub(b, d) + case token.MUL: + // (ac-bd) + i(bc+ad) + var ac, bd, bc, ad big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + re.Sub(&ac, &bd) + im.Add(&bc, &ad) + case token.QUO: + // (ac+bd)/s + i(bc-ad)/s, with s = cc + dd + var ac, bd, bc, ad, s big.Rat + ac.Mul(a, c) + bd.Mul(b, d) + bc.Mul(b, c) + ad.Mul(a, d) + s.Add(c.Mul(c, c), d.Mul(d, d)) + re.Add(&ac, &bd) + re.Quo(&re, &s) + im.Sub(&bc, &ad) + im.Quo(&im, &s) + default: + goto Error + } + return normComplex(&re, &im) + + case stringVal: + if op == token.ADD { + return x + y.(stringVal) + } + } + +Error: + panic(fmt.Sprintf("invalid binary operation %v %s %v", x, op, y)) +} + +// Shift returns the result of the shift expression x op s +// with op == token.SHL or token.SHR (<< or >>). x must be +// an Int. +// +func Shift(x Value, op token.Token, s uint) Value { + switch x := x.(type) { + case unknownVal: + return x + + case int64Val: + if s == 0 { + return x + } + switch op { + case token.SHL: + z := big.NewInt(int64(x)) + return normInt(z.Lsh(z, s)) + case token.SHR: + return x >> s + } + + case intVal: + if s == 0 { + return x + } + var z big.Int + switch op { + case token.SHL: + return normInt(z.Lsh(x.val, s)) + case token.SHR: + return normInt(z.Rsh(x.val, s)) + } + } + + panic(fmt.Sprintf("invalid shift %v %s %d", x, op, s)) +} + +func cmpZero(x int, op token.Token) bool { + switch op { + case token.EQL: + return x == 0 + case token.NEQ: + return x != 0 + case token.LSS: + return x < 0 + case token.LEQ: + return x <= 0 + case token.GTR: + return x > 0 + case token.GEQ: + return x >= 0 + } + panic("unreachable") +} + +// Compare returns the result of the comparison x op y. +// The comparison must be defined for the operands. +// +func Compare(x Value, op token.Token, y Value) bool { + x, y = match(x, y) + + switch x := x.(type) { + case unknownVal: + return true + + case boolVal: + y := y.(boolVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + } + + case int64Val: + y := y.(int64Val) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + + case intVal: + return cmpZero(x.val.Cmp(y.(intVal).val), op) + + case floatVal: + return cmpZero(x.val.Cmp(y.(floatVal).val), op) + + case complexVal: + y := y.(complexVal) + re := x.re.Cmp(y.re) + im := x.im.Cmp(y.im) + switch op { + case token.EQL: + return re == 0 && im == 0 + case token.NEQ: + return re != 0 || im != 0 + } + + case stringVal: + y := y.(stringVal) + switch op { + case token.EQL: + return x == y + case token.NEQ: + return x != y + case token.LSS: + return x < y + case token.LEQ: + return x <= y + case token.GTR: + return x > y + case token.GEQ: + return x >= y + } + } + + panic(fmt.Sprintf("invalid comparison %v %s %v", x, op, y)) +} diff --git a/go/exact/exact_test.go b/go/exact/exact_test.go new file mode 100644 index 0000000000..aed87b7699 --- /dev/null +++ b/go/exact/exact_test.go @@ -0,0 +1,186 @@ +// 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 exact + +import ( + "go/token" + "strings" + "testing" +) + +// TODO(gri) expand this test framework + +var tests = []string{ + // unary operations + `+ 0 = 0`, + `- 1 = -1`, + + `! true = false`, + `! false = true`, + // etc. + + // binary operations + `"" + "" = ""`, + `"foo" + "" = "foo"`, + `"" + "bar" = "bar"`, + `"foo" + "bar" = "foobar"`, + + `0 + 0 = 0`, + `0 + 0.1 = 0.1`, + `0 + 0.1i = 0.1i`, + `0.1 + 0.9 = 1`, + `1e100 + 1e100 = 2e100`, + + `0 - 0 = 0`, + `0 - 0.1 = -0.1`, + `0 - 0.1i = -0.1i`, + `1e100 - 1e100 = 0`, + + `0 * 0 = 0`, + `1 * 0.1 = 0.1`, + `1 * 0.1i = 0.1i`, + `1i * 1i = -1`, + + `0 / 0 = "division_by_zero"`, + `10 / 2 = 5`, + `5 / 3 = 5/3`, + + `0 % 0 = "runtime_error:_integer_divide_by_zero"`, // TODO(gri) should be the same as for / + `10 % 3 = 1`, + // etc. + + // shifts + `0 << 0 = 0`, + `1 << 10 = 1024`, + // etc. + + // comparisons + `false == false = true`, + `false == true = false`, + `true == false = false`, + `true == true = true`, + + `false != false = false`, + `false != true = true`, + `true != false = true`, + `true != true = false`, + + `"foo" == "bar" = false`, + `"foo" != "bar" = true`, + `"foo" < "bar" = false`, + `"foo" <= "bar" = false`, + `"foo" > "bar" = true`, + `"foo" >= "bar" = true`, + + `0 != 0 = false`, + + // etc. +} + +func TestOps(t *testing.T) { + for _, test := range tests { + var got, want Value + + switch a := strings.Split(test, " "); len(a) { + case 4: + got = doOp(nil, op[a[0]], val(a[1])) + want = val(a[3]) + case 5: + got = doOp(val(a[0]), op[a[1]], val(a[2])) + want = val(a[4]) + default: + t.Errorf("invalid test case: %s", test) + continue + } + + if !Compare(got, token.EQL, want) { + t.Errorf("%s failed: got %s; want %s", test, got, want) + } + } +} + +// ---------------------------------------------------------------------------- +// Support functions + +func val(lit string) Value { + if len(lit) == 0 { + return MakeUnknown() + } + + switch lit { + case "?": + return MakeUnknown() + case "nil": + return MakeNil() + case "true": + return MakeBool(true) + case "false": + return MakeBool(false) + } + + tok := token.FLOAT + switch first, last := lit[0], lit[len(lit)-1]; { + case first == '"' || first == '`': + tok = token.STRING + lit = strings.Replace(lit, "_", " ", -1) + case first == '\'': + tok = token.CHAR + case last == 'i': + tok = token.IMAG + } + + return MakeFromLiteral(lit, tok) +} + +var op = map[string]token.Token{ + "!": token.NOT, + + "+": token.ADD, + "-": token.SUB, + "*": token.MUL, + "/": token.QUO, + "%": token.REM, + + "<<": token.SHL, + ">>": token.SHR, + + "==": token.EQL, + "!=": token.NEQ, + "<": token.LSS, + "<=": token.LEQ, + ">": token.GTR, + ">=": token.GEQ, +} + +func panicHandler(v *Value) { + switch p := recover().(type) { + case nil: + // nothing to do + case string: + *v = MakeString(p) + case error: + *v = MakeString(p.Error()) + default: + panic(p) + } +} + +func doOp(x Value, op token.Token, y Value) (z Value) { + defer panicHandler(&z) + + if x == nil { + return UnaryOp(op, y, -1) + } + + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return MakeBool(Compare(x, op, y)) + case token.SHL, token.SHR: + s, _ := Int64Val(y) + return Shift(x, op, uint(s)) + default: + return BinaryOp(x, op, y) + } +} diff --git a/go/types/api.go b/go/types/api.go new file mode 100644 index 0000000000..70920c0397 --- /dev/null +++ b/go/types/api.go @@ -0,0 +1,122 @@ +// 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. + +// Package types declares the data structures for representing +// Go types and implements typechecking of package files. +// +// WARNING: THE TYPES API IS SUBJECT TO CHANGE. +// +package types + +// Most correct programs should now be accepted by the types package, +// but there are several known bugs which permit incorrect programs to +// pass without errors. Please do not file issues against these for now +// since they are known already: +// +// BUG(gri): Method sets are computed loosely and don't distinguish between ptr vs non-pointer receivers. +// BUG(gri): Method expressions and method values work accidentally and may not be fully checked. +// BUG(gri): Conversions of constants only change the type, not the value (e.g., int(1.1) is wrong). +// BUG(gri): Some built-ins don't check parameters fully, yet (e.g. append). +// BUG(gri): Use of labels is not checked. +// BUG(gri): Unused variables and imports are not reported. +// BUG(gri): Interface vs non-interface comparisons are not correctly implemented. +// BUG(gri): Switch statements don't check correct use of 'fallthrough'. +// BUG(gri): Switch statements don't check duplicate cases for all types for which it is required. +// BUG(gri): Some built-ins may not be callable if in statement-context. +// BUG(gri): Duplicate declarations in different files may not be reported. +// BUG(gri): The type-checker assumes that the input *ast.Files were created by go/parser. + +// The API is still slightly in flux and the following changes are considered: +// +// API(gri): Provide accessors uniformly to all fields and do not export fields directly. +// API(gri): Provide scope information for all objects. +// API(gri): Provide position information for all objects. +// API(gri): The semantics of QualifiedIdent needs to be revisited. +// API(gri): The GcImporter should probably be in its own package - it is only one of possible importers. + +import ( + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// A Context specifies the supporting context for type checking. +// An empty Context is a ready-to-use default context. +type Context struct { + // If Error != nil, it is called with each error found + // during type checking. The error strings of errors with + // detailed position information are formatted as follows: + // filename:line:column: message + Error func(err error) + + // If Ident != nil, it is called for each identifier id + // denoting an Object in the files provided to Check, and + // obj is the denoted object. + // Ident is not called for fields and methods in struct or + // interface types or composite literals, or for blank (_) + // or dot (.) identifiers in dot-imports. + // TODO(gri) Consider making Fields and Methods ordinary + // Objects - than we could lift this restriction. + Ident func(id *ast.Ident, obj Object) + + // If Expr != nil, it is called exactly once for each expression x + // that is type-checked: typ is the expression type, and val is the + // value if x is constant, val is nil otherwise. + // + // If x is a literal value (constant, composite literal), typ is always + // the dynamic type of x (never an interface type). Otherwise, typ is x's + // static type (possibly an interface type). + // TODO(gri): Should this hold for all constant expressions? + Expr func(x ast.Expr, typ Type, val exact.Value) + + // If Import != nil, it is called for each imported package. + // Otherwise, GcImporter is called. + Import Importer + + // If Alignof != nil, it is called to determine the alignment + // of the given type. Otherwise DefaultAlignmentof is called. + // Alignof must implement the alignment guarantees required by + // the spec. + Alignof func(Type) int64 + + // If Offsetsof != nil, it is called to determine the offsets + // of the given struct fields, in bytes. Otherwise DefaultOffsetsof + // is called. Offsetsof must implement the offset guarantees + // required by the spec. + Offsetsof func(fields []*Field) []int64 + + // If Sizeof != nil, it is called to determine the size of the + // given type. Otherwise, DefaultSizeof is called. Sizeof must + // implement the size guarantees required by the spec. + Sizeof func(Type) int64 +} + +// An Importer resolves import paths to Package objects. +// The imports map records the packages already imported, +// indexed by package id (canonical import path). +// An Importer must determine the canonical import path and +// check the map to see if it is already present in the imports map. +// If so, the Importer can return the map entry. Otherwise, the +// Importer should load the package data for the given path into +// a new *Package, record pkg in the imports map, and then +// return pkg. +type Importer func(imports map[string]*Package, path string) (pkg *Package, err error) + +// Check resolves and typechecks a set of package files within the given +// context. It returns the package and the first error encountered, if +// any. If the context's Error handler is nil, Check terminates as soon +// as the first error is encountered; otherwise it continues until the +// entire package is checked. If there are errors, the package may be +// only partially type-checked, and the resulting package may be incomplete +// (missing objects, imports, etc.). +func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) { + return check(ctxt, fset, files) +} + +// Check is shorthand for ctxt.Check where ctxt is a default (empty) context. +func Check(fset *token.FileSet, files []*ast.File) (*Package, error) { + var ctxt Context + return ctxt.Check(fset, files) +} diff --git a/go/types/builtins.go b/go/types/builtins.go new file mode 100644 index 0000000000..5ede421910 --- /dev/null +++ b/go/types/builtins.go @@ -0,0 +1,463 @@ +// 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 implements typechecking of builtin function calls. + +package types + +import ( + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// TODO(gri): Several built-ins are missing assignment checks. As a result, +// non-constant shift arguments may not be properly type-checked. + +// builtin typechecks a built-in call. The built-in type is bin, and iota is the current +// value of iota or -1 if iota doesn't have a value in the current context. The result +// of the call is returned via x. If the call has type errors, the returned x is marked +// as invalid (x.mode == invalid). +// +func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota int) { + args := call.Args + id := bin.id + + // declare before goto's + var arg0 ast.Expr // first argument, if present + + // check argument count + n := len(args) + msg := "" + if n < bin.nargs { + msg = "not enough" + } else if !bin.isVariadic && n > bin.nargs { + msg = "too many" + } + if msg != "" { + check.invalidOp(call.Pos(), msg+" arguments for %s (expected %d, found %d)", call, bin.nargs, n) + goto Error + } + + // common case: evaluate first argument if present; + // if it is an expression, x has the expression value + if n > 0 { + arg0 = args[0] + switch id { + case _Make, _New, _Print, _Println, _Offsetof, _Trace: + // respective cases below do the work + default: + // argument must be an expression + check.expr(x, arg0, nil, iota) + if x.mode == invalid { + goto Error + } + } + } + + switch id { + case _Append: + if _, ok := underlying(x.typ).(*Slice); !ok { + check.invalidArg(x.pos(), "%s is not a typed slice", x) + goto Error + } + resultTyp := x.typ + for _, arg := range args[1:] { + check.expr(x, arg, nil, iota) + if x.mode == invalid { + goto Error + } + // TODO(gri) check assignability + } + x.mode = value + x.typ = resultTyp + + case _Cap, _Len: + mode := invalid + var val exact.Value + switch typ := implicitArrayDeref(underlying(x.typ)).(type) { + case *Basic: + if isString(typ) && id == _Len { + if x.mode == constant { + mode = constant + val = exact.MakeInt64(int64(len(exact.StringVal(x.val)))) + } else { + mode = value + } + } + + case *Array: + mode = value + // spec: "The expressions len(s) and cap(s) are constants + // if the type of s is an array or pointer to an array and + // the expression s does not contain channel receives or + // function calls; in this case s is not evaluated." + if !check.containsCallsOrReceives(arg0) { + mode = constant + val = exact.MakeInt64(typ.Len) + } + + case *Slice, *Chan: + mode = value + + case *Map: + if id == _Len { + mode = value + } + } + + if mode == invalid { + check.invalidArg(x.pos(), "%s for %s", x, bin.name) + goto Error + } + x.mode = mode + x.typ = Typ[Int] + x.val = val + + case _Close: + ch, ok := underlying(x.typ).(*Chan) + if !ok { + check.invalidArg(x.pos(), "%s is not a channel", x) + goto Error + } + if ch.Dir&ast.SEND == 0 { + check.invalidArg(x.pos(), "%s must not be a receive-only channel", x) + goto Error + } + x.mode = novalue + + case _Complex: + if !check.complexArg(x) { + goto Error + } + + var y operand + check.expr(&y, args[1], nil, iota) + if y.mode == invalid { + goto Error + } + if !check.complexArg(&y) { + goto Error + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + goto Error + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + goto Error + } + + if !IsIdentical(x.typ, y.typ) { + check.invalidArg(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + goto Error + } + + typ := underlying(x.typ).(*Basic) + if x.mode == constant && y.mode == constant { + x.val = exact.BinaryOp(x.val, token.ADD, exact.MakeImag(y.val)) + } else { + x.mode = value + } + + switch typ.Kind { + case Float32: + x.typ = Typ[Complex64] + case Float64: + x.typ = Typ[Complex128] + case UntypedInt, UntypedRune, UntypedFloat: + if x.mode == constant { + typ = defaultType(typ).(*Basic) + x.typ = Typ[UntypedComplex] + } else { + // untyped but not constant; probably because one + // operand is a non-constant shift of untyped lhs + typ = Typ[Float64] + x.typ = Typ[Complex128] + } + default: + check.invalidArg(x.pos(), "float32 or float64 arguments expected") + goto Error + } + + if x.mode != constant { + // The arguments have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + // (If the result is constant, the arguments are never + // materialized and there is nothing to do.) + check.updateExprType(args[0], typ, true) + check.updateExprType(args[1], typ, true) + } + + case _Copy: + var y operand + check.expr(&y, args[1], nil, iota) + if y.mode == invalid { + goto Error + } + + var dst, src Type + if t, ok := underlying(x.typ).(*Slice); ok { + dst = t.Elt + } + switch t := underlying(y.typ).(type) { + case *Basic: + if isString(y.typ) { + src = Typ[Byte] + } + case *Slice: + src = t.Elt + } + + if dst == nil || src == nil { + check.invalidArg(x.pos(), "copy expects slice arguments; found %s and %s", x, &y) + goto Error + } + + if !IsIdentical(dst, src) { + check.invalidArg(x.pos(), "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src) + goto Error + } + + x.mode = value + x.typ = Typ[Int] + + case _Delete: + m, ok := underlying(x.typ).(*Map) + if !ok { + check.invalidArg(x.pos(), "%s is not a map", x) + goto Error + } + check.expr(x, args[1], nil, iota) + if x.mode == invalid { + goto Error + } + if !x.isAssignable(check.ctxt, m.Key) { + check.invalidArg(x.pos(), "%s is not assignable to %s", x, m.Key) + goto Error + } + x.mode = novalue + + case _Imag, _Real: + if !isComplex(x.typ) { + check.invalidArg(x.pos(), "%s must be a complex number", x) + goto Error + } + if x.mode == constant { + if id == _Real { + x.val = exact.Real(x.val) + } else { + x.val = exact.Imag(x.val) + } + } else { + x.mode = value + } + k := Invalid + switch underlying(x.typ).(*Basic).Kind { + case Complex64: + k = Float32 + case Complex128: + k = Float64 + case UntypedComplex: + k = UntypedFloat + default: + unreachable() + } + x.typ = Typ[k] + + case _Make: + resultTyp := check.typ(arg0, false) + if resultTyp == Typ[Invalid] { + goto Error + } + var min int // minimum number of arguments + switch underlying(resultTyp).(type) { + case *Slice: + min = 2 + case *Map, *Chan: + min = 1 + default: + check.invalidArg(arg0.Pos(), "cannot make %s; type must be slice, map, or channel", arg0) + goto Error + } + if n := len(args); n < min || min+1 < n { + check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n) + goto Error + } + var sizes []int64 // constant integer arguments, if any + for _, arg := range args[1:] { + if s, ok := check.index(arg, -1, iota); ok && s >= 0 { + sizes = append(sizes, s) + } + } + if len(sizes) == 2 && sizes[0] > sizes[1] { + check.invalidArg(args[1].Pos(), "length and capacity swapped") + // safe to continue + } + x.mode = variable + x.typ = resultTyp + + case _New: + resultTyp := check.typ(arg0, false) + if resultTyp == Typ[Invalid] { + goto Error + } + x.mode = variable + x.typ = &Pointer{Base: resultTyp} + + case _Panic: + x.mode = novalue + + case _Print, _Println: + for _, arg := range args { + check.expr(x, arg, nil, -1) + if x.mode == invalid { + goto Error + } + } + x.mode = novalue + + case _Recover: + x.mode = value + x.typ = new(Interface) + + case _Alignof: + x.mode = constant + x.val = exact.MakeInt64(check.ctxt.alignof(x.typ)) + x.typ = Typ[Uintptr] + + case _Offsetof: + arg, ok := unparen(arg0).(*ast.SelectorExpr) + if !ok { + check.invalidArg(arg0.Pos(), "%s is not a selector expression", arg0) + goto Error + } + check.expr(x, arg.X, nil, -1) + if x.mode == invalid { + goto Error + } + sel := arg.Sel.Name + res := lookupField(x.typ, QualifiedName{check.pkg, arg.Sel.Name}) + if res.index == nil { + check.invalidArg(x.pos(), "%s has no single field %s", x, sel) + goto Error + } + offs := check.ctxt.offsetof(deref(x.typ), res.index) + if offs < 0 { + check.invalidArg(x.pos(), "field %s is embedded via a pointer in %s", sel, x) + goto Error + } + x.mode = constant + x.val = exact.MakeInt64(offs) + x.typ = Typ[Uintptr] + + case _Sizeof: + x.mode = constant + x.val = exact.MakeInt64(check.ctxt.sizeof(x.typ)) + x.typ = Typ[Uintptr] + + case _Assert: + // assert(pred) causes a typechecker error if pred is false. + // The result of assert is the value of pred if there is no error. + // Note: assert is only available in self-test mode. + if x.mode != constant || !isBoolean(x.typ) { + check.invalidArg(x.pos(), "%s is not a boolean constant", x) + goto Error + } + if x.val.Kind() != exact.Bool { + check.errorf(x.pos(), "internal error: value of %s should be a boolean constant", x) + goto Error + } + if !exact.BoolVal(x.val) { + check.errorf(call.Pos(), "%s failed", call) + // compile-time assertion failure - safe to continue + } + + case _Trace: + // trace(x, y, z, ...) dumps the positions, expressions, and + // values of its arguments. The result of trace is the value + // of the first argument. + // Note: trace is only available in self-test mode. + if len(args) == 0 { + check.dump("%s: trace() without arguments", call.Pos()) + x.mode = novalue + x.expr = call + return + } + var t operand + x1 := x + for _, arg := range args { + check.rawExpr(x1, arg, nil, iota, true) // permit trace for types, e.g.: new(trace(T)) + check.dump("%s: %s", x1.pos(), x1) + x1 = &t // use incoming x only for first argument + } + + default: + check.invalidAST(call.Pos(), "unknown builtin id %d", id) + goto Error + } + + x.expr = call + return + +Error: + x.mode = invalid + x.expr = call +} + +// implicitArrayDeref returns A if typ is of the form *A and A is an array; +// otherwise it returns typ. +// +func implicitArrayDeref(typ Type) Type { + if p, ok := typ.(*Pointer); ok { + if a, ok := underlying(p.Base).(*Array); ok { + return a + } + } + return typ +} + +// containsCallsOrReceives reports if x contains function calls or channel receives. +// Expects that x was type-checked already. +// +func (check *checker) containsCallsOrReceives(x ast.Expr) (found bool) { + ast.Inspect(x, func(x ast.Node) bool { + switch x := x.(type) { + case *ast.CallExpr: + // calls and conversions look the same + if !check.conversions[x] { + found = true + } + case *ast.UnaryExpr: + if x.Op == token.ARROW { + found = true + } + } + return !found // no need to continue if found + }) + return +} + +// unparen removes any parentheses surrounding an expression and returns +// the naked expression. +// +func unparen(x ast.Expr) ast.Expr { + if p, ok := x.(*ast.ParenExpr); ok { + return unparen(p.X) + } + return x +} + +func (check *checker) complexArg(x *operand) bool { + t, _ := underlying(x.typ).(*Basic) + if t != nil && (t.Info&IsFloat != 0 || t.Kind == UntypedInt || t.Kind == UntypedRune) { + return true + } + check.invalidArg(x.pos(), "%s must be a float32, float64, or an untyped non-complex numeric constant", x) + return false +} diff --git a/go/types/check.go b/go/types/check.go new file mode 100644 index 0000000000..5a8cf5fb24 --- /dev/null +++ b/go/types/check.go @@ -0,0 +1,511 @@ +// Copyright 2011 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 implements the Check function, which typechecks a package. + +package types + +import ( + "fmt" + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// debugging support +const ( + debug = true // leave on during development + trace = false // turn on for detailed type resolution traces +) + +// exprInfo stores type and constant value for an untyped expression. +type exprInfo struct { + isLhs bool // expression is lhs operand of a shift with delayed type check + typ *Basic + val exact.Value // constant value; or nil (if not a constant) +} + +// A checker is an instance of the type checker. +type checker struct { + ctxt *Context + fset *token.FileSet + files []*ast.File + + // lazily initialized + pkg *Package // current package + firsterr error // first error encountered + idents map[*ast.Ident]Object // maps identifiers to their unique object + objects map[*ast.Object]Object // maps *ast.Objects to their unique object + initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations + methods map[*TypeName]*Scope // maps type names to associated methods + conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls) + untyped map[ast.Expr]exprInfo // map of expressions without final type + + // functions + funclist []function // list of functions/methods with correct signatures and non-empty bodies + funcsig *Signature // signature of currently typechecked function + pos []token.Pos // stack of expr positions; debugging support, used if trace is set +} + +func (check *checker) register(id *ast.Ident, obj Object) { + // When an expression is evaluated more than once (happens + // in rare cases, e.g. for statement expressions, see + // comment in stmt.go), the object has been registered + // before. Don't do anything in that case. + if alt := check.idents[id]; alt != nil { + assert(alt == obj) + return + } + check.idents[id] = obj + if f := check.ctxt.Ident; f != nil { + f(id, obj) + } +} + +// lookup returns the unique Object denoted by the identifier. +// For identifiers without assigned *ast.Object, it uses the +// checker.idents map; for identifiers with an *ast.Object it +// uses the checker.objects map. +// +// TODO(gri) Once identifier resolution is done entirely by +// the typechecker, only the idents map is needed. +// +func (check *checker) lookup(ident *ast.Ident) Object { + obj := check.idents[ident] + astObj := ident.Obj + + if obj != nil { + assert(astObj == nil || check.objects[astObj] == nil || check.objects[astObj] == obj) + return obj + } + + if astObj == nil { + return nil + } + + if obj = check.objects[astObj]; obj == nil { + obj = newObj(check.pkg, astObj) + check.objects[astObj] = obj + } + check.register(ident, obj) + + return obj +} + +type function struct { + obj *Func // for debugging/tracing only + sig *Signature + body *ast.BlockStmt +} + +// later adds a function with non-empty body to the list of functions +// that need to be processed after all package-level declarations +// are typechecked. +// +func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) { + // functions implemented elsewhere (say in assembly) have no body + if body != nil { + check.funclist = append(check.funclist, function{f, sig, body}) + } +} + +func (check *checker) declareIdent(scope *Scope, ident *ast.Ident, obj Object) { + assert(check.lookup(ident) == nil) // identifier already declared or resolved + check.register(ident, obj) + if ident.Name != "_" { + if alt := scope.Insert(obj); alt != nil { + prevDecl := "" + if pos := alt.GetPos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tprevious declaration at %s", check.fset.Position(pos)) + } + check.errorf(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl)) + } + } +} + +func (check *checker) valueSpec(pos token.Pos, obj Object, lhs []*ast.Ident, spec *ast.ValueSpec, iota int) { + if len(lhs) == 0 { + check.invalidAST(pos, "missing lhs in declaration") + return + } + + // determine type for all of lhs, if any + // (but only set it for the object we typecheck!) + var typ Type + if spec.Type != nil { + typ = check.typ(spec.Type, false) + } + + // len(lhs) > 0 + rhs := spec.Values + if len(lhs) == len(rhs) { + // check only lhs and rhs corresponding to obj + var l, r ast.Expr + for i, name := range lhs { + if check.lookup(name) == obj { + l = lhs[i] + r = rhs[i] + break + } + } + assert(l != nil) + switch obj := obj.(type) { + case *Const: + obj.Type = typ + case *Var: + obj.Type = typ + default: + unreachable() + } + check.assign1to1(l, r, nil, true, iota) + return + } + + // there must be a type or initialization expressions + if typ == nil && len(rhs) == 0 { + check.invalidAST(pos, "missing type or initialization expression") + typ = Typ[Invalid] + } + + // if we have a type, mark all of lhs + if typ != nil { + for _, name := range lhs { + switch obj := check.lookup(name).(type) { + case *Const: + obj.Type = typ + case *Var: + obj.Type = typ + default: + unreachable() + } + } + } + + // check initial values, if any + if len(rhs) > 0 { + // TODO(gri) should try to avoid this conversion + lhx := make([]ast.Expr, len(lhs)) + for i, e := range lhs { + lhx[i] = e + } + check.assignNtoM(lhx, rhs, true, iota) + } +} + +// object typechecks an object by assigning it a type. +// +func (check *checker) object(obj Object, cycleOk bool) { + switch obj := obj.(type) { + case *Package: + // nothing to do + + case *Const: + if obj.Type != nil { + return // already checked + } + // The obj.Val field for constants is initialized to its respective + // iota value (type int) by the parser. + // If the object's type is Typ[Invalid], the object value is ignored. + // If the object's type is valid, the object value must be a legal + // constant value; it may be nil to indicate that we don't know the + // value of the constant (e.g., in: "const x = float32("foo")" we + // know that x is a constant and has type float32, but we don't + // have a value due to the error in the conversion). + if obj.visited { + check.errorf(obj.GetPos(), "illegal cycle in initialization of constant %s", obj.Name) + obj.Type = Typ[Invalid] + return + } + obj.visited = true + spec := obj.spec + iota, ok := exact.Int64Val(obj.Val) + assert(ok) + obj.Val = exact.MakeUnknown() + // determine spec for type and initialization expressions + init := spec + if len(init.Values) == 0 { + init = check.initspecs[spec] + } + check.valueSpec(spec.Pos(), obj, spec.Names, init, int(iota)) + + case *Var: + if obj.Type != nil { + return // already checked + } + if obj.visited { + check.errorf(obj.GetPos(), "illegal cycle in initialization of variable %s", obj.Name) + obj.Type = Typ[Invalid] + return + } + obj.visited = true + switch d := obj.decl.(type) { + case *ast.Field: + unreachable() // function parameters are always typed when collected + case *ast.ValueSpec: + check.valueSpec(d.Pos(), obj, d.Names, d, 0) + case *ast.AssignStmt: + unreachable() // assign1to1 sets the type for failing short var decls + default: + unreachable() // see also function newObj + } + + case *TypeName: + if obj.Type != nil { + return // already checked + } + typ := &NamedType{Obj: obj} + obj.Type = typ // "mark" object so recursion terminates + typ.Underlying = underlying(check.typ(obj.spec.Type, cycleOk)) + // typecheck associated method signatures + if scope := check.methods[obj]; scope != nil { + switch t := typ.Underlying.(type) { + case *Struct: + // struct fields must not conflict with methods + for _, f := range t.Fields { + if m := scope.Lookup(f.Name); m != nil { + check.errorf(m.GetPos(), "type %s has both field and method named %s", obj.Name, f.Name) + // ok to continue + } + } + case *Interface: + // methods cannot be associated with an interface type + for _, m := range scope.Entries { + recv := m.(*Func).decl.Recv.List[0].Type + check.errorf(recv.Pos(), "invalid receiver type %s (%s is an interface type)", obj.Name, obj.Name) + // ok to continue + } + } + // typecheck method signatures + var methods []*Method + for _, obj := range scope.Entries { + m := obj.(*Func) + sig := check.typ(m.decl.Type, cycleOk).(*Signature) + params, _ := check.collectParams(m.decl.Recv, false) + sig.Recv = params[0] // the parser/assocMethod ensure there is exactly one parameter + m.Type = sig + methods = append(methods, &Method{QualifiedName{check.pkg, m.Name}, sig}) + check.later(m, sig, m.decl.Body) + } + typ.Methods = methods + delete(check.methods, obj) // we don't need this scope anymore + } + + case *Func: + if obj.Type != nil { + return // already checked + } + fdecl := obj.decl + // methods are typechecked when their receivers are typechecked + if fdecl.Recv == nil { + sig := check.typ(fdecl.Type, cycleOk).(*Signature) + if obj.Name == "init" && (len(sig.Params) != 0 || len(sig.Results) != 0) { + check.errorf(fdecl.Pos(), "func init must have no arguments and no return values") + // ok to continue + } + obj.Type = sig + check.later(obj, sig, fdecl.Body) + } + + default: + unreachable() + } +} + +// assocInitvals associates "inherited" initialization expressions +// with the corresponding *ast.ValueSpec in the check.initspecs map +// for constant declarations without explicit initialization expressions. +// +func (check *checker) assocInitvals(decl *ast.GenDecl) { + var last *ast.ValueSpec + for _, s := range decl.Specs { + if s, ok := s.(*ast.ValueSpec); ok { + if len(s.Values) > 0 { + last = s + } else { + check.initspecs[s] = last + } + } + } + if last == nil { + check.invalidAST(decl.Pos(), "no initialization values provided") + } +} + +// assocMethod associates a method declaration with the respective +// receiver base type. meth.Recv must exist. +// +func (check *checker) assocMethod(meth *ast.FuncDecl) { + // The receiver type is one of the following (enforced by parser): + // - *ast.Ident + // - *ast.StarExpr{*ast.Ident} + // - *ast.BadExpr (parser error) + typ := meth.Recv.List[0].Type + if ptr, ok := typ.(*ast.StarExpr); ok { + typ = ptr.X + } + // determine receiver base type name + ident, ok := typ.(*ast.Ident) + if !ok { + // not an identifier - parser reported error already + return // ignore this method + } + // determine receiver base type object + var tname *TypeName + if obj := check.lookup(ident); obj != nil { + obj, ok := obj.(*TypeName) + if !ok { + check.errorf(ident.Pos(), "%s is not a type", ident.Name) + return // ignore this method + } + if obj.spec == nil { + check.errorf(ident.Pos(), "cannot define method on non-local type %s", ident.Name) + return // ignore this method + } + tname = obj + } else { + // identifier not declared/resolved - parser reported error already + return // ignore this method + } + // declare method in receiver base type scope + scope := check.methods[tname] + if scope == nil { + scope = new(Scope) + check.methods[tname] = scope + } + check.declareIdent(scope, meth.Name, &Func{Pkg: check.pkg, Name: meth.Name.Name, decl: meth}) +} + +func (check *checker) decl(decl ast.Decl) { + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + case *ast.GenDecl: + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ImportSpec: + // nothing to do (handled by check.resolve) + case *ast.ValueSpec: + for _, name := range s.Names { + check.object(check.lookup(name), false) + } + case *ast.TypeSpec: + check.object(check.lookup(s.Name), false) + default: + check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) + } + } + case *ast.FuncDecl: + // methods are checked when their respective base types are checked + if d.Recv != nil { + return + } + obj := check.lookup(d.Name) + // Initialization functions don't have an object associated with them + // since they are not in any scope. Create a dummy object for them. + if d.Name.Name == "init" { + assert(obj == nil) // all other functions should have an object + obj = &Func{Pkg: check.pkg, Name: d.Name.Name, decl: d} + check.register(d.Name, obj) + } + check.object(obj, false) + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } +} + +// A bailout panic is raised to indicate early termination. +type bailout struct{} + +func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, err error) { + // initialize checker + check := checker{ + ctxt: ctxt, + fset: fset, + files: files, + idents: make(map[*ast.Ident]Object), + objects: make(map[*ast.Object]Object), + initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec), + methods: make(map[*TypeName]*Scope), + conversions: make(map[*ast.CallExpr]bool), + untyped: make(map[ast.Expr]exprInfo), + } + + // set results and handle panics + defer func() { + pkg = check.pkg + switch p := recover().(type) { + case nil, bailout: + // normal return or early exit + err = check.firsterr + default: + // unexpected panic: don't crash clients + if debug { + check.dump("INTERNAL PANIC: %v", p) + panic(p) + } + // TODO(gri) add a test case for this scenario + err = fmt.Errorf("types internal error: %v", p) + } + }() + + // resolve identifiers + imp := ctxt.Import + if imp == nil { + imp = GcImport + } + methods := check.resolve(imp) + + // associate methods with types + for _, m := range methods { + check.assocMethod(m) + } + + // typecheck all declarations + for _, f := range check.files { + for _, d := range f.Decls { + check.decl(d) + } + } + + // typecheck all function/method bodies + // (funclist may grow when checking statements - do not use range clause!) + for i := 0; i < len(check.funclist); i++ { + f := check.funclist[i] + if trace { + s := "" + if f.obj != nil { + s = f.obj.Name + } + fmt.Println("---", s) + } + check.funcsig = f.sig + check.stmtList(f.body.List) + if len(f.sig.Results) > 0 && f.body != nil && !check.isTerminating(f.body, "") { + check.errorf(f.body.Rbrace, "missing return") + } + } + + // remaining untyped expressions must indeed be untyped + if debug { + for x, info := range check.untyped { + if !isUntyped(info.typ) { + check.dump("%s: %s (type %s) is not untyped", x.Pos(), x, info.typ) + panic(0) + } + } + } + + // notify client of any untyped types left + // TODO(gri) Consider doing this before and + // after function body checking for smaller + // map size and more immediate feedback. + if ctxt.Expr != nil { + for x, info := range check.untyped { + ctxt.Expr(x, info.typ, info.val) + } + } + + return +} diff --git a/go/types/check_test.go b/go/types/check_test.go new file mode 100644 index 0000000000..2ee7f6eef8 --- /dev/null +++ b/go/types/check_test.go @@ -0,0 +1,244 @@ +// Copyright 2011 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 implements a typechecker test harness. The packages specified +// in tests are typechecked. Error messages reported by the typechecker are +// compared against the error messages expected in the test files. +// +// Expected errors are indicated in the test files by putting a comment +// of the form /* ERROR "rx" */ immediately following an offending token. +// The harness will verify that an error matching the regular expression +// rx is reported at that source position. Consecutive comments may be +// used to indicate multiple errors for the same token position. +// +// For instance, the following test file indicates that a "not declared" +// error should be reported for the undeclared variable x: +// +// package p +// func f() { +// _ = x /* ERROR "not declared" */ + 1 +// } + +package types + +import ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "io/ioutil" + "os" + "regexp" + "testing" +) + +var listErrors = flag.Bool("list", false, "list errors") + +// The test filenames do not end in .go so that they are invisible +// to gofmt since they contain comments that must not change their +// positions relative to surrounding tokens. + +var tests = []struct { + name string + files []string +}{ + {"decls0", []string{"testdata/decls0.src"}}, + {"decls1", []string{"testdata/decls1.src"}}, + {"decls2", []string{"testdata/decls2a.src", "testdata/decls2b.src"}}, + {"decls3", []string{"testdata/decls3.src"}}, + {"const0", []string{"testdata/const0.src"}}, + {"expr0", []string{"testdata/expr0.src"}}, + {"expr1", []string{"testdata/expr1.src"}}, + {"expr2", []string{"testdata/expr2.src"}}, + {"expr3", []string{"testdata/expr3.src"}}, + {"shifts", []string{"testdata/shifts.src"}}, + {"builtins", []string{"testdata/builtins.src"}}, + {"conversions", []string{"testdata/conversions.src"}}, + {"stmt0", []string{"testdata/stmt0.src"}}, + {"stmt1", []string{"testdata/stmt1.src"}}, +} + +var fset = token.NewFileSet() + +// Positioned errors are of the form filename:line:column: message . +var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) + +// splitError splits an error's error message into a position string +// and the actual error message. If there's no position information, +// pos is the empty string, and msg is the entire error message. +// +func splitError(err error) (pos, msg string) { + msg = err.Error() + if m := posMsgRx.FindStringSubmatch(msg); len(m) == 3 { + pos = m[1] + msg = m[2] + } + return +} + +func parseFiles(t *testing.T, testname string, filenames []string) ([]*ast.File, []error) { + var files []*ast.File + var errlist []error + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors|parser.AllErrors) + if file == nil { + t.Fatalf("%s: could not parse file %s", testname, filename) + } + files = append(files, file) + if err != nil { + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errlist = append(errlist, err) + } + } else { + errlist = append(errlist, err) + } + } + } + return files, errlist +} + +// ERROR comments must be of the form /* ERROR "rx" */ and rx is +// a regular expression that matches the expected error message. +// +var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) + +// errMap collects the regular expressions of ERROR comments found +// in files and returns them as a map of error positions to error messages. +// +func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { + // map of position strings to lists of error message patterns + errmap := make(map[string][]string) + + for _, file := range files { + filename := fset.Position(file.Package).Filename + src, err := ioutil.ReadFile(filename) + if err != nil { + t.Fatalf("%s: could not read %s", testname, filename) + } + + var s scanner.Scanner + s.Init(fset.AddFile(filename, fset.Base(), len(src)), src, nil, scanner.ScanComments) + var prev string // position string of last non-comment, non-semicolon token + + scanFile: + for { + pos, tok, lit := s.Scan() + switch tok { + case token.EOF: + break scanFile + case token.COMMENT: + if s := errRx.FindStringSubmatch(lit); len(s) == 2 { + errmap[prev] = append(errmap[prev], s[1]) + } + case token.SEMICOLON: + // ignore automatically inserted semicolon + if lit == "\n" { + continue scanFile + } + fallthrough + default: + prev = fset.Position(pos).String() + } + } + } + + return errmap +} + +func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { + for _, err := range errlist { + pos, gotMsg := splitError(err) + list := errmap[pos] + index := -1 // list index of matching message, if any + // we expect one of the messages in list to match the error at pos + for i, wantRx := range list { + rx, err := regexp.Compile(wantRx) + if err != nil { + t.Errorf("%s: %v", pos, err) + continue + } + if rx.MatchString(gotMsg) { + index = i + break + } + } + if index >= 0 { + // eliminate from list + if n := len(list) - 1; n > 0 { + // not the last entry - swap in last element and shorten list by 1 + list[index] = list[n] + errmap[pos] = list[:n] + } else { + // last entry - remove list from map + delete(errmap, pos) + } + } else { + t.Errorf("%s: no error expected: %q", pos, gotMsg) + } + } +} + +func checkFiles(t *testing.T, testname string, testfiles []string) { + // parse files and collect parser errors + files, errlist := parseFiles(t, testname, testfiles) + + // typecheck and collect typechecker errors + var ctxt Context + ctxt.Error = func(err error) { errlist = append(errlist, err) } + ctxt.Check(fset, files) + + if *listErrors { + t.Errorf("--- %s: %d errors found:", testname, len(errlist)) + for _, err := range errlist { + t.Error(err) + } + return + } + + // match and eliminate errors; + // we are expecting the following errors + errmap := errMap(t, testname, files) + eliminate(t, errmap, errlist) + + // there should be no expected errors left + if len(errmap) > 0 { + t.Errorf("--- %s: %d source positions with expected (but not reported) errors:", testname, len(errmap)) + for pos, list := range errmap { + for _, rx := range list { + t.Errorf("%s: %q", pos, rx) + } + } + } +} + +var testBuiltinsDeclared = false + +func TestCheck(t *testing.T) { + // Declare builtins for testing. + // Not done in an init func to avoid an init race with + // the construction of the Universe var. + if !testBuiltinsDeclared { + testBuiltinsDeclared = true + // Pkg == nil for Universe objects + def(&Func{Name: "assert", Type: &builtin{_Assert, "assert", 1, false, true}}) + def(&Func{Name: "trace", Type: &builtin{_Trace, "trace", 0, true, true}}) + } + + // For easy debugging w/o changing the testing code, + // if there is a local test file, only test that file. + const testfile = "testdata/test.go" + if fi, err := os.Stat(testfile); err == nil && !fi.IsDir() { + fmt.Printf("WARNING: Testing only %s (remove it to run all tests)\n", testfile) + checkFiles(t, testfile, []string{testfile}) + return + } + + // Otherwise, run all the tests. + for _, test := range tests { + checkFiles(t, test.name, test.files) + } +} diff --git a/go/types/conversions.go b/go/types/conversions.go new file mode 100644 index 0000000000..c8433b425c --- /dev/null +++ b/go/types/conversions.go @@ -0,0 +1,158 @@ +// 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 implements typechecking of conversions. + +package types + +import ( + "go/ast" + + "code.google.com/p/go.tools/go/exact" +) + +// conversion typechecks the type conversion conv to type typ. iota is the current +// value of iota or -1 if iota doesn't have a value in the current context. The result +// of the conversion is returned via x. If the conversion has type errors, the returned +// x is marked as invalid (x.mode == invalid). +// +func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota int) { + // all conversions have one argument + if len(conv.Args) != 1 { + check.invalidOp(conv.Pos(), "%s conversion requires exactly one argument", conv) + goto Error + } + + // evaluate argument + check.expr(x, conv.Args[0], nil, iota) + if x.mode == invalid { + goto Error + } + + if x.mode == constant && isConstType(typ) { + // constant conversion + typ := underlying(typ).(*Basic) + // For now just implement string(x) where x is an integer, + // as a temporary work-around for issue 4982, which is a + // common issue. + if typ.Kind == String { + switch { + case x.isInteger(): + codepoint := int64(-1) + if i, ok := exact.Int64Val(x.val); ok { + codepoint = i + } + // If codepoint < 0 the absolute value is too large (or unknown) for + // conversion. This is the same as converting any other out-of-range + // value - let string(codepoint) do the work. + x.val = exact.MakeString(string(codepoint)) + case isString(x.typ): + // nothing to do + default: + goto ErrorMsg + } + } + // TODO(gri) verify the remaining conversions. + } else { + // non-constant conversion + if !x.isConvertible(check.ctxt, typ) { + goto ErrorMsg + } + x.mode = value + } + + // the conversion argument types are final; for now we just use x.typ + // TODO(gri) Fix this. For untyped constants, the type should be typ. + check.updateExprType(x.expr, x.typ, true) + + check.conversions[conv] = true // for cap/len checking + x.expr = conv + x.typ = typ + return + +ErrorMsg: + check.invalidOp(conv.Pos(), "cannot convert %s to %s", x, typ) +Error: + x.mode = invalid + x.expr = conv +} + +func (x *operand) isConvertible(ctxt *Context, T Type) bool { + // "x is assignable to T" + if x.isAssignable(ctxt, T) { + return true + } + + // "x's type and T have identical underlying types" + V := x.typ + Vu := underlying(V) + Tu := underlying(T) + if IsIdentical(Vu, Tu) { + return true + } + + // "x's type and T are unnamed pointer types and their pointer base types have identical underlying types" + if V, ok := V.(*Pointer); ok { + if T, ok := T.(*Pointer); ok { + if IsIdentical(underlying(V.Base), underlying(T.Base)) { + return true + } + } + } + + // "x's type and T are both integer or floating point types" + if (isInteger(V) || isFloat(V)) && (isInteger(T) || isFloat(T)) { + return true + } + + // "x's type and T are both complex types" + if isComplex(V) && isComplex(T) { + return true + } + + // "x is an integer or a slice of bytes or runes and T is a string type" + if (isInteger(V) || isBytesOrRunes(Vu)) && isString(T) { + return true + } + + // "x is a string and T is a slice of bytes or runes" + if isString(V) && isBytesOrRunes(Tu) { + return true + } + + // package unsafe: + // "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer" + if (isPointer(Vu) || isUintptr(Vu)) && isUnsafePointer(T) { + return true + } + // "and vice versa" + if isUnsafePointer(V) && (isPointer(Tu) || isUintptr(Tu)) { + return true + } + + return false +} + +func isUintptr(typ Type) bool { + t, ok := typ.(*Basic) + return ok && t.Kind == Uintptr +} + +func isUnsafePointer(typ Type) bool { + t, ok := typ.(*Basic) + return ok && t.Kind == UnsafePointer +} + +func isPointer(typ Type) bool { + _, ok := typ.(*Pointer) + return ok +} + +func isBytesOrRunes(typ Type) bool { + if s, ok := typ.(*Slice); ok { + t, ok := underlying(s.Elt).(*Basic) + return ok && (t.Kind == Byte || t.Kind == Rune) + } + return false +} diff --git a/go/types/errors.go b/go/types/errors.go new file mode 100644 index 0000000000..62ee547917 --- /dev/null +++ b/go/types/errors.go @@ -0,0 +1,335 @@ +// 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 implements various error reporters. + +package types + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" +) + +// TODO(gri) eventually assert and unimplemented should disappear. +func assert(p bool) { + if !p { + panic("assertion failed") + } +} + +func unreachable() { + panic("unreachable") +} + +func (check *checker) printTrace(format string, args []interface{}) { + const dots = ". . . . . . . . . . . . . . . . . . . . " + n := len(check.pos) - 1 + i := 3 * n + for i > len(dots) { + fmt.Print(dots) + i -= len(dots) + } + // i <= len(dots) + fmt.Printf("%s:\t", check.fset.Position(check.pos[n])) + fmt.Print(dots[0:i]) + fmt.Println(check.formatMsg(format, args)) +} + +func (check *checker) trace(pos token.Pos, format string, args ...interface{}) { + check.pos = append(check.pos, pos) + check.printTrace(format, args) +} + +func (check *checker) untrace(format string, args ...interface{}) { + if len(format) > 0 { + check.printTrace(format, args) + } + check.pos = check.pos[:len(check.pos)-1] +} + +func (check *checker) formatMsg(format string, args []interface{}) string { + for i, arg := range args { + switch a := arg.(type) { + case token.Pos: + args[i] = check.fset.Position(a).String() + case ast.Expr: + args[i] = exprString(a) + case Type: + args[i] = typeString(a) + case operand: + panic("internal error: should always pass *operand") + } + } + return fmt.Sprintf(format, args...) +} + +// dump is only needed for debugging +func (check *checker) dump(format string, args ...interface{}) { + fmt.Println(check.formatMsg(format, args)) +} + +func (check *checker) err(err error) { + if check.firsterr == nil { + check.firsterr = err + } + f := check.ctxt.Error + if f == nil { + panic(bailout{}) // report only first error + } + f(err) +} + +func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) { + check.err(fmt.Errorf("%s: %s", check.fset.Position(pos), check.formatMsg(format, args))) +} + +func (check *checker) invalidAST(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid AST: "+format, args...) +} + +func (check *checker) invalidArg(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid argument: "+format, args...) +} + +func (check *checker) invalidOp(pos token.Pos, format string, args ...interface{}) { + check.errorf(pos, "invalid operation: "+format, args...) +} + +// exprString returns a (simplified) string representation for an expression. +func exprString(expr ast.Expr) string { + var buf bytes.Buffer + writeExpr(&buf, expr) + return buf.String() +} + +// TODO(gri) Need to merge with typeString since some expressions are types (try: ([]int)(a)) +func writeExpr(buf *bytes.Buffer, expr ast.Expr) { + switch x := expr.(type) { + case *ast.Ident: + buf.WriteString(x.Name) + + case *ast.BasicLit: + buf.WriteString(x.Value) + + case *ast.FuncLit: + buf.WriteString("(func literal)") + + case *ast.CompositeLit: + buf.WriteString("(composite literal)") + + case *ast.ParenExpr: + buf.WriteByte('(') + writeExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + writeExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr: + writeExpr(buf, x.X) + buf.WriteByte('[') + writeExpr(buf, x.Index) + buf.WriteByte(']') + + case *ast.SliceExpr: + writeExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + writeExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + writeExpr(buf, x.High) + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + writeExpr(buf, x.X) + buf.WriteString(".(...)") + + case *ast.CallExpr: + writeExpr(buf, x.Fun) + buf.WriteByte('(') + for i, arg := range x.Args { + if i > 0 { + buf.WriteString(", ") + } + writeExpr(buf, arg) + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + writeExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + writeExpr(buf, x.X) + + case *ast.BinaryExpr: + // The AST preserves source-level parentheses so there is + // no need to introduce parentheses here for correctness. + writeExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + writeExpr(buf, x.Y) + + default: + fmt.Fprintf(buf, "", x) + } +} + +// typeString returns a string representation for typ. +func typeString(typ Type) string { + var buf bytes.Buffer + writeType(&buf, typ) + return buf.String() +} + +func writeParams(buf *bytes.Buffer, params []*Var, isVariadic bool) { + buf.WriteByte('(') + for i, par := range params { + if i > 0 { + buf.WriteString(", ") + } + if par.Name != "" { + buf.WriteString(par.Name) + buf.WriteByte(' ') + } + if isVariadic && i == len(params)-1 { + buf.WriteString("...") + } + writeType(buf, par.Type) + } + buf.WriteByte(')') +} + +func writeSignature(buf *bytes.Buffer, sig *Signature) { + writeParams(buf, sig.Params, sig.IsVariadic) + if len(sig.Results) == 0 { + // no result + return + } + + buf.WriteByte(' ') + if len(sig.Results) == 1 && sig.Results[0].Name == "" { + // single unnamed result + writeType(buf, sig.Results[0].Type.(Type)) + return + } + + // multiple or named result(s) + writeParams(buf, sig.Results, false) +} + +func writeType(buf *bytes.Buffer, typ Type) { + switch t := typ.(type) { + case nil: + buf.WriteString("") + + case *Basic: + buf.WriteString(t.Name) + + case *Array: + fmt.Fprintf(buf, "[%d]", t.Len) + writeType(buf, t.Elt) + + case *Slice: + buf.WriteString("[]") + writeType(buf, t.Elt) + + case *Struct: + buf.WriteString("struct{") + for i, f := range t.Fields { + if i > 0 { + buf.WriteString("; ") + } + if !f.IsAnonymous { + buf.WriteString(f.Name) + buf.WriteByte(' ') + } + writeType(buf, f.Type) + if f.Tag != "" { + fmt.Fprintf(buf, " %q", f.Tag) + } + } + buf.WriteByte('}') + + case *Pointer: + buf.WriteByte('*') + writeType(buf, t.Base) + + case *Result: + writeParams(buf, t.Values, false) + + case *Signature: + buf.WriteString("func") + writeSignature(buf, t) + + case *builtin: + fmt.Fprintf(buf, "", t.name) + + case *Interface: + buf.WriteString("interface{") + for i, m := range t.Methods { + if i > 0 { + buf.WriteString("; ") + } + buf.WriteString(m.Name) + writeSignature(buf, m.Type) + } + buf.WriteByte('}') + + case *Map: + buf.WriteString("map[") + writeType(buf, t.Key) + buf.WriteByte(']') + writeType(buf, t.Elt) + + case *Chan: + var s string + switch t.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + writeType(buf, t.Elt) + + case *NamedType: + s := "" + if obj := t.Obj; obj != nil { + if obj.Pkg != nil && obj.Pkg.Path != "" { + buf.WriteString(obj.Pkg.Path) + buf.WriteString(".") + } + s = t.Obj.GetName() + } + buf.WriteString(s) + + default: + fmt.Fprintf(buf, "", t) + } +} + +func (t *Array) String() string { return typeString(t) } +func (t *Basic) String() string { return typeString(t) } +func (t *Chan) String() string { return typeString(t) } +func (t *Interface) String() string { return typeString(t) } +func (t *Map) String() string { return typeString(t) } +func (t *NamedType) String() string { return typeString(t) } +func (t *Pointer) String() string { return typeString(t) } +func (t *Result) String() string { return typeString(t) } +func (t *Signature) String() string { return typeString(t) } +func (t *Slice) String() string { return typeString(t) } +func (t *Struct) String() string { return typeString(t) } +func (t *builtin) String() string { return typeString(t) } diff --git a/go/types/exportdata.go b/go/types/exportdata.go new file mode 100644 index 0000000000..1f6a3c7252 --- /dev/null +++ b/go/types/exportdata.go @@ -0,0 +1,111 @@ +// Copyright 2011 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 implements FindGcExportData. + +package types + +import ( + "bufio" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +func readGopackHeader(r *bufio.Reader) (name string, size int, err error) { + // See $GOROOT/include/ar.h. + hdr := make([]byte, 16+12+6+6+8+10+2) + _, err = io.ReadFull(r, hdr) + if err != nil { + return + } + // leave for debugging + if false { + fmt.Printf("header: %s", hdr) + } + s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10])) + size, err = strconv.Atoi(s) + if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' { + err = errors.New("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindGcExportData positions the reader r at the beginning of the +// export data section of an underlying GC-created object/archive +// file by reading from it. The reader must be positioned at the +// start of the file before calling this function. +// +func FindGcExportData(r *bufio.Reader) (err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + return + } + if string(line) == "!\n" { + // Archive file. Scan to __.PKGDEF, which should + // be second archive entry. + var name string + var size int + + // First entry should be __.GOSYMDEF. + // Older archives used __.SYMDEF, so allow that too. + // Read and discard. + if name, size, err = readGopackHeader(r); err != nil { + return + } + if name != "__.SYMDEF" && name != "__.GOSYMDEF" { + err = errors.New("go archive does not begin with __.SYMDEF or __.GOSYMDEF") + return + } + const block = 4096 + tmp := make([]byte, block) + for size > 0 { + n := size + if n > block { + n = block + } + if _, err = io.ReadFull(r, tmp[:n]); err != nil { + return + } + size -= n + } + + // Second entry should be __.PKGDEF. + if name, size, err = readGopackHeader(r); err != nil { + return + } + if name != "__.PKGDEF" { + err = errors.New("go archive is missing __.PKGDEF") + return + } + + // Read first line of __.PKGDEF data, so that line + // is once again the first line of the input. + if line, err = r.ReadSlice('\n'); err != nil { + return + } + } + + // Now at __.PKGDEF in archive or still at beginning of file. + // Either way, line should begin with "go object ". + if !strings.HasPrefix(string(line), "go object ") { + err = errors.New("not a go object file") + return + } + + // Skip over object header to export data. + // Begins after first line with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + return + } + } + + return +} diff --git a/go/types/expr.go b/go/types/expr.go new file mode 100644 index 0000000000..0af67a53e4 --- /dev/null +++ b/go/types/expr.go @@ -0,0 +1,1758 @@ +// 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 implements typechecking of expressions. + +package types + +import ( + "go/ast" + "go/token" + "strconv" + + "code.google.com/p/go.tools/go/exact" +) + +// TODO(gri) Internal cleanups +// - don't print error messages referring to invalid types (they are likely spurious errors) +// - simplify invalid handling: maybe just use Typ[Invalid] as marker, get rid of invalid Mode for values? +// - rethink error handling: should all callers check if x.mode == valid after making a call? +// - at the moment, iota is passed around almost everywhere - in many places we know it cannot be used +// - use "" or "_" consistently for anonymous identifiers? (e.g. reeceivers that have no name) +// - consider storing error messages in invalid operands for better error messages/debugging output + +// TODO(gri) Test issues +// - API tests are missing (e.g., identifiers should be handled as expressions in callbacks) + +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *ast.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +If an iota argument >= 0 is present, it is the value of iota for the +specific expression. + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +if present the Context.Expr method is invoked to notify a go/types client. +*/ + +func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) { + if list == nil { + return + } + var last *Var + for i, field := range list.List { + ftype := field.Type + if t, _ := ftype.(*ast.Ellipsis); t != nil { + ftype = t.Elt + if variadicOk && i == len(list.List)-1 { + isVariadic = true + } else { + check.invalidAST(field.Pos(), "... not permitted") + // ok to continue + } + } + // the parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag + typ := check.typ(ftype, true) + if len(field.Names) > 0 { + // named parameter + for _, name := range field.Names { + par := check.lookup(name).(*Var) + par.Type = typ + last = par + copy := *par + params = append(params, ©) + } + } else { + // anonymous parameter + par := &Var{Type: typ} + last = nil // not accessible inside function + params = append(params, par) + } + } + // For a variadic function, change the last parameter's object type + // from T to []T (this is the type used inside the function), but + // keep the params list unchanged (this is the externally visible type). + if isVariadic && last != nil { + last.Type = &Slice{Elt: last.Type} + } + return +} + +func (check *checker) collectMethods(list *ast.FieldList) (methods []*Method) { + if list == nil { + return + } + for _, f := range list.List { + typ := check.typ(f.Type, len(f.Names) > 0) // cycles are not ok for embedded interfaces + // the parser ensures that f.Tag is nil and we don't + // care if a constructed AST contains a non-nil tag + if len(f.Names) > 0 { + // methods (the parser ensures that there's only one + // and we don't care if a constructed AST has more) + sig, ok := typ.(*Signature) + if !ok { + check.invalidAST(f.Type.Pos(), "%s is not a method signature", typ) + continue + } + for _, name := range f.Names { + methods = append(methods, &Method{QualifiedName{check.pkg, name.Name}, sig}) + } + } else { + // embedded interface + utyp := underlying(typ) + if ityp, ok := utyp.(*Interface); ok { + methods = append(methods, ityp.Methods...) + } else if utyp != Typ[Invalid] { + // if utyp is invalid, don't complain (the root cause was reported before) + check.errorf(f.Type.Pos(), "%s is not an interface type", typ) + } + } + } + // Check for double declarations. + // The parser inserts methods into an interface-local scope, so local + // double declarations are reported by the parser already. We need to + // check again for conflicts due to embedded interfaces. This will lead + // to a 2nd error message if the double declaration was reported before + // by the parser. + // TODO(gri) clean this up a bit + seen := make(map[string]bool) + for _, m := range methods { + if seen[m.Name] { + check.errorf(list.Pos(), "multiple methods named %s", m.Name) + return // keep multiple entries, lookup will only return the first entry + } + seen[m.Name] = true + } + return +} + +func (check *checker) tag(t *ast.BasicLit) string { + if t != nil { + if t.Kind == token.STRING { + if val, err := strconv.Unquote(t.Value); err == nil { + return val + } + } + check.invalidAST(t.Pos(), "incorrect tag syntax: %q", t.Value) + } + return "" +} + +func (check *checker) collectFields(list *ast.FieldList, cycleOk bool) (fields []*Field) { + if list == nil { + return + } + + var typ Type // current field typ + var tag string // current field tag + add := func(name string, isAnonymous bool) { + fields = append(fields, &Field{QualifiedName{check.pkg, name}, typ, tag, isAnonymous}) + } + + for _, f := range list.List { + typ = check.typ(f.Type, cycleOk) + tag = check.tag(f.Tag) + if len(f.Names) > 0 { + // named fields + for _, name := range f.Names { + add(name.Name, false) + } + } else { + // anonymous field + switch t := deref(typ).(type) { + case *Basic: + add(t.Name, true) + case *NamedType: + add(t.Obj.GetName(), true) + default: + if typ != Typ[Invalid] { + check.invalidAST(f.Type.Pos(), "anonymous field type %s must be named", typ) + } + } + } + } + + return +} + +type opPredicates map[token.Token]func(Type) bool + +var unaryOpPredicates = opPredicates{ + token.ADD: isNumeric, + token.SUB: isNumeric, + token.XOR: isInteger, + token.NOT: isBoolean, +} + +func (check *checker) op(m opPredicates, x *operand, op token.Token) bool { + if pred := m[op]; pred != nil { + if !pred(x.typ) { + check.invalidOp(x.pos(), "operator %s not defined for %s", op, x) + return false + } + } else { + check.invalidAST(x.pos(), "unknown operator %s", op) + return false + } + return true +} + +func (check *checker) unary(x *operand, op token.Token) { + switch op { + case token.AND: + // spec: "As an exception to the addressability + // requirement x may also be a composite literal." + if _, ok := unparen(x.expr).(*ast.CompositeLit); ok { + x.mode = variable + } + if x.mode != variable { + check.invalidOp(x.pos(), "cannot take address of %s", x) + goto Error + } + x.typ = &Pointer{Base: x.typ} + return + + case token.ARROW: + typ, ok := underlying(x.typ).(*Chan) + if !ok { + check.invalidOp(x.pos(), "cannot receive from non-channel %s", x) + goto Error + } + if typ.Dir&ast.RECV == 0 { + check.invalidOp(x.pos(), "cannot receive from send-only channel %s", x) + goto Error + } + x.mode = valueok + x.typ = typ.Elt + return + } + + if !check.op(unaryOpPredicates, x, op) { + goto Error + } + + if x.mode == constant { + typ := underlying(x.typ).(*Basic) + size := -1 + if isUnsigned(typ) { + size = int(check.ctxt.sizeof(typ)) + } + x.val = exact.UnaryOp(op, x.val, size) + // Typed constants must be representable in + // their type after each constant operation. + check.isRepresentable(x, typ) + return + } + + x.mode = value + // x.typ remains unchanged + return + +Error: + x.mode = invalid +} + +func isShift(op token.Token) bool { + return op == token.SHL || op == token.SHR +} + +func isComparison(op token.Token) bool { + // Note: tokens are not ordered well to make this much easier + switch op { + case token.EQL, token.NEQ, token.LSS, token.LEQ, token.GTR, token.GEQ: + return true + } + return false +} + +func isRepresentableConst(x exact.Value, ctxt *Context, as BasicKind) bool { + switch x.Kind() { + case exact.Unknown: + return true + + case exact.Bool: + return as == Bool || as == UntypedBool + + case exact.Int: + if x, ok := exact.Int64Val(x); ok { + switch as { + case Int: + var s = uint(ctxt.sizeof(Typ[as])) * 8 + return int64(-1)<<(s-1) <= x && x <= int64(1)<<(s-1)-1 + case Int8: + const s = 8 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int16: + const s = 16 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int32: + const s = 32 + return -1<<(s-1) <= x && x <= 1<<(s-1)-1 + case Int64: + return true + case Uint, Uintptr: + if s := uint(ctxt.sizeof(Typ[as])) * 8; s < 64 { + return 0 <= x && x <= int64(1)<= 0 && n <= int(s) + case Uint64: + return exact.Sign(x) >= 0 && n <= 64 + case Float32: + return true // TODO(gri) fix this + case Float64: + return true // TODO(gri) fix this + case Complex64: + return true // TODO(gri) fix this + case Complex128: + return true // TODO(gri) fix this + case UntypedInt, UntypedFloat, UntypedComplex: + return true + } + + case exact.Float: + switch as { + case Float32: + return true // TODO(gri) fix this + case Float64: + return true // TODO(gri) fix this + case Complex64: + return true // TODO(gri) fix this + case Complex128: + return true // TODO(gri) fix this + case UntypedFloat, UntypedComplex: + return true + } + + case exact.Complex: + switch as { + case Complex64: + return true // TODO(gri) fix this + case Complex128: + return true // TODO(gri) fix this + case UntypedComplex: + return true + } + + case exact.String: + return as == String || as == UntypedString + + case exact.Nil: + return as == UntypedNil || as == UnsafePointer + + default: + unreachable() + } + + return false +} + +// isRepresentable checks that a constant operand is representable in the given type. +func (check *checker) isRepresentable(x *operand, typ *Basic) { + if x.mode != constant || isUntyped(typ) { + return + } + + if !isRepresentableConst(x.val, check.ctxt, typ.Kind) { + var msg string + if isNumeric(x.typ) && isNumeric(typ) { + msg = "%s overflows (or cannot be accurately represented as) %s" + } else { + msg = "cannot convert %s to %s" + } + check.errorf(x.pos(), msg, x, typ) + x.mode = invalid + } +} + +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), Context.Expr is invoked, if present. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary + switch x := x.(type) { + case *ast.BadExpr, + *ast.FuncLit, + *ast.CompositeLit, + *ast.IndexExpr, + *ast.SliceExpr, + *ast.TypeAssertExpr, + *ast.StarExpr, + *ast.KeyValueExpr, + *ast.ArrayType, + *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType, + *ast.ChanType: + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%s: found old type(%s): %s (new: %s)", x.Pos(), x, old.typ, typ) + unreachable() + } + return + + case *ast.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *ast.Ident, *ast.BasicLit, *ast.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. + + case *ast.ParenExpr: + check.updateExprType(x.X, typ, final) + + case *ast.UnaryExpr: + // If x is a constant, the operands were constants. + // They don't need to be updated since they never + // get "materialized" into a typed value; and they + // will be processed at the end of the type check. + if old.val != nil { + break + } + check.updateExprType(x.X, typ, final) + + case *ast.BinaryExpr: + if old.val != nil { + break // see comment for unary expressions + } + if isComparison(x.Op) { + // The result type is independent of operand types + // and the operand types must have final types. + } else if isShift(x.Op) { + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) + } else { + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) + } + + default: + unreachable() + } + + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = underlying(typ).(*Basic) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map. + delete(check.untyped, x) + + // If x is the lhs of a shift, its final type must be integer. + // We already know from the shift check that it is representable + // as an integer if it is a constant. + if old.isLhs && !isInteger(typ) { + check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) + return + } + + // Everything's fine, notify client of final type for x. + if f := check.ctxt.Expr; f != nil { + f(x, typ, old.val) + } +} + +// convertUntyped attempts to set the type of an untyped value to the target type. +func (check *checker) convertUntyped(x *operand, target Type) { + if x.mode == invalid || !isUntyped(x.typ) { + return + } + + // TODO(gri) Sloppy code - clean up. This function is central + // to assignment and expression checking. + + if isUntyped(target) { + // both x and target are untyped + xkind := x.typ.(*Basic).Kind + tkind := target.(*Basic).Kind + if isNumeric(x.typ) && isNumeric(target) { + if xkind < tkind { + x.typ = target + check.updateExprType(x.expr, target, false) + } + } else if xkind != tkind { + goto Error + } + return + } + + // typed target + switch t := underlying(target).(type) { + case nil: + // We may reach here due to previous type errors. + // Be conservative and don't crash. + x.mode = invalid + return + case *Basic: + check.isRepresentable(x, t) + if x.mode == invalid { + return // error already reported + } + case *Interface: + if !x.isNil() && len(t.Methods) > 0 /* empty interfaces are ok */ { + goto Error + } + // Update operand types to the default type rather then + // the target (interface) type: values must have concrete + // dynamic types. If the value is nil, keep it untyped + // (this is important for tools such as go vet which need + // the dynamic type for argument checking of say, print + // functions) + if x.isNil() { + target = Typ[UntypedNil] + } else { + // cannot assign untyped values to non-empty interfaces + if len(t.Methods) > 0 { + goto Error + } + target = defaultType(x.typ) + } + case *Pointer, *Signature, *Slice, *Map, *Chan: + if !x.isNil() { + goto Error + } + // keep nil untyped - see comment for interfaces, above + target = Typ[UntypedNil] + default: + if debug { + check.dump("convertUntyped(x = %v, target = %v)", x, target) + } + unreachable() + } + + x.typ = target + check.updateExprType(x.expr, target, true) // UntypedNils are final + return + +Error: + check.errorf(x.pos(), "cannot convert %s to %s", x, target) + x.mode = invalid +} + +func (check *checker) comparison(x, y *operand, op token.Token) { + // TODO(gri) deal with interface vs non-interface comparison + + valid := false + if x.isAssignable(check.ctxt, y.typ) || y.isAssignable(check.ctxt, x.typ) { + switch op { + case token.EQL, token.NEQ: + valid = isComparable(x.typ) || + x.isNil() && hasNil(y.typ) || + y.isNil() && hasNil(x.typ) + case token.LSS, token.LEQ, token.GTR, token.GEQ: + valid = isOrdered(x.typ) + default: + unreachable() + } + } + + if !valid { + check.invalidOp(x.pos(), "cannot compare %s %s %s", x, op, y) + x.mode = invalid + return + } + + if x.mode == constant && y.mode == constant { + x.val = exact.MakeBool(exact.Compare(x.val, op, y.val)) + // The operands are never materialized; no need to update + // their types. + } else { + x.mode = value + // The operands have now their final types, which at run- + // time will be materialized. Update the expression trees. + // If the current types are untyped, the materialized type + // is the respective default type. + check.updateExprType(x.expr, defaultType(x.typ), true) + check.updateExprType(y.expr, defaultType(y.typ), true) + } + + // spec: "Comparison operators compare two operands and yield + // an untyped boolean value." + x.typ = Typ[UntypedBool] +} + +func (check *checker) shift(x, y *operand, op token.Token) { + untypedx := isUntyped(x.typ) + + // The lhs must be of integer type or be representable + // as an integer; otherwise the shift has no chance. + if !isInteger(x.typ) && (!untypedx || !isRepresentableConst(x.val, nil, UntypedInt)) { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + // spec: "The right operand in a shift expression must have unsigned + // integer type or be an untyped constant that can be converted to + // unsigned integer type." + switch { + case isInteger(y.typ) && isUnsigned(y.typ): + // nothing to do + case isUntyped(y.typ): + check.convertUntyped(y, Typ[UntypedInt]) + if y.mode == invalid { + x.mode = invalid + return + } + default: + check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) + x.mode = invalid + return + } + + if x.mode == constant { + if y.mode == constant { + if untypedx { + x.typ = Typ[UntypedInt] + } + // rhs must be within reasonable bounds + const stupidShift = 1024 + s, ok := exact.Uint64Val(y.val) + if !ok || s >= stupidShift { + check.invalidOp(y.pos(), "%s: stupid shift", y) + x.mode = invalid + return + } + // everything's ok + x.val = exact.Shift(x.val, op, uint(s)) + return + } + + // non-constant shift with constant lhs + if untypedx { + // spec: "If the left operand of a non-constant shift expression is + // an untyped constant, the type of the constant is what it would be + // if the shift expression were replaced by its left operand alone; + // the type is int if it cannot be determined from the context (for + // instance, if the shift expression is an operand in a comparison + // against an untyped constant)". + + // Delay operand checking until we know the final type: + // The lhs expression must be in the untyped map, mark + // the entry as lhs shift operand. + if info, ok := check.untyped[x.expr]; ok { + info.isLhs = true + check.untyped[x.expr] = info + } else { + unreachable() + } + // keep x's type + x.mode = value + return + } + } + + // non-constant shift - lhs must be an integer + if !isInteger(x.typ) { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + + // non-constant shift + x.mode = value +} + +var binaryOpPredicates = opPredicates{ + token.ADD: func(typ Type) bool { return isNumeric(typ) || isString(typ) }, + token.SUB: isNumeric, + token.MUL: isNumeric, + token.QUO: isNumeric, + token.REM: isInteger, + + token.AND: isInteger, + token.OR: isInteger, + token.XOR: isInteger, + token.AND_NOT: isInteger, + + token.LAND: isBoolean, + token.LOR: isBoolean, +} + +func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota int) { + var y operand + + check.expr(x, lhs, nil, iota) + check.expr(&y, rhs, nil, iota) + + if x.mode == invalid { + return + } + if y.mode == invalid { + x.mode = invalid + x.expr = y.expr + return + } + + if isShift(op) { + check.shift(x, &y, op) + return + } + + check.convertUntyped(x, y.typ) + if x.mode == invalid { + return + } + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + x.mode = invalid + return + } + + if isComparison(op) { + check.comparison(x, &y, op) + return + } + + if !IsIdentical(x.typ, y.typ) { + // only report an error if we have valid types + // (otherwise we had an error reported elsewhere already) + if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] { + check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + } + x.mode = invalid + return + } + + if !check.op(binaryOpPredicates, x, op) { + x.mode = invalid + return + } + + if (op == token.QUO || op == token.REM) && y.mode == constant && exact.Sign(y.val) == 0 { + check.invalidOp(y.pos(), "division by zero") + x.mode = invalid + return + } + + if x.mode == constant && y.mode == constant { + typ := underlying(x.typ).(*Basic) + // force integer division of integer operands + if op == token.QUO && isInteger(typ) { + op = token.QUO_ASSIGN + } + x.val = exact.BinaryOp(x.val, op, y.val) + // Typed constants must be representable in + // their type after each constant operation. + check.isRepresentable(x, typ) + return + } + + x.mode = value + // x.typ is unchanged +} + +// index checks an index/size expression arg for validity. +// If length >= 0, it is the upper bound for arg. +// TODO(gri): Do we need iota? +func (check *checker) index(arg ast.Expr, length int64, iota int) (i int64, ok bool) { + var x operand + check.expr(&x, arg, nil, iota) + + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return + } + + // the index/size must be of integer type + if !isInteger(x.typ) { + check.invalidArg(x.pos(), "%s must be integer", &x) + return + } + + // a constant index/size i must be 0 <= i < length + if x.mode == constant { + if exact.Sign(x.val) < 0 { + check.invalidArg(x.pos(), "%s must not be negative", &x) + return + } + i, ok = exact.Int64Val(x.val) + if !ok || length >= 0 && i >= length { + check.errorf(x.pos(), "index %s is out of bounds", &x) + return i, false + } + // 0 <= i [ && i < length ] + return i, true + } + + return -1, true +} + +// compositeLitKey resolves unresolved composite literal keys. +// For details, see comment in go/parser/parser.go, method parseElement. +func (check *checker) compositeLitKey(key ast.Expr) { + if ident, ok := key.(*ast.Ident); ok && ident.Obj == nil { + if obj := check.pkg.Scope.Lookup(ident.Name); obj != nil { + check.register(ident, obj) + } else if obj := Universe.Lookup(ident.Name); obj != nil { + check.register(ident, obj) + } else { + check.errorf(ident.Pos(), "undeclared name: %s", ident.Name) + } + } +} + +// indexElts checks the elements (elts) of an array or slice composite literal +// against the literal's element type (typ), and the element indices against +// the literal length if known (length >= 0). It returns the length of the +// literal (maximum index value + 1). +// +func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota int) int64 { + visited := make(map[int64]bool, len(elts)) + var index, max int64 + for _, e := range elts { + // determine and check index + validIndex := false + eval := e + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + check.compositeLitKey(kv.Key) + if i, ok := check.index(kv.Key, length, iota); ok { + if i >= 0 { + index = i + } + validIndex = true + } + eval = kv.Value + } else if length >= 0 && index >= length { + check.errorf(e.Pos(), "index %d is out of bounds (>= %d)", index, length) + } else { + validIndex = true + } + + // if we have a valid index, check for duplicate entries + if validIndex { + if visited[index] { + check.errorf(e.Pos(), "duplicate index %d in array or slice literal", index) + } + visited[index] = true + } + index++ + if index > max { + max = index + } + + // check element against composite literal element type + var x operand + check.expr(&x, eval, typ, iota) + if !check.assignment(&x, typ) && x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in array or slice literal", &x, typ) + } + } + return max +} + +// argument typechecks passing an argument arg (if arg != nil) or +// x (if arg == nil) to the i'th parameter of the given signature. +// If passSlice is set, the argument is followed by ... in the call. +// +func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, passSlice bool) { + // determine parameter + var par *Var + n := len(sig.Params) + if i < n { + par = sig.Params[i] + } else if sig.IsVariadic { + par = sig.Params[n-1] + } else { + check.errorf(arg.Pos(), "too many arguments") + return + } + + // determine argument + var z operand + z.mode = variable + z.expr = nil // TODO(gri) can we do better here? (for good error messages) + z.typ = par.Type + + if arg != nil { + check.expr(x, arg, z.typ, -1) + } + if x.mode == invalid { + return // ignore this argument + } + + // check last argument of the form x... + if passSlice { + if i+1 != n { + check.errorf(x.pos(), "can only use ... with matching parameter") + return // ignore this argument + } + // spec: "If the final argument is assignable to a slice type []T, + // it may be passed unchanged as the value for a ...T parameter if + // the argument is followed by ..." + z.typ = &Slice{Elt: z.typ} // change final parameter type to []T + } + + if !check.assignment(x, z.typ) && x.mode != invalid { + check.errorf(x.pos(), "cannot pass argument %s to %s", x, &z) + } +} + +var emptyResult Result + +func (check *checker) callExpr(x *operand) { + // convert x into a user-friendly set of values + var typ Type + var val exact.Value + switch x.mode { + case invalid: + return // nothing to do + case novalue: + typ = &emptyResult + case constant: + typ = x.typ + val = x.val + default: + typ = x.typ + } + + // if the operand is untyped, delay notification + // until it becomes typed or until the end of + // type checking + if isUntyped(typ) { + check.untyped[x.expr] = exprInfo{false, typ.(*Basic), val} + return + } + + // TODO(gri) ensure that literals always report + // their dynamic (never interface) type. + // This is not the case yet. + + if check.ctxt.Expr != nil { + check.ctxt.Expr(x.expr, typ, val) + } +} + +// rawExpr typechecks expression e and initializes x with the expression +// value or type. If an error occurred, x.mode is set to invalid. +// If hint != nil, it is the type of a composite literal element. +// iota >= 0 indicates that the expression is part of a constant declaration. +// cycleOk indicates whether it is ok for a type expression to refer to itself. +// +func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycleOk bool) { + if trace { + c := "" + if cycleOk { + c = " ⨁" + } + check.trace(e.Pos(), "%s%s", e, c) + defer check.untrace("=> %s", x) + } + + // record final type of x if untyped, notify clients of type otherwise + defer check.callExpr(x) + + switch e := e.(type) { + case *ast.BadExpr: + goto Error // error was reported before + + case *ast.Ident: + if e.Name == "_" { + check.invalidOp(e.Pos(), "cannot use _ as value or type") + goto Error + } + obj := check.lookup(e) + if obj == nil { + goto Error // error was reported before + } + check.object(obj, cycleOk) + switch obj := obj.(type) { + case *Package: + check.errorf(e.Pos(), "use of package %s not in selector", obj.Name) + goto Error + case *Const: + if obj.Type == Typ[Invalid] { + goto Error + } + x.mode = constant + if obj == universeIota { + if iota < 0 { + check.invalidAST(e.Pos(), "cannot use iota outside constant declaration") + goto Error + } + x.val = exact.MakeInt64(int64(iota)) + } else { + x.val = obj.Val // may be nil if we don't know the constant value + } + case *TypeName: + x.mode = typexpr + if !cycleOk && underlying(obj.Type) == nil { + check.errorf(obj.spec.Pos(), "illegal cycle in declaration of %s", obj.Name) + x.expr = e + x.typ = Typ[Invalid] + return // don't goto Error - need x.mode == typexpr + } + case *Var: + x.mode = variable + case *Func: + x.mode = value + default: + unreachable() + } + x.typ = obj.GetType() + + case *ast.Ellipsis: + // ellipses are handled explicitly where they are legal + // (array composite literals and parameter lists) + check.errorf(e.Pos(), "invalid use of '...'") + goto Error + + case *ast.BasicLit: + x.setConst(e.Kind, e.Value) + if x.mode == invalid { + check.invalidAST(e.Pos(), "invalid literal %v", e.Value) + goto Error + } + + case *ast.FuncLit: + if sig, ok := check.typ(e.Type, false).(*Signature); ok { + x.mode = value + x.typ = sig + check.later(nil, sig, e.Body) + } else { + check.invalidAST(e.Pos(), "invalid function literal %s", e) + goto Error + } + + case *ast.CompositeLit: + typ := hint + openArray := false + if e.Type != nil { + // [...]T array types may only appear with composite literals. + // Check for them here so we don't have to handle ... in general. + typ = nil + if atyp, _ := e.Type.(*ast.ArrayType); atyp != nil && atyp.Len != nil { + if ellip, _ := atyp.Len.(*ast.Ellipsis); ellip != nil && ellip.Elt == nil { + // We have an "open" [...]T array type. + // Create a new ArrayType with unknown length (-1) + // and finish setting it up after analyzing the literal. + typ = &Array{Len: -1, Elt: check.typ(atyp.Elt, cycleOk)} + openArray = true + } + } + if typ == nil { + typ = check.typ(e.Type, false) + } + } + if typ == nil { + check.errorf(e.Pos(), "missing type in composite literal") + goto Error + } + + switch utyp := underlying(deref(typ)).(type) { + case *Struct: + if len(e.Elts) == 0 { + break + } + fields := utyp.Fields + if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok { + // all elements must have keys + visited := make([]bool, len(fields)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal") + continue + } + key, _ := kv.Key.(*ast.Ident) + if key == nil { + check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key) + continue + } + i := utyp.fieldIndex(QualifiedName{check.pkg, key.Name}) + if i < 0 { + check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name) + continue + } + // 0 <= i < len(fields) + if visited[i] { + check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) + continue + } + visited[i] = true + check.expr(x, kv.Value, nil, iota) + etyp := fields[i].Type + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } + continue + } + } + } else { + // no element must have a key + for i, e := range e.Elts { + if kv, _ := e.(*ast.KeyValueExpr); kv != nil { + check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal") + continue + } + check.expr(x, e, nil, iota) + if i >= len(fields) { + check.errorf(x.pos(), "too many values in struct literal") + break // cannot continue + } + // i < len(fields) + etyp := fields[i].Type + if !check.assignment(x, etyp) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in struct literal", x, etyp) + } + continue + } + } + if len(e.Elts) < len(fields) { + check.errorf(e.Rbrace, "too few values in struct literal") + // ok to continue + } + } + + case *Array: + n := check.indexedElts(e.Elts, utyp.Elt, utyp.Len, iota) + // if we have an "open" [...]T array, set the length now that we know it + if openArray { + utyp.Len = n + } + + case *Slice: + check.indexedElts(e.Elts, utyp.Elt, -1, iota) + + case *Map: + visited := make(map[interface{}]bool, len(e.Elts)) + for _, e := range e.Elts { + kv, _ := e.(*ast.KeyValueExpr) + if kv == nil { + check.errorf(e.Pos(), "missing key in map literal") + continue + } + check.compositeLitKey(kv.Key) + check.expr(x, kv.Key, nil, iota) + if !check.assignment(x, utyp.Key) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s key in map literal", x, utyp.Key) + } + continue + } + if x.mode == constant { + if visited[x.val] { + check.errorf(x.pos(), "duplicate key %s in map literal", x.val) + continue + } + visited[x.val] = true + } + check.expr(x, kv.Value, utyp.Elt, iota) + if !check.assignment(x, utyp.Elt) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot use %s as %s value in map literal", x, utyp.Elt) + } + continue + } + } + + default: + check.errorf(e.Pos(), "%s is not a valid composite literal type", typ) + goto Error + } + + x.mode = value + x.typ = typ + + case *ast.ParenExpr: + check.rawExpr(x, e.X, nil, iota, cycleOk) + + case *ast.SelectorExpr: + sel := e.Sel.Name + // If the identifier refers to a package, handle everything here + // so we don't need a "package" mode for operands: package names + // can only appear in qualified identifiers which are mapped to + // selector expressions. + if ident, ok := e.X.(*ast.Ident); ok { + if pkg, ok := check.lookup(ident).(*Package); ok { + exp := pkg.Scope.Lookup(sel) + if exp == nil { + check.errorf(e.Pos(), "%s not declared by package %s", sel, ident) + goto Error + } else if !ast.IsExported(exp.GetName()) { + // gcimported package scopes contain non-exported + // objects such as types used in partially exported + // objects - do not accept them + check.errorf(e.Pos(), "%s not exported by package %s", sel, ident) + goto Error + } + check.register(e.Sel, exp) + // Simplified version of the code for *ast.Idents: + // - imported packages use types.Scope and types.Objects + // - imported objects are always fully initialized + switch exp := exp.(type) { + case *Const: + assert(exp.Val != nil) + x.mode = constant + x.typ = exp.Type + x.val = exp.Val + case *TypeName: + x.mode = typexpr + x.typ = exp.Type + case *Var: + x.mode = variable + x.typ = exp.Type + case *Func: + x.mode = value + x.typ = exp.Type + default: + unreachable() + } + x.expr = e + return + } + } + + check.exprOrType(x, e.X, iota, false) + if x.mode == invalid { + goto Error + } + res := lookupField(x.typ, QualifiedName{check.pkg, sel}) + if res.mode == invalid { + check.invalidOp(e.Pos(), "%s has no single field or method %s", x, sel) + goto Error + } + if x.mode == typexpr { + // method expression + sig, ok := res.typ.(*Signature) + if !ok { + check.invalidOp(e.Pos(), "%s has no method %s", x, sel) + goto Error + } + // the receiver type becomes the type of the first function + // argument of the method expression's function type + // TODO(gri) at the moment, method sets don't correctly track + // pointer vs non-pointer receivers => typechecker is too lenient + x.mode = value + x.typ = &Signature{ + Params: append([]*Var{{Type: x.typ}}, sig.Params...), + Results: sig.Results, + IsVariadic: sig.IsVariadic, + } + } else { + // regular selector + x.mode = res.mode + x.typ = res.typ + } + + case *ast.IndexExpr: + check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := underlying(x.typ).(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant { + length = int64(len(exact.StringVal(x.val))) + } + // an indexed string always yields a byte value + // (not a constant) even if the string and the + // index are constant + x.mode = value + x.typ = Typ[Byte] + } + + case *Array: + valid = true + length = typ.Len + if x.mode != variable { + x.mode = value + } + x.typ = typ.Elt + + case *Pointer: + if typ, _ := underlying(typ.Base).(*Array); typ != nil { + valid = true + length = typ.Len + x.mode = variable + x.typ = typ.Elt + } + + case *Slice: + valid = true + x.mode = variable + x.typ = typ.Elt + + case *Map: + var key operand + check.expr(&key, e.Index, nil, iota) + if !check.assignment(&key, typ.Key) { + if key.mode != invalid { + check.invalidOp(key.pos(), "cannot use %s as map index of type %s", &key, typ.Key) + } + goto Error + } + x.mode = valueok + x.typ = typ.Elt + x.expr = e + return + } + + if !valid { + check.invalidOp(x.pos(), "cannot index %s", x) + goto Error + } + + if e.Index == nil { + check.invalidAST(e.Pos(), "missing index expression for %s", x) + return + } + + check.index(e.Index, length, iota) + // ok to continue + + case *ast.SliceExpr: + check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } + + valid := false + length := int64(-1) // valid if >= 0 + switch typ := underlying(x.typ).(type) { + case *Basic: + if isString(typ) { + valid = true + if x.mode == constant { + length = int64(len(exact.StringVal(x.val))) + 1 // +1 for slice + } + // a sliced string always yields a string value + // of the same type as the original string (not + // a constant) even if the string and the indices + // are constant + x.mode = value + // x.typ doesn't change, but if it is an untyped + // string it becomes string (see also issue 4913). + if typ.Kind == UntypedString { + x.typ = Typ[String] + } + } + + case *Array: + valid = true + length = typ.Len + 1 // +1 for slice + if x.mode != variable { + check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x) + goto Error + } + x.typ = &Slice{Elt: typ.Elt} + + case *Pointer: + if typ, _ := underlying(typ.Base).(*Array); typ != nil { + valid = true + length = typ.Len + 1 // +1 for slice + x.mode = variable + x.typ = &Slice{Elt: typ.Elt} + } + + case *Slice: + valid = true + x.mode = variable + // x.typ doesn't change + } + + if !valid { + check.invalidOp(x.pos(), "cannot slice %s", x) + goto Error + } + + lo := int64(0) + if e.Low != nil { + if i, ok := check.index(e.Low, length, iota); ok && i >= 0 { + lo = i + } + } + + hi := int64(-1) + if e.High != nil { + if i, ok := check.index(e.High, length, iota); ok && i >= 0 { + hi = i + } + } else if length >= 0 { + hi = length + } + + if lo >= 0 && hi >= 0 && lo > hi { + check.errorf(e.Low.Pos(), "inverted slice range: %d > %d", lo, hi) + // ok to continue + } + + case *ast.TypeAssertExpr: + check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } + var T *Interface + if T, _ = underlying(x.typ).(*Interface); T == nil { + check.invalidOp(x.pos(), "%s is not an interface", x) + goto Error + } + // x.(type) expressions are handled explicitly in type switches + if e.Type == nil { + check.errorf(e.Pos(), "use of .(type) outside type switch") + goto Error + } + typ := check.typ(e.Type, false) + if typ == Typ[Invalid] { + goto Error + } + if method, wrongType := missingMethod(typ, T); method != nil { + var msg string + if wrongType { + msg = "%s cannot have dynamic type %s (wrong type for method %s)" + } else { + msg = "%s cannot have dynamic type %s (missing method %s)" + } + check.errorf(e.Type.Pos(), msg, x, typ, method.Name) + // ok to continue + } + x.mode = valueok + x.expr = e + x.typ = typ + + case *ast.CallExpr: + check.exprOrType(x, e.Fun, iota, false) + if x.mode == invalid { + goto Error + } else if x.mode == typexpr { + check.conversion(x, e, x.typ, iota) + } else if sig, ok := underlying(x.typ).(*Signature); ok { + // check parameters + + // If we have a trailing ... at the end of the parameter + // list, the last argument must match the parameter type + // []T of a variadic function parameter x ...T. + passSlice := false + if e.Ellipsis.IsValid() { + if sig.IsVariadic { + passSlice = true + } else { + check.errorf(e.Ellipsis, "cannot use ... in call to %s", e.Fun) + // ok to continue + } + } + + // If we have a single argument that is a function call + // we need to handle it separately. Determine if this + // is the case without checking the argument. + var call *ast.CallExpr + if len(e.Args) == 1 { + call, _ = unparen(e.Args[0]).(*ast.CallExpr) + } + + n := 0 // parameter count + if call != nil { + // We have a single argument that is a function call. + check.expr(x, call, nil, -1) + if x.mode == invalid { + goto Error // TODO(gri): we can do better + } + if t, _ := x.typ.(*Result); t != nil { + // multiple result values + n = len(t.Values) + for i, obj := range t.Values { + x.mode = value + x.expr = nil // TODO(gri) can we do better here? (for good error messages) + x.typ = obj.Type + check.argument(sig, i, nil, x, passSlice && i+1 == n) + } + } else { + // single result value + n = 1 + check.argument(sig, 0, nil, x, passSlice) + } + + } else { + // We don't have a single argument or it is not a function call. + n = len(e.Args) + for i, arg := range e.Args { + check.argument(sig, i, arg, x, passSlice && i+1 == n) + } + } + + // determine if we have enough arguments + if sig.IsVariadic { + // a variadic function accepts an "empty" + // last argument: count one extra + n++ + } + if n < len(sig.Params) { + check.errorf(e.Fun.Pos(), "too few arguments in call to %s", e.Fun) + // ok to continue + } + + // determine result + switch len(sig.Results) { + case 0: + x.mode = novalue + case 1: + x.mode = value + x.typ = sig.Results[0].Type + default: + x.mode = value + x.typ = &Result{Values: sig.Results} + } + + } else if bin, ok := x.typ.(*builtin); ok { + check.builtin(x, e, bin, iota) + + } else { + check.invalidOp(x.pos(), "cannot call non-function %s", x) + goto Error + } + + case *ast.StarExpr: + check.exprOrType(x, e.X, iota, true) + switch x.mode { + case invalid: + goto Error + case typexpr: + x.typ = &Pointer{Base: x.typ} + default: + if typ, ok := underlying(x.typ).(*Pointer); ok { + x.mode = variable + x.typ = typ.Base + } else { + check.invalidOp(x.pos(), "cannot indirect %s", x) + goto Error + } + } + + case *ast.UnaryExpr: + check.expr(x, e.X, nil, iota) + if x.mode == invalid { + goto Error + } + check.unary(x, e.Op) + if x.mode == invalid { + goto Error + } + + case *ast.BinaryExpr: + check.binary(x, e.X, e.Y, e.Op, iota) + if x.mode == invalid { + goto Error + } + + case *ast.KeyValueExpr: + // key:value expressions are handled in composite literals + check.invalidAST(e.Pos(), "no key:value expected") + goto Error + + case *ast.ArrayType: + if e.Len != nil { + check.expr(x, e.Len, nil, iota) + if x.mode == invalid { + goto Error + } + if x.mode != constant { + if x.mode != invalid { + check.errorf(x.pos(), "array length %s must be constant", x) + } + goto Error + } + if !x.isInteger() { + check.errorf(x.pos(), "array length %s must be integer", x) + goto Error + } + n, ok := exact.Int64Val(x.val) + if !ok || n < 0 { + check.errorf(x.pos(), "invalid array length %s", x) + goto Error + } + x.typ = &Array{Len: n, Elt: check.typ(e.Elt, cycleOk)} + } else { + x.typ = &Slice{Elt: check.typ(e.Elt, true)} + } + x.mode = typexpr + + case *ast.StructType: + x.mode = typexpr + x.typ = &Struct{Fields: check.collectFields(e.Fields, cycleOk)} + + case *ast.FuncType: + params, isVariadic := check.collectParams(e.Params, true) + results, _ := check.collectParams(e.Results, false) + x.mode = typexpr + x.typ = &Signature{Recv: nil, Params: params, Results: results, IsVariadic: isVariadic} + + case *ast.InterfaceType: + x.mode = typexpr + x.typ = &Interface{Methods: check.collectMethods(e.Methods)} + + case *ast.MapType: + x.mode = typexpr + x.typ = &Map{Key: check.typ(e.Key, true), Elt: check.typ(e.Value, true)} + + case *ast.ChanType: + x.mode = typexpr + x.typ = &Chan{Dir: e.Dir, Elt: check.typ(e.Value, true)} + + default: + if debug { + check.dump("expr = %v (%T)", e, e) + } + unreachable() + } + + // everything went well + x.expr = e + return + +Error: + x.mode = invalid + x.expr = e +} + +// exprOrType is like rawExpr but reports an error if e doesn't represents a value or type. +func (check *checker) exprOrType(x *operand, e ast.Expr, iota int, cycleOk bool) { + check.rawExpr(x, e, nil, iota, cycleOk) + if x.mode == novalue { + check.errorf(x.pos(), "%s used as value or type", x) + x.mode = invalid + } +} + +// expr is like rawExpr but reports an error if e doesn't represents a value. +func (check *checker) expr(x *operand, e ast.Expr, hint Type, iota int) { + check.rawExpr(x, e, hint, iota, false) + switch x.mode { + case novalue: + check.errorf(x.pos(), "%s used as value", x) + x.mode = invalid + case typexpr: + check.errorf(x.pos(), "%s is not an expression", x) + x.mode = invalid + } +} + +func (check *checker) rawTyp(e ast.Expr, cycleOk, nilOk bool) Type { + var x operand + check.rawExpr(&x, e, nil, -1, cycleOk) + switch x.mode { + case invalid: + // ignore - error reported before + case novalue: + check.errorf(x.pos(), "%s used as type", &x) + case typexpr: + return x.typ + case constant: + if nilOk && x.isNil() { + return nil + } + fallthrough + default: + check.errorf(x.pos(), "%s is not a type", &x) + } + return Typ[Invalid] +} + +// typOrNil is like rawExpr but reports an error if e doesn't represents a type or the predeclared value nil. +// It returns e's type, nil, or Typ[Invalid] if an error occurred. +// +func (check *checker) typOrNil(e ast.Expr, cycleOk bool) Type { + return check.rawTyp(e, cycleOk, true) +} + +// typ is like rawExpr but reports an error if e doesn't represents a type. +// It returns e's type, or Typ[Invalid] if an error occurred. +// +func (check *checker) typ(e ast.Expr, cycleOk bool) Type { + return check.rawTyp(e, cycleOk, false) +} diff --git a/go/types/gcimporter.go b/go/types/gcimporter.go new file mode 100644 index 0000000000..ed9b856e86 --- /dev/null +++ b/go/types/gcimporter.go @@ -0,0 +1,945 @@ +// Copyright 2011 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 implements an Importer for gc-generated object files. + +package types + +import ( + "bufio" + "errors" + "fmt" + "go/ast" + "go/build" + "go/token" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "text/scanner" + + "code.google.com/p/go.tools/go/exact" +) + +var pkgExts = [...]string{".a", ".5", ".6", ".8"} + +// FindPkg returns the filename and unique package id for an import +// path based on package information provided by build.Import (using +// the build.Default build.Context). +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if len(path) == 0 { + return + } + + id = path + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + + case build.IsLocalImport(path): + // "./x" -> "/this/directory/x.ext", "/this/directory/x" + noext = filepath.Join(srcDir, path) + id = noext + + case filepath.IsAbs(path): + // for completeness only - go/build.Import + // does not support absolute imports + // "/x" -> "/x.ext", "/x" + noext = path + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// GcImportData imports a package by reading the gc-generated export data, +// adds the corresponding package object to the imports map indexed by id, +// and returns the object. +// +// The imports map must contains all packages already imported. The data +// reader position must be the beginning of the export data section. The +// filename is only used in error messages. +// +// If imports[id] contains the completely imported package, that package +// can be used directly, and there is no need to call this function (but +// there is also no harm but for extra time used). +// +func GcImportData(imports map[string]*Package, filename, id string, data *bufio.Reader) (pkg *Package, err error) { + // support for gcParser error handling + defer func() { + if r := recover(); r != nil { + err = r.(importError) // will re-panic if r is not an importError + } + }() + + var p gcParser + p.init(filename, id, data, imports) + pkg = p.parseExport() + + return +} + +// GcImport imports a gc-generated package given its import path, adds the +// corresponding package object to the imports map, and returns the object. +// Local import paths are interpreted relative to the current working directory. +// The imports map must contains all packages already imported. +// GcImport satisfies the ast.Importer signature. +// +func GcImport(imports map[string]*Package, path string) (pkg *Package, err error) { + if path == "unsafe" { + return Unsafe, nil + } + + srcDir := "." + if build.IsLocalImport(path) { + srcDir, err = os.Getwd() + if err != nil { + return + } + } + + filename, id := FindPkg(path, srcDir) + if filename == "" { + err = errors.New("can't find import: " + id) + return + } + + // no need to re-import if the package was imported completely before + if pkg = imports[id]; pkg != nil && pkg.Complete { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return + } + defer func() { + f.Close() + if err != nil { + // add file name to error + err = fmt.Errorf("reading export data: %s: %v", filename, err) + } + }() + + buf := bufio.NewReader(f) + if err = FindGcExportData(buf); err != nil { + return + } + + pkg, err = GcImportData(imports, filename, id, buf) + + return +} + +// ---------------------------------------------------------------------------- +// gcParser + +// gcParser parses the exports inside a gc compiler-produced +// object/archive file and populates its scope with the results. +type gcParser struct { + scanner scanner.Scanner + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + id string // package id of imported package + imports map[string]*Package // package id -> package object +} + +func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*Package) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments + p.scanner.Whitespace = 1<<'\t' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() + p.id = id + p.imports = imports + // leave for debugging + if false { + // check consistency of imports map + for _, pkg := range imports { + if pkg.Name == "" { + fmt.Printf("no package name for %s\n", pkg.Path) + } + } + } +} + +func (p *gcParser) next() { + p.tok = p.scanner.Scan() + switch p.tok { + case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·': + p.lit = p.scanner.TokenText() + default: + p.lit = "" + } + // leave for debugging + if false { + fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit) + } +} + +func declConst(pkg *Package, name string) *Const { + // the constant may have been imported before - if it exists + // already in the respective scope, return that constant + scope := pkg.Scope + if obj := scope.Lookup(name); obj != nil { + return obj.(*Const) + } + // otherwise create a new constant and insert it into the scope + obj := &Const{Pkg: pkg, Name: name} + scope.Insert(obj) + return obj +} + +func declTypeName(pkg *Package, name string) *TypeName { + scope := pkg.Scope + if obj := scope.Lookup(name); obj != nil { + return obj.(*TypeName) + } + obj := &TypeName{Pkg: pkg, Name: name} + // a named type may be referred to before the underlying type + // is known - set it up + obj.Type = &NamedType{Obj: obj} + scope.Insert(obj) + return obj +} + +func declVar(pkg *Package, name string) *Var { + scope := pkg.Scope + if obj := scope.Lookup(name); obj != nil { + return obj.(*Var) + } + obj := &Var{Pkg: pkg, Name: name} + scope.Insert(obj) + return obj +} + +func declFunc(pkg *Package, name string) *Func { + scope := pkg.Scope + if obj := scope.Lookup(name); obj != nil { + return obj.(*Func) + } + obj := &Func{Pkg: pkg, Name: name} + scope.Insert(obj) + return obj +} + +// ---------------------------------------------------------------------------- +// Error handling + +// Internal errors are boxed as importErrors. +type importError struct { + pos scanner.Position + err error +} + +func (e importError) Error() string { + return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err) +} + +func (p *gcParser) error(err interface{}) { + if s, ok := err.(string); ok { + err = errors.New(s) + } + // panic with a runtime.Error if err is not an error + panic(importError{p.scanner.Pos(), err.(error)}) +} + +func (p *gcParser) errorf(format string, args ...interface{}) { + p.error(fmt.Sprintf(format, args...)) +} + +func (p *gcParser) expect(tok rune) string { + lit := p.lit + if p.tok != tok { + p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit) + } + p.next() + return lit +} + +func (p *gcParser) expectSpecial(tok string) { + sep := 'x' // not white space + i := 0 + for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' { + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + i++ + } + if i < len(tok) { + p.errorf("expected %q, got %q", tok, tok[0:i]) + } +} + +func (p *gcParser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +// ---------------------------------------------------------------------------- +// Qualified and unqualified names + +// PackageId = string_lit . +// +func (p *gcParser) parsePackageId() string { + id, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + // id == "" stands for the imported package id + // (only known at time of package installation) + if id == "" { + id = p.id + } + return id +} + +// PackageName = ident . +// +func (p *gcParser) parsePackageName() string { + return p.expect(scanner.Ident) +} + +// dotIdentifier = ( ident | '·' ) { ident | int | '·' } . +func (p *gcParser) parseDotIdent() string { + ident := "" + if p.tok != scanner.Int { + sep := 'x' // not white space + for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' { + ident += p.lit + sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token + p.next() + } + } + if ident == "" { + p.expect(scanner.Ident) // use expect() for error handling + } + return ident +} + +// QualifiedName = "@" PackageId "." dotIdentifier . +// +func (p *gcParser) parseQualifiedName() (id, name string) { + p.expect('@') + id = p.parsePackageId() + p.expect('.') + name = p.parseDotIdent() + return +} + +// getPkg returns the package for a given id. If the package is +// not found but we have a package name, create the package and +// add it to the p.imports map. +// +func (p *gcParser) getPkg(id, name string) *Package { + // package unsafe is not in the imports map - handle explicitly + if id == "unsafe" { + return Unsafe + } + pkg := p.imports[id] + if pkg == nil && name != "" { + pkg = &Package{Name: name, Path: id, Scope: new(Scope)} + p.imports[id] = pkg + } + return pkg +} + +// parseExportedName is like parseQualifiedName, but +// the package id is resolved to an imported *Package. +// +func (p *gcParser) parseExportedName() (pkg *Package, name string) { + id, name := p.parseQualifiedName() + pkg = p.getPkg(id, "") + if pkg == nil { + p.errorf("%s package not found", id) + } + return +} + +// ---------------------------------------------------------------------------- +// Types + +// BasicType = identifier . +// +func (p *gcParser) parseBasicType() Type { + id := p.expect(scanner.Ident) + obj := Universe.Lookup(id) + if obj, ok := obj.(*TypeName); ok { + return obj.Type + } + p.errorf("not a basic type: %s", id) + return nil +} + +// ArrayType = "[" int_lit "]" Type . +// +func (p *gcParser) parseArrayType() Type { + // "[" already consumed and lookahead known not to be "]" + lit := p.expect(scanner.Int) + p.expect(']') + elt := p.parseType() + n, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + p.error(err) + } + return &Array{Len: n, Elt: elt} +} + +// MapType = "map" "[" Type "]" Type . +// +func (p *gcParser) parseMapType() Type { + p.expectKeyword("map") + p.expect('[') + key := p.parseType() + p.expect(']') + elt := p.parseType() + return &Map{Key: key, Elt: elt} +} + +// Name = identifier | "?" | QualifiedName . +// +// If materializePkg is set, a package is returned for fully qualified names. +// That package may be a fake package (without name, scope, and not in the +// p.imports map), created for the sole purpose of providing a package path +// for QualifiedNames. Fake packages are created when the package id is not +// found in the p.imports map; we cannot create a real package in that case +// because we don't have a package name. +// +// TODO(gri): consider changing QualifiedIdents to (path, name) pairs to +// simplify this code. +// +func (p *gcParser) parseName(materializePkg bool) (pkg *Package, name string) { + switch p.tok { + case scanner.Ident: + name = p.lit + p.next() + case '?': + // anonymous + p.next() + case '@': + // exported name prefixed with package path + var id string + id, name = p.parseQualifiedName() + if materializePkg { + // we don't have a package name - if the package + // doesn't exist yet, create a fake package instead + pkg = p.getPkg(id, "") + if pkg == nil { + pkg = &Package{Path: id} + } + } + default: + p.error("name expected") + } + return +} + +// Field = Name Type [ string_lit ] . +// +func (p *gcParser) parseField() *Field { + var f Field + f.Pkg, f.Name = p.parseName(true) + f.Type = p.parseType() + if p.tok == scanner.String { + f.Tag = p.expect(scanner.String) + } + if f.Name == "" { + // anonymous field - typ must be T or *T and T must be a type name + if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil { + f.Name = typ.Obj.GetName() + f.IsAnonymous = true + } else { + p.errorf("anonymous field expected") + } + } + return &f +} + +// StructType = "struct" "{" [ FieldList ] "}" . +// FieldList = Field { ";" Field } . +// +func (p *gcParser) parseStructType() Type { + var fields []*Field + + p.expectKeyword("struct") + p.expect('{') + for p.tok != '}' { + if len(fields) > 0 { + p.expect(';') + } + fields = append(fields, p.parseField()) + } + p.expect('}') + + return &Struct{Fields: fields} +} + +// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] . +// +func (p *gcParser) parseParameter() (par *Var, isVariadic bool) { + _, name := p.parseName(false) + if name == "" { + name = "_" // cannot access unnamed identifiers + } + if p.tok == '.' { + p.expectSpecial("...") + isVariadic = true + } + typ := p.parseType() + // ignore argument tag (e.g. "noescape") + if p.tok == scanner.String { + p.next() + } + par = &Var{Name: name, Type: typ} // Pkg == nil + return +} + +// Parameters = "(" [ ParameterList ] ")" . +// ParameterList = { Parameter "," } Parameter . +// +func (p *gcParser) parseParameters() (list []*Var, isVariadic bool) { + p.expect('(') + for p.tok != ')' { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParameter() + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return +} + +// Signature = Parameters [ Result ] . +// Result = Type | Parameters . +// +func (p *gcParser) parseSignature() *Signature { + params, isVariadic := p.parseParameters() + + // optional result type + var results []*Var + if p.tok == '(' { + var variadic bool + results, variadic = p.parseParameters() + if variadic { + p.error("... not permitted on result type") + } + } + + return &Signature{Params: params, Results: results, IsVariadic: isVariadic} +} + +// InterfaceType = "interface" "{" [ MethodList ] "}" . +// MethodList = Method { ";" Method } . +// Method = Name Signature . +// +// The methods of embedded interfaces are always "inlined" +// by the compiler and thus embedded interfaces are never +// visible in the export data. +// +func (p *gcParser) parseInterfaceType() Type { + var methods []*Method + + p.expectKeyword("interface") + p.expect('{') + for p.tok != '}' { + if len(methods) > 0 { + p.expect(';') + } + pkg, name := p.parseName(true) + typ := p.parseSignature() + methods = append(methods, &Method{QualifiedName{pkg, name}, typ}) + } + p.expect('}') + + return &Interface{Methods: methods} +} + +// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . +// +func (p *gcParser) parseChanType() Type { + dir := ast.SEND | ast.RECV + if p.tok == scanner.Ident { + p.expectKeyword("chan") + if p.tok == '<' { + p.expectSpecial("<-") + dir = ast.SEND + } + } else { + p.expectSpecial("<-") + p.expectKeyword("chan") + dir = ast.RECV + } + elt := p.parseType() + return &Chan{Dir: dir, Elt: elt} +} + +// Type = +// BasicType | TypeName | ArrayType | SliceType | StructType | +// PointerType | FuncType | InterfaceType | MapType | ChanType | +// "(" Type ")" . +// +// BasicType = ident . +// TypeName = ExportedName . +// SliceType = "[" "]" Type . +// PointerType = "*" Type . +// FuncType = "func" Signature . +// +func (p *gcParser) parseType() Type { + switch p.tok { + case scanner.Ident: + switch p.lit { + default: + return p.parseBasicType() + case "struct": + return p.parseStructType() + case "func": + // FuncType + p.next() + return p.parseSignature() + case "interface": + return p.parseInterfaceType() + case "map": + return p.parseMapType() + case "chan": + return p.parseChanType() + } + case '@': + // TypeName + pkg, name := p.parseExportedName() + return declTypeName(pkg, name).Type + case '[': + p.next() // look ahead + if p.tok == ']' { + // SliceType + p.next() + return &Slice{Elt: p.parseType()} + } + return p.parseArrayType() + case '*': + // PointerType + p.next() + return &Pointer{Base: p.parseType()} + case '<': + return p.parseChanType() + case '(': + // "(" Type ")" + p.next() + typ := p.parseType() + p.expect(')') + return typ + } + p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil +} + +// ---------------------------------------------------------------------------- +// Declarations + +// ImportDecl = "import" PackageName PackageId . +// +func (p *gcParser) parseImportDecl() { + p.expectKeyword("import") + name := p.parsePackageName() + p.getPkg(p.parsePackageId(), name) +} + +// int_lit = [ "+" | "-" ] { "0" ... "9" } . +// +func (p *gcParser) parseInt() string { + s := "" + switch p.tok { + case '-': + s = "-" + p.next() + case '+': + p.next() + } + return s + p.expect(scanner.Int) +} + +// number = int_lit [ "p" int_lit ] . +// +func (p *gcParser) parseNumber() (x operand) { + x.mode = constant + + // mantissa + mant := exact.MakeFromLiteral(p.parseInt(), token.INT) + assert(mant != nil) + + if p.lit == "p" { + // exponent (base 2) + p.next() + exp, err := strconv.ParseInt(p.parseInt(), 10, 0) + if err != nil { + p.error(err) + } + if exp < 0 { + denom := exact.MakeInt64(1) + denom = exact.Shift(denom, token.SHL, uint(-exp)) + x.typ = Typ[UntypedFloat] + x.val = exact.BinaryOp(mant, token.QUO, denom) + return + } + if exp > 0 { + mant = exact.Shift(mant, token.SHL, uint(exp)) + } + x.typ = Typ[UntypedFloat] + x.val = mant + return + } + + x.typ = Typ[UntypedInt] + x.val = mant + return +} + +// ConstDecl = "const" ExportedName [ Type ] "=" Literal . +// Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit . +// bool_lit = "true" | "false" . +// complex_lit = "(" float_lit "+" float_lit "i" ")" . +// rune_lit = "(" int_lit "+" int_lit ")" . +// string_lit = `"` { unicode_char } `"` . +// +func (p *gcParser) parseConstDecl() { + p.expectKeyword("const") + pkg, name := p.parseExportedName() + obj := declConst(pkg, name) + var x operand + if p.tok != '=' { + obj.Type = p.parseType() + } + p.expect('=') + switch p.tok { + case scanner.Ident: + // bool_lit + if p.lit != "true" && p.lit != "false" { + p.error("expected true or false") + } + x.typ = Typ[UntypedBool] + x.val = exact.MakeBool(p.lit == "true") + p.next() + + case '-', scanner.Int: + // int_lit + x = p.parseNumber() + + case '(': + // complex_lit or rune_lit + p.next() + if p.tok == scanner.Char { + p.next() + p.expect('+') + x = p.parseNumber() + x.typ = Typ[UntypedRune] + p.expect(')') + break + } + re := p.parseNumber() + p.expect('+') + im := p.parseNumber() + p.expectKeyword("i") + p.expect(')') + x.typ = Typ[UntypedComplex] + // TODO(gri) fix this + _, _ = re, im + x.val = exact.MakeInt64(0) + + case scanner.Char: + // rune_lit + x.setConst(token.CHAR, p.lit) + p.next() + + case scanner.String: + // string_lit + x.setConst(token.STRING, p.lit) + p.next() + + default: + p.errorf("expected literal got %s", scanner.TokenString(p.tok)) + } + if obj.Type == nil { + obj.Type = x.typ + } + assert(x.val != nil) + obj.Val = x.val +} + +// TypeDecl = "type" ExportedName Type . +// +func (p *gcParser) parseTypeDecl() { + p.expectKeyword("type") + pkg, name := p.parseExportedName() + obj := declTypeName(pkg, name) + + // The type object may have been imported before and thus already + // have a type associated with it. We still need to parse the type + // structure, but throw it away if the object already has a type. + // This ensures that all imports refer to the same type object for + // a given type declaration. + typ := p.parseType() + + if name := obj.Type.(*NamedType); name.Underlying == nil { + name.Underlying = typ + } +} + +// VarDecl = "var" ExportedName Type . +// +func (p *gcParser) parseVarDecl() { + p.expectKeyword("var") + pkg, name := p.parseExportedName() + obj := declVar(pkg, name) + obj.Type = p.parseType() +} + +// Func = Signature [ Body ] . +// Body = "{" ... "}" . +// +func (p *gcParser) parseFunc() *Signature { + sig := p.parseSignature() + if p.tok == '{' { + p.next() + for i := 1; i > 0; p.next() { + switch p.tok { + case '{': + i++ + case '}': + i-- + } + } + } + return sig +} + +// MethodDecl = "func" Receiver Name Func . +// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" . +// +func (p *gcParser) parseMethodDecl() { + // "func" already consumed + p.expect('(') + recv, _ := p.parseParameter() // receiver + p.expect(')') + + // determine receiver base type object + typ := recv.Type + if ptr, ok := typ.(*Pointer); ok { + typ = ptr.Base + } + base := typ.(*NamedType) + + // parse method name, signature, and possibly inlined body + pkg, name := p.parseName(true) // unexported method names in imports are qualified with their package. + sig := p.parseFunc() + sig.Recv = recv + + // add method to type unless type was imported before + // and method exists already + // TODO(gri) investigate if this can be avoided + for _, m := range base.Methods { + if m.Name == name { + return // method was added before + } + } + base.Methods = append(base.Methods, &Method{QualifiedName{pkg, name}, sig}) +} + +// FuncDecl = "func" ExportedName Func . +// +func (p *gcParser) parseFuncDecl() { + // "func" already consumed + pkg, name := p.parseExportedName() + typ := p.parseFunc() + declFunc(pkg, name).Type = typ +} + +// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" . +// +func (p *gcParser) parseDecl() { + switch p.lit { + case "import": + p.parseImportDecl() + case "const": + p.parseConstDecl() + case "type": + p.parseTypeDecl() + case "var": + p.parseVarDecl() + case "func": + p.next() // look ahead + if p.tok == '(' { + p.parseMethodDecl() + } else { + p.parseFuncDecl() + } + } + p.expect('\n') +} + +// ---------------------------------------------------------------------------- +// Export + +// Export = "PackageClause { Decl } "$$" . +// PackageClause = "package" PackageName [ "safe" ] "\n" . +// +func (p *gcParser) parseExport() *Package { + p.expectKeyword("package") + name := p.parsePackageName() + if p.tok != '\n' { + // A package is safe if it was compiled with the -u flag, + // which disables the unsafe package. + // TODO(gri) remember "safe" package + p.expectKeyword("safe") + } + p.expect('\n') + + pkg := p.getPkg(p.id, name) + + for p.tok != '$' && p.tok != scanner.EOF { + p.parseDecl() + } + + if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' { + // don't call next()/expect() since reading past the + // export data may cause scanner errors (e.g. NUL chars) + p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch) + } + + if n := p.scanner.ErrorCount; n != 0 { + p.errorf("expected no scanner errors, got %d", n) + } + + // package was imported completely and without errors + pkg.Complete = true + + return pkg +} diff --git a/go/types/gcimporter_test.go b/go/types/gcimporter_test.go new file mode 100644 index 0000000000..b793eb4cb3 --- /dev/null +++ b/go/types/gcimporter_test.go @@ -0,0 +1,180 @@ +// Copyright 2011 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 types + +import ( + "go/ast" + "go/build" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +var gcPath string // Go compiler path + +func init() { + // determine compiler + var gc string + switch runtime.GOARCH { + case "386": + gc = "8g" + case "amd64": + gc = "6g" + case "arm": + gc = "5g" + default: + gcPath = "unknown-GOARCH-compiler" + return + } + gcPath = filepath.Join(build.ToolDir, gc) +} + +func compile(t *testing.T, dirname, filename string) string { + cmd := exec.Command(gcPath, filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("%s %s failed: %s", gcPath, filename, err) + } + archCh, _ := build.ArchChar(runtime.GOARCH) + // filename should end with ".go" + return filepath.Join(dirname, filename[:len(filename)-2]+archCh) +} + +// Use the same global imports map for all tests. The effect is +// as if all tested packages were imported into a single package. +var imports = make(map[string]*Package) + +func testPath(t *testing.T, path string) bool { + t0 := time.Now() + _, err := GcImport(imports, path) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return false + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return true +} + +const maxTime = 30 * time.Second + +func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) { + dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir) + list, err := ioutil.ReadDir(dirname) + if err != nil { + t.Fatalf("testDir(%s): %s", dirname, err) + } + for _, f := range list { + if time.Now().After(endTime) { + t.Log("testing time used up") + return + } + switch { + case !f.IsDir(): + // try extensions + for _, ext := range pkgExts { + if strings.HasSuffix(f.Name(), ext) { + name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension + if testPath(t, filepath.Join(dir, name)) { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func TestGcImport(t *testing.T) { + // On cross-compile builds, the path will not exist. + // Need to use GOHOSTOS, which is not available. + if _, err := os.Stat(gcPath); err != nil { + t.Skipf("skipping test: %v", err) + } + + if outFn := compile(t, "testdata", "exports.go"); outFn != "" { + defer os.Remove(outFn) + } + + nimports := 0 + if testPath(t, "./testdata/exports") { + nimports++ + } + nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + kind ast.ObjKind + typ string +}{ + {"unsafe.Pointer", ast.Typ, "Pointer"}, + {"math.Pi", ast.Con, "untyped float"}, + {"io.Reader", ast.Typ, "interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", ast.Typ, "interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"}, + {"math.Sin", ast.Fun, "func(x·2 float64) (_ float64)"}, + // TODO(gri) add more tests +} + +func TestGcImportedTypes(t *testing.T) { + // This package does not yet know how to read gccgo export data. + if runtime.Compiler == "gccgo" { + return + } + for _, test := range importedObjectTests { + s := strings.Split(test.name, ".") + if len(s) != 2 { + t.Fatal("inconsistent test data") + } + importPath := s[0] + objName := s[1] + + pkg, err := GcImport(imports, importPath) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope.Lookup(objName) + + // TODO(gri) should define an accessor on Object + var kind ast.ObjKind + var typ Type + switch obj := obj.(type) { + case *Const: + kind = ast.Con + typ = obj.Type + case *TypeName: + kind = ast.Typ + typ = obj.Type + case *Var: + kind = ast.Var + typ = obj.Type + case *Func: + kind = ast.Fun + typ = obj.Type + default: + unreachable() + } + + if kind != test.kind { + t.Errorf("%s: got kind = %q; want %q", test.name, kind, test.kind) + } + + str := typeString(underlying(typ)) + if str != test.typ { + t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ) + } + } +} diff --git a/go/types/objects.go b/go/types/objects.go new file mode 100644 index 0000000000..e91f818b3a --- /dev/null +++ b/go/types/objects.go @@ -0,0 +1,193 @@ +// 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 types + +import ( + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// An Object describes a named language entity such as a package, +// constant, type, variable, function (incl. methods), or label. +// All objects implement the Object interface. +// +type Object interface { + GetPkg() *Package + GetName() string + GetType() Type + GetPos() token.Pos + + anObject() +} + +// A Package represents the contents (objects) of a Go package. +type Package struct { + Name string + Path string // import path, "" for current (non-imported) package + Scope *Scope // package-level scope + Imports map[string]*Package // map of import paths to imported packages + Complete bool // if set, this package was imported completely + + spec *ast.ImportSpec +} + +// A Const represents a declared constant. +type Const struct { + Pkg *Package + Name string + Type Type + Val exact.Value + + visited bool // for initialization cycle detection + spec *ast.ValueSpec +} + +// A TypeName represents a declared type. +type TypeName struct { + Pkg *Package + Name string + Type Type // *NamedType or *Basic + + spec *ast.TypeSpec +} + +// A Variable represents a declared variable (including function parameters and results). +type Var struct { + Pkg *Package // nil for parameters + Name string + Type Type + + visited bool // for initialization cycle detection + decl interface{} +} + +// A Func represents a declared function. +type Func struct { + Pkg *Package + Name string + Type Type // *Signature or *Builtin + + decl *ast.FuncDecl +} + +func (obj *Package) GetPkg() *Package { return obj } +func (obj *Const) GetPkg() *Package { return obj.Pkg } +func (obj *TypeName) GetPkg() *Package { return obj.Pkg } +func (obj *Var) GetPkg() *Package { return obj.Pkg } +func (obj *Func) GetPkg() *Package { return obj.Pkg } + +func (obj *Package) GetName() string { return obj.Name } +func (obj *Const) GetName() string { return obj.Name } +func (obj *TypeName) GetName() string { return obj.Name } +func (obj *Var) GetName() string { return obj.Name } +func (obj *Func) GetName() string { return obj.Name } + +func (obj *Package) GetType() Type { return Typ[Invalid] } +func (obj *Const) GetType() Type { return obj.Type } +func (obj *TypeName) GetType() Type { return obj.Type } +func (obj *Var) GetType() Type { return obj.Type } +func (obj *Func) GetType() Type { return obj.Type } + +func (obj *Package) GetPos() token.Pos { + if obj.spec == nil { + return token.NoPos + } + return obj.spec.Pos() +} + +func (obj *Const) GetPos() token.Pos { + if obj.spec == nil { + return token.NoPos + } + for _, n := range obj.spec.Names { + if n.Name == obj.Name { + return n.Pos() + } + } + return token.NoPos +} +func (obj *TypeName) GetPos() token.Pos { + if obj.spec == nil { + return token.NoPos + } + return obj.spec.Pos() +} + +func (obj *Var) GetPos() token.Pos { + switch d := obj.decl.(type) { + case *ast.Field: + for _, n := range d.Names { + if n.Name == obj.Name { + return n.Pos() + } + } + case *ast.ValueSpec: + for _, n := range d.Names { + if n.Name == obj.Name { + return n.Pos() + } + } + case *ast.AssignStmt: + for _, x := range d.Lhs { + if ident, isIdent := x.(*ast.Ident); isIdent && ident.Name == obj.Name { + return ident.Pos() + } + } + } + return token.NoPos +} +func (obj *Func) GetPos() token.Pos { + if obj.decl != nil && obj.decl.Name != nil { + return obj.decl.Name.Pos() + } + return token.NoPos +} + +func (*Package) anObject() {} +func (*Const) anObject() {} +func (*TypeName) anObject() {} +func (*Var) anObject() {} +func (*Func) anObject() {} + +// newObj returns a new Object for a given *ast.Object. +// It does not canonicalize them (it always returns a new one). +// For canonicalization, see check.lookup. +// +// TODO(gri) Once we do identifier resolution completely in +// the typechecker, this functionality can go. +// +func newObj(pkg *Package, astObj *ast.Object) Object { + assert(pkg != nil) + name := astObj.Name + typ, _ := astObj.Type.(Type) + switch astObj.Kind { + case ast.Bad: + // ignore + case ast.Pkg: + unreachable() + case ast.Con: + iota := astObj.Data.(int) + return &Const{Pkg: pkg, Name: name, Type: typ, Val: exact.MakeInt64(int64(iota)), spec: astObj.Decl.(*ast.ValueSpec)} + case ast.Typ: + return &TypeName{Pkg: pkg, Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)} + case ast.Var: + switch astObj.Decl.(type) { + case *ast.Field: // function parameters + case *ast.ValueSpec: // proper variable declarations + case *ast.AssignStmt: // short variable declarations + default: + unreachable() // everything else is not ok + } + return &Var{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl} + case ast.Fun: + return &Func{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl.(*ast.FuncDecl)} + case ast.Lbl: + unreachable() // for now + } + unreachable() + return nil +} diff --git a/go/types/operand.go b/go/types/operand.go new file mode 100644 index 0000000000..ce84cbb35c --- /dev/null +++ b/go/types/operand.go @@ -0,0 +1,406 @@ +// 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 defines operands and associated operations. + +package types + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// An operandMode specifies the (addressing) mode of an operand. +type operandMode int + +const ( + invalid operandMode = iota // operand is invalid (due to an earlier error) - ignore + novalue // operand represents no value (result of a function call w/o result) + typexpr // operand is a type + constant // operand is a constant; the operand's typ is a Basic type + variable // operand is an addressable variable + value // operand is a computed value + valueok // like mode == value, but operand may be used in a comma,ok expression +) + +var operandModeString = [...]string{ + invalid: "invalid", + novalue: "no value", + typexpr: "type", + constant: "constant", + variable: "variable", + value: "value", + valueok: "value,ok", +} + +// An operand represents an intermediate value during type checking. +// Operands have an (addressing) mode, the expression evaluating to +// the operand, the operand's type, and for constants a constant value. +// +type operand struct { + mode operandMode + expr ast.Expr + typ Type + val exact.Value +} + +// pos returns the position of the expression corresponding to x. +// If x is invalid the position is token.NoPos. +// +func (x *operand) pos() token.Pos { + // x.expr may not be set if x is invalid + if x.expr == nil { + return token.NoPos + } + return x.expr.Pos() +} + +func (x *operand) String() string { + if x.mode == invalid { + return "invalid operand" + } + var buf bytes.Buffer + if x.expr != nil { + buf.WriteString(exprString(x.expr)) + buf.WriteString(" (") + } + buf.WriteString(operandModeString[x.mode]) + if x.mode == constant { + format := " %v" + if isString(x.typ) { + format = " %q" + } + fmt.Fprintf(&buf, format, x.val) + } + if x.mode != novalue && (x.mode != constant || !isUntyped(x.typ)) { + fmt.Fprintf(&buf, " of type %s", typeString(x.typ)) + } + if x.expr != nil { + buf.WriteByte(')') + } + return buf.String() +} + +// setConst sets x to the untyped constant for literal lit. +func (x *operand) setConst(tok token.Token, lit string) { + val := exact.MakeFromLiteral(lit, tok) + if val == nil { + // TODO(gri) Should we make it an unknown constant instead? + x.mode = invalid + return + } + + var kind BasicKind + switch tok { + case token.INT: + kind = UntypedInt + case token.FLOAT: + kind = UntypedFloat + case token.IMAG: + kind = UntypedComplex + case token.CHAR: + kind = UntypedRune + case token.STRING: + kind = UntypedString + } + + x.mode = constant + x.typ = Typ[kind] + x.val = val +} + +// isNil reports whether x is the predeclared nil constant. +func (x *operand) isNil() bool { + return x.mode == constant && x.val.Kind() == exact.Nil +} + +// TODO(gri) The functions operand.isAssignable, checker.convertUntyped, +// checker.isRepresentable, and checker.assignOperand are +// overlapping in functionality. Need to simplify and clean up. + +// isAssignable reports whether x is assignable to a variable of type T. +func (x *operand) isAssignable(ctxt *Context, T Type) bool { + if x.mode == invalid || T == Typ[Invalid] { + return true // avoid spurious errors + } + + V := x.typ + + // x's type is identical to T + if IsIdentical(V, T) { + return true + } + + Vu := underlying(V) + Tu := underlying(T) + + // x's type V and T have identical underlying types + // and at least one of V or T is not a named type + if IsIdentical(Vu, Tu) { + return !isNamed(V) || !isNamed(T) + } + + // T is an interface type and x implements T + if Ti, ok := Tu.(*Interface); ok { + if m, _ := missingMethod(x.typ, Ti); m == nil { + return true + } + } + + // x is a bidirectional channel value, T is a channel + // type, x's type V and T have identical element types, + // and at least one of V or T is not a named type + if Vc, ok := Vu.(*Chan); ok && Vc.Dir == ast.SEND|ast.RECV { + if Tc, ok := Tu.(*Chan); ok && IsIdentical(Vc.Elt, Tc.Elt) { + return !isNamed(V) || !isNamed(T) + } + } + + // x is the predeclared identifier nil and T is a pointer, + // function, slice, map, channel, or interface type + if x.isNil() { + switch t := Tu.(type) { + case *Basic: + if t.Kind == UnsafePointer { + return true + } + case *Pointer, *Signature, *Slice, *Map, *Chan, *Interface: + return true + } + return false + } + + // x is an untyped constant representable by a value of type T + // TODO(gri) This is borrowing from checker.convertUntyped and + // checker.isRepresentable. Need to clean up. + if isUntyped(Vu) { + switch t := Tu.(type) { + case *Basic: + if x.mode == constant { + return isRepresentableConst(x.val, ctxt, t.Kind) + } + // The result of a comparison is an untyped boolean, + // but may not be a constant. + if Vb, _ := Vu.(*Basic); Vb != nil { + return Vb.Kind == UntypedBool && isBoolean(Tu) + } + case *Interface: + return x.isNil() || len(t.Methods) == 0 + case *Pointer, *Signature, *Slice, *Map, *Chan: + return x.isNil() + } + } + + return false +} + +// isInteger reports whether x is a (typed or untyped) integer value. +func (x *operand) isInteger() bool { + return x.mode == invalid || + isInteger(x.typ) || + x.mode == constant && isRepresentableConst(x.val, nil, UntypedInt) // no context required for UntypedInt +} + +// lookupResult represents the result of a struct field/method lookup. +type lookupResult struct { + mode operandMode + typ Type + index []int // field index sequence; nil for methods +} + +type embeddedType struct { + typ *NamedType + index []int // field index sequence + multiples bool // if set, typ is embedded multiple times at the same level +} + +// lookupFieldBreadthFirst searches all types in list for a single entry (field +// or method) of the given name from the given package. If such a field is found, +// the result describes the field mode and type; otherwise the result mode is invalid. +// (This function is similar in structure to FieldByNameFunc in reflect/type.go) +// +func lookupFieldBreadthFirst(list []embeddedType, name QualifiedName) (res lookupResult) { + // visited records the types that have been searched already. + visited := make(map[*NamedType]bool) + + // embedded types of the next lower level + var next []embeddedType + + // potentialMatch is invoked every time a match is found. + potentialMatch := func(multiples bool, mode operandMode, typ Type) bool { + if multiples || res.mode != invalid { + // name appeared already at this level - annihilate + res.mode = invalid + return false + } + // first appearance of name + res.mode = mode + res.typ = typ + res.index = nil + return true + } + + // Search the current level if there is any work to do and collect + // embedded types of the next lower level in the next list. + for len(list) > 0 { + // The res.mode indicates whether we have found a match already + // on this level (mode != invalid), or not (mode == invalid). + assert(res.mode == invalid) + + // start with empty next list (don't waste underlying array) + next = next[:0] + + // look for name in all types at this level + for _, e := range list { + typ := e.typ + if visited[typ] { + continue + } + visited[typ] = true + + // look for a matching attached method + for _, m := range typ.Methods { + if name.IsSame(m.QualifiedName) { + assert(m.Type != nil) + if !potentialMatch(e.multiples, value, m.Type) { + return // name collision + } + } + } + + switch t := typ.Underlying.(type) { + case *Struct: + // look for a matching field and collect embedded types + for i, f := range t.Fields { + if name.IsSame(f.QualifiedName) { + assert(f.Type != nil) + if !potentialMatch(e.multiples, variable, f.Type) { + return // name collision + } + var index []int + index = append(index, e.index...) // copy e.index + index = append(index, i) + res.index = index + continue + } + // Collect embedded struct fields for searching the next + // lower level, but only if we have not seen a match yet + // (if we have a match it is either the desired field or + // we have a name collision on the same level; in either + // case we don't need to look further). + // Embedded fields are always of the form T or *T where + // T is a named type. If typ appeared multiple times at + // this level, f.Type appears multiple times at the next + // level. + if f.IsAnonymous && res.mode == invalid { + // Ignore embedded basic types - only user-defined + // named types can have methods or have struct fields. + if t, _ := deref(f.Type).(*NamedType); t != nil { + var index []int + index = append(index, e.index...) // copy e.index + index = append(index, i) + next = append(next, embeddedType{t, index, e.multiples}) + } + } + } + + case *Interface: + // look for a matching method + for _, m := range t.Methods { + if name.IsSame(m.QualifiedName) { + assert(m.Type != nil) + if !potentialMatch(e.multiples, value, m.Type) { + return // name collision + } + } + } + } + } + + if res.mode != invalid { + // we found a single match on this level + return + } + + // No match and no collision so far. + // Compute the list to search for the next level. + list = list[:0] // don't waste underlying array + for _, e := range next { + // Instead of adding the same type multiple times, look for + // it in the list and mark it as multiple if it was added + // before. + // We use a sequential search (instead of a map for next) + // because the lists tend to be small, can easily be reused, + // and explicit search appears to be faster in this case. + if alt := findType(list, e.typ); alt != nil { + alt.multiples = true + } else { + list = append(list, e) + } + } + + } + + return +} + +func findType(list []embeddedType, typ *NamedType) *embeddedType { + for i := range list { + if p := &list[i]; p.typ == typ { + return p + } + } + return nil +} + +func lookupField(typ Type, name QualifiedName) lookupResult { + typ = deref(typ) + + if t, ok := typ.(*NamedType); ok { + for _, m := range t.Methods { + if name.IsSame(m.QualifiedName) { + assert(m.Type != nil) + return lookupResult{value, m.Type, nil} + } + } + typ = t.Underlying + } + + switch t := typ.(type) { + case *Struct: + var next []embeddedType + for i, f := range t.Fields { + if name.IsSame(f.QualifiedName) { + return lookupResult{variable, f.Type, []int{i}} + } + if f.IsAnonymous { + // Possible optimization: If the embedded type + // is a pointer to the current type we could + // ignore it. + // Ignore embedded basic types - only user-defined + // named types can have methods or have struct fields. + if t, _ := deref(f.Type).(*NamedType); t != nil { + next = append(next, embeddedType{t, []int{i}, false}) + } + } + } + if len(next) > 0 { + return lookupFieldBreadthFirst(next, name) + } + + case *Interface: + for _, m := range t.Methods { + if name.IsSame(m.QualifiedName) { + return lookupResult{value, m.Type, nil} + } + } + } + + // not found + return lookupResult{mode: invalid} +} diff --git a/go/types/predicates.go b/go/types/predicates.go new file mode 100644 index 0000000000..a99c91a4ef --- /dev/null +++ b/go/types/predicates.go @@ -0,0 +1,303 @@ +// 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 implements commonly used type predicates. + +package types + +func isNamed(typ Type) bool { + if _, ok := typ.(*Basic); ok { + return ok + } + _, ok := typ.(*NamedType) + return ok +} + +func isBoolean(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsBoolean != 0 +} + +func isInteger(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsInteger != 0 +} + +func isUnsigned(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsUnsigned != 0 +} + +func isFloat(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsFloat != 0 +} + +func isComplex(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsComplex != 0 +} + +func isNumeric(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsNumeric != 0 +} + +func isString(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsString != 0 +} + +func isUntyped(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsUntyped != 0 +} + +func isOrdered(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsOrdered != 0 +} + +func isConstType(typ Type) bool { + t, ok := underlying(typ).(*Basic) + return ok && t.Info&IsConstType != 0 +} + +func isComparable(typ Type) bool { + switch t := underlying(typ).(type) { + case *Basic: + return t.Kind != Invalid && t.Kind != UntypedNil + case *Pointer, *Interface, *Chan: + // assumes types are equal for pointers and channels + return true + case *Struct: + for _, f := range t.Fields { + if !isComparable(f.Type) { + return false + } + } + return true + case *Array: + return isComparable(t.Elt) + } + return false +} + +func hasNil(typ Type) bool { + switch underlying(typ).(type) { + case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan: + return true + } + return false +} + +// IsIdentical returns true if x and y are identical. +func IsIdentical(x, y Type) bool { + if x == y { + return true + } + + switch x := x.(type) { + case *Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. + if y, ok := y.(*Basic); ok { + return x.Kind == y.Kind + } + + case *Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*Array); ok { + return x.Len == y.Len && IsIdentical(x.Elt, y.Elt) + } + + case *Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*Slice); ok { + return IsIdentical(x.Elt, y.Elt) + } + + case *Struct: + // Two struct types are identical if they have the same sequence of fields, + // and if corresponding fields have the same names, and identical types, + // and identical tags. Two anonymous fields are considered to have the same + // name. Lower-case field names from different packages are always different. + if y, ok := y.(*Struct); ok { + if len(x.Fields) == len(y.Fields) { + for i, f := range x.Fields { + g := y.Fields[i] + if !f.QualifiedName.IsSame(g.QualifiedName) || + !IsIdentical(f.Type, g.Type) || + f.Tag != g.Tag || + f.IsAnonymous != g.IsAnonymous { + return false + } + } + return true + } + } + + case *Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*Pointer); ok { + return IsIdentical(x.Base, y.Base) + } + + case *Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + if y, ok := y.(*Signature); ok { + return identicalTypes(x.Params, y.Params) && + identicalTypes(x.Results, y.Results) && + x.IsVariadic == y.IsVariadic + } + + case *Interface: + // Two interface types are identical if they have the same set of methods with + // the same names and identical function types. Lower-case method names from + // different packages are always different. The order of the methods is irrelevant. + if y, ok := y.(*Interface); ok { + return identicalMethods(x.Methods, y.Methods) // methods are sorted + } + + case *Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*Map); ok { + return IsIdentical(x.Key, y.Key) && IsIdentical(x.Elt, y.Elt) + } + + case *Chan: + // Two channel types are identical if they have identical value types + // and the same direction. + if y, ok := y.(*Chan); ok { + return x.Dir == y.Dir && IsIdentical(x.Elt, y.Elt) + } + + case *NamedType: + // Two named types are identical if their type names originate + // in the same type declaration. + if y, ok := y.(*NamedType); ok { + return x.Obj == y.Obj + } + } + + return false +} + +// identicalTypes returns true if both lists a and b have the +// same length and corresponding objects have identical types. +func identicalTypes(a, b []*Var) bool { + if len(a) != len(b) { + return false + } + for i, x := range a { + y := b[i] + if !IsIdentical(x.Type, y.Type) { + return false + } + } + return true +} + +// identicalMethods returns true if both lists a and b have the +// same length and corresponding methods have identical types. +// TODO(gri) make this more efficient +func identicalMethods(a, b []*Method) bool { + if len(a) != len(b) { + return false + } + m := make(map[QualifiedName]*Method) + for _, x := range a { + assert(m[x.QualifiedName] == nil) // method list must not have duplicate entries + m[x.QualifiedName] = x + } + for _, y := range b { + if x := m[y.QualifiedName]; x == nil || !IsIdentical(x.Type, y.Type) { + return false + } + } + return true +} + +// underlying returns the underlying type of typ. +func underlying(typ Type) Type { + // Basic types are representing themselves directly even though they are named. + if typ, ok := typ.(*NamedType); ok { + return typ.Underlying // underlying types are never NamedTypes + } + return typ +} + +// deref returns a pointer's base type; otherwise it returns typ. +func deref(typ Type) Type { + if typ, ok := underlying(typ).(*Pointer); ok { + return typ.Base + } + return typ +} + +// defaultType returns the default "typed" type for an "untyped" type; +// it returns the incoming type for all other types. If there is no +// corresponding untyped type, the result is Typ[Invalid]. +// +func defaultType(typ Type) Type { + if t, ok := typ.(*Basic); ok { + k := Invalid + switch t.Kind { + // case UntypedNil: + // There is no default type for nil. For a good error message, + // catch this case before calling this function. + case UntypedBool: + k = Bool + case UntypedInt: + k = Int + case UntypedRune: + k = Rune + case UntypedFloat: + k = Float64 + case UntypedComplex: + k = Complex128 + case UntypedString: + k = String + } + typ = Typ[k] + } + return typ +} + +// missingMethod returns (nil, false) if typ implements T, otherwise +// it returns the first missing method required by T and whether it +// is missing or simply has the wrong type. +// +func missingMethod(typ Type, T *Interface) (method *Method, wrongType bool) { + // TODO(gri): this needs to correctly compare method names (taking package into account) + // TODO(gri): distinguish pointer and non-pointer receivers + // an interface type implements T if it has no methods with conflicting signatures + // Note: This is stronger than the current spec. Should the spec require this? + if ityp, _ := underlying(typ).(*Interface); ityp != nil { + for _, m := range T.Methods { + res := lookupField(ityp, m.QualifiedName) // TODO(gri) no need to go via lookupField + if res.mode != invalid && !IsIdentical(res.typ, m.Type) { + return m, true + } + } + return + } + + // a concrete type implements T if it implements all methods of T. + for _, m := range T.Methods { + res := lookupField(typ, m.QualifiedName) + if res.mode == invalid { + return m, false + } + if !IsIdentical(res.typ, m.Type) { + return m, true + } + } + return +} diff --git a/go/types/resolve.go b/go/types/resolve.go new file mode 100644 index 0000000000..43db60708f --- /dev/null +++ b/go/types/resolve.go @@ -0,0 +1,197 @@ +// 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 types + +import ( + "fmt" + "go/ast" + "go/token" + "strconv" +) + +func (check *checker) declareObj(scope, altScope *Scope, obj Object, dotImport token.Pos) { + alt := scope.Insert(obj) + if alt == nil && altScope != nil { + // see if there is a conflicting declaration in altScope + alt = altScope.Lookup(obj.GetName()) + } + if alt != nil { + prevDecl := "" + + // for dot-imports, local declarations are declared first - swap messages + if dotImport.IsValid() { + if pos := alt.GetPos(); pos.IsValid() { + check.errorf(pos, fmt.Sprintf("%s redeclared in this block by dot-import at %s", + obj.GetName(), check.fset.Position(dotImport))) + return + } + + // get by w/o other position + check.errorf(dotImport, fmt.Sprintf("dot-import redeclares %s", obj.GetName())) + return + } + + if pos := alt.GetPos(); pos.IsValid() { + prevDecl = fmt.Sprintf("\n\tother declaration at %s", check.fset.Position(pos)) + } + check.errorf(obj.GetPos(), fmt.Sprintf("%s redeclared in this block%s", obj.GetName(), prevDecl)) + } +} + +func (check *checker) resolveIdent(scope *Scope, ident *ast.Ident) bool { + for ; scope != nil; scope = scope.Outer { + if obj := scope.Lookup(ident.Name); obj != nil { + check.register(ident, obj) + return true + } + } + return false +} + +func (check *checker) resolve(importer Importer) (methods []*ast.FuncDecl) { + pkg := &Package{Scope: &Scope{Outer: Universe}, Imports: make(map[string]*Package)} + check.pkg = pkg + + // complete package scope + i := 0 + for _, file := range check.files { + // package names must match + switch name := file.Name.Name; { + case pkg.Name == "": + pkg.Name = name + case name != pkg.Name: + check.errorf(file.Package, "package %s; expected %s", name, pkg.Name) + continue // ignore this file + } + + // keep this file + check.files[i] = file + i++ + + // the package identifier denotes the current package + check.register(file.Name, pkg) + + // insert top-level file objects in package scope + // (the parser took care of declaration errors) + for _, decl := range file.Decls { + switch d := decl.(type) { + case *ast.BadDecl: + // ignore + case *ast.GenDecl: + if d.Tok == token.CONST { + check.assocInitvals(d) + } + for _, spec := range d.Specs { + switch s := spec.(type) { + case *ast.ImportSpec: + // handled separately below + case *ast.ValueSpec: + for _, name := range s.Names { + if name.Name == "_" { + continue + } + pkg.Scope.Insert(check.lookup(name)) + } + case *ast.TypeSpec: + if s.Name.Name == "_" { + continue + } + pkg.Scope.Insert(check.lookup(s.Name)) + default: + check.invalidAST(s.Pos(), "unknown ast.Spec node %T", s) + } + } + case *ast.FuncDecl: + if d.Recv != nil { + // collect method + methods = append(methods, d) + continue + } + if d.Name.Name == "_" || d.Name.Name == "init" { + continue // blank (_) and init functions are inaccessible + } + pkg.Scope.Insert(check.lookup(d.Name)) + default: + check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d) + } + } + } + check.files = check.files[0:i] + + // complete file scopes with imports and resolve identifiers + for _, file := range check.files { + // build file scope by processing all imports + importErrors := false + fileScope := &Scope{Outer: pkg.Scope} + for _, spec := range file.Imports { + if importer == nil { + importErrors = true + continue + } + path, _ := strconv.Unquote(spec.Path.Value) + imp, err := importer(pkg.Imports, path) + if err != nil { + check.errorf(spec.Path.Pos(), "could not import %s (%s)", path, err) + importErrors = true + continue + } + // TODO(gri) If a local package name != "." is provided, + // global identifier resolution could proceed even if the + // import failed. Consider adjusting the logic here a bit. + + // local name overrides imported package name + name := imp.Name + if spec.Name != nil { + name = spec.Name.Name + } + + // add import to file scope + if name == "." { + // merge imported scope with file scope + for _, obj := range imp.Scope.Entries { + // gcimported package scopes contain non-exported + // objects such as types used in partially exported + // objects - do not accept them + if ast.IsExported(obj.GetName()) { + check.declareObj(fileScope, pkg.Scope, obj, spec.Pos()) + } + } + // TODO(gri) consider registering the "." identifier + // if we have Context.Ident callbacks for say blank + // (_) identifiers + // check.register(spec.Name, pkg) + } else if name != "_" { + // declare imported package object in file scope + // (do not re-use imp in the file scope but create + // a new object instead; the Decl field is different + // for different files) + obj := &Package{Name: name, Scope: imp.Scope, spec: spec} + check.declareObj(fileScope, pkg.Scope, obj, token.NoPos) + } + } + + // resolve identifiers + if importErrors { + // don't use the universe scope without correct imports + // (objects in the universe may be shadowed by imports; + // with missing imports, identifiers might get resolved + // incorrectly to universe objects) + pkg.Scope.Outer = nil + } + i := 0 + for _, ident := range file.Unresolved { + if !check.resolveIdent(fileScope, ident) { + check.errorf(ident.Pos(), "undeclared name: %s", ident.Name) + file.Unresolved[i] = ident + i++ + } + + } + file.Unresolved = file.Unresolved[0:i] + pkg.Scope.Outer = Universe // reset outer scope (is nil if there were importErrors) + } + + return +} diff --git a/go/types/resolver_test.go b/go/types/resolver_test.go new file mode 100644 index 0000000000..d4e364451d --- /dev/null +++ b/go/types/resolver_test.go @@ -0,0 +1,167 @@ +// Copyright 2011 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 types + +import ( + "go/ast" + "go/parser" + "go/token" + "testing" +) + +var sources = []string{ + ` + package p + import "fmt" + import "math" + const pi = math.Pi + func sin(x float64) float64 { + return math.Sin(x) + } + var Println = fmt.Println + `, + ` + package p + import "fmt" + func f() string { + _ = "foo" + return fmt.Sprintf("%d", g()) + } + func g() (x int) { return } + `, + ` + package p + import . "go/parser" + import "sync" + func g() Mode { return ImportsOnly } + var _, x int = 1, 2 + func init() {} + type T struct{ sync.Mutex; a, b, c int} + type I interface{ m() } + var _ = T{a: 1, b: 2, c: 3} + func (_ T) m() {} + `, +} + +var pkgnames = []string{ + "fmt", + "math", +} + +func TestResolveQualifiedIdents(t *testing.T) { + // parse package files + fset := token.NewFileSet() + var files []*ast.File + for _, src := range sources { + f, err := parser.ParseFile(fset, "", src, parser.DeclarationErrors) + if err != nil { + t.Fatal(err) + } + files = append(files, f) + } + + // resolve and type-check package AST + idents := make(map[*ast.Ident]Object) + var ctxt Context + ctxt.Ident = func(id *ast.Ident, obj Object) { idents[id] = obj } + pkg, err := ctxt.Check(fset, files) + if err != nil { + t.Fatal(err) + } + + // check that all packages were imported + for _, name := range pkgnames { + if pkg.Imports[name] == nil { + t.Errorf("package %s not imported", name) + } + } + + // check that there are no top-level unresolved identifiers + for _, f := range files { + for _, x := range f.Unresolved { + t.Errorf("%s: unresolved global identifier %s", fset.Position(x.Pos()), x.Name) + } + } + + // check that qualified identifiers are resolved + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if s, ok := n.(*ast.SelectorExpr); ok { + if x, ok := s.X.(*ast.Ident); ok { + obj := idents[x] + if obj == nil { + t.Errorf("%s: unresolved qualified identifier %s", fset.Position(x.Pos()), x.Name) + return false + } + if _, ok := obj.(*Package); ok && idents[s.Sel] == nil { + t.Errorf("%s: unresolved selector %s", fset.Position(s.Sel.Pos()), s.Sel.Name) + return false + } + return false + } + return false + } + return true + }) + } + + // Currently, the Check API doesn't call Ident for fields, methods, and composite literal keys. + // Introduce them artifically so that we can run the check below. + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.StructType: + for _, list := range x.Fields.List { + for _, f := range list.Names { + assert(idents[f] == nil) + idents[f] = &Var{Pkg: pkg, Name: f.Name} + } + } + case *ast.InterfaceType: + for _, list := range x.Methods.List { + for _, f := range list.Names { + assert(idents[f] == nil) + idents[f] = &Func{Pkg: pkg, Name: f.Name} + } + } + case *ast.CompositeLit: + for _, e := range x.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + if k, ok := kv.Key.(*ast.Ident); ok { + assert(idents[k] == nil) + idents[k] = &Var{Pkg: pkg, Name: k.Name} + } + } + } + } + return true + }) + } + + // check that each identifier in the source is enumerated by the Context.Ident callback + for _, f := range files { + ast.Inspect(f, func(n ast.Node) bool { + if x, ok := n.(*ast.Ident); ok && x.Name != "_" && x.Name != "." { + obj := idents[x] + if obj == nil { + t.Errorf("%s: unresolved identifier %s", fset.Position(x.Pos()), x.Name) + } else { + delete(idents, x) + } + return false + } + return true + }) + } + + // TODO(gri) enable code below + // At the moment, the type checker introduces artifical identifiers which are not + // present in the source. Once it doesn't do that anymore, enable the checks below. + /* + for x := range idents { + t.Errorf("%s: identifier %s not present in source", fset.Position(x.Pos()), x.Name) + } + */ +} diff --git a/go/types/return.go b/go/types/return.go new file mode 100644 index 0000000000..8644d28c91 --- /dev/null +++ b/go/types/return.go @@ -0,0 +1,189 @@ +// 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 implements isTerminating. + +package types + +import ( + "go/ast" + "go/token" +) + +// isTerminating reports if s is a terminating statement. +// If s is labeled, label is the label name; otherwise s +// is "". +func (check *checker) isTerminating(s ast.Stmt, label string) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.SendStmt, + *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, *ast.DeferStmt, + *ast.RangeStmt: + // no chance + + case *ast.LabeledStmt: + return check.isTerminating(s.Stmt, s.Label.Name) + + case *ast.ExprStmt: + // the predeclared panic() function is terminating + if call, _ := s.X.(*ast.CallExpr); call != nil { + if id, _ := call.Fun.(*ast.Ident); id != nil { + if obj := check.lookup(id); obj != nil { + // TODO(gri) Predeclared functions should be modelled as objects + // rather then ordinary functions that have a predeclared + // function type. This would simplify code here and else- + // where. + if f, _ := obj.(*Func); f != nil && f.Type == predeclaredFunctions[_Panic] { + return true + } + } + } + } + + case *ast.ReturnStmt: + return true + + case *ast.BranchStmt: + if s.Tok == token.GOTO || s.Tok == token.FALLTHROUGH { + return true + } + + case *ast.BlockStmt: + return check.isTerminatingList(s.List, "") + + case *ast.IfStmt: + if s.Else != nil && + check.isTerminating(s.Body, "") && + check.isTerminating(s.Else, "") { + return true + } + + case *ast.SwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.TypeSwitchStmt: + return check.isTerminatingSwitch(s.Body, label) + + case *ast.SelectStmt: + for _, s := range s.Body.List { + cc := s.(*ast.CommClause) + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + + } + return true + + case *ast.ForStmt: + if s.Cond == nil && !hasBreak(s.Body, label, true) { + return true + } + } + + return false +} + +func (check *checker) isTerminatingList(list []ast.Stmt, label string) bool { + n := len(list) + return n > 0 && check.isTerminating(list[n-1], label) +} + +func (check *checker) isTerminatingSwitch(body *ast.BlockStmt, label string) bool { + hasDefault := false + for _, s := range body.List { + cc := s.(*ast.CaseClause) + if cc.List == nil { + hasDefault = true + } + if !check.isTerminatingList(cc.Body, "") || hasBreakList(cc.Body, label, true) { + return false + } + } + return hasDefault +} + +// TODO(gri) For nested breakable statements, the current implementation of hasBreak +// will traverse the same subtree repeatedly, once for each label. Replace +// with a single-pass label/break matching phase. + +// hasBreak reports if s is or contains a break statement +// referring to the label-ed statement or implicit-ly the +// closest outer breakable statement. +func hasBreak(s ast.Stmt, label string, implicit bool) bool { + switch s := s.(type) { + default: + unreachable() + + case *ast.BadStmt, *ast.DeclStmt, *ast.EmptyStmt, *ast.ExprStmt, + *ast.SendStmt, *ast.IncDecStmt, *ast.AssignStmt, *ast.GoStmt, + *ast.DeferStmt, *ast.ReturnStmt: + // no chance + + case *ast.LabeledStmt: + return hasBreak(s.Stmt, label, implicit) + + case *ast.BranchStmt: + if s.Tok == token.BREAK { + if s.Label == nil { + return implicit + } + if s.Label.Name == label { + return true + } + } + + case *ast.BlockStmt: + return hasBreakList(s.List, label, implicit) + + case *ast.IfStmt: + if hasBreak(s.Body, label, implicit) || + s.Else != nil && hasBreak(s.Else, label, implicit) { + return true + } + + case *ast.CaseClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.TypeSwitchStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.CommClause: + return hasBreakList(s.Body, label, implicit) + + case *ast.SelectStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.ForStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + + case *ast.RangeStmt: + if label != "" && hasBreak(s.Body, label, false) { + return true + } + } + + return false +} + +func hasBreakList(list []ast.Stmt, label string, implicit bool) bool { + for _, s := range list { + if hasBreak(s, label, implicit) { + return true + } + } + return false +} diff --git a/go/types/scope.go b/go/types/scope.go new file mode 100644 index 0000000000..463ee40c54 --- /dev/null +++ b/go/types/scope.go @@ -0,0 +1,78 @@ +// 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 types + +import ( + "bytes" + "fmt" +) + +// A Scope maintains the set of named language entities declared +// in the scope and a link to the immediately surrounding (outer) +// scope. +// +type Scope struct { + Outer *Scope + Entries []Object // scope entries in insertion order + large map[string]Object // for fast lookup - only used for larger scopes +} + +// Lookup returns the object with the given name if it is +// found in scope s, otherwise it returns nil. Outer scopes +// are ignored. +// +func (s *Scope) Lookup(name string) Object { + if s.large != nil { + return s.large[name] + } + for _, obj := range s.Entries { + if obj.GetName() == name { + return obj + } + } + return nil +} + +// Insert attempts to insert an object obj into scope s. +// If s already contains an object with the same name, +// Insert leaves s unchanged and returns that object. +// Otherwise it inserts obj and returns nil. +// +func (s *Scope) Insert(obj Object) Object { + name := obj.GetName() + if alt := s.Lookup(name); alt != nil { + return alt + } + s.Entries = append(s.Entries, obj) + + // If the scope size reaches a threshold, use a map for faster lookups. + const threshold = 20 + if len(s.Entries) > threshold { + if s.large == nil { + m := make(map[string]Object, len(s.Entries)) + for _, obj := range s.Entries { + m[obj.GetName()] = obj + } + s.large = m + } + s.large[name] = obj + } + + return nil +} + +// Debugging support +func (s *Scope) String() string { + var buf bytes.Buffer + fmt.Fprintf(&buf, "scope %p {", s) + if s != nil && len(s.Entries) > 0 { + fmt.Fprintln(&buf) + for _, obj := range s.Entries { + fmt.Fprintf(&buf, "\t%s\t%T\n", obj.GetName(), obj) + } + } + fmt.Fprintf(&buf, "}\n") + return buf.String() +} diff --git a/go/types/sizes.go b/go/types/sizes.go new file mode 100644 index 0000000000..ef6499ba43 --- /dev/null +++ b/go/types/sizes.go @@ -0,0 +1,162 @@ +// 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 implements support for (unsafe) Alignof, Offsetof, and Sizeof. + +package types + +func (ctxt *Context) alignof(typ Type) int64 { + if f := ctxt.Alignof; f != nil { + if a := f(typ); a >= 1 { + return a + } + panic("Context.Alignof returned an alignment < 1") + } + return DefaultAlignof(typ) +} + +func (ctxt *Context) offsetsof(s *Struct) []int64 { + offsets := s.offsets + if offsets == nil { + // compute offsets on demand + if f := ctxt.Offsetsof; f != nil { + offsets = f(s.Fields) + // sanity checks + if len(offsets) != len(s.Fields) { + panic("Context.Offsetsof returned the wrong number of offsets") + } + for _, o := range offsets { + if o < 0 { + panic("Context.Offsetsof returned an offset < 0") + } + } + } else { + offsets = DefaultOffsetsof(s.Fields) + } + s.offsets = offsets + } + return offsets +} + +// offsetof returns the offset of the field specified via +// the index sequence relative to typ. It returns a value +// < 0 if the field is in an embedded pointer type. +func (ctxt *Context) offsetof(typ Type, index []int) int64 { + var o int64 + for _, i := range index { + s, _ := underlying(typ).(*Struct) + if s == nil { + return -1 + } + o += ctxt.offsetsof(s)[i] + typ = s.Fields[i].Type + } + return o +} + +func (ctxt *Context) sizeof(typ Type) int64 { + if f := ctxt.Sizeof; f != nil { + if s := f(typ); s >= 0 { + return s + } + panic("Context.Sizeof returned a size < 0") + } + return DefaultSizeof(typ) +} + +// DefaultMaxAlign is the default maximum alignment, in bytes, +// used by DefaultAlignof. +const DefaultMaxAlign = 8 + +// DefaultAlignof implements the default alignment computation +// for unsafe.Alignof. It is used if Context.Alignof == nil. +func DefaultAlignof(typ Type) int64 { + // For arrays and structs, alignment is defined in terms + // of alignment of the elements and fields, respectively. + switch t := underlying(typ).(type) { + case *Array: + // spec: "For a variable x of array type: unsafe.Alignof(x) + // is the same as unsafe.Alignof(x[0]), but at least 1." + return DefaultAlignof(t.Elt) + case *Struct: + // spec: "For a variable x of struct type: unsafe.Alignof(x) + // is the largest of the values unsafe.Alignof(x.f) for each + // field f of x, but at least 1." + max := int64(1) + for _, f := range t.Fields { + if a := DefaultAlignof(f.Type); a > max { + max = a + } + } + return max + } + a := DefaultSizeof(typ) // may be 0 + // spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1." + if a < 1 { + return 1 + } + if a > DefaultMaxAlign { + return DefaultMaxAlign + } + return a +} + +// align returns the smallest y >= x such that y % a == 0. +func align(x, a int64) int64 { + y := x + a - 1 + return y - y%a +} + +// DefaultOffsetsof implements the default field offset computation +// for unsafe.Offsetof. It is used if Context.Offsetsof == nil. +func DefaultOffsetsof(fields []*Field) []int64 { + offsets := make([]int64, len(fields)) + var o int64 + for i, f := range fields { + a := DefaultAlignof(f.Type) + o = align(o, a) + offsets[i] = o + o += DefaultSizeof(f.Type) + } + return offsets +} + +// DefaultPtrSize is the default size of ints, uint, and pointers, in bytes, +// used by DefaultSizeof. +const DefaultPtrSize = 8 + +// DefaultSizeof implements the default size computation +// for unsafe.Sizeof. It is used if Context.Sizeof == nil. +func DefaultSizeof(typ Type) int64 { + switch t := underlying(typ).(type) { + case *Basic: + if s := t.size; s > 0 { + return s + } + if t.Kind == String { + return DefaultPtrSize * 2 + } + case *Array: + a := DefaultAlignof(t.Elt) + s := DefaultSizeof(t.Elt) + return align(s, a) * t.Len // may be 0 + case *Slice: + return DefaultPtrSize * 3 + case *Struct: + n := len(t.Fields) + if n == 0 { + return 0 + } + offsets := t.offsets + if t.offsets == nil { + // compute offsets on demand + offsets = DefaultOffsetsof(t.Fields) + t.offsets = offsets + } + return offsets[n-1] + DefaultSizeof(t.Fields[n-1].Type) + case *Signature: + return DefaultPtrSize * 2 + } + return DefaultPtrSize // catch-all +} diff --git a/go/types/stdlib_test.go b/go/types/stdlib_test.go new file mode 100644 index 0000000000..8b264119d5 --- /dev/null +++ b/go/types/stdlib_test.go @@ -0,0 +1,133 @@ +// 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 tests types.Check by using it to +// typecheck the standard library. + +package types + +import ( + "flag" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + "io/ioutil" + "path/filepath" + "runtime" + "testing" + "time" +) + +var verbose = flag.Bool("types.v", false, "verbose mode") + +var ( + pkgCount int // number of packages processed + start = time.Now() +) + +func TestStdlib(t *testing.T) { + walkDirs(t, filepath.Join(runtime.GOROOT(), "src/pkg")) + if *verbose { + fmt.Println(pkgCount, "packages typechecked in", time.Since(start)) + } +} + +// Package paths of excluded packages. +var excluded = map[string]bool{ + "builtin": true, +} + +// typecheck typechecks the given package files. +func typecheck(t *testing.T, filenames []string) { + fset := token.NewFileSet() + + // parse package files + var files []*ast.File + for _, filename := range filenames { + file, err := parser.ParseFile(fset, filename, nil, parser.DeclarationErrors|parser.AllErrors) + if err != nil { + // the parser error may be a list of individual errors; report them all + if list, ok := err.(scanner.ErrorList); ok { + for _, err := range list { + t.Error(err) + } + return + } + t.Error(err) + return + } + + if *verbose { + if len(files) == 0 { + fmt.Println("package", file.Name.Name) + } + fmt.Println("\t", filename) + } + + files = append(files, file) + } + + // typecheck package files + ctxt := Context{ + Error: func(err error) { t.Error(err) }, + } + ctxt.Check(fset, files) + pkgCount++ +} + +// pkgfiles returns the list of package files for the given directory. +func pkgfiles(t *testing.T, dir string) []string { + ctxt := build.Default + ctxt.CgoEnabled = false + pkg, err := ctxt.ImportDir(dir, 0) + if err != nil { + if _, nogo := err.(*build.NoGoError); !nogo { + t.Error(err) + } + return nil + } + if excluded[pkg.ImportPath] { + return nil + } + var filenames []string + for _, name := range pkg.GoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + for _, name := range pkg.TestGoFiles { + filenames = append(filenames, filepath.Join(pkg.Dir, name)) + } + return filenames +} + +// Note: Could use filepath.Walk instead of walkDirs but that wouldn't +// necessarily be shorter or clearer after adding the code to +// terminate early for -short tests. + +func walkDirs(t *testing.T, dir string) { + // limit run time for short tests + if testing.Short() && time.Since(start) >= 750*time.Millisecond { + return + } + + fis, err := ioutil.ReadDir(dir) + if err != nil { + t.Error(err) + return + } + + // typecheck package in directory + if files := pkgfiles(t, dir); files != nil { + typecheck(t, files) + } + + // traverse subdirectories, but don't walk into testdata + for _, fi := range fis { + if fi.IsDir() && fi.Name() != "testdata" { + walkDirs(t, filepath.Join(dir, fi.Name())) + } + } +} diff --git a/go/types/stmt.go b/go/types/stmt.go new file mode 100644 index 0000000000..224386849d --- /dev/null +++ b/go/types/stmt.go @@ -0,0 +1,724 @@ +// 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 implements typechecking of statements. + +package types + +import ( + "go/ast" + "go/token" + + "code.google.com/p/go.tools/go/exact" +) + +// assigment reports whether x can be assigned to a variable of type 'to', +// if necessary by attempting to convert untyped values to the appropriate +// type. If x.mode == invalid upon return, then assignment has already +// issued an error message and the caller doesn't have to report another. +// TODO(gri) This latter behavior is for historic reasons and complicates +// callers. Needs to be cleaned up. +func (check *checker) assignment(x *operand, to Type) bool { + if x.mode == invalid { + return false + } + + if t, ok := x.typ.(*Result); ok { + // TODO(gri) elsewhere we use "assignment count mismatch" (consolidate) + check.errorf(x.pos(), "%d-valued expression %s used as single value", len(t.Values), x) + x.mode = invalid + return false + } + + check.convertUntyped(x, to) + + return x.mode != invalid && x.isAssignable(check.ctxt, to) +} + +// assign1to1 typechecks a single assignment of the form lhs = rhs (if rhs != nil), or +// lhs = x (if rhs == nil). If decl is set, the lhs expression must be an identifier; +// if its type is not set, it is deduced from the type of x or set to Typ[Invalid] in +// case of an error. +// +func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota int) { + // Start with rhs so we have an expression type + // for declarations with implicit type. + if x == nil { + x = new(operand) + check.expr(x, rhs, nil, iota) + // don't exit for declarations - we need the lhs first + if x.mode == invalid && !decl { + return + } + } + // x.mode == valid || decl + + // lhs may be an identifier + ident, _ := lhs.(*ast.Ident) + + // regular assignment; we know x is valid + if !decl { + // anything can be assigned to the blank identifier + if ident != nil && ident.Name == "_" { + // the rhs has its final type + check.updateExprType(rhs, x.typ, true) + return + } + + var z operand + check.expr(&z, lhs, nil, -1) + if z.mode == invalid { + return + } + + // TODO(gri) verify that all other z.mode values + // that may appear here are legal + if z.mode == constant || !check.assignment(x, z.typ) { + if x.mode != invalid { + check.errorf(x.pos(), "cannot assign %s to %s", x, &z) + } + } + return + } + + // declaration with initialization; lhs must be an identifier + if ident == nil { + check.errorf(lhs.Pos(), "cannot declare %s", lhs) + return + } + + // Determine typ of lhs: If the object doesn't have a type + // yet, determine it from the type of x; if x is invalid, + // set the object type to Typ[Invalid]. + var typ Type + obj := check.lookup(ident) + switch obj := obj.(type) { + default: + unreachable() + + case nil: + // TODO(gri) is this really unreachable? + unreachable() + + case *Const: + typ = obj.Type // may already be Typ[Invalid] + if typ == nil { + typ = Typ[Invalid] + if x.mode != invalid { + typ = x.typ + } + obj.Type = typ + } + + case *Var: + typ = obj.Type // may already be Typ[Invalid] + if typ == nil { + typ = Typ[Invalid] + if x.mode != invalid { + typ = x.typ + if isUntyped(typ) { + // convert untyped types to default types + if typ == Typ[UntypedNil] { + check.errorf(x.pos(), "use of untyped nil") + typ = Typ[Invalid] + } else { + typ = defaultType(typ) + } + } + } + obj.Type = typ + } + } + + // nothing else to check if we don't have a valid lhs or rhs + if typ == Typ[Invalid] || x.mode == invalid { + return + } + + if !check.assignment(x, typ) { + if x.mode != invalid { + if x.typ != Typ[Invalid] && typ != Typ[Invalid] { + check.errorf(x.pos(), "cannot initialize %s (type %s) with %s", ident.Name, typ, x) + } + } + return + } + + // for constants, set their value + if obj, _ := obj.(*Const); obj != nil { + obj.Val = exact.MakeUnknown() // failure case: we don't know the constant value + if x.mode == constant { + if isConstType(x.typ) { + obj.Val = x.val + } else if x.typ != Typ[Invalid] { + check.errorf(x.pos(), "%s has invalid constant type", x) + } + } else if x.mode != invalid { + check.errorf(x.pos(), "%s is not constant", x) + } + } +} + +// assignNtoM typechecks a general assignment. If decl is set, the lhs expressions +// must be identifiers; if their types are not set, they are deduced from the types +// of the corresponding rhs expressions, or set to Typ[Invalid] in case of an error. +// Precondition: len(lhs) > 0 . +// +func (check *checker) assignNtoM(lhs, rhs []ast.Expr, decl bool, iota int) { + assert(len(lhs) > 0) + + // If the lhs and rhs have corresponding expressions, treat each + // matching pair as an individual pair. + if len(lhs) == len(rhs) { + for i, e := range rhs { + check.assign1to1(lhs[i], e, nil, decl, iota) + } + return + } + + // Otherwise, the rhs must be a single expression (possibly + // a function call returning multiple values, or a comma-ok + // expression). + if len(rhs) == 1 { + // len(lhs) > 1 + // Start with rhs so we have expression types + // for declarations with implicit types. + var x operand + check.expr(&x, rhs[0], nil, iota) + if x.mode == invalid { + goto Error + } + + if t, _ := x.typ.(*Result); t != nil && len(lhs) == len(t.Values) { + // function result + x.mode = value + for i, obj := range t.Values { + x.expr = nil // TODO(gri) should do better here + x.typ = obj.Type + check.assign1to1(lhs[i], nil, &x, decl, iota) + } + return + } + + if x.mode == valueok && len(lhs) == 2 { + // comma-ok expression + x.mode = value + check.assign1to1(lhs[0], nil, &x, decl, iota) + + x.typ = Typ[UntypedBool] + check.assign1to1(lhs[1], nil, &x, decl, iota) + return + } + } + + check.errorf(lhs[0].Pos(), "assignment count mismatch: %d = %d", len(lhs), len(rhs)) + +Error: + // In case of a declaration, set all lhs types to Typ[Invalid]. + if decl { + for _, e := range lhs { + ident, _ := e.(*ast.Ident) + if ident == nil { + check.errorf(e.Pos(), "cannot declare %s", e) + continue + } + switch obj := check.lookup(ident).(type) { + case *Const: + obj.Type = Typ[Invalid] + case *Var: + obj.Type = Typ[Invalid] + default: + unreachable() + } + } + } +} + +func (check *checker) optionalStmt(s ast.Stmt) { + if s != nil { + check.stmt(s) + } +} + +func (check *checker) stmtList(list []ast.Stmt) { + for _, s := range list { + check.stmt(s) + } +} + +func (check *checker) call(call *ast.CallExpr) { + var x operand + check.rawExpr(&x, call, nil, -1, false) // don't check if value is used + // TODO(gri) If a builtin is called, the builtin must be valid in statement context. +} + +func (check *checker) multipleDefaults(list []ast.Stmt) { + var first ast.Stmt + for _, s := range list { + var d ast.Stmt + switch c := s.(type) { + case *ast.CaseClause: + if len(c.List) == 0 { + d = s + } + case *ast.CommClause: + if c.Comm == nil { + d = s + } + default: + check.invalidAST(s.Pos(), "case/communication clause expected") + } + if d != nil { + if first != nil { + check.errorf(d.Pos(), "multiple defaults (first at %s)", first.Pos()) + } else { + first = d + } + } + } +} + +// stmt typechecks statement s. +func (check *checker) stmt(s ast.Stmt) { + switch s := s.(type) { + case *ast.BadStmt, *ast.EmptyStmt: + // ignore + + case *ast.DeclStmt: + d, _ := s.Decl.(*ast.GenDecl) + if d == nil || (d.Tok != token.CONST && d.Tok != token.TYPE && d.Tok != token.VAR) { + check.invalidAST(token.NoPos, "const, type, or var declaration expected") + return + } + if d.Tok == token.CONST { + check.assocInitvals(d) + } + check.decl(d) + + case *ast.LabeledStmt: + // TODO(gri) anything to do with label itself? + check.stmt(s.Stmt) + + case *ast.ExprStmt: + var x operand + used := false + switch e := unparen(s.X).(type) { + case *ast.CallExpr: + // function calls are permitted + used = true + // but some builtins are excluded + // (Caution: This evaluates e.Fun twice, once here and once + // below as part of s.X. This has consequences for + // check.register. Perhaps this can be avoided.) + check.expr(&x, e.Fun, nil, -1) + if x.mode != invalid { + if b, ok := x.typ.(*builtin); ok && !b.isStatement { + used = false + } + } + case *ast.UnaryExpr: + // receive operations are permitted + if e.Op == token.ARROW { + used = true + } + } + if !used { + check.errorf(s.Pos(), "%s not used", s.X) + // ok to continue + } + check.rawExpr(&x, s.X, nil, -1, false) + if x.mode == typexpr { + check.errorf(x.pos(), "%s is not an expression", &x) + } + + case *ast.SendStmt: + var ch, x operand + check.expr(&ch, s.Chan, nil, -1) + check.expr(&x, s.Value, nil, -1) + if ch.mode == invalid || x.mode == invalid { + return + } + if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !check.assignment(&x, tch.Elt) { + if x.mode != invalid { + check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) + } + } + + case *ast.IncDecStmt: + var op token.Token + switch s.Tok { + case token.INC: + op = token.ADD + case token.DEC: + op = token.SUB + default: + check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) + return + } + var x operand + Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position + check.binary(&x, s.X, Y, op, -1) + if x.mode == invalid { + return + } + check.assign1to1(s.X, nil, &x, false, -1) + + case *ast.AssignStmt: + switch s.Tok { + case token.ASSIGN, token.DEFINE: + if len(s.Lhs) == 0 { + check.invalidAST(s.Pos(), "missing lhs in assignment") + return + } + check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) + default: + // assignment operations + if len(s.Lhs) != 1 || len(s.Rhs) != 1 { + check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) + return + } + // TODO(gri) make this conversion more efficient + var op token.Token + switch s.Tok { + case token.ADD_ASSIGN: + op = token.ADD + case token.SUB_ASSIGN: + op = token.SUB + case token.MUL_ASSIGN: + op = token.MUL + case token.QUO_ASSIGN: + op = token.QUO + case token.REM_ASSIGN: + op = token.REM + case token.AND_ASSIGN: + op = token.AND + case token.OR_ASSIGN: + op = token.OR + case token.XOR_ASSIGN: + op = token.XOR + case token.SHL_ASSIGN: + op = token.SHL + case token.SHR_ASSIGN: + op = token.SHR + case token.AND_NOT_ASSIGN: + op = token.AND_NOT + default: + check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) + return + } + var x operand + check.binary(&x, s.Lhs[0], s.Rhs[0], op, -1) + if x.mode == invalid { + return + } + check.assign1to1(s.Lhs[0], nil, &x, false, -1) + } + + case *ast.GoStmt: + check.call(s.Call) + + case *ast.DeferStmt: + check.call(s.Call) + + case *ast.ReturnStmt: + sig := check.funcsig + if n := len(sig.Results); n > 0 { + // TODO(gri) should not have to compute lhs, named every single time - clean this up + lhs := make([]ast.Expr, n) + named := false // if set, function has named results + for i, res := range sig.Results { + if len(res.Name) > 0 { + // a blank (_) result parameter is a named result + named = true + } + name := ast.NewIdent(res.Name) + name.NamePos = s.Pos() + check.register(name, &Var{Name: res.Name, Type: res.Type}) // Pkg == nil + lhs[i] = name + } + if len(s.Results) > 0 || !named { + // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 + check.assignNtoM(lhs, s.Results, false, -1) + } + } else if len(s.Results) > 0 { + check.errorf(s.Pos(), "no result values expected") + } + + case *ast.BranchStmt: + // TODO(gri) implement this + + case *ast.BlockStmt: + check.stmtList(s.List) + + case *ast.IfStmt: + check.optionalStmt(s.Init) + var x operand + check.expr(&x, s.Cond, nil, -1) + if x.mode != invalid && !isBoolean(x.typ) { + check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") + } + check.stmt(s.Body) + check.optionalStmt(s.Else) + + case *ast.SwitchStmt: + check.optionalStmt(s.Init) + var x operand + tag := s.Tag + if tag == nil { + // use fake true tag value and position it at the opening { of the switch + ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} + check.register(ident, Universe.Lookup("true")) + tag = ident + } + check.expr(&x, tag, nil, -1) + + check.multipleDefaults(s.Body.List) + // TODO(gri) check also correct use of fallthrough + seen := make(map[interface{}]token.Pos) + for _, s := range s.Body.List { + clause, _ := s.(*ast.CaseClause) + if clause == nil { + continue // error reported before + } + if x.mode != invalid { + for _, expr := range clause.List { + x := x // copy of x (don't modify original) + var y operand + check.expr(&y, expr, nil, -1) + if y.mode == invalid { + continue // error reported before + } + // If we have a constant case value, it must appear only + // once in the switch statement. Determine if there is a + // duplicate entry, but only report an error if there are + // no other errors. + var dupl token.Pos + var yy operand + if y.mode == constant { + // TODO(gri) This code doesn't work correctly for + // large integer, floating point, or + // complex values - the respective struct + // comparisons are shallow. Need to use a + // hash function to index the map. + dupl = seen[y.val] + seen[y.val] = y.pos() + yy = y // remember y + } + // TODO(gri) The convertUntyped call pair below appears in other places. Factor! + // Order matters: By comparing y against x, error positions are at the case values. + check.convertUntyped(&y, x.typ) + if y.mode == invalid { + continue // error reported before + } + check.convertUntyped(&x, y.typ) + if x.mode == invalid { + continue // error reported before + } + check.comparison(&y, &x, token.EQL) + if y.mode != invalid && dupl.IsValid() { + check.errorf(yy.pos(), "%s is duplicate case (previous at %s)", + &yy, check.fset.Position(dupl)) + } + } + } + check.stmtList(clause.Body) + } + + case *ast.TypeSwitchStmt: + check.optionalStmt(s.Init) + + // A type switch guard must be of the form: + // + // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . + // + // The parser is checking syntactic correctness; + // remaining syntactic errors are considered AST errors here. + // TODO(gri) better factoring of error handling (invalid ASTs) + // + var lhs *Var // lhs variable or nil + var rhs ast.Expr + switch guard := s.Assign.(type) { + case *ast.ExprStmt: + rhs = guard.X + case *ast.AssignStmt: + if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + ident, _ := guard.Lhs[0].(*ast.Ident) + if ident == nil { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + lhs = check.lookup(ident).(*Var) + rhs = guard.Rhs[0] + default: + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + + // rhs must be of the form: expr.(type) and expr must be an interface + expr, _ := rhs.(*ast.TypeAssertExpr) + if expr == nil || expr.Type != nil { + check.invalidAST(s.Pos(), "incorrect form of type switch guard") + return + } + var x operand + check.expr(&x, expr.X, nil, -1) + if x.mode == invalid { + return + } + var T *Interface + if T, _ = underlying(x.typ).(*Interface); T == nil { + check.errorf(x.pos(), "%s is not an interface", &x) + return + } + + check.multipleDefaults(s.Body.List) + for _, s := range s.Body.List { + clause, _ := s.(*ast.CaseClause) + if clause == nil { + continue // error reported before + } + // Check each type in this type switch case. + var typ Type + for _, expr := range clause.List { + typ = check.typOrNil(expr, false) + if typ != nil && typ != Typ[Invalid] { + if method, wrongType := missingMethod(typ, T); method != nil { + var msg string + if wrongType { + msg = "%s cannot have dynamic type %s (wrong type for method %s)" + } else { + msg = "%s cannot have dynamic type %s (missing method %s)" + } + check.errorf(expr.Pos(), msg, &x, typ, method.Name) + // ok to continue + } + } + } + // If lhs exists, set its type for each clause. + if lhs != nil { + // In clauses with a case listing exactly one type, the variable has that type; + // otherwise, the variable has the type of the expression in the TypeSwitchGuard. + if len(clause.List) != 1 || typ == nil { + typ = x.typ + } + lhs.Type = typ + } + check.stmtList(clause.Body) + } + + // There is only one object (lhs) associated with a lhs identifier, but that object + // assumes different types for different clauses. Set it back to the type of the + // TypeSwitchGuard expression so that that variable always has a valid type. + if lhs != nil { + lhs.Type = x.typ + } + + case *ast.SelectStmt: + check.multipleDefaults(s.Body.List) + for _, s := range s.Body.List { + clause, _ := s.(*ast.CommClause) + if clause == nil { + continue // error reported before + } + check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) + check.stmtList(clause.Body) + } + + case *ast.ForStmt: + check.optionalStmt(s.Init) + if s.Cond != nil { + var x operand + check.expr(&x, s.Cond, nil, -1) + if x.mode != invalid && !isBoolean(x.typ) { + check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") + } + } + check.optionalStmt(s.Post) + check.stmt(s.Body) + + case *ast.RangeStmt: + // check expression to iterate over + decl := s.Tok == token.DEFINE + var x operand + check.expr(&x, s.X, nil, -1) + if x.mode == invalid { + // if we don't have a declaration, we can still check the loop's body + if !decl { + check.stmt(s.Body) + } + return + } + + // determine key/value types + var key, val Type + switch typ := underlying(x.typ).(type) { + case *Basic: + if isString(typ) { + key = Typ[UntypedInt] + val = Typ[UntypedRune] + } + case *Array: + key = Typ[UntypedInt] + val = typ.Elt + case *Slice: + key = Typ[UntypedInt] + val = typ.Elt + case *Pointer: + if typ, _ := underlying(typ.Base).(*Array); typ != nil { + key = Typ[UntypedInt] + val = typ.Elt + } + case *Map: + key = typ.Key + val = typ.Elt + case *Chan: + key = typ.Elt + if typ.Dir&ast.RECV == 0 { + check.errorf(x.pos(), "cannot range over send-only channel %s", &x) + // ok to continue + } + if s.Value != nil { + check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) + // ok to continue + } + } + + if key == nil { + check.errorf(x.pos(), "cannot range over %s", &x) + // if we don't have a declaration, we can still check the loop's body + if !decl { + check.stmt(s.Body) + } + return + } + + // check assignment to/declaration of iteration variables + // TODO(gri) The error messages/positions are not great here, + // they refer to the expression in the range clause. + // Should give better messages w/o too much code + // duplication (assignment checking). + x.mode = value + if s.Key != nil { + x.typ = key + x.expr = s.Key + check.assign1to1(s.Key, nil, &x, decl, -1) + } else { + check.invalidAST(s.Pos(), "range clause requires index iteration variable") + // ok to continue + } + if s.Value != nil { + x.typ = val + x.expr = s.Value + check.assign1to1(s.Value, nil, &x, decl, -1) + } + + check.stmt(s.Body) + + default: + check.errorf(s.Pos(), "invalid statement") + } +} diff --git a/go/types/testdata/builtins.src b/go/types/testdata/builtins.src new file mode 100644 index 0000000000..43f3e506f0 --- /dev/null +++ b/go/types/testdata/builtins.src @@ -0,0 +1,432 @@ +// 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. + +// builtin calls + +package builtins + +import "unsafe" + +func _append() { + var x int + var s []byte + _0 := append /* ERROR "argument" */ () + _1 := append("foo" /* ERROR "not a typed slice" */) + _2 := append(nil /* ERROR "not a typed slice" */, s) + _3 := append(x /* ERROR "not a typed slice" */, s) + _4 := append(s) + append /* ERROR "not used" */ (s) +} + +func _cap() { + var a [10]bool + var p *[20]int + var s []int + var c chan string + _0 := cap /* ERROR "argument" */ () + _1 := cap /* ERROR "argument" */ (1, 2) + _2 := cap(42 /* ERROR "invalid" */) + const _3 = cap(a) + assert(_3 == 10) + const _4 = cap(p) + assert(_4 == 20) + _5 := cap(c) + cap /* ERROR "not used" */ (c) + + // issue 4744 + type T struct{ a [10]int } + const _ = cap(((*T)(nil)).a) +} + +func _close() { + var c chan int + var r <-chan int + close /* ERROR "argument" */ () + close /* ERROR "argument" */ (1, 2) + close(42 /* ERROR "not a channel" */) + close(r /* ERROR "receive-only channel" */) + close(c) +} + +func _complex() { + var i32 int32 + var f32 float32 + var f64 float64 + var c64 complex64 + var c128 complex128 + _ = complex /* ERROR "argument" */ () + _ = complex /* ERROR "argument" */ (1) + _ = complex(true /* ERROR "invalid argument" */ , 0) + _ = complex(i32 /* ERROR "invalid argument" */ , 0) + _ = complex("foo" /* ERROR "invalid argument" */ , 0) + _ = complex(c64 /* ERROR "invalid argument" */ , 0) + _ = complex(0, true /* ERROR "invalid argument" */ ) + _ = complex(0, i32 /* ERROR "invalid argument" */ ) + _ = complex(0, "foo" /* ERROR "invalid argument" */ ) + _ = complex(0, c64 /* ERROR "invalid argument" */ ) + _ = complex(f32, f32) + _ = complex(f32, 1) + _ = complex(f32, 1.0) + _ = complex(f32, 'a') + _ = complex(f64, f64) + _ = complex(f64, 1) + _ = complex(f64, 1.0) + _ = complex(f64, 'a') + _ = complex(f32 /* ERROR "mismatched types" */, f64) + _ = complex(f64 /* ERROR "mismatched types" */, f32) + _ = complex(1, 1) + _ = complex(1, 1.1) + _ = complex(1, 'a') + complex /* ERROR "not used" */ (1, 2) + + var _ complex64 = complex(f32, f32) + var _ complex64 = complex /* ERROR "cannot initialize" */ (f64, f64) + + var _ complex128 = complex /* ERROR "cannot initialize" */ (f32, f32) + var _ complex128 = complex(f64, f64) + + // untyped constants + const _ int = complex(1, 0) + const _ float32 = complex(1, 0) + const _ complex64 = complex(1, 0) + const _ complex128 = complex(1, 0) + + const _ int = complex /* ERROR "int" */ (1.1, 0) + const _ float32 = complex /* ERROR "float32" */ (1, 2) + + // untyped values + var s uint + _ = complex(1 /* ERROR "integer" */ <X, T2->X + ) + + var t T3 + _ = t /* ERROR "no single field or method" */ .X +} + +func _() { + type ( + T1 struct { X int } + T2 struct { T1 } + T3 struct { T1 } + T4 struct { T2; T3 } // X is embedded twice at the same level via T2->T1->X, T3->T1->X + ) + + var t T4 + _ = t /* ERROR "no single field or method" */ .X +} + +func issue4355() { + type ( + T1 struct {X int} + T2 struct {T1} + T3 struct {T2} + T4 struct {T2} + T5 struct {T3; T4} // X is embedded twice at the same level via T3->T2->T1->X, T4->T2->T1->X + ) + + var t T5 + _ = t /* ERROR "no single field or method" */ .X +} + +// Embedded fields can be predeclared types. + +func _() { + type T0 struct{ + int + float32 + f int + } + var x T0 + _ = x.int + _ = x.float32 + _ = x.f + + type T1 struct{ + T0 + } + var y T1 + _ = y.int + _ = y.float32 + _ = y.f +} + +// Borrowed from the FieldByName test cases in reflect/all_test.go. + +type D1 struct { + d int +} +type D2 struct { + d int +} + +type S0 struct { + A, B, C int + D1 + D2 +} + +type S1 struct { + B int + S0 +} + +type S2 struct { + A int + *S1 +} + +type S1x struct { + S1 +} + +type S1y struct { + S1 +} + +type S3 struct { + S1x + S2 + D, E int + *S1y +} + +type S4 struct { + *S4 + A int +} + +// The X in S6 and S7 annihilate, but they also block the X in S8.S9. +type S5 struct { + S6 + S7 + S8 +} + +type S6 struct { + X int +} + +type S7 S6 + +type S8 struct { + S9 +} + +type S9 struct { + X int + Y int +} + +// The X in S11.S6 and S12.S6 annihilate, but they also block the X in S13.S8.S9. +type S10 struct { + S11 + S12 + S13 +} + +type S11 struct { + S6 +} + +type S12 struct { + S6 +} + +type S13 struct { + S8 +} + +func _() { + _ = struct /* ERROR "no single field or method" */ {}{}.Foo + _ = S0{}.A + _ = S0 /* ERROR "no single field or method" */ {}.D + _ = S1{}.A + _ = S1{}.B + _ = S1{}.S0 + _ = S1{}.C + _ = S2{}.A + _ = S2{}.S1 + _ = S2{}.B + _ = S2{}.C + _ = S2 /* ERROR "no single field or method" */ {}.D + _ = S3 /* ERROR "no single field or method" */ {}.S1 + _ = S3{}.A + _ = S3 /* ERROR "no single field or method" */ {}.B + _ = S3{}.D + _ = S3{}.E + _ = S4{}.A + _ = S4 /* ERROR "no single field or method" */ {}.B + _ = S5 /* ERROR "no single field or method" */ {}.X + _ = S5{}.Y + _ = S10 /* ERROR "no single field or method" */ {}.X + _ = S10{}.Y +} + +// Borrowed from the FieldByName benchmark in reflect/all_test.go. + +type R0 struct { + *R1 + *R2 + *R3 + *R4 +} + +type R1 struct { + *R5 + *R6 + *R7 + *R8 +} + +type R2 R1 +type R3 R1 +type R4 R1 + +type R5 struct { + *R9 + *R10 + *R11 + *R12 +} + +type R6 R5 +type R7 R5 +type R8 R5 + +type R9 struct { + *R13 + *R14 + *R15 + *R16 +} + +type R10 R9 +type R11 R9 +type R12 R9 + +type R13 struct { + *R17 + *R18 + *R19 + *R20 +} + +type R14 R13 +type R15 R13 +type R16 R13 + +type R17 struct { + *R21 + *R22 + *R23 + *R24 +} + +type R18 R17 +type R19 R17 +type R20 R17 + +type R21 struct { + X int +} + +type R22 R21 +type R23 R21 +type R24 R21 + +var _ = R0 /* ERROR "no single field or method" */ {}.X \ No newline at end of file diff --git a/go/types/testdata/exports.go b/go/types/testdata/exports.go new file mode 100644 index 0000000000..8ee28b0942 --- /dev/null +++ b/go/types/testdata/exports.go @@ -0,0 +1,89 @@ +// Copyright 2011 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 is used to generate an object file which +// serves as test file for gcimporter_test.go. + +package exports + +import ( + "go/ast" +) + +// Issue 3682: Correctly read dotted identifiers from export data. +const init1 = 0 + +func init() {} + +const ( + C0 int = 0 + C1 = 3.14159265 + C2 = 2.718281828i + C3 = -123.456e-789 + C4 = +123.456E+789 + C5 = 1234i + C6 = "foo\n" + C7 = `bar\n` +) + +type ( + T1 int + T2 [10]int + T3 []int + T4 *int + T5 chan int + T6a chan<- int + T6b chan (<-chan int) + T6c chan<- (chan int) + T7 <-chan *ast.File + T8 struct{} + T9 struct { + a int + b, c float32 + d []string `go:"tag"` + } + T10 struct { + T8 + T9 + _ *T10 + } + T11 map[int]string + T12 interface{} + T13 interface { + m1() + m2(int) float32 + } + T14 interface { + T12 + T13 + m3(x ...struct{}) []T9 + } + T15 func() + T16 func(int) + T17 func(x int) + T18 func() float32 + T19 func() (x float32) + T20 func(...interface{}) + T21 struct{ next *T21 } + T22 struct{ link *T23 } + T23 struct{ link *T22 } + T24 *T24 + T25 *T26 + T26 *T27 + T27 *T25 + T28 func(T28) T28 +) + +var ( + V0 int + V1 = -991.0 +) + +func F1() {} +func F2(x int) {} +func F3() int { return 0 } +func F4() float32 { return 0 } +func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10) + +func (p *T1) M1() diff --git a/go/types/testdata/expr0.src b/go/types/testdata/expr0.src new file mode 100644 index 0000000000..8d057f63c1 --- /dev/null +++ b/go/types/testdata/expr0.src @@ -0,0 +1,161 @@ +// 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. + +// unary expressions + +package expr0 + +var ( + // bool + b0 = true + b1 bool = b0 + b2 = !true + b3 = !b1 + b4 bool = !true + b5 bool = !b4 + b6 = +b0 /* ERROR "not defined" */ + b7 = -b0 /* ERROR "not defined" */ + b8 = ^b0 /* ERROR "not defined" */ + b9 = *b0 /* ERROR "cannot indirect" */ + b10 = &true /* ERROR "cannot take address" */ + b11 = &b0 + b12 = <-b0 /* ERROR "cannot receive" */ + + // int + i0 = 1 + i1 int = i0 + i2 = +1 + i3 = +i0 + i4 int = +1 + i5 int = +i4 + i6 = -1 + i7 = -i0 + i8 int = -1 + i9 int = -i4 + i10 = !i0 /* ERROR "not defined" */ + i11 = ^1 + i12 = ^i0 + i13 int = ^1 + i14 int = ^i4 + i15 = *i0 /* ERROR "cannot indirect" */ + i16 = &i0 + i17 = *i16 + i18 = <-i16 /* ERROR "cannot receive" */ + + // uint + u0 = uint(1) + u1 uint = u0 + u2 = +1 + u3 = +u0 + u4 uint = +1 + u5 uint = +u4 + u6 = -1 + u7 = -u0 + u8 uint = - /* ERROR "overflows" */ 1 + u9 uint = -u4 + u10 = !u0 /* ERROR "not defined" */ + u11 = ^1 + u12 = ^i0 + u13 uint = ^ /* ERROR "overflows" */ 1 + u14 uint = ^u4 + u15 = *u0 /* ERROR "cannot indirect" */ + u16 = &u0 + u17 = *u16 + u18 = <-u16 /* ERROR "cannot receive" */ + u19 = ^uint(0) + + // float64 + f0 = float64(1) + f1 float64 = f0 + f2 = +1 + f3 = +f0 + f4 float64 = +1 + f5 float64 = +f4 /* ERROR not defined */ + f6 = -1 + f7 = -f0 + f8 float64 = -1 + f9 float64 = -f4 + f10 = !f0 /* ERROR "not defined" */ + f11 = ^1 + f12 = ^i0 + f13 float64 = ^1 + f14 float64 = ^f4 /* ERROR "not defined" */ + f15 = *f0 /* ERROR "cannot indirect" */ + f16 = &f0 + f17 = *u16 + f18 = <-u16 /* ERROR "cannot receive" */ + + // complex128 + c0 = complex128(1) + c1 complex128 = c0 + c2 = +1 + c3 = +c0 + c4 complex128 = +1 + c5 complex128 = +c4 /* ERROR not defined */ + c6 = -1 + c7 = -c0 + c8 complex128 = -1 + c9 complex128 = -c4 + c10 = !c0 /* ERROR "not defined" */ + c11 = ^1 + c12 = ^i0 + c13 complex128 = ^1 + c14 complex128 = ^c4 /* ERROR "not defined" */ + c15 = *c0 /* ERROR "cannot indirect" */ + c16 = &c0 + c17 = *u16 + c18 = <-u16 /* ERROR "cannot receive" */ + + // string + s0 = "foo" + s1 = +"foo" /* ERROR "not defined" */ + s2 = -s0 /* ERROR "not defined" */ + s3 = !s0 /* ERROR "not defined" */ + s4 = ^s0 /* ERROR "not defined" */ + s5 = *s4 /* ERROR "cannot indirect" */ + s6 = &s4 + s7 = *s6 + s8 = <-s7 /* ERROR "cannot receive" */ + + // channel + ch chan int + rc <-chan float64 + sc chan <- string + ch0 = +ch /* ERROR "not defined" */ + ch1 = -ch /* ERROR "not defined" */ + ch2 = !ch /* ERROR "not defined" */ + ch3 = ^ch /* ERROR "not defined" */ + ch4 = *ch /* ERROR "cannot indirect" */ + ch5 = &ch + ch6 = *ch5 + ch7 = <-ch + ch8 = <-rc + ch9 = <-sc /* ERROR "cannot receive" */ +) + +// address of composite literals +type T struct{x, y int} + +func f() T { return T{} } + +var ( + _ = &T{1, 2} + _ = &[...]int{} + _ = &[]int{} + _ = &[]int{} + _ = &map[string]T{} + _ = &(T{1, 2}) + _ = &((((T{1, 2})))) + _ = &f /* ERROR "cannot take address" */ () +) + +// recursive pointer types +type P *P + +var ( + p1 P = new(P) + p2 P = *p1 + p3 P = &p2 +) + diff --git a/go/types/testdata/expr1.src b/go/types/testdata/expr1.src new file mode 100644 index 0000000000..8ef0aed6d2 --- /dev/null +++ b/go/types/testdata/expr1.src @@ -0,0 +1,7 @@ +// 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. + +// binary expressions + +package expr1 diff --git a/go/types/testdata/expr2.src b/go/types/testdata/expr2.src new file mode 100644 index 0000000000..674be4005d --- /dev/null +++ b/go/types/testdata/expr2.src @@ -0,0 +1,23 @@ +// 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. + +// comparisons + +package expr2 + +func _bool() { + const t = true == true + const f = true == false + _ = t /* ERROR "cannot compare" */ < f + _ = 0 /* ERROR "cannot convert" */ == t + var b bool + var x, y float32 + b = x < y + _ = struct{b bool}{x < y} +} + +// corner cases +var ( + v0 = nil /* ERROR "cannot compare" */ == nil +) \ No newline at end of file diff --git a/go/types/testdata/expr3.src b/go/types/testdata/expr3.src new file mode 100644 index 0000000000..48f6bc770f --- /dev/null +++ b/go/types/testdata/expr3.src @@ -0,0 +1,338 @@ +// 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. + +package expr3 + +func indexes() { + _ = 1 /* ERROR "cannot index" */ [0] + _ = indexes /* ERROR "cannot index" */ [0] + _ = ( /* ERROR "cannot slice" */ 12 + 3)[1:2] + + var a [10]int + _ = a[true /* ERROR "cannot convert" */ ] + _ = a["foo" /* ERROR "cannot convert" */ ] + _ = a[1.1 /* ERROR "overflows" */ ] + _ = a[1.0] + _ = a[- /* ERROR "negative" */ 1] + _ = a[- /* ERROR "negative" */ 1 :] + _ = a[: - /* ERROR "negative" */ 1] + var a0 int + a0 = a[0] + var a1 int32 + a1 = a /* ERROR "cannot assign" */ [1] + _ = a[9] + _ = a[10 /* ERROR "index .* out of bounds" */ ] + _ = a[1 /* ERROR "overflows" */ <<100] + _ = a[10:] + _ = a[:10] + _ = a[10:10] + _ = a[11 /* ERROR "index .* out of bounds" */ :] + _ = a[: 11 /* ERROR "index .* out of bounds" */ ] + _ = a[: 1 /* ERROR "overflows" */ <<100] + + pa := &a + _ = pa[9] + _ = pa[10 /* ERROR "index .* out of bounds" */ ] + _ = pa[1 /* ERROR "overflows" */ <<100] + _ = pa[10:] + _ = pa[:10] + _ = pa[10:10] + _ = pa[11 /* ERROR "index .* out of bounds" */ :] + _ = pa[: 11 /* ERROR "index .* out of bounds" */ ] + _ = pa[: 1 /* ERROR "overflows" */ <<100] + + var b [0]int + _ = b[0 /* ERROR "index .* out of bounds" */ ] + _ = b[:] + _ = b[0:] + _ = b[:0] + _ = b[0:0] + + var s []int + _ = s[- /* ERROR "negative" */ 1] + _ = s[- /* ERROR "negative" */ 1 :] + _ = s[: - /* ERROR "negative" */ 1] + _ = s[0] + _ = s[1 : 2] + _ = s[2 /* ERROR "inverted slice range" */ : 1] + _ = s[2 :] + _ = s[: 1 /* ERROR "overflows" */ <<100] + _ = s[1 /* ERROR "overflows" */ <<100 :] + _ = s[1 /* ERROR "overflows" */ <<100 : 1 /* ERROR "overflows" */ <<100] + + var t string + _ = t[- /* ERROR "negative" */ 1] + _ = t[- /* ERROR "negative" */ 1 :] + _ = t[: - /* ERROR "negative" */ 1] + var t0 byte + t0 = t[0] + var t1 rune + t1 = t /* ERROR "cannot assign" */ [2] + _ = ("foo" + "bar")[5] + _ = ("foo" + "bar")[6 /* ERROR "index .* out of bounds" */ ] + + const c = "foo" + _ = c[- /* ERROR "negative" */ 1] + _ = c[- /* ERROR "negative" */ 1 :] + _ = c[: - /* ERROR "negative" */ 1] + var c0 byte + c0 = c[0] + var c2 float32 + c2 = c /* ERROR "cannot assign" */ [2] + _ = c[3 /* ERROR "index .* out of bounds" */ ] + _ = ""[0 /* ERROR "index .* out of bounds" */ ] + + _ = s[1<<30] // no compile-time error here + + // issue 4913 + type mystring string + var ss string + var ms mystring + var i, j int + ss = "foo"[1:2] + ss = "foo"[i:j] + ms = "foo" /* ERROR "cannot assign" */ [1:2] + ms = "foo" /* ERROR "cannot assign" */ [i:j] +} + +type T struct { + x int +} + +func (*T) m() {} + +func method_expressions() { + _ = T /* ERROR "no single field or method" */ .a + _ = T /* ERROR "has no method" */ .x + _ = T.m + var f func(*T) = (*T).m + var g func(*T) = ( /* ERROR "cannot initialize" */ T).m +} + +func struct_literals() { + type T0 struct { + a, b, c int + } + + type T1 struct { + T0 + a, b int + u float64 + s string + } + + // keyed elements + _ = T1{} + _ = T1{a: 0, 1 /* ERROR "mixture of .* elements" */ } + _ = T1{aa /* ERROR "unknown field" */ : 0} + _ = T1{1 /* ERROR "invalid field name" */ : 0} + _ = T1{a: 0, s: "foo", u: 0, a /* ERROR "duplicate field" */: 10} + _ = T1{a: "foo" /* ERROR "cannot convert" */ } + _ = T1{c /* ERROR "unknown field" */ : 0} + _ = T1{T0: { /* ERROR "missing type" */ }} + _ = T1{T0: T0{}} + _ = T1{T0 /* ERROR "invalid field name" */ .a: 0} + + // unkeyed elements + _ = T0{1, 2, 3} + _ = T0{1, b /* ERROR "mixture" */ : 2, 3} + _ = T0{1, 2} /* ERROR "too few values" */ + _ = T0{1, 2, 3, 4 /* ERROR "too many values" */ } + _ = T0{1, "foo" /* ERROR "cannot convert" */, 3.4 /* ERROR "overflows" */} +} + +func array_literals() { + type A0 [0]int + _ = A0{} + _ = A0{0 /* ERROR "index .* out of bounds" */} + _ = A0{0 /* ERROR "index .* out of bounds" */ : 0} + + type A1 [10]int + _ = A1{} + _ = A1{0, 1, 2} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = A1{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{- /* ERROR "negative" */ 1: 0} + _ = A1{8: 8, 9} + _ = A1{8: 8, 9, 10 /* ERROR "index .* out of bounds" */ } + _ = A1{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4} + _ = A1{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = A1{10 /* ERROR "index .* out of bounds" */ : 10, 10 /* ERROR "index .* out of bounds" */ : 10} + _ = A1{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = A1{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = A1{2.0} + _ = A1{2.1 /* ERROR "overflows" */ } + _ = A1{"foo" /* ERROR "cannot convert" */ } + + a0 := [...]int{} + assert(len(a0) == 0) + + a1 := [...]int{0, 1, 2} + assert(len(a1) == 3) + var a13 [3]int + var a14 [4]int + a13 = a1 + a14 = a1 /* ERROR "cannot assign" */ + + a2 := [...]int{- /* ERROR "negative" */ 1: 0} + + a3 := [...]int{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + assert(len(a3) == 5) // somewhat arbitrary + + a4 := [...]complex128{0, 1, 2, 1<<10-2: -1i, 1i, 400: 10, 12, 14} + assert(len(a4) == 1024) + + // from the spec + type Point struct { x, y float32 } + _ = [...]Point{Point{1.5, -3.5}, Point{0, 0}} + _ = [...]Point{{1.5, -3.5}, {0, 0}} + _ = [][]int{[]int{1, 2, 3}, []int{4, 5}} + _ = [][]int{{1, 2, 3}, {4, 5}} + _ = [...]*Point{&Point{1.5, -3.5}, &Point{0, 0}} + _ = [...]*Point{{1.5, -3.5}, {0, 0}} +} + +func slice_literals() { + type S0 []int + _ = S0{} + _ = S0{0, 1, 2} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} + _ = S0{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + _ = S0{- /* ERROR "negative" */ 1: 0} + _ = S0{8: 8, 9} + _ = S0{8: 8, 9, 10} + _ = S0{0, 1, 2, 0 /* ERROR "duplicate index" */ : 0, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4} + _ = S0{5: 5, 6, 7, 3: 3, 4, 5 /* ERROR "duplicate index" */ } + _ = S0{10: 10, 10 /* ERROR "duplicate index" */ : 10} + _ = S0{5: 5, 6, 7, 3: 3, 1 /* ERROR "overflows" */ <<100: 4, 5 /* ERROR "duplicate index" */ } + _ = S0{5: 5, 6, 7, 4: 4, 1 /* ERROR "overflows" */ <<100: 4} + _ = S0{2.0} + _ = S0{2.1 /* ERROR "overflows" */ } + _ = S0{"foo" /* ERROR "cannot convert" */ } + + // indices must be resolved correctly + // (for details, see comment in go/parser/parser.go, method parseElement) + index1 := 1 + _ = S0{index1: 1} + _ = S0{index2: 2} + _ = S0{index3 /* ERROR "undeclared name" */ : 3} +} + +var index2 int = 2 + +func map_literals() { + type M0 map[string]int + type M1 map[bool]int + type M2 map[*int]int + + _ = M0{} + _ = M0{1 /* ERROR "missing key" */ } + _ = M0{1 /* ERROR "cannot convert" */ : 2} + _ = M0{"foo": "bar" /* ERROR "cannot convert" */ } + _ = M0{"foo": 1, "bar": 2, "foo" /* ERROR "duplicate key" */ : 3 } + + // map keys must be resolved correctly + // (for details, see comment in go/parser/parser.go, method parseElement) + key1 := "foo" + _ = M0{key1: 1} + _ = M0{key2: 2} + _ = M0{key3 /* ERROR "undeclared name" */ : 2} + + _ = M1{true: 1, false: 0} + _ = M2{nil: 0, &index2: 1} +} + +var key2 string = "bar" + +type I interface { + m() +} + +type I2 interface { + m(int) +} + +type T1 struct{} +type T2 struct{} + +func (T2) m(int) {} + +func type_asserts() { + var x int + _ = x /* ERROR "not an interface" */ .(int) + + var e interface{} + var ok bool + x, ok = e.(int) + + var t I + _ = t /* ERROR "use of .* outside type switch" */ .(type) + _ = t.(T) + _ = t.(T1 /* ERROR "missing method m" */ ) + _ = t.(T2 /* ERROR "wrong type for method m" */ ) + _ = t.(I2 /* ERROR "wrong type for method m" */ ) +} + +func f0() {} +func f1(x int) {} +func f2(u float32, s string) {} +func fs(s []byte) {} +func fv(x ...int) {} +func fi(x ... interface{}) {} + +func g0() {} +func g1() int { return 0} +func g2() (u float32, s string) { return } +func gs() []byte { return nil } + +func _calls() { + var x int + var y float32 + var s []int + + f0() + _ = f0 /* ERROR "used as value" */ () + f0(g0 /* ERROR "too many arguments" */ ) + + f1(0) + f1(x) + f1(10.0) + f1 /* ERROR "too few arguments" */ () + f1(x, y /* ERROR "too many arguments" */ ) + f1(s /* ERROR "cannot pass" */ ) + f1(x ... /* ERROR "cannot use ..." */ ) + f1(g0 /* ERROR "used as value" */ ()) + f1(g1()) + // f1(g2()) // TODO(gri) missing position in error message + + f2 /* ERROR "too few arguments" */ () + f2 /* ERROR "too few arguments" */ (3.14) + f2(3.14, "foo") + f2(x /* ERROR "cannot pass" */ , "foo") + f2(g0 /* ERROR "used as value" */ ()) + f2 /* ERROR "too few arguments" */ (g1 /* ERROR "cannot pass" */ ()) + f2(g2()) + + fs /* ERROR "too few arguments" */ () + fs(g0 /* ERROR "used as value" */ ()) + fs(g1 /* ERROR "cannot pass" */ ()) + // fs(g2()) // TODO(gri) missing position in error message + fs(gs()) + + fv() + fv(1, 2.0, x) + fv(s /* ERROR "cannot pass" */ ) + fv(s...) + fv(1, s /* ERROR "can only use ... with matching parameter" */ ...) + fv(gs /* ERROR "cannot pass" */ ()) + fv(gs /* ERROR "cannot pass" */ ()...) + + fi() + fi(1, 2.0, x, 3.14, "foo") + fi(g2()) + fi(0, g2) + fi(0, g2 /* ERROR "2-valued expression" */ ()) +} diff --git a/go/types/testdata/shifts.src b/go/types/testdata/shifts.src new file mode 100644 index 0000000000..c6d49e185c --- /dev/null +++ b/go/types/testdata/shifts.src @@ -0,0 +1,293 @@ +// 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 shifts + +func shifts1() { + // basics + var ( + i0 int + u0 uint + + v0 = 1<<0 + v1 = 1<> s) +} + +func shifts4() { + // shifts in comparisons w/ untyped operands + var s uint + + _ = 1<= 0 { + return // nothing to do + } + // fix Obj link for named types + if typ, ok := obj.GetType().(*NamedType); ok { + typ.Obj = obj.(*TypeName) + } + // exported identifiers go into package unsafe + scope := Universe + if ast.IsExported(name) { + scope = Unsafe.Scope + // set Pkg field + switch obj := obj.(type) { + case *TypeName: + obj.Pkg = Unsafe + case *Func: + obj.Pkg = Unsafe + default: + unreachable() + } + } + if scope.Insert(obj) != nil { + panic("internal error: double declaration") + } +} diff --git a/gotype/doc.go b/gotype/doc.go new file mode 100644 index 0000000000..4d980f80dc --- /dev/null +++ b/gotype/doc.go @@ -0,0 +1,63 @@ +// Copyright 2011 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. + +/* +The gotype command does syntactic and semantic analysis of Go files +and packages similar to the analysis performed by the front-end of +a Go compiler. Errors are reported if the analysis fails; otherwise +gotype is quiet (unless -v is set). + +Without a list of paths, gotype processes the standard input, which must +be the source of a single package file. + +Given a list of file names, each file must be a source file belonging to +the same package unless the package name is explicitly specified with the +-p flag. + +Given a directory name, gotype collects all .go files in the directory +and processes them as if they were provided as an explicit list of file +names. Each directory is processed independently. Files starting with . +or not ending in .go are ignored. + +Usage: + gotype [flags] [path ...] + +The flags are: + -e + Print all (including spurious) errors. + -p pkgName + Process only those files in package pkgName. + -r + Recursively process subdirectories. + -v + Verbose mode. + +Debugging flags: + -comments + Parse comments (ignored if -ast not set). + -ast + Print AST (disables concurrent parsing). + -trace + Print parse trace (disables concurrent parsing). + + +Examples + +To check the files file.go, old.saved, and .ignored: + + gotype file.go old.saved .ignored + +To check all .go files belonging to package main in the current directory +and recursively in all subdirectories: + + gotype -p main -r . + +To verify the output of a pipe: + + echo "package foo" | gotype + +*/ +package main + +// BUG(gri): At the moment, only single-file scope analysis is performed. diff --git a/gotype/gotype.go b/gotype/gotype.go new file mode 100644 index 0000000000..e8805c8416 --- /dev/null +++ b/gotype/gotype.go @@ -0,0 +1,206 @@ +// Copyright 2011 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 ( + "flag" + "fmt" + "go/ast" + "go/parser" + "go/scanner" + "go/token" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "code.google.com/p/go.tools/go/types" +) + +var ( + // main operation modes + pkgName = flag.String("p", "", "process only those files in package pkgName") + recursive = flag.Bool("r", false, "recursively process subdirectories") + verbose = flag.Bool("v", false, "verbose mode") + allErrors = flag.Bool("e", false, "report all errors (not just the first 10 on different lines)") + + // debugging support + parseComments = flag.Bool("comments", false, "parse comments (ignored if -ast not set)") + printTrace = flag.Bool("trace", false, "print parse trace") + printAST = flag.Bool("ast", false, "print AST") +) + +var errorCount int + +func usage() { + fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n") + flag.PrintDefaults() + os.Exit(2) +} + +func report(err error) { + scanner.PrintError(os.Stderr, err) + if list, ok := err.(scanner.ErrorList); ok { + errorCount += len(list) + return + } + errorCount++ +} + +// parse returns the AST for the Go source src. +// The filename is for error reporting only. +// The result is nil if there were errors or if +// the file does not belong to the -p package. +func parse(fset *token.FileSet, filename string, src []byte) *ast.File { + if *verbose { + fmt.Println(filename) + } + + // ignore files with different package name + if *pkgName != "" { + file, err := parser.ParseFile(fset, filename, src, parser.PackageClauseOnly) + if err != nil { + report(err) + return nil + } + if file.Name.Name != *pkgName { + if *verbose { + fmt.Printf("\tignored (package %s)\n", file.Name.Name) + } + return nil + } + } + + // parse entire file + mode := parser.DeclarationErrors + if *allErrors { + mode |= parser.AllErrors + } + if *parseComments && *printAST { + mode |= parser.ParseComments + } + if *printTrace { + mode |= parser.Trace + } + file, err := parser.ParseFile(fset, filename, src, mode) + if err != nil { + report(err) + return nil + } + if *printAST { + ast.Print(fset, file) + } + + return file +} + +func parseStdin(fset *token.FileSet) (files []*ast.File) { + src, err := ioutil.ReadAll(os.Stdin) + if err != nil { + report(err) + return + } + const filename = "" + if file := parse(fset, filename, src); file != nil { + files = []*ast.File{file} + } + return +} + +func parseFiles(fset *token.FileSet, filenames []string) (files []*ast.File) { + for _, filename := range filenames { + src, err := ioutil.ReadFile(filename) + if err != nil { + report(err) + continue + } + if file := parse(fset, filename, src); file != nil { + files = append(files, file) + } + } + return +} + +func isGoFilename(filename string) bool { + // ignore non-Go files + return !strings.HasPrefix(filename, ".") && strings.HasSuffix(filename, ".go") +} + +func processDirectory(dirname string) { + f, err := os.Open(dirname) + if err != nil { + report(err) + return + } + filenames, err := f.Readdirnames(-1) + f.Close() + if err != nil { + report(err) + // continue since filenames may not be empty + } + for i, filename := range filenames { + filenames[i] = filepath.Join(dirname, filename) + } + processFiles(filenames, false) +} + +func processFiles(filenames []string, allFiles bool) { + i := 0 + for _, filename := range filenames { + switch info, err := os.Stat(filename); { + case err != nil: + report(err) + case info.IsDir(): + if allFiles || *recursive { + processDirectory(filename) + } + default: + if allFiles || isGoFilename(info.Name()) { + filenames[i] = filename + i++ + } + } + } + fset := token.NewFileSet() + processPackage(fset, parseFiles(fset, filenames[0:i])) +} + +func processPackage(fset *token.FileSet, files []*ast.File) { + type bailout struct{} + ctxt := types.Context{ + Error: func(err error) { + if !*allErrors && errorCount >= 10 { + panic(bailout{}) + } + report(err) + }, + } + + defer func() { + switch err := recover().(type) { + case nil, bailout: + default: + panic(err) + } + }() + + ctxt.Check(fset, files) +} + +func main() { + flag.Usage = usage + flag.Parse() + + if flag.NArg() == 0 { + fset := token.NewFileSet() + processPackage(fset, parseStdin(fset)) + } else { + processFiles(flag.Args(), true) + } + + if errorCount > 0 { + os.Exit(2) + } +} diff --git a/ssa/interp/external.go b/ssa/interp/external.go new file mode 100644 index 0000000000..f2533076b7 --- /dev/null +++ b/ssa/interp/external.go @@ -0,0 +1,344 @@ +// 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 interp + +// Emulated functions that we cannot interpret because they are +// external or because they use "unsafe" or "reflect" operations. + +import ( + "math" + "os" + "runtime" + "syscall" + "time" + + "code.google.com/p/go.tools/ssa" +) + +type externalFn func(fn *ssa.Function, args []value) value + +// Key strings are from Function.FullName(). +// That little dot ۰ is an Arabic zero numeral (U+06F0), categories [Nd]. +var externals = map[string]externalFn{ + "(reflect.Value).Bool": ext۰reflect۰Value۰Bool, + "(reflect.Value).CanAddr": ext۰reflect۰Value۰CanAddr, + "(reflect.Value).CanInterface": ext۰reflect۰Value۰CanInterface, + "(reflect.Value).Elem": ext۰reflect۰Value۰Elem, + "(reflect.Value).Field": ext۰reflect۰Value۰Field, + "(reflect.Value).Index": ext۰reflect۰Value۰Index, + "(reflect.Value).Int": ext۰reflect۰Value۰Int, + "(reflect.Value).Interface": ext۰reflect۰Value۰Interface, + "(reflect.Value).IsNil": ext۰reflect۰Value۰IsNil, + "(reflect.Value).IsValid": ext۰reflect۰Value۰IsValid, + "(reflect.Value).Kind": ext۰reflect۰Value۰Kind, + "(reflect.Value).Len": ext۰reflect۰Value۰Len, + "(reflect.Value).NumField": ext۰reflect۰Value۰NumField, + "(reflect.Value).Pointer": ext۰reflect۰Value۰Pointer, + "(reflect.Value).String": ext۰reflect۰Value۰String, + "(reflect.Value).Type": ext۰reflect۰Value۰Type, + "(reflect.error).Error": ext۰reflect۰error۰Error, + "(reflect.rtype).Bits": ext۰reflect۰rtype۰Bits, + "(reflect.rtype).Elem": ext۰reflect۰rtype۰Elem, + "(reflect.rtype).Kind": ext۰reflect۰rtype۰Kind, + "(reflect.rtype).NumOut": ext۰reflect۰rtype۰NumOut, + "(reflect.rtype).Out": ext۰reflect۰rtype۰Out, + "(reflect.rtype).String": ext۰reflect۰rtype۰String, + "bytes.Equal": ext۰bytes۰Equal, + "bytes.IndexByte": ext۰bytes۰IndexByte, + "math.Float32bits": ext۰math۰Float32bits, + "math.Float32frombits": ext۰math۰Float32frombits, + "math.Float64bits": ext۰math۰Float64bits, + "math.Float64frombits": ext۰math۰Float64frombits, + "reflect.TypeOf": ext۰reflect۰TypeOf, + "reflect.ValueOf": ext۰reflect۰ValueOf, + "reflect.init": ext۰reflect۰Init, + "reflect.valueInterface": ext۰reflect۰valueInterface, + "runtime.Breakpoint": ext۰runtime۰Breakpoint, + "runtime.GC": ext۰runtime۰GC, + "runtime.GOMAXPROCS": ext۰runtime۰GOMAXPROCS, + "runtime.Gosched": ext۰runtime۰Gosched, + "runtime.ReadMemStats": ext۰runtime۰ReadMemStats, + "runtime.SetFinalizer": ext۰runtime۰SetFinalizer, + "runtime.getgoroot": ext۰runtime۰getgoroot, + "sync/atomic.AddInt32": ext۰atomic۰AddInt32, + "sync/atomic.CompareAndSwapInt32": ext۰atomic۰CompareAndSwapInt32, + "sync/atomic.LoadInt32": ext۰atomic۰LoadInt32, + "sync/atomic.LoadUint32": ext۰atomic۰LoadUint32, + "sync/atomic.StoreInt32": ext۰atomic۰StoreInt32, + "sync/atomic.StoreUint32": ext۰atomic۰StoreUint32, + "syscall.Close": ext۰syscall۰Close, + "syscall.Exit": ext۰syscall۰Exit, + "syscall.Fstat": ext۰syscall۰Fstat, + "syscall.Getpid": ext۰syscall۰Getpid, + "syscall.Getwd": ext۰syscall۰Getwd, + "syscall.Kill": ext۰syscall۰Kill, + "syscall.Lstat": ext۰syscall۰Lstat, + "syscall.Open": ext۰syscall۰Open, + "syscall.ParseDirent": ext۰syscall۰ParseDirent, + "syscall.Read": ext۰syscall۰Read, + "syscall.ReadDirent": ext۰syscall۰ReadDirent, + "syscall.Stat": ext۰syscall۰Stat, + "syscall.Write": ext۰syscall۰Write, + "time.Sleep": ext۰time۰Sleep, + "time.now": ext۰time۰now, +} + +// wrapError returns an interpreted 'error' interface value for err. +func wrapError(err error) value { + if err == nil { + return iface{} + } + return iface{t: errorType, v: err.Error()} +} + +func ext۰bytes۰Equal(fn *ssa.Function, args []value) value { + // func Equal(a, b []byte) bool + a := args[0].([]value) + b := args[1].([]value) + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func ext۰bytes۰IndexByte(fn *ssa.Function, args []value) value { + // func IndexByte(s []byte, c byte) int + s := args[0].([]value) + c := args[1].(byte) + for i, b := range s { + if b.(byte) == c { + return i + } + } + return -1 +} + +func ext۰math۰Float64frombits(fn *ssa.Function, args []value) value { + return math.Float64frombits(args[0].(uint64)) +} + +func ext۰math۰Float64bits(fn *ssa.Function, args []value) value { + return math.Float64bits(args[0].(float64)) +} + +func ext۰math۰Float32frombits(fn *ssa.Function, args []value) value { + return math.Float32frombits(args[0].(uint32)) +} + +func ext۰math۰Float32bits(fn *ssa.Function, args []value) value { + return math.Float32bits(args[0].(float32)) +} + +func ext۰runtime۰Breakpoint(fn *ssa.Function, args []value) value { + runtime.Breakpoint() + return nil +} + +func ext۰runtime۰getgoroot(fn *ssa.Function, args []value) value { + return os.Getenv("GOROOT") +} + +func ext۰runtime۰GOMAXPROCS(fn *ssa.Function, args []value) value { + return runtime.GOMAXPROCS(args[0].(int)) +} + +func ext۰runtime۰GC(fn *ssa.Function, args []value) value { + runtime.GC() + return nil +} + +func ext۰runtime۰Gosched(fn *ssa.Function, args []value) value { + runtime.Gosched() + return nil +} + +func ext۰runtime۰ReadMemStats(fn *ssa.Function, args []value) value { + // TODO(adonovan): populate args[0].(Struct) + return nil +} + +func ext۰atomic۰LoadUint32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + return (*args[0].(*value)).(uint32) +} + +func ext۰atomic۰StoreUint32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + *args[0].(*value) = args[1].(uint32) + return nil +} + +func ext۰atomic۰LoadInt32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + return (*args[0].(*value)).(int32) +} + +func ext۰atomic۰StoreInt32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + *args[0].(*value) = args[1].(int32) + return nil +} + +func ext۰atomic۰CompareAndSwapInt32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + p := args[0].(*value) + if (*p).(int32) == args[1].(int32) { + *p = args[2].(int32) + return true + } + return false +} + +func ext۰atomic۰AddInt32(fn *ssa.Function, args []value) value { + // TODO(adonovan): fix: not atomic! + p := args[0].(*value) + newv := (*p).(int32) + args[1].(int32) + *p = newv + return newv +} + +func ext۰runtime۰SetFinalizer(fn *ssa.Function, args []value) value { + return nil // ignore +} + +func ext۰time۰now(fn *ssa.Function, args []value) value { + nano := time.Now().UnixNano() + return tuple{int64(nano / 1e9), int32(nano % 1e9)} +} + +func ext۰time۰Sleep(fn *ssa.Function, args []value) value { + time.Sleep(time.Duration(args[0].(int64))) + return nil +} + +func ext۰syscall۰Exit(fn *ssa.Function, args []value) value { + panic(exitPanic(args[0].(int))) +} + +func ext۰syscall۰Getwd(fn *ssa.Function, args []value) value { + s, err := syscall.Getwd() + return tuple{s, wrapError(err)} +} + +func ext۰syscall۰Getpid(fn *ssa.Function, args []value) value { + return syscall.Getpid() +} + +// The set of remaining native functions we need to implement (as needed): + +// crypto/aes/cipher_asm.go:10:func hasAsm() bool +// crypto/aes/cipher_asm.go:11:func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) +// crypto/aes/cipher_asm.go:12:func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) +// crypto/aes/cipher_asm.go:13:func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) +// hash/crc32/crc32_amd64.go:12:func haveSSE42() bool +// hash/crc32/crc32_amd64.go:16:func castagnoliSSE42(crc uint32, p []byte) uint32 +// math/abs.go:12:func Abs(x float64) float64 +// math/asin.go:19:func Asin(x float64) float64 +// math/asin.go:51:func Acos(x float64) float64 +// math/atan.go:95:func Atan(x float64) float64 +// math/atan2.go:29:func Atan2(y, x float64) float64 +// math/big/arith_decl.go:8:func mulWW(x, y Word) (z1, z0 Word) +// math/big/arith_decl.go:9:func divWW(x1, x0, y Word) (q, r Word) +// math/big/arith_decl.go:10:func addVV(z, x, y []Word) (c Word) +// math/big/arith_decl.go:11:func subVV(z, x, y []Word) (c Word) +// math/big/arith_decl.go:12:func addVW(z, x []Word, y Word) (c Word) +// math/big/arith_decl.go:13:func subVW(z, x []Word, y Word) (c Word) +// math/big/arith_decl.go:14:func shlVU(z, x []Word, s uint) (c Word) +// math/big/arith_decl.go:15:func shrVU(z, x []Word, s uint) (c Word) +// math/big/arith_decl.go:16:func mulAddVWW(z, x []Word, y, r Word) (c Word) +// math/big/arith_decl.go:17:func addMulVVW(z, x []Word, y Word) (c Word) +// math/big/arith_decl.go:18:func divWVW(z []Word, xn Word, x []Word, y Word) (r Word) +// math/big/arith_decl.go:19:func bitLen(x Word) (n int) +// math/dim.go:13:func Dim(x, y float64) float64 +// math/dim.go:26:func Max(x, y float64) float64 +// math/dim.go:53:func Min(x, y float64) float64 +// math/exp.go:14:func Exp(x float64) float64 +// math/exp.go:135:func Exp2(x float64) float64 +// math/expm1.go:124:func Expm1(x float64) float64 +// math/floor.go:13:func Floor(x float64) float64 +// math/floor.go:36:func Ceil(x float64) float64 +// math/floor.go:48:func Trunc(x float64) float64 +// math/frexp.go:16:func Frexp(f float64) (frac float64, exp int) +// math/hypot.go:17:func Hypot(p, q float64) float64 +// math/ldexp.go:14:func Ldexp(frac float64, exp int) float64 +// math/log.go:80:func Log(x float64) float64 +// math/log10.go:9:func Log10(x float64) float64 +// math/log10.go:17:func Log2(x float64) float64 +// math/log1p.go:95:func Log1p(x float64) float64 +// math/mod.go:21:func Mod(x, y float64) float64 +// math/modf.go:13:func Modf(f float64) (int float64, frac float64) +// math/remainder.go:37:func Remainder(x, y float64) float64 +// math/sin.go:117:func Cos(x float64) float64 +// math/sin.go:174:func Sin(x float64) float64 +// math/sincos.go:15:func Sincos(x float64) (sin, cos float64) +// math/sqrt.go:14:func Sqrt(x float64) float64 +// math/tan.go:82:func Tan(x float64) float64 +// os/file_posix.go:14:func sigpipe() // implemented in package runtime +// os/signal/signal_unix.go:15:func signal_enable(uint32) +// os/signal/signal_unix.go:16:func signal_recv() uint32 +// runtime/debug.go:13:func LockOSThread() +// runtime/debug.go:17:func UnlockOSThread() +// runtime/debug.go:27:func NumCPU() int +// runtime/debug.go:30:func NumCgoCall() int64 +// runtime/debug.go:33:func NumGoroutine() int +// runtime/debug.go:90:func MemProfile(p []MemProfileRecord, inuseZero bool) (n int, ok bool) +// runtime/debug.go:114:func ThreadCreateProfile(p []StackRecord) (n int, ok bool) +// runtime/debug.go:122:func GoroutineProfile(p []StackRecord) (n int, ok bool) +// runtime/debug.go:132:func CPUProfile() []byte +// runtime/debug.go:141:func SetCPUProfileRate(hz int) +// runtime/debug.go:149:func SetBlockProfileRate(rate int) +// runtime/debug.go:166:func BlockProfile(p []BlockProfileRecord) (n int, ok bool) +// runtime/debug.go:172:func Stack(buf []byte, all bool) int +// runtime/error.go:81:func typestring(interface{}) string +// runtime/extern.go:19:func Goexit() +// runtime/extern.go:27:func Caller(skip int) (pc uintptr, file string, line int, ok bool) +// runtime/extern.go:34:func Callers(skip int, pc []uintptr) int +// runtime/extern.go:51:func FuncForPC(pc uintptr) *Func +// runtime/extern.go:68:func funcline_go(*Func, uintptr) (string, int) +// runtime/extern.go:71:func mid() uint32 +// runtime/pprof/pprof.go:667:func runtime_cyclesPerSecond() int64 +// runtime/race.go:16:func RaceDisable() +// runtime/race.go:19:func RaceEnable() +// runtime/race.go:21:func RaceAcquire(addr unsafe.Pointer) +// runtime/race.go:22:func RaceRelease(addr unsafe.Pointer) +// runtime/race.go:23:func RaceReleaseMerge(addr unsafe.Pointer) +// runtime/race.go:25:func RaceRead(addr unsafe.Pointer) +// runtime/race.go:26:func RaceWrite(addr unsafe.Pointer) +// runtime/race.go:28:func RaceSemacquire(s *uint32) +// runtime/race.go:29:func RaceSemrelease(s *uint32) +// sync/atomic/doc.go:49:func CompareAndSwapInt64(addr *int64, old, new int64) (swapped bool) +// sync/atomic/doc.go:52:func CompareAndSwapUint32(addr *uint32, old, new uint32) (swapped bool) +// sync/atomic/doc.go:55:func CompareAndSwapUint64(addr *uint64, old, new uint64) (swapped bool) +// sync/atomic/doc.go:58:func CompareAndSwapUintptr(addr *uintptr, old, new uintptr) (swapped bool) +// sync/atomic/doc.go:61:func CompareAndSwapPointer(addr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) +// sync/atomic/doc.go:67:func AddUint32(addr *uint32, delta uint32) (new uint32) +// sync/atomic/doc.go:70:func AddInt64(addr *int64, delta int64) (new int64) +// sync/atomic/doc.go:73:func AddUint64(addr *uint64, delta uint64) (new uint64) +// sync/atomic/doc.go:76:func AddUintptr(addr *uintptr, delta uintptr) (new uintptr) +// sync/atomic/doc.go:82:func LoadInt64(addr *int64) (val int64) +// sync/atomic/doc.go:88:func LoadUint64(addr *uint64) (val uint64) +// sync/atomic/doc.go:91:func LoadUintptr(addr *uintptr) (val uintptr) +// sync/atomic/doc.go:94:func LoadPointer(addr *unsafe.Pointer) (val unsafe.Pointer) +// sync/atomic/doc.go:100:func StoreInt64(addr *int64, val int64) +// sync/atomic/doc.go:106:func StoreUint64(addr *uint64, val uint64) +// sync/atomic/doc.go:109:func StoreUintptr(addr *uintptr, val uintptr) +// sync/atomic/doc.go:112:func StorePointer(addr *unsafe.Pointer, val unsafe.Pointer) +// sync/runtime.go:12:func runtime_Semacquire(s *uint32) +// sync/runtime.go:18:func runtime_Semrelease(s *uint32) +// syscall/env_unix.go:30:func setenv_c(k, v string) +// syscall/syscall_linux_amd64.go:60:func Gettimeofday(tv *Timeval) (err error) +// syscall/syscall_linux_amd64.go:61:func Time(t *Time_t) (tt Time_t, err error) +// syscall/syscall_linux_arm.go:28:func Seek(fd int, offset int64, whence int) (newoffset int64, err error) +// time/sleep.go:25:func startTimer(*runtimeTimer) +// time/sleep.go:26:func stopTimer(*runtimeTimer) bool +// time/time.go:758:func now() (sec int64, nsec int32) +// unsafe/unsafe.go:27:func Sizeof(v ArbitraryType) uintptr +// unsafe/unsafe.go:32:func Offsetof(v ArbitraryType) uintptr +// unsafe/unsafe.go:37:func Alignof(v ArbitraryType) uintptr diff --git a/ssa/interp/external_plan9.go b/ssa/interp/external_plan9.go new file mode 100644 index 0000000000..49940cd93e --- /dev/null +++ b/ssa/interp/external_plan9.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. + +package interp + +import ( + "syscall" + + "code.google.com/p/go.tools/ssa" +) + +func ext۰syscall۰Close(fn *ssa.Function, args []value) value { + panic("syscall.Close not yet implemented") +} +func ext۰syscall۰Fstat(fn *ssa.Function, args []value) value { + panic("syscall.Fstat not yet implemented") +} +func ext۰syscall۰Kill(fn *ssa.Function, args []value) value { + panic("syscall.Kill not yet implemented") +} +func ext۰syscall۰Lstat(fn *ssa.Function, args []value) value { + panic("syscall.Lstat not yet implemented") +} +func ext۰syscall۰Open(fn *ssa.Function, args []value) value { + panic("syscall.Open not yet implemented") +} +func ext۰syscall۰ParseDirent(fn *ssa.Function, args []value) value { + panic("syscall.ParseDirent not yet implemented") +} +func ext۰syscall۰Read(fn *ssa.Function, args []value) value { + panic("syscall.Read not yet implemented") +} +func ext۰syscall۰ReadDirent(fn *ssa.Function, args []value) value { + panic("syscall.ReadDirent not yet implemented") +} +func ext۰syscall۰Stat(fn *ssa.Function, args []value) value { + panic("syscall.Stat not yet implemented") +} + +func ext۰syscall۰Write(fn *ssa.Function, args []value) value { + p := args[1].([]value) + b := make([]byte, 0, len(p)) + for i := range p { + b = append(b, p[i].(byte)) + } + n, err := syscall.Write(args[0].(int), b) + return tuple{n, wrapError(err)} +} diff --git a/ssa/interp/external_unix.go b/ssa/interp/external_unix.go new file mode 100644 index 0000000000..a227e5cb1d --- /dev/null +++ b/ssa/interp/external_unix.go @@ -0,0 +1,137 @@ +// 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 !windows,!plan9 + +package interp + +import ( + "syscall" + + "code.google.com/p/go.tools/ssa" +) + +func valueToBytes(v value) []byte { + in := v.([]value) + b := make([]byte, len(in)) + for i := range in { + b[i] = in[i].(byte) + } + return b +} + +func fillStat(st *syscall.Stat_t, stat structure) { + stat[0] = st.Dev + stat[1] = st.Ino + stat[2] = st.Nlink + stat[3] = st.Mode + stat[4] = st.Uid + stat[5] = st.Gid + + stat[7] = st.Rdev + stat[8] = st.Size + stat[9] = st.Blksize + stat[10] = st.Blocks + // TODO(adonovan): fix: copy Timespecs. + // stat[11] = st.Atim + // stat[12] = st.Mtim + // stat[13] = st.Ctim +} + +func ext۰syscall۰Close(fn *ssa.Function, args []value) value { + // func Close(fd int) (err error) + return wrapError(syscall.Close(args[0].(int))) +} + +func ext۰syscall۰Fstat(fn *ssa.Function, args []value) value { + // func Fstat(fd int, stat *Stat_t) (err error) + fd := args[0].(int) + stat := (*args[1].(*value)).(structure) + + var st syscall.Stat_t + err := syscall.Fstat(fd, &st) + fillStat(&st, stat) + return wrapError(err) +} + +func ext۰syscall۰ReadDirent(fn *ssa.Function, args []value) value { + // func ReadDirent(fd int, buf []byte) (n int, err error) + fd := args[0].(int) + p := args[1].([]value) + b := make([]byte, len(p)) + n, err := syscall.ReadDirent(fd, b) + for i := 0; i < n; i++ { + p[i] = b[i] + } + return tuple{n, wrapError(err)} +} + +func ext۰syscall۰Kill(fn *ssa.Function, args []value) value { + // func Kill(pid int, sig Signal) (err error) + return wrapError(syscall.Kill(args[0].(int), syscall.Signal(args[1].(int)))) +} + +func ext۰syscall۰Lstat(fn *ssa.Function, args []value) value { + // func Lstat(name string, stat *Stat_t) (err error) + name := args[0].(string) + stat := (*args[1].(*value)).(structure) + + var st syscall.Stat_t + err := syscall.Lstat(name, &st) + fillStat(&st, stat) + return wrapError(err) +} + +func ext۰syscall۰Open(fn *ssa.Function, args []value) value { + // func Open(path string, mode int, perm uint32) (fd int, err error) { + path := args[0].(string) + mode := args[1].(int) + perm := args[2].(uint32) + fd, err := syscall.Open(path, mode, perm) + return tuple{fd, wrapError(err)} +} + +func ext۰syscall۰ParseDirent(fn *ssa.Function, args []value) value { + // func ParseDirent(buf []byte, max int, names []string) (consumed int, count int, newnames []string) + max := args[1].(int) + var names []string + for _, iname := range args[2].([]value) { + names = append(names, iname.(string)) + } + consumed, count, newnames := syscall.ParseDirent(valueToBytes(args[0]), max, names) + var inewnames []value + for _, newname := range newnames { + inewnames = append(inewnames, newname) + } + return tuple{consumed, count, inewnames} +} + +func ext۰syscall۰Read(fn *ssa.Function, args []value) value { + // func Read(fd int, p []byte) (n int, err error) + fd := args[0].(int) + p := args[1].([]value) + b := make([]byte, len(p)) + n, err := syscall.Read(fd, b) + for i := 0; i < n; i++ { + p[i] = b[i] + } + return tuple{n, wrapError(err)} +} + +func ext۰syscall۰Stat(fn *ssa.Function, args []value) value { + // func Stat(name string, stat *Stat_t) (err error) + name := args[0].(string) + stat := (*args[1].(*value)).(structure) + + var st syscall.Stat_t + err := syscall.Stat(name, &st) + fillStat(&st, stat) + return wrapError(err) +} + +func ext۰syscall۰Write(fn *ssa.Function, args []value) value { + // func Write(fd int, p []byte) (n int, err error) + n, err := syscall.Write(args[0].(int), valueToBytes(args[1])) + return tuple{n, wrapError(err)} +} diff --git a/ssa/interp/external_windows.go b/ssa/interp/external_windows.go new file mode 100644 index 0000000000..bbd272a5fb --- /dev/null +++ b/ssa/interp/external_windows.go @@ -0,0 +1,42 @@ +// 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 windows plan9 + +package interp + +import ( + "code.google.com/p/go.tools/ssa" +) + +func ext۰syscall۰Close(fn *ssa.Function, args []value) value { + panic("syscall.Close not yet implemented") +} +func ext۰syscall۰Fstat(fn *ssa.Function, args []value) value { + panic("syscall.Fstat not yet implemented") +} +func ext۰syscall۰Kill(fn *ssa.Function, args []value) value { + panic("syscall.Kill not yet implemented") +} +func ext۰syscall۰Lstat(fn *ssa.Function, args []value) value { + panic("syscall.Lstat not yet implemented") +} +func ext۰syscall۰Open(fn *ssa.Function, args []value) value { + panic("syscall.Open not yet implemented") +} +func ext۰syscall۰ParseDirent(fn *ssa.Function, args []value) value { + panic("syscall.ParseDirent not yet implemented") +} +func ext۰syscall۰Read(fn *ssa.Function, args []value) value { + panic("syscall.Read not yet implemented") +} +func ext۰syscall۰ReadDirent(fn *ssa.Function, args []value) value { + panic("syscall.ReadDirent not yet implemented") +} +func ext۰syscall۰Stat(fn *ssa.Function, args []value) value { + panic("syscall.Stat not yet implemented") +} +func ext۰syscall۰Write(fn *ssa.Function, args []value) value { + panic("syscall.Write not yet implemented") +} diff --git a/ssa/interp/interp.go b/ssa/interp/interp.go new file mode 100644 index 0000000000..ad6db3ab54 --- /dev/null +++ b/ssa/interp/interp.go @@ -0,0 +1,622 @@ +// Package exp/ssa/interp defines an interpreter for the SSA +// representation of Go programs. +// +// This interpreter is provided as an adjunct for testing the SSA +// construction algorithm. Its purpose is to provide a minimal +// metacircular implementation of the dynamic semantics of each SSA +// instruction. It is not, and will never be, a production-quality Go +// interpreter. +// +// The following is a partial list of Go features that are currently +// unsupported or incomplete in the interpreter. +// +// * Unsafe operations, including all uses of unsafe.Pointer, are +// impossible to support given the "boxed" value representation we +// have chosen. +// +// * The reflect package is only partially implemented. +// +// * "sync/atomic" operations are not currently atomic due to the +// "boxed" value representation: it is not possible to read, modify +// and write an interface value atomically. As a consequence, Mutexes +// are currently broken. TODO(adonovan): provide a metacircular +// implementation of Mutex avoiding the broken atomic primitives. +// +// * recover is only partially implemented. Also, the interpreter +// makes no attempt to distinguish target panics from interpreter +// crashes. +// +// * map iteration is asymptotically inefficient. +// +// * the equivalence relation for structs doesn't skip over blank +// fields. +// +// * the sizes of the int, uint and uintptr types in the target +// program are assumed to be the same as those of the interpreter +// itself. +// +// * all values occupy space, even those of types defined by the spec +// to have zero size, e.g. struct{}. This can cause asymptotic +// performance degradation. +// +// * os.Exit is implemented using panic, causing deferred functions to +// run. +package interp + +import ( + "fmt" + "go/ast" + "go/token" + "os" + "reflect" + "runtime" + + "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/ssa" +) + +type status int + +const ( + stRunning status = iota + stComplete + stPanic +) + +type continuation int + +const ( + kNext continuation = iota + kReturn + kJump +) + +// Mode is a bitmask of options affecting the interpreter. +type Mode uint + +const ( + DisableRecover Mode = 1 << iota // Disable recover() in target programs; show interpreter crash instead. + EnableTracing // Print a trace of all instructions as they are interpreted. +) + +// State shared between all interpreted goroutines. +type interpreter struct { + prog *ssa.Program // the SSA program + globals map[ssa.Value]*value // addresses of global variables (immutable) + mode Mode // interpreter options + reflectPackage *ssa.Package // the fake reflect package + errorMethods ssa.MethodSet // the method set of reflect.error, which implements the error interface. + rtypeMethods ssa.MethodSet // the method set of rtype, which implements the reflect.Type interface. +} + +type frame struct { + i *interpreter + caller *frame + fn *ssa.Function + block, prevBlock *ssa.BasicBlock + env map[ssa.Value]value // dynamic values of SSA variables + locals []value + defers []func() + result value + status status + panic interface{} +} + +func (fr *frame) get(key ssa.Value) value { + switch key := key.(type) { + case nil: + // Hack; simplifies handling of optional attributes + // such as ssa.Slice.{Low,High}. + return nil + case *ssa.Function, *ssa.Builtin: + return key + case *ssa.Literal: + return literalValue(key) + case *ssa.Global: + if r, ok := fr.i.globals[key]; ok { + return r + } + } + if r, ok := fr.env[key]; ok { + return r + } + panic(fmt.Sprintf("get: no value for %T: %v", key, key.Name())) +} + +func (fr *frame) rundefers() { + for i := range fr.defers { + if fr.i.mode&EnableTracing != 0 { + fmt.Fprintln(os.Stderr, "Invoking deferred function", i) + } + fr.defers[len(fr.defers)-1-i]() + } + fr.defers = fr.defers[:0] +} + +// findMethodSet returns the method set for type typ, which may be one +// of the interpreter's fake types. +func findMethodSet(i *interpreter, typ types.Type) ssa.MethodSet { + switch typ { + case rtypeType: + return i.rtypeMethods + case errorType: + return i.errorMethods + } + return i.prog.MethodSet(typ) +} + +// visitInstr interprets a single ssa.Instruction within the activation +// record frame. It returns a continuation value indicating where to +// read the next instruction from. +func visitInstr(fr *frame, instr ssa.Instruction) continuation { + switch instr := instr.(type) { + case *ssa.UnOp: + fr.env[instr] = unop(instr, fr.get(instr.X)) + + case *ssa.BinOp: + fr.env[instr] = binop(instr.Op, fr.get(instr.X), fr.get(instr.Y)) + + case *ssa.Call: + fn, args := prepareCall(fr, &instr.Call) + fr.env[instr] = call(fr.i, fr, instr.Call.Pos, fn, args) + + case *ssa.Conv: + fr.env[instr] = conv(instr.Type(), instr.X.Type(), fr.get(instr.X)) + + case *ssa.ChangeInterface: + fr.env[instr] = fr.get(instr.X) // (can't fail) + + case *ssa.MakeInterface: + fr.env[instr] = iface{t: instr.X.Type(), v: fr.get(instr.X)} + + case *ssa.Extract: + fr.env[instr] = fr.get(instr.Tuple).(tuple)[instr.Index] + + case *ssa.Slice: + fr.env[instr] = slice(fr.get(instr.X), fr.get(instr.Low), fr.get(instr.High)) + + case *ssa.Ret: + switch len(instr.Results) { + case 0: + case 1: + fr.result = fr.get(instr.Results[0]) + default: + var res []value + for _, r := range instr.Results { + res = append(res, fr.get(r)) + } + fr.result = tuple(res) + } + return kReturn + + case *ssa.RunDefers: + fr.rundefers() + + case *ssa.Panic: + panic(targetPanic{fr.get(instr.X)}) + + case *ssa.Send: + fr.get(instr.Chan).(chan value) <- copyVal(fr.get(instr.X)) + + case *ssa.Store: + *fr.get(instr.Addr).(*value) = copyVal(fr.get(instr.Val)) + + case *ssa.If: + succ := 1 + if fr.get(instr.Cond).(bool) { + succ = 0 + } + fr.prevBlock, fr.block = fr.block, fr.block.Succs[succ] + return kJump + + case *ssa.Jump: + fr.prevBlock, fr.block = fr.block, fr.block.Succs[0] + return kJump + + case *ssa.Defer: + pos := instr.Call.Pos // TODO(gri): workaround for go/types bug in typeswitch+funclit. + fn, args := prepareCall(fr, &instr.Call) + fr.defers = append(fr.defers, func() { call(fr.i, fr, pos, fn, args) }) + + case *ssa.Go: + fn, args := prepareCall(fr, &instr.Call) + go call(fr.i, nil, instr.Call.Pos, fn, args) + + case *ssa.MakeChan: + fr.env[instr] = make(chan value, asInt(fr.get(instr.Size))) + + case *ssa.Alloc: + var addr *value + if instr.Heap { + // new + addr = new(value) + fr.env[instr] = addr + } else { + // local + addr = fr.env[instr].(*value) + } + *addr = zero(indirectType(instr.Type())) + + case *ssa.MakeSlice: + slice := make([]value, asInt(fr.get(instr.Cap))) + tElt := underlyingType(instr.Type()).(*types.Slice).Elt + for i := range slice { + slice[i] = zero(tElt) + } + fr.env[instr] = slice[:asInt(fr.get(instr.Len))] + + case *ssa.MakeMap: + reserve := 0 + if instr.Reserve != nil { + reserve = asInt(fr.get(instr.Reserve)) + } + fr.env[instr] = makeMap(underlyingType(instr.Type()).(*types.Map).Key, reserve) + + case *ssa.Range: + fr.env[instr] = rangeIter(fr.get(instr.X), instr.X.Type()) + + case *ssa.Next: + fr.env[instr] = fr.get(instr.Iter).(iter).next() + + case *ssa.FieldAddr: + x := fr.get(instr.X) + fr.env[instr] = &(*x.(*value)).(structure)[instr.Field] + + case *ssa.Field: + fr.env[instr] = copyVal(fr.get(instr.X).(structure)[instr.Field]) + + case *ssa.IndexAddr: + x := fr.get(instr.X) + idx := fr.get(instr.Index) + switch x := x.(type) { + case []value: + fr.env[instr] = &x[asInt(idx)] + case *value: // *array + fr.env[instr] = &(*x).(array)[asInt(idx)] + default: + panic(fmt.Sprintf("unexpected x type in IndexAddr: %T", x)) + } + + case *ssa.Index: + fr.env[instr] = copyVal(fr.get(instr.X).(array)[asInt(fr.get(instr.Index))]) + + case *ssa.Lookup: + fr.env[instr] = lookup(instr, fr.get(instr.X), fr.get(instr.Index)) + + case *ssa.MapUpdate: + m := fr.get(instr.Map) + key := fr.get(instr.Key) + v := fr.get(instr.Value) + switch m := m.(type) { + case map[value]value: + m[key] = v + case *hashmap: + m.insert(key.(hashable), v) + default: + panic(fmt.Sprintf("illegal map type: %T", m)) + } + + case *ssa.TypeAssert: + fr.env[instr] = typeAssert(fr.i, instr, fr.get(instr.X).(iface)) + + case *ssa.MakeClosure: + var bindings []value + for _, binding := range instr.Bindings { + bindings = append(bindings, fr.get(binding)) + } + fr.env[instr] = &closure{instr.Fn.(*ssa.Function), bindings} + + case *ssa.Phi: + for i, pred := range instr.Block_.Preds { + if fr.prevBlock == pred { + fr.env[instr] = fr.get(instr.Edges[i]) + break + } + } + + case *ssa.Select: + var cases []reflect.SelectCase + if !instr.Blocking { + cases = append(cases, reflect.SelectCase{ + Dir: reflect.SelectDefault, + }) + } + for _, state := range instr.States { + var dir reflect.SelectDir + if state.Dir == ast.RECV { + dir = reflect.SelectRecv + } else { + dir = reflect.SelectSend + } + var send reflect.Value + if state.Send != nil { + send = reflect.ValueOf(fr.get(state.Send)) + } + cases = append(cases, reflect.SelectCase{ + Dir: dir, + Chan: reflect.ValueOf(fr.get(state.Chan)), + Send: send, + }) + } + chosen, recv, recvOk := reflect.Select(cases) + if !instr.Blocking { + chosen-- // default case should have index -1. + } + var recvV iface + if chosen != -1 { + recvV.t = underlyingType(instr.States[chosen].Chan.Type()).(*types.Chan).Elt + if recvOk { + // No need to copy since send makes an unaliased copy. + recvV.v = recv.Interface().(value) + } else { + recvV.v = zero(recvV.t) + } + } + fr.env[instr] = tuple{chosen, recvV, recvOk} + + default: + panic(fmt.Sprintf("unexpected instruction: %T", instr)) + } + + // if val, ok := instr.(ssa.Value); ok { + // fmt.Println(toString(fr.env[val])) // debugging + // } + + return kNext +} + +// prepareCall determines the function value and argument values for a +// function call in a Call, Go or Defer instruction, peforming +// interface method lookup if needed. +// +func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) { + if call.Func != nil { + // Function call. + fn = fr.get(call.Func) + } else { + // Interface method invocation. + recv := fr.get(call.Recv).(iface) + if recv.t == nil { + panic("method invoked on nil interface") + } + id := call.MethodId() + m := findMethodSet(fr.i, recv.t)[id] + if m == nil { + // Unreachable in well-typed programs. + panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, id)) + } + _, aptr := recv.v.(*value) // actual pointerness + _, fptr := m.Signature.Recv.Type.(*types.Pointer) // formal pointerness + switch { + case aptr == fptr: + args = append(args, copyVal(recv.v)) + case aptr: + // Calling func(T) with a *T receiver: make a copy. + args = append(args, copyVal(*recv.v.(*value))) + case fptr: + panic("illegal call of *T method with T receiver") + } + fn = m + } + for _, arg := range call.Args { + args = append(args, fr.get(arg)) + } + return +} + +// call interprets a call to a function (function, builtin or closure) +// fn with arguments args, returning its result. +// callpos is the position of the callsite. +// +func call(i *interpreter, caller *frame, callpos token.Pos, fn value, args []value) value { + switch fn := fn.(type) { + case *ssa.Function: + if fn == nil { + panic("call of nil function") // nil of func type + } + return callSSA(i, caller, callpos, fn, args, nil) + case *closure: + return callSSA(i, caller, callpos, fn.Fn, args, fn.Env) + case *ssa.Builtin: + return callBuiltin(caller, callpos, fn, args) + } + panic(fmt.Sprintf("cannot call %T", fn)) +} + +func loc(fset *token.FileSet, pos token.Pos) string { + if pos == token.NoPos { + return "" + } + return " at " + fset.Position(pos).String() +} + +// callSSA interprets a call to function fn with arguments args, +// and lexical environment env, returning its result. +// callpos is the position of the callsite. +// +func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value { + if i.mode&EnableTracing != 0 { + fset := fn.Prog.Files + // TODO(adonovan): fix: loc() lies for external functions. + fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn.FullName(), loc(fset, fn.Pos)) + suffix := "" + if caller != nil { + suffix = ", resuming " + caller.fn.FullName() + loc(fset, callpos) + } + defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn.FullName(), suffix) + } + if fn.Enclosing == nil { + name := fn.FullName() + if ext := externals[name]; ext != nil { + if i.mode&EnableTracing != 0 { + fmt.Fprintln(os.Stderr, "\t(external)") + } + return ext(fn, args) + } + if fn.Blocks == nil { + panic("no code for function: " + name) + } + } + fr := &frame{ + i: i, + caller: caller, // currently unused; for unwinding. + fn: fn, + env: make(map[ssa.Value]value), + block: fn.Blocks[0], + locals: make([]value, len(fn.Locals)), + } + for i, l := range fn.Locals { + fr.locals[i] = zero(indirectType(l.Type())) + fr.env[l] = &fr.locals[i] + } + for i, p := range fn.Params { + fr.env[p] = args[i] + } + for i, fv := range fn.FreeVars { + fr.env[fv] = env[i] + } + var instr ssa.Instruction + + defer func() { + if fr.status != stComplete { + if fr.i.mode&DisableRecover != 0 { + return // let interpreter crash + } + fr.status, fr.panic = stPanic, recover() + } + fr.rundefers() + // Destroy the locals to avoid accidental use after return. + for i := range fn.Locals { + fr.locals[i] = bad{} + } + if fr.status == stPanic { + panic(fr.panic) // panic stack is not entirely clean + } + }() + + for { + if i.mode&EnableTracing != 0 { + fmt.Fprintf(os.Stderr, ".%s:\n", fr.block) + } + block: + for _, instr = range fr.block.Instrs { + if i.mode&EnableTracing != 0 { + if v, ok := instr.(ssa.Value); ok { + fmt.Fprintln(os.Stderr, "\t", v.Name(), "=", instr) + } else { + fmt.Fprintln(os.Stderr, "\t", instr) + } + } + switch visitInstr(fr, instr) { + case kReturn: + fr.status = stComplete + return fr.result + case kNext: + // no-op + case kJump: + break block + } + } + } + panic("unreachable") +} + +// setGlobal sets the value of a system-initialized global variable. +func setGlobal(i *interpreter, pkg *ssa.Package, name string, v value) { + if g, ok := i.globals[pkg.Var(name)]; ok { + *g = v + return + } + panic("no global variable: " + pkg.Name() + "." + name) +} + +// Interpret interprets the Go program whose main package is mainpkg. +// mode specifies various interpreter options. filename and args are +// the initial values of os.Args for the target program. +// +// Interpret returns the exit code of the program: 2 for panic (like +// gc does), or the argument to os.Exit for normal termination. +// +func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string) (exitCode int) { + i := &interpreter{ + prog: mainpkg.Prog, + globals: make(map[ssa.Value]*value), + mode: mode, + } + initReflect(i) + + for importPath, pkg := range i.prog.Packages { + // Initialize global storage. + for _, m := range pkg.Members { + switch v := m.(type) { + case *ssa.Global: + cell := zero(indirectType(v.Type())) + i.globals[v] = &cell + } + } + + // Ad-hoc initialization for magic system variables. + switch importPath { + case "syscall": + var envs []value + for _, s := range os.Environ() { + envs = append(envs, s) + } + envs = append(envs, "GOSSAINTERP=1") + setGlobal(i, pkg, "envs", envs) + + case "runtime": + // TODO(gri): expose go/types.sizeof so we can + // avoid this fragile magic number; + // unsafe.Sizeof(memStats) won't work since gc + // and go/types have different sizeof + // functions. + setGlobal(i, pkg, "sizeof_C_MStats", uintptr(3696)) + + case "os": + Args := []value{filename} + for _, s := range args { + Args = append(Args, s) + } + setGlobal(i, pkg, "Args", Args) + } + } + + // Top-level error handler. + exitCode = 2 + defer func() { + if exitCode != 2 || i.mode&DisableRecover != 0 { + return + } + switch p := recover().(type) { + case exitPanic: + exitCode = int(p) + return + case targetPanic: + fmt.Fprintln(os.Stderr, "panic:", toString(p.v)) + case runtime.Error: + fmt.Fprintln(os.Stderr, "panic:", p.Error()) + case string: + fmt.Fprintln(os.Stderr, "panic:", p) + default: + fmt.Fprintf(os.Stderr, "panic: unexpected type: %T\n", p) + } + + // TODO(adonovan): dump panicking interpreter goroutine? + // buf := make([]byte, 0x10000) + // runtime.Stack(buf, false) + // fmt.Fprintln(os.Stderr, string(buf)) + // (Or dump panicking target goroutine?) + }() + + // Run! + call(i, nil, token.NoPos, mainpkg.Init, nil) + if mainFn := mainpkg.Func("main"); mainFn != nil { + call(i, nil, token.NoPos, mainFn, nil) + exitCode = 0 + } else { + fmt.Fprintln(os.Stderr, "No main function.") + exitCode = 1 + } + return +} diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go new file mode 100644 index 0000000000..678381671e --- /dev/null +++ b/ssa/interp/interp_test.go @@ -0,0 +1,208 @@ +// +build !windows,!plan9 + +package interp_test + +import ( + "fmt" + "go/build" + "os" + "path/filepath" + "strings" + "testing" + + "code.google.com/p/go.tools/ssa" + "code.google.com/p/go.tools/ssa/interp" +) + +// Each line contains a space-separated list of $GOROOT/test/ +// filenames comprising the main package of a program. +// They are ordered quickest-first, roughly. +// +// TODO(adonovan): integrate into the $GOROOT/test driver scripts, +// golden file checking, etc. +var gorootTests = []string{ + "235.go", + "alias1.go", + "chancap.go", + "func5.go", + "func6.go", + "func7.go", + "func8.go", + "helloworld.go", + "varinit.go", + "escape3.go", + "initcomma.go", + "compos.go", + "turing.go", + "indirect.go", + "complit.go", + "for.go", + "struct0.go", + "intcvt.go", + "printbig.go", + "deferprint.go", + "escape.go", + "range.go", + "const4.go", + "float_lit.go", + "bigalg.go", + "decl.go", + "if.go", + "named.go", + "bigmap.go", + "func.go", + "reorder2.go", + "closure.go", + "gc.go", + "simassign.go", + "iota.go", + "goprint.go", // doesn't actually assert anything + "utf.go", + "method.go", + "char_lit.go", + "env.go", + "int_lit.go", + "string_lit.go", + "defer.go", + "typeswitch.go", + "stringrange.go", + "reorder.go", + "literal.go", + "nul1.go", + "zerodivide.go", + "convert.go", + "convT2X.go", + "initialize.go", + "ddd.go", + "blank.go", // partly disabled; TODO(adonovan): skip blank fields in struct{_} equivalence. + "map.go", + "bom.go", + "closedchan.go", + "divide.go", + "rename.go", + "const3.go", + "nil.go", + "recover.go", // partly disabled; TODO(adonovan): fix. + // Slow tests follow. + "cmplxdivide.go cmplxdivide1.go", + "append.go", + "crlf.go", // doesn't actually assert anything + "typeswitch1.go", + "floatcmp.go", + "gc1.go", + + // Working, but not worth enabling: + // "gc2.go", // works, but slow, and cheats on the memory check. + // "sigchld.go", // works, but only on POSIX. + // "peano.go", // works only up to n=9, and slow even then. + // "stack.go", // works, but too slow (~30s) by default. + // "solitaire.go", // works, but too slow (~30s). + // "const.go", // works but for but one bug: constant folder doesn't consider representations. + // "init1.go", // too slow (80s) and not that interesting. Cheats on ReadMemStats check too. + + // Typechecker failures: + // "switch.go", // bug re: switch ... { case 1.0:... case 1:... } + // "rune.go", // error re: rune as index + // "64bit.go", // error re: comparison + // "cmp.go", // error re: comparison + // "rotate.go rotate0.go", // error re: shifts + // "rotate.go rotate1.go", // error re: shifts + // "rotate.go rotate2.go", // error re: shifts + // "rotate.go rotate3.go", // error re: shifts + // "run.go", // produces wrong constant for bufio.runeError; also, not really a test. + + // Broken. TODO(adonovan): fix. + // copy.go // very slow; but with N=4 quickly crashes, slice index out of range. + // nilptr.go // interp: V > uintptr not implemented. Slow test, lots of mem + // recover1.go // error: "spurious recover" + // recover2.go // panic: interface conversion: string is not error: missing method Error + // recover3.go // logic errors: panicked with wrong Error. + // method3.go // Fails dynamically; (*T).f vs (T).f are distinct methods. + // args.go // works, but requires specific os.Args from the driver. + // index.go // a template, not a real test. + // mallocfin.go // SetFinalizer not implemented. + + // TODO(adonovan): add tests from $GOROOT/test/* subtrees: + // bench chan bugs fixedbugs interface ken. +} + +// These are files in go.exp/ssa/interp/testdata/. +var testdataTests = []string{ + "coverage.go", + "mrvchain.go", +} + +func run(t *testing.T, dir, input string) bool { + fmt.Printf("Input: %s\n", input) + + var inputs []string + for _, i := range strings.Split(input, " ") { + inputs = append(inputs, dir+i) + } + + b := ssa.NewBuilder(&ssa.Context{Mode: ssa.SanityCheckFunctions, Loader: ssa.GorootLoader}) + files, err := ssa.ParseFiles(b.Prog.Files, ".", inputs...) + if err != nil { + t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error()) + return false + } + + // Print a helpful hint if we don't make it to the end. + var hint string + defer func() { + if hint != "" { + fmt.Println("FAIL") + fmt.Println(hint) + } else { + fmt.Println("PASS") + } + }() + + hint = fmt.Sprintf("To dump SSA representation, run:\n%% go run exp/ssa/ssadump.go -build=CFP %s\n", input) + mainpkg, err := b.CreatePackage("main", files) + if err != nil { + t.Errorf("ssa.Builder.CreatePackage(%s) failed: %s", inputs, err.Error()) + + return false + } + + b.BuildAllPackages() + b = nil // discard Builder + + hint = fmt.Sprintf("To trace execution, run:\n%% go run exp/ssa/ssadump.go -build=C -run --interp=T %s\n", input) + if exitCode := interp.Interpret(mainpkg, 0, inputs[0], []string{}); exitCode != 0 { + t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode) + return false + } + + hint = "" // call off the hounds + return true +} + +const slash = string(os.PathSeparator) + +// TestInterp runs the interpreter on a selection of small Go programs. +func TestInterp(t *testing.T) { + var failures []string + + for _, input := range testdataTests { + if !run(t, "testdata"+slash, input) { + failures = append(failures, input) + } + } + + if !testing.Short() { + for _, input := range gorootTests { + if !run(t, filepath.Join(build.Default.GOROOT, "test")+slash, input) { + failures = append(failures, input) + } + } + } + + if failures != nil { + fmt.Println("The following tests failed:") + for _, f := range failures { + fmt.Printf("\t%s\n", f) + } + } +} diff --git a/ssa/interp/map.go b/ssa/interp/map.go new file mode 100644 index 0000000000..b81ffeddd6 --- /dev/null +++ b/ssa/interp/map.go @@ -0,0 +1,101 @@ +package interp + +// Custom hashtable atop map. +// For use when the key's equivalence relation is not consistent with ==. + +// The Go specification doesn't address the atomicity of map operations. +// The FAQ states that an implementation is permitted to crash on +// concurrent map access. + +import ( + "code.google.com/p/go.tools/go/types" +) + +type hashable interface { + hash() int + eq(x interface{}) bool +} + +type entry struct { + key hashable + value value + next *entry +} + +// A hashtable atop the built-in map. Since each bucket contains +// exactly one hash value, there's no need to perform hash-equality +// tests when walking the linked list. Rehashing is done by the +// underlying map. +type hashmap struct { + table map[int]*entry + length int // number of entries in map +} + +// makeMap returns an empty initialized map of key type kt, +// preallocating space for reserve elements. +func makeMap(kt types.Type, reserve int) value { + if usesBuiltinMap(kt) { + return make(map[value]value, reserve) + } + return &hashmap{table: make(map[int]*entry, reserve)} +} + +// delete removes the association for key k, if any. +func (m *hashmap) delete(k hashable) { + hash := k.hash() + head := m.table[hash] + if head != nil { + if k.eq(head.key) { + m.table[hash] = head.next + m.length-- + return + } + prev := head + for e := head.next; e != nil; e = e.next { + if k.eq(e.key) { + prev.next = e.next + m.length-- + return + } + prev = e + } + } +} + +// lookup returns the value associated with key k, if present, or +// value(nil) otherwise. +func (m *hashmap) lookup(k hashable) value { + hash := k.hash() + for e := m.table[hash]; e != nil; e = e.next { + if k.eq(e.key) { + return e.value + } + } + return nil +} + +// insert updates the map to associate key k with value v. If there +// was already an association for an eq() (though not necessarily ==) +// k, the previous key remains in the map and its associated value is +// updated. +func (m *hashmap) insert(k hashable, v value) { + hash := k.hash() + head := m.table[hash] + for e := head; e != nil; e = e.next { + if k.eq(e.key) { + e.value = v + return + } + } + m.table[hash] = &entry{ + key: k, + value: v, + next: head, + } + m.length++ +} + +// len returns the number of key/value associations in the map. +func (m *hashmap) len() int { + return m.length +} diff --git a/ssa/interp/ops.go b/ssa/interp/ops.go new file mode 100644 index 0000000000..d0b31af385 --- /dev/null +++ b/ssa/interp/ops.go @@ -0,0 +1,1375 @@ +package interp + +import ( + "fmt" + "go/token" + "os" + "runtime" + "strings" + "unsafe" + + "code.google.com/p/go.tools/go/exact" + "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/ssa" +) + +// If the target program panics, the interpreter panics with this type. +type targetPanic struct { + v value +} + +// If the target program calls exit, the interpreter panics with this type. +type exitPanic int + +// literalValue returns the value of the literal with the +// dynamic type tag appropriate for l.Type(). +func literalValue(l *ssa.Literal) value { + if l.IsNil() { + return zero(l.Type()) // typed nil + } + + // By destination type: + switch t := underlyingType(l.Type()).(type) { + case *types.Basic: + switch t.Kind { + case types.Bool, types.UntypedBool: + return exact.BoolVal(l.Value) + case types.Int, types.UntypedInt: + // Assume sizeof(int) is same on host and target. + return int(l.Int64()) + case types.Int8: + return int8(l.Int64()) + case types.Int16: + return int16(l.Int64()) + case types.Int32, types.UntypedRune: + return int32(l.Int64()) + case types.Int64: + return l.Int64() + case types.Uint: + // Assume sizeof(uint) is same on host and target. + return uint(l.Uint64()) + case types.Uint8: + return uint8(l.Uint64()) + case types.Uint16: + return uint16(l.Uint64()) + case types.Uint32: + return uint32(l.Uint64()) + case types.Uint64: + return l.Uint64() + case types.Uintptr: + // Assume sizeof(uintptr) is same on host and target. + return uintptr(l.Uint64()) + case types.Float32: + return float32(l.Float64()) + case types.Float64, types.UntypedFloat: + return l.Float64() + case types.Complex64: + return complex64(l.Complex128()) + case types.Complex128, types.UntypedComplex: + return l.Complex128() + case types.String, types.UntypedString: + if l.Value.Kind() == exact.String { + return exact.StringVal(l.Value) + } + return string(rune(l.Int64())) + case types.UnsafePointer: + panic("unsafe.Pointer literal") // not possible + case types.UntypedNil: + // nil was handled above. + } + + case *types.Slice: + switch et := underlyingType(t.Elt).(type) { + case *types.Basic: + switch et.Kind { + case types.Byte: // string -> []byte + var v []value + for _, b := range []byte(exact.StringVal(l.Value)) { + v = append(v, b) + } + return v + case types.Rune: // string -> []rune + var v []value + for _, r := range []rune(exact.StringVal(l.Value)) { + v = append(v, r) + } + return v + } + } + } + + panic(fmt.Sprintf("literalValue: Value.(type)=%T Type()=%s", l.Value, l.Type())) +} + +// asInt converts x, which must be an integer, to an int suitable for +// use as a slice or array index or operand to make(). +func asInt(x value) int { + switch x := x.(type) { + case int: + return x + case int8: + return int(x) + case int16: + return int(x) + case int32: + return int(x) + case int64: + return int(x) + case uint: + return int(x) + case uint8: + return int(x) + case uint16: + return int(x) + case uint32: + return int(x) + case uint64: + return int(x) + case uintptr: + return int(x) + } + panic(fmt.Sprintf("cannot convert %T to int", x)) +} + +// asUint64 converts x, which must be an unsigned integer, to a uint64 +// suitable for use as a bitwise shift count. +func asUint64(x value) uint64 { + switch x := x.(type) { + case uint: + return uint64(x) + case uint8: + return uint64(x) + case uint16: + return uint64(x) + case uint32: + return uint64(x) + case uint64: + return x + case uintptr: + return uint64(x) + } + panic(fmt.Sprintf("cannot convert %T to uint64", x)) +} + +// zero returns a new "zero" value of the specified type. +func zero(t types.Type) value { + switch t := t.(type) { + case *types.Basic: + if t.Kind == types.UntypedNil { + panic("untyped nil has no zero value") + } + if t.Info&types.IsUntyped != 0 { + t = ssa.DefaultType(t).(*types.Basic) + } + switch t.Kind { + case types.Bool: + return false + case types.Int: + return int(0) + case types.Int8: + return int8(0) + case types.Int16: + return int16(0) + case types.Int32: + return int32(0) + case types.Int64: + return int64(0) + case types.Uint: + return uint(0) + case types.Uint8: + return uint8(0) + case types.Uint16: + return uint16(0) + case types.Uint32: + return uint32(0) + case types.Uint64: + return uint64(0) + case types.Uintptr: + return uintptr(0) + case types.Float32: + return float32(0) + case types.Float64: + return float64(0) + case types.Complex64: + return complex64(0) + case types.Complex128: + return complex128(0) + case types.String: + return "" + case types.UnsafePointer: + return unsafe.Pointer(nil) + default: + panic(fmt.Sprint("zero for unexpected type:", t)) + } + case *types.Pointer: + return (*value)(nil) + case *types.Array: + a := make(array, t.Len) + for i := range a { + a[i] = zero(t.Elt) + } + return a + case *types.NamedType: + return zero(t.Underlying) + case *types.Interface: + return iface{} // nil type, methodset and value + case *types.Slice: + return []value(nil) + case *types.Struct: + s := make(structure, len(t.Fields)) + for i := range s { + s[i] = zero(t.Fields[i].Type) + } + return s + case *types.Chan: + return chan value(nil) + case *types.Map: + if usesBuiltinMap(t.Key) { + return map[value]value(nil) + } + return (*hashmap)(nil) + case *types.Signature: + return (*ssa.Function)(nil) + } + panic(fmt.Sprint("zero: unexpected ", t)) +} + +// slice returns x[lo:hi]. Either or both of lo and hi may be nil. +func slice(x, lo, hi value) value { + l := 0 + if lo != nil { + l = asInt(lo) + } + switch x := x.(type) { + case string: + if hi != nil { + return x[l:asInt(hi)] + } + return x[l:] + case []value: + if hi != nil { + return x[l:asInt(hi)] + } + return x[l:] + case *value: // *array + a := (*x).(array) + if hi != nil { + return []value(a)[l:asInt(hi)] + } + return []value(a)[l:] + } + panic(fmt.Sprintf("slice: unexpected X type: %T", x)) +} + +// lookup returns x[idx] where x is a map or string. +func lookup(instr *ssa.Lookup, x, idx value) value { + switch x := x.(type) { // map or string + case map[value]value, *hashmap: + var v value + var ok bool + switch x := x.(type) { + case map[value]value: + v, ok = x[idx] + case *hashmap: + v = x.lookup(idx.(hashable)) + ok = v != nil + } + if ok { + v = copyVal(v) + } else { + v = zero(underlyingType(instr.X.Type()).(*types.Map).Elt) + } + if instr.CommaOk { + v = tuple{v, ok} + } + return v + case string: + return x[asInt(idx)] + } + panic(fmt.Sprintf("unexpected x type in Lookup: %T", x)) +} + +// binop implements all arithmetic and logical binary operators for +// numeric datatypes and strings. Both operands must have identical +// dynamic type. +// +func binop(op token.Token, x, y value) value { + switch op { + case token.ADD: + switch x.(type) { + case int: + return x.(int) + y.(int) + case int8: + return x.(int8) + y.(int8) + case int16: + return x.(int16) + y.(int16) + case int32: + return x.(int32) + y.(int32) + case int64: + return x.(int64) + y.(int64) + case uint: + return x.(uint) + y.(uint) + case uint8: + return x.(uint8) + y.(uint8) + case uint16: + return x.(uint16) + y.(uint16) + case uint32: + return x.(uint32) + y.(uint32) + case uint64: + return x.(uint64) + y.(uint64) + case uintptr: + return x.(uintptr) + y.(uintptr) + case float32: + return x.(float32) + y.(float32) + case float64: + return x.(float64) + y.(float64) + case complex64: + return x.(complex64) + y.(complex64) + case complex128: + return x.(complex128) + y.(complex128) + case string: + return x.(string) + y.(string) + } + + case token.SUB: + switch x.(type) { + case int: + return x.(int) - y.(int) + case int8: + return x.(int8) - y.(int8) + case int16: + return x.(int16) - y.(int16) + case int32: + return x.(int32) - y.(int32) + case int64: + return x.(int64) - y.(int64) + case uint: + return x.(uint) - y.(uint) + case uint8: + return x.(uint8) - y.(uint8) + case uint16: + return x.(uint16) - y.(uint16) + case uint32: + return x.(uint32) - y.(uint32) + case uint64: + return x.(uint64) - y.(uint64) + case uintptr: + return x.(uintptr) - y.(uintptr) + case float32: + return x.(float32) - y.(float32) + case float64: + return x.(float64) - y.(float64) + case complex64: + return x.(complex64) - y.(complex64) + case complex128: + return x.(complex128) - y.(complex128) + } + + case token.MUL: + switch x.(type) { + case int: + return x.(int) * y.(int) + case int8: + return x.(int8) * y.(int8) + case int16: + return x.(int16) * y.(int16) + case int32: + return x.(int32) * y.(int32) + case int64: + return x.(int64) * y.(int64) + case uint: + return x.(uint) * y.(uint) + case uint8: + return x.(uint8) * y.(uint8) + case uint16: + return x.(uint16) * y.(uint16) + case uint32: + return x.(uint32) * y.(uint32) + case uint64: + return x.(uint64) * y.(uint64) + case uintptr: + return x.(uintptr) * y.(uintptr) + case float32: + return x.(float32) * y.(float32) + case float64: + return x.(float64) * y.(float64) + case complex64: + return x.(complex64) * y.(complex64) + case complex128: + return x.(complex128) * y.(complex128) + } + + case token.QUO: + switch x.(type) { + case int: + return x.(int) / y.(int) + case int8: + return x.(int8) / y.(int8) + case int16: + return x.(int16) / y.(int16) + case int32: + return x.(int32) / y.(int32) + case int64: + return x.(int64) / y.(int64) + case uint: + return x.(uint) / y.(uint) + case uint8: + return x.(uint8) / y.(uint8) + case uint16: + return x.(uint16) / y.(uint16) + case uint32: + return x.(uint32) / y.(uint32) + case uint64: + return x.(uint64) / y.(uint64) + case uintptr: + return x.(uintptr) / y.(uintptr) + case float32: + return x.(float32) / y.(float32) + case float64: + return x.(float64) / y.(float64) + case complex64: + return x.(complex64) / y.(complex64) + case complex128: + return x.(complex128) / y.(complex128) + } + + case token.REM: + switch x.(type) { + case int: + return x.(int) % y.(int) + case int8: + return x.(int8) % y.(int8) + case int16: + return x.(int16) % y.(int16) + case int32: + return x.(int32) % y.(int32) + case int64: + return x.(int64) % y.(int64) + case uint: + return x.(uint) % y.(uint) + case uint8: + return x.(uint8) % y.(uint8) + case uint16: + return x.(uint16) % y.(uint16) + case uint32: + return x.(uint32) % y.(uint32) + case uint64: + return x.(uint64) % y.(uint64) + case uintptr: + return x.(uintptr) % y.(uintptr) + } + + case token.AND: + switch x.(type) { + case int: + return x.(int) & y.(int) + case int8: + return x.(int8) & y.(int8) + case int16: + return x.(int16) & y.(int16) + case int32: + return x.(int32) & y.(int32) + case int64: + return x.(int64) & y.(int64) + case uint: + return x.(uint) & y.(uint) + case uint8: + return x.(uint8) & y.(uint8) + case uint16: + return x.(uint16) & y.(uint16) + case uint32: + return x.(uint32) & y.(uint32) + case uint64: + return x.(uint64) & y.(uint64) + case uintptr: + return x.(uintptr) & y.(uintptr) + } + + case token.OR: + switch x.(type) { + case int: + return x.(int) | y.(int) + case int8: + return x.(int8) | y.(int8) + case int16: + return x.(int16) | y.(int16) + case int32: + return x.(int32) | y.(int32) + case int64: + return x.(int64) | y.(int64) + case uint: + return x.(uint) | y.(uint) + case uint8: + return x.(uint8) | y.(uint8) + case uint16: + return x.(uint16) | y.(uint16) + case uint32: + return x.(uint32) | y.(uint32) + case uint64: + return x.(uint64) | y.(uint64) + case uintptr: + return x.(uintptr) | y.(uintptr) + } + + case token.XOR: + switch x.(type) { + case int: + return x.(int) ^ y.(int) + case int8: + return x.(int8) ^ y.(int8) + case int16: + return x.(int16) ^ y.(int16) + case int32: + return x.(int32) ^ y.(int32) + case int64: + return x.(int64) ^ y.(int64) + case uint: + return x.(uint) ^ y.(uint) + case uint8: + return x.(uint8) ^ y.(uint8) + case uint16: + return x.(uint16) ^ y.(uint16) + case uint32: + return x.(uint32) ^ y.(uint32) + case uint64: + return x.(uint64) ^ y.(uint64) + case uintptr: + return x.(uintptr) ^ y.(uintptr) + } + + case token.AND_NOT: + switch x.(type) { + case int: + return x.(int) &^ y.(int) + case int8: + return x.(int8) &^ y.(int8) + case int16: + return x.(int16) &^ y.(int16) + case int32: + return x.(int32) &^ y.(int32) + case int64: + return x.(int64) &^ y.(int64) + case uint: + return x.(uint) &^ y.(uint) + case uint8: + return x.(uint8) &^ y.(uint8) + case uint16: + return x.(uint16) &^ y.(uint16) + case uint32: + return x.(uint32) &^ y.(uint32) + case uint64: + return x.(uint64) &^ y.(uint64) + case uintptr: + return x.(uintptr) &^ y.(uintptr) + } + + case token.SHL: + y := asUint64(y) + switch x.(type) { + case int: + return x.(int) << y + case int8: + return x.(int8) << y + case int16: + return x.(int16) << y + case int32: + return x.(int32) << y + case int64: + return x.(int64) << y + case uint: + return x.(uint) << y + case uint8: + return x.(uint8) << y + case uint16: + return x.(uint16) << y + case uint32: + return x.(uint32) << y + case uint64: + return x.(uint64) << y + case uintptr: + return x.(uintptr) << y + } + + case token.SHR: + y := asUint64(y) + switch x.(type) { + case int: + return x.(int) >> y + case int8: + return x.(int8) >> y + case int16: + return x.(int16) >> y + case int32: + return x.(int32) >> y + case int64: + return x.(int64) >> y + case uint: + return x.(uint) >> y + case uint8: + return x.(uint8) >> y + case uint16: + return x.(uint16) >> y + case uint32: + return x.(uint32) >> y + case uint64: + return x.(uint64) >> y + case uintptr: + return x.(uintptr) >> y + } + + case token.LSS: + switch x.(type) { + case int: + return x.(int) < y.(int) + case int8: + return x.(int8) < y.(int8) + case int16: + return x.(int16) < y.(int16) + case int32: + return x.(int32) < y.(int32) + case int64: + return x.(int64) < y.(int64) + case uint: + return x.(uint) < y.(uint) + case uint8: + return x.(uint8) < y.(uint8) + case uint16: + return x.(uint16) < y.(uint16) + case uint32: + return x.(uint32) < y.(uint32) + case uint64: + return x.(uint64) < y.(uint64) + case uintptr: + return x.(uintptr) < y.(uintptr) + case float32: + return x.(float32) < y.(float32) + case float64: + return x.(float64) < y.(float64) + case string: + return x.(string) < y.(string) + } + + case token.LEQ: + switch x.(type) { + case int: + return x.(int) <= y.(int) + case int8: + return x.(int8) <= y.(int8) + case int16: + return x.(int16) <= y.(int16) + case int32: + return x.(int32) <= y.(int32) + case int64: + return x.(int64) <= y.(int64) + case uint: + return x.(uint) <= y.(uint) + case uint8: + return x.(uint8) <= y.(uint8) + case uint16: + return x.(uint16) <= y.(uint16) + case uint32: + return x.(uint32) <= y.(uint32) + case uint64: + return x.(uint64) <= y.(uint64) + case uintptr: + return x.(uintptr) <= y.(uintptr) + case float32: + return x.(float32) <= y.(float32) + case float64: + return x.(float64) <= y.(float64) + case string: + return x.(string) <= y.(string) + } + + case token.EQL: + return equals(x, y) + + case token.NEQ: + return !equals(x, y) + + case token.GTR: + switch x.(type) { + case int: + return x.(int) > y.(int) + case int8: + return x.(int8) > y.(int8) + case int16: + return x.(int16) > y.(int16) + case int32: + return x.(int32) > y.(int32) + case int64: + return x.(int64) > y.(int64) + case uint: + return x.(uint) > y.(uint) + case uint8: + return x.(uint8) > y.(uint8) + case uint16: + return x.(uint16) > y.(uint16) + case uint32: + return x.(uint32) > y.(uint32) + case uint64: + return x.(uint64) > y.(uint64) + case uintptr: + return x.(uintptr) > y.(uintptr) + case float32: + return x.(float32) > y.(float32) + case float64: + return x.(float64) > y.(float64) + case string: + return x.(string) > y.(string) + } + + case token.GEQ: + switch x.(type) { + case int: + return x.(int) >= y.(int) + case int8: + return x.(int8) >= y.(int8) + case int16: + return x.(int16) >= y.(int16) + case int32: + return x.(int32) >= y.(int32) + case int64: + return x.(int64) >= y.(int64) + case uint: + return x.(uint) >= y.(uint) + case uint8: + return x.(uint8) >= y.(uint8) + case uint16: + return x.(uint16) >= y.(uint16) + case uint32: + return x.(uint32) >= y.(uint32) + case uint64: + return x.(uint64) >= y.(uint64) + case uintptr: + return x.(uintptr) >= y.(uintptr) + case float32: + return x.(float32) >= y.(float32) + case float64: + return x.(float64) >= y.(float64) + case string: + return x.(string) >= y.(string) + } + } + panic(fmt.Sprintf("invalid binary op: %T %s %T", x, op, y)) +} + +func unop(instr *ssa.UnOp, x value) value { + switch instr.Op { + case token.ARROW: // receive + v, ok := <-x.(chan value) + if !ok { + v = zero(underlyingType(instr.X.Type()).(*types.Chan).Elt) + } + if instr.CommaOk { + v = tuple{v, ok} + } + return v + case token.SUB: + switch x := x.(type) { + case int: + return -x + case int8: + return -x + case int16: + return -x + case int32: + return -x + case int64: + return -x + case uint: + return -x + case uint8: + return -x + case uint16: + return -x + case uint32: + return -x + case uint64: + return -x + case uintptr: + return -x + case float32: + return -x + case float64: + return -x + } + case token.MUL: + return copyVal(*x.(*value)) // load + case token.NOT: + return !x.(bool) + case token.XOR: + switch x := x.(type) { + case int: + return ^x + case int8: + return ^x + case int16: + return ^x + case int32: + return ^x + case int64: + return ^x + case uint: + return ^x + case uint8: + return ^x + case uint16: + return ^x + case uint32: + return ^x + case uint64: + return ^x + case uintptr: + return ^x + } + } + panic(fmt.Sprintf("invalid unary op %s %T", instr.Op, x)) +} + +// typeAssert checks whether dynamic type of itf is instr.AssertedType. +// It returns the extracted value on success, and panics on failure, +// unless instr.CommaOk, in which case it always returns a "value,ok" tuple. +// +func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { + var v value + err := "" + if idst, ok := underlyingType(instr.AssertedType).(*types.Interface); ok { + v = itf + err = checkInterface(i, idst, itf) + + } else if types.IsIdentical(itf.t, instr.AssertedType) { + v = copyVal(itf.v) // extract value + + } else { + err = fmt.Sprintf("type assert failed: expected %s, got %s", instr.AssertedType, itf.t) + } + + if err != "" { + if !instr.CommaOk { + panic(err) + } + return tuple{zero(instr.AssertedType), false} + } + if instr.CommaOk { + return tuple{v, true} + } + return v +} + +// callBuiltin interprets a call to builtin fn with arguments args, +// returning its result. +func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value) value { + switch fn.Name() { + case "append": + if len(args) == 1 { + return args[0] + } + if s, ok := args[1].(string); ok { + // append([]byte, ...string) []byte + arg0 := args[0].([]value) + for i := 0; i < len(s); i++ { + arg0 = append(arg0, s[i]) + } + return arg0 + } + // append([]T, ...[]T) []T + return append(args[0].([]value), args[1].([]value)...) + + case "copy": // copy([]T, []T) int + if _, ok := args[1].(string); ok { + panic("copy([]byte, string) not yet implemented") + } + return copy(args[0].([]value), args[1].([]value)) + + case "close": // close(chan T) + close(args[0].(chan value)) + return nil + + case "delete": // delete(map[K]value, K) + switch m := args[0].(type) { + case map[value]value: + delete(m, args[1]) + case *hashmap: + m.delete(args[1].(hashable)) + default: + panic(fmt.Sprintf("illegal map type: %T", m)) + } + return nil + + case "print", "println": // print(anytype, ...interface{}) + ln := fn.Name() == "println" + fmt.Print(toString(args[0])) + if len(args) == 2 { + for _, arg := range args[1].([]value) { + if ln { + fmt.Print(" ") + } + fmt.Print(toString(arg)) + } + } + if ln { + fmt.Println() + } + return nil + + case "len": + switch x := args[0].(type) { + case string: + return len(x) + case array: + return len(x) + case *value: + return len((*x).(array)) + case []value: + return len(x) + case map[value]value: + return len(x) + case *hashmap: + return x.len() + case chan value: + return len(x) + default: + panic(fmt.Sprintf("len: illegal operand: %T", x)) + } + + case "cap": + switch x := args[0].(type) { + case array: + return cap(x) + case *value: + return cap((*x).(array)) + case []value: + return cap(x) + case chan value: + return cap(x) + default: + panic(fmt.Sprintf("cap: illegal operand: %T", x)) + } + + case "real": + switch c := args[0].(type) { + case complex64: + return real(c) + case complex128: + return real(c) + default: + panic(fmt.Sprintf("real: illegal operand: %T", c)) + } + + case "imag": + switch c := args[0].(type) { + case complex64: + return imag(c) + case complex128: + return imag(c) + default: + panic(fmt.Sprintf("imag: illegal operand: %T", c)) + } + + case "complex": + switch f := args[0].(type) { + case float32: + return complex(f, args[1].(float32)) + case float64: + return complex(f, args[1].(float64)) + default: + panic(fmt.Sprintf("complex: illegal operand: %T", f)) + } + + case "panic": + // ssa.Panic handles most cases; this is only for "go + // panic" or "defer panic". + panic(targetPanic{args[0]}) + + case "recover": + // recover() must be exactly one level beneath the + // deferred function (two levels beneath the panicking + // function) to have any effect. Thus we ignore both + // "defer recover()" and "defer f() -> g() -> + // recover()". + if caller.i.mode&DisableRecover == 0 && + caller != nil && caller.status == stRunning && + caller.caller != nil && caller.caller.status == stPanic { + caller.caller.status = stComplete + p := caller.caller.panic + caller.caller.panic = nil + switch p := p.(type) { + case targetPanic: + return p.v + case runtime.Error: + // TODO(adonovan): must box this up + // inside instance of interface 'error'. + return iface{types.Typ[types.String], p.Error()} + case string: + return iface{types.Typ[types.String], p} + default: + panic(fmt.Sprintf("unexpected panic type %T in target call to recover()", p)) + } + } + return iface{} + } + + panic("unknown built-in: " + fn.Name()) +} + +func rangeIter(x value, t types.Type) iter { + switch x := x.(type) { + case map[value]value: + // TODO(adonovan): fix: leaks goroutines and channels + // on each incomplete map iteration. We need to open + // up an iteration interface using the + // reflect.(Value).MapKeys machinery. + it := make(mapIter) + x2 := x // TODO(gri): workaround for go/types bug in typeswitch+funclit. + go func() { + for k, v := range x2 { + it <- [2]value{k, v} + } + close(it) + }() + return it + case *hashmap: + // TODO(adonovan): fix: leaks goroutines and channels + // on each incomplete map iteration. We need to open + // up an iteration interface using the + // reflect.(Value).MapKeys machinery. + it := make(mapIter) + x2 := x // TODO(gri): workaround for go/types bug in typeswitch+funclit. + go func() { + for _, e := range x2.table { + for e != nil { + it <- [2]value{e.key, e.value} + e = e.next + } + } + close(it) + }() + return it + case string: + return &stringIter{Reader: strings.NewReader(x)} + } + panic(fmt.Sprintf("cannot range over %T", x)) +} + +// widen widens a basic typed value x to the widest type of its +// category, one of: +// bool, int64, uint64, float64, complex128, string. +// This is inefficient but reduces the size of the cross-product of +// cases we have to consider. +// +func widen(x value) value { + switch y := x.(type) { + case bool, int64, uint64, float64, complex128, string, unsafe.Pointer: + return x + case int: + return int64(y) + case int8: + return int64(y) + case int16: + return int64(y) + case int32: + return int64(y) + case uint: + return uint64(y) + case uint8: + return uint64(y) + case uint16: + return uint64(y) + case uint32: + return uint64(y) + case uintptr: + return uint64(y) + case float32: + return float64(y) + case complex64: + return complex128(y) + } + panic(fmt.Sprintf("cannot widen %T", x)) +} + +// conv converts the value x of type t_src to type t_dst and returns +// the result. Possible cases are described with the ssa.Conv +// operator. Panics if the dynamic conversion fails. +// +func conv(t_dst, t_src types.Type, x value) value { + ut_src := underlyingType(t_src) + ut_dst := underlyingType(t_dst) + + // Same underlying types? + // TODO(adonovan): consider a dedicated ssa.ChangeType instruction. + // TODO(adonovan): fix: what about channels of different direction? + if types.IsIdentical(ut_dst, ut_src) { + return x + } + + // Destination type is not an "untyped" type. + if b, ok := ut_dst.(*types.Basic); ok && b.Info&types.IsUntyped != 0 { + panic("conversion to 'untyped' type: " + b.String()) + } + + // Nor is it an interface type. + if _, ok := ut_dst.(*types.Interface); ok { + if _, ok := ut_src.(*types.Interface); ok { + panic("oops: Conv should be ChangeInterface") + } else { + panic("oops: Conv should be MakeInterface") + } + } + + // Remaining conversions: + // + untyped string/number/bool constant to a specific + // representation. + // + conversions between non-complex numeric types. + // + conversions between complex numeric types. + // + integer/[]byte/[]rune -> string. + // + string -> []byte/[]rune. + // + // All are treated the same: first we extract the value to the + // widest representation (bool, int64, uint64, float64, + // complex128, or string), then we convert it to the desired + // type. + + switch ut_src := ut_src.(type) { + case *types.Signature: + // TODO(adonovan): fix: this is a hacky workaround for the + // unsound conversion of Signature types from + // func(T)() to func()(T), i.e. arg0 <-> receiver + // conversion. Talk to gri about correct approach. + fmt.Fprintln(os.Stderr, "Warning: unsound Signature conversion") + return x + + case *types.Pointer: + switch ut_dst := ut_dst.(type) { + case *types.Basic: + // *value to unsafe.Pointer? + if ut_dst.Kind == types.UnsafePointer { + return unsafe.Pointer(x.(*value)) + } + case *types.Pointer: + return x + } + + case *types.Slice: + // []byte or []rune -> string + // TODO(adonovan): fix: type B byte; conv([]B -> string). + switch ut_src.Elt.(*types.Basic).Kind { + case types.Byte: + x := x.([]value) + b := make([]byte, 0, len(x)) + for i := range x { + b = append(b, x[i].(byte)) + } + return string(b) + + case types.Rune: + x := x.([]value) + r := make([]rune, 0, len(x)) + for i := range x { + r = append(r, x[i].(rune)) + } + return string(r) + } + + case *types.Basic: + x = widen(x) + + // bool? + if _, ok := x.(bool); ok { + return x + } + + // integer -> string? + // TODO(adonovan): fix: test integer -> named alias of string. + if ut_src.Info&types.IsInteger != 0 { + if ut_dst, ok := ut_dst.(*types.Basic); ok && ut_dst.Kind == types.String { + return string(asInt(x)) + } + } + + // string -> []rune, []byte or string? + if s, ok := x.(string); ok { + switch ut_dst := ut_dst.(type) { + case *types.Slice: + var res []value + // TODO(adonovan): fix: test named alias of rune, byte. + switch ut_dst.Elt.(*types.Basic).Kind { + case types.Rune: + for _, r := range []rune(s) { + res = append(res, r) + } + return res + case types.Byte: + for _, b := range []byte(s) { + res = append(res, b) + } + return res + } + case *types.Basic: + if ut_dst.Kind == types.String { + return x.(string) + } + } + break // fail: no other conversions for string + } + + // unsafe.Pointer -> *value + if ut_src.Kind == types.UnsafePointer { + // TODO(adonovan): this is wrong and cannot + // really be fixed with the current design. + // + // It creates a new pointer of a different + // type but the underlying interface value + // knows its "true" type and so cannot be + // meaningfully used through the new pointer. + // + // To make this work, the interpreter needs to + // simulate the memory layout of a real + // compiled implementation. + return (*value)(x.(unsafe.Pointer)) + } + + // Conversions between complex numeric types? + if ut_src.Info&types.IsComplex != 0 { + switch ut_dst.(*types.Basic).Kind { + case types.Complex64: + return complex64(x.(complex128)) + case types.Complex128: + return x.(complex128) + } + break // fail: no other conversions for complex + } + + // Conversions between non-complex numeric types? + if ut_src.Info&types.IsNumeric != 0 { + kind := ut_dst.(*types.Basic).Kind + switch x := x.(type) { + case int64: // signed integer -> numeric? + switch kind { + case types.Int: + return int(x) + case types.Int8: + return int8(x) + case types.Int16: + return int16(x) + case types.Int32: + return int32(x) + case types.Int64: + return int64(x) + case types.Uint: + return uint(x) + case types.Uint8: + return uint8(x) + case types.Uint16: + return uint16(x) + case types.Uint32: + return uint32(x) + case types.Uint64: + return uint64(x) + case types.Uintptr: + return uintptr(x) + case types.Float32: + return float32(x) + case types.Float64: + return float64(x) + } + + case uint64: // unsigned integer -> numeric? + switch kind { + case types.Int: + return int(x) + case types.Int8: + return int8(x) + case types.Int16: + return int16(x) + case types.Int32: + return int32(x) + case types.Int64: + return int64(x) + case types.Uint: + return uint(x) + case types.Uint8: + return uint8(x) + case types.Uint16: + return uint16(x) + case types.Uint32: + return uint32(x) + case types.Uint64: + return uint64(x) + case types.Uintptr: + return uintptr(x) + case types.Float32: + return float32(x) + case types.Float64: + return float64(x) + } + + case float64: // floating point -> numeric? + switch kind { + case types.Int: + return int(x) + case types.Int8: + return int8(x) + case types.Int16: + return int16(x) + case types.Int32: + return int32(x) + case types.Int64: + return int64(x) + case types.Uint: + return uint(x) + case types.Uint8: + return uint8(x) + case types.Uint16: + return uint16(x) + case types.Uint32: + return uint32(x) + case types.Uint64: + return uint64(x) + case types.Uintptr: + return uintptr(x) + case types.Float32: + return float32(x) + case types.Float64: + return float64(x) + } + } + } + } + + panic(fmt.Sprintf("unsupported conversion: %s -> %s, dynamic type %T", t_src, t_dst, x)) +} + +// checkInterface checks that the method set of x implements the +// interface itype. +// On success it returns "", on failure, an error message. +// +func checkInterface(i *interpreter, itype types.Type, x iface) string { + mset := findMethodSet(i, x.t) + for _, m := range underlyingType(itype).(*types.Interface).Methods { + id := ssa.IdFromQualifiedName(m.QualifiedName) + if mset[id] == nil { + return fmt.Sprintf("interface conversion: %v is not %v: missing method %v", x.t, itype, id) + } + } + return "" // ok +} + +// underlyingType returns the underlying type of typ. +// Copied from go/types.underlying. +// +func underlyingType(typ types.Type) types.Type { + if typ, ok := typ.(*types.NamedType); ok { + return typ.Underlying + } + return typ +} + +// indirectType(typ) assumes that typ is a pointer type, +// or named alias thereof, and returns its base type. +// Panic ensues if it is not a pointer. +// Copied from exp/ssa.indirectType. +// +func indirectType(ptr types.Type) types.Type { + return underlyingType(ptr).(*types.Pointer).Base +} diff --git a/ssa/interp/reflect.go b/ssa/interp/reflect.go new file mode 100644 index 0000000000..fd25c39321 --- /dev/null +++ b/ssa/interp/reflect.go @@ -0,0 +1,441 @@ +package interp + +// Emulated "reflect" package. +// +// We completely replace the built-in "reflect" package. +// The only thing clients can depend upon are that reflect.Type is an +// interface and reflect.Value is an (opaque) struct. + +import ( + "fmt" + "reflect" + "unsafe" + + "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/ssa" +) + +// A bogus "reflect" type-checker package. Shared across interpreters. +var reflectTypesPackage = &types.Package{ + Name: "reflect", + Path: "reflect", + Complete: true, +} + +// rtype is the concrete type the interpreter uses to implement the +// reflect.Type interface. Since its type is opaque to the target +// language, we use a types.Basic. +// +// type rtype +var rtypeType = makeNamedType("rtype", &types.Basic{Name: "rtype"}) + +// error is an (interpreted) named type whose underlying type is string. +// The interpreter uses it for all implementations of the built-in error +// interface that it creates. +// We put it in the "reflect" package for expedience. +// +// type error string +var errorType = makeNamedType("error", &types.Basic{Name: "error"}) + +func makeNamedType(name string, underlying types.Type) *types.NamedType { + nt := &types.NamedType{Underlying: underlying} + nt.Obj = &types.TypeName{ + Name: name, + Type: nt, + Pkg: reflectTypesPackage, + } + return nt +} + +func makeReflectValue(t types.Type, v value) value { + return structure{rtype{t}, v} +} + +// Given a reflect.Value, returns its rtype. +func rV2T(v value) rtype { + return v.(structure)[0].(rtype) +} + +// Given a reflect.Value, returns the underlying interpreter value. +func rV2V(v value) value { + return v.(structure)[1] +} + +// makeReflectType boxes up an rtype in a reflect.Type interface. +func makeReflectType(rt rtype) value { + return iface{rtypeType, rt} +} + +func ext۰reflect۰Init(fn *ssa.Function, args []value) value { + // Signature: func() + return nil +} + +func ext۰reflect۰rtype۰Bits(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) int + rt := args[0].(rtype).t + basic, ok := underlyingType(rt).(*types.Basic) + if !ok { + panic(fmt.Sprintf("reflect.Type.Bits(%T): non-basic type", rt)) + } + switch basic.Kind { + case types.Int8, types.Uint8: + return 8 + case types.Int16, types.Uint16: + return 16 + case types.Int, types.UntypedInt: + // Assume sizeof(int) is same on host and target; ditto uint. + return reflect.TypeOf(int(0)).Bits() + case types.Uintptr: + // Assume sizeof(uintptr) is same on host and target. + return reflect.TypeOf(uintptr(0)).Bits() + case types.Int32, types.Uint32: + return 32 + case types.Int64, types.Uint64: + return 64 + case types.Float32: + return 32 + case types.Float64, types.UntypedFloat: + return 64 + case types.Complex64: + return 64 + case types.Complex128, types.UntypedComplex: + return 128 + default: + panic(fmt.Sprintf("reflect.Type.Bits(%s)", basic)) + } + return nil +} + +func ext۰reflect۰rtype۰Elem(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) reflect.Type + var elem types.Type + switch rt := underlyingType(args[0].(rtype).t).(type) { + case *types.Array: + elem = rt.Elt + case *types.Chan: + elem = rt.Elt + case *types.Map: + elem = rt.Elt + case *types.Pointer: + elem = rt.Base + case *types.Slice: + elem = rt.Elt + default: + panic(fmt.Sprintf("reflect.Type.Elem(%T)", rt)) + } + return makeReflectType(rtype{elem}) +} + +func ext۰reflect۰rtype۰Kind(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) uint + return uint(reflectKind(args[0].(rtype).t)) +} + +func ext۰reflect۰rtype۰NumOut(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) int + return len(args[0].(rtype).t.(*types.Signature).Results) +} + +func ext۰reflect۰rtype۰Out(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype, i int) int + i := args[1].(int) + return makeReflectType(rtype{args[0].(rtype).t.(*types.Signature).Results[i].Type}) +} + +func ext۰reflect۰rtype۰String(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) string + return args[0].(rtype).t.String() +} + +func ext۰reflect۰TypeOf(fn *ssa.Function, args []value) value { + // Signature: func (t reflect.rtype) string + return makeReflectType(rtype{args[0].(iface).t}) +} + +func ext۰reflect۰ValueOf(fn *ssa.Function, args []value) value { + // Signature: func (interface{}) reflect.Value + itf := args[0].(iface) + return makeReflectValue(itf.t, itf.v) +} + +func reflectKind(t types.Type) reflect.Kind { + switch t := t.(type) { + case *types.NamedType: + return reflectKind(t.Underlying) + case *types.Basic: + switch t.Kind { + case types.Bool: + return reflect.Bool + case types.Int: + return reflect.Int + case types.Int8: + return reflect.Int8 + case types.Int16: + return reflect.Int16 + case types.Int32: + return reflect.Int32 + case types.Int64: + return reflect.Int64 + case types.Uint: + return reflect.Uint + case types.Uint8: + return reflect.Uint8 + case types.Uint16: + return reflect.Uint16 + case types.Uint32: + return reflect.Uint32 + case types.Uint64: + return reflect.Uint64 + case types.Uintptr: + return reflect.Uintptr + case types.Float32: + return reflect.Float32 + case types.Float64: + return reflect.Float64 + case types.Complex64: + return reflect.Complex64 + case types.Complex128: + return reflect.Complex128 + case types.String: + return reflect.String + case types.UnsafePointer: + return reflect.UnsafePointer + } + case *types.Array: + return reflect.Array + case *types.Chan: + return reflect.Chan + case *types.Signature: + return reflect.Func + case *types.Interface: + return reflect.Interface + case *types.Map: + return reflect.Map + case *types.Pointer: + return reflect.Ptr + case *types.Slice: + return reflect.Slice + case *types.Struct: + return reflect.Struct + } + panic(fmt.Sprint("unexpected type: ", t)) +} + +func ext۰reflect۰Value۰Kind(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) uint + return uint(reflectKind(rV2T(args[0]).t)) +} + +func ext۰reflect۰Value۰String(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) string + return toString(rV2V(args[0])) +} + +func ext۰reflect۰Value۰Type(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) reflect.Type + return makeReflectType(rV2T(args[0])) +} + +func ext۰reflect۰Value۰Len(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) int + switch v := rV2V(args[0]).(type) { + case string: + return len(v) + case array: + return len(v) + case chan value: + return cap(v) + case []value: + return len(v) + case *hashmap: + return v.len() + case map[value]value: + return len(v) + default: + panic(fmt.Sprintf("reflect.(Value).Len(%v)", v)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰NumField(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) int + return len(rV2V(args[0]).(structure)) +} + +func ext۰reflect۰Value۰Pointer(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value) uintptr + switch v := rV2V(args[0]).(type) { + case *value: + return uintptr(unsafe.Pointer(v)) + case chan value: + return reflect.ValueOf(v).Pointer() + case []value: + return reflect.ValueOf(v).Pointer() + case *hashmap: + return reflect.ValueOf(v.table).Pointer() + case map[value]value: + return reflect.ValueOf(v).Pointer() + case *ssa.Function: + return uintptr(unsafe.Pointer(v)) + default: + panic(fmt.Sprintf("reflect.(Value).Pointer(%T)", v)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰Index(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value, i int) Value + i := args[1].(int) + t := underlyingType(rV2T(args[0]).t) + switch v := rV2V(args[0]).(type) { + case array: + return makeReflectValue(t.(*types.Array).Elt, v[i]) + case []value: + return makeReflectValue(t.(*types.Slice).Elt, v[i]) + default: + panic(fmt.Sprintf("reflect.(Value).Index(%T)", v)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰Bool(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) bool + return rV2V(args[0]).(bool) +} + +func ext۰reflect۰Value۰CanAddr(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value) bool + // Always false for our representation. + return false +} + +func ext۰reflect۰Value۰CanInterface(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value) bool + // Always true for our representation. + return true +} + +func ext۰reflect۰Value۰Elem(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value) reflect.Value + switch x := rV2V(args[0]).(type) { + case iface: + return makeReflectValue(x.t, x.v) + case *value: + return makeReflectValue(underlyingType(rV2T(args[0]).t).(*types.Pointer).Base, *x) + default: + panic(fmt.Sprintf("reflect.(Value).Elem(%T)", x)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰Field(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value, i int) reflect.Value + v := args[0] + i := args[1].(int) + return makeReflectValue(underlyingType(rV2T(v).t).(*types.Struct).Fields[i].Type, rV2V(v).(structure)[i]) +} + +func ext۰reflect۰Value۰Interface(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value) interface{} + return ext۰reflect۰valueInterface(fn, args) +} + +func ext۰reflect۰Value۰Int(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) int64 + switch x := rV2V(args[0]).(type) { + case int: + return int64(x) + case int8: + return int64(x) + case int16: + return int64(x) + case int32: + return int64(x) + case int64: + return x + default: + panic(fmt.Sprintf("reflect.(Value).Int(%T)", x)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰IsNil(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) bool + switch x := rV2V(args[0]).(type) { + case *value: + return x == nil + case chan value: + return x == nil + case map[value]value: + return x == nil + case *hashmap: + return x == nil + case iface: + return x.t == nil + case []value: + return x == nil + case *ssa.Function: + return x == nil + case *ssa.Builtin: + return x == nil + case *closure: + return x == nil + default: + panic(fmt.Sprintf("reflect.(Value).IsNil(%T)", x)) + } + return nil // unreachable +} + +func ext۰reflect۰Value۰IsValid(fn *ssa.Function, args []value) value { + // Signature: func (reflect.Value) bool + return rV2V(args[0]) != nil +} + +func ext۰reflect۰valueInterface(fn *ssa.Function, args []value) value { + // Signature: func (v reflect.Value, safe bool) interface{} + v := args[0].(structure) + return iface{rV2T(v).t, rV2V(v)} +} + +func ext۰reflect۰error۰Error(fn *ssa.Function, args []value) value { + return args[0] +} + +// newMethod creates a new method of the specified name, package and receiver type. +func newMethod(pkg *ssa.Package, recvType types.Type, name string) *ssa.Function { + fn := &ssa.Function{ + Name_: name, + Pkg: pkg, + Prog: pkg.Prog, + } + // TODO(adonovan): fix: hack: currently the only part of Signature + // that is needed is the "pointerness" of Recv.Type, and for + // now, we'll set it to always be false since we're only + // concerned with rtype. Encapsulate this better. + fn.Signature = &types.Signature{Recv: &types.Var{ + Name: "recv", + Type: recvType, + }} + return fn +} + +func initReflect(i *interpreter) { + i.reflectPackage = &ssa.Package{ + Prog: i.prog, + Types: reflectTypesPackage, + Members: make(map[string]ssa.Member), + } + + i.rtypeMethods = ssa.MethodSet{ + ssa.Id{nil, "Bits"}: newMethod(i.reflectPackage, rtypeType, "Bits"), + ssa.Id{nil, "Elem"}: newMethod(i.reflectPackage, rtypeType, "Elem"), + ssa.Id{nil, "Kind"}: newMethod(i.reflectPackage, rtypeType, "Kind"), + ssa.Id{nil, "NumOut"}: newMethod(i.reflectPackage, rtypeType, "NumOut"), + ssa.Id{nil, "Out"}: newMethod(i.reflectPackage, rtypeType, "Out"), + ssa.Id{nil, "String"}: newMethod(i.reflectPackage, rtypeType, "String"), + } + i.errorMethods = ssa.MethodSet{ + ssa.Id{nil, "Error"}: newMethod(i.reflectPackage, errorType, "Error"), + } +} diff --git a/ssa/interp/testdata/coverage.go b/ssa/interp/testdata/coverage.go new file mode 100644 index 0000000000..03e14275aa --- /dev/null +++ b/ssa/interp/testdata/coverage.go @@ -0,0 +1,447 @@ +// This interpreter test is designed to run very quickly yet provide +// some coverage of a broad selection of constructs. +// TODO(adonovan): more. +// +// Validate this file with 'go run' after editing. +// TODO(adonovan): break this into small files organized by theme. + +package main + +import ( + "fmt" + "reflect" +) + +const zero int = 1 + +var v = []int{1 + zero: 42} + +// Nonliteral keys in composite literal. +func init() { + if x := fmt.Sprint(v); x != "[0 0 42]" { + panic(x) + } +} + +func init() { + // Call of variadic function with (implicit) empty slice. + if x := fmt.Sprint(); x != "" { + panic(x) + } +} + +type empty interface{} + +type I interface { + f() int +} + +type T struct{ z int } + +func (t T) f() int { return t.z } + +func use(interface{}) {} + +var counter = 2 + +// Test initialization, including init blocks containing 'return'. +// Assertion is in main. +func init() { + counter *= 3 + return + counter *= 3 +} + +func init() { + counter *= 5 + return + counter *= 5 +} + +// Recursion. +func fib(x int) int { + if x < 2 { + return x + } + return fib(x-1) + fib(x-2) +} + +func fibgen(ch chan int) { + for x := 0; x < 10; x++ { + ch <- fib(x) + } + close(ch) +} + +// Goroutines and channels. +func init() { + ch := make(chan int) + go fibgen(ch) + var fibs []int + for v := range ch { + fibs = append(fibs, v) + if len(fibs) == 10 { + break + } + } + if x := fmt.Sprint(fibs); x != "[0 1 1 2 3 5 8 13 21 34]" { + panic(x) + } +} + +// Test of aliasing. +func init() { + type S struct { + a, b string + } + + s1 := []string{"foo", "bar"} + s2 := s1 // creates an alias + s2[0] = "wiz" + if x := fmt.Sprint(s1, s2); x != "[wiz bar] [wiz bar]" { + panic(x) + } + + pa1 := &[2]string{"foo", "bar"} + pa2 := pa1 // creates an alias + (*pa2)[0] = "wiz" // * required to workaround typechecker bug + if x := fmt.Sprint(*pa1, *pa2); x != "[wiz bar] [wiz bar]" { + panic(x) + } + + a1 := [2]string{"foo", "bar"} + a2 := a1 // creates a copy + a2[0] = "wiz" + if x := fmt.Sprint(a1, a2); x != "[foo bar] [wiz bar]" { + panic(x) + } + + t1 := S{"foo", "bar"} + t2 := t1 // copy + t2.a = "wiz" + if x := fmt.Sprint(t1, t2); x != "{foo bar} {wiz bar}" { + panic(x) + } +} + +// Range over string. +func init() { + if x := len("Hello, 世界"); x != 13 { // bytes + panic(x) + } + var indices []int + var runes []rune + for i, r := range "Hello, 世界" { + runes = append(runes, r) + indices = append(indices, i) + } + if x := fmt.Sprint(runes); x != "[72 101 108 108 111 44 32 19990 30028]" { + panic(x) + } + if x := fmt.Sprint(indices); x != "[0 1 2 3 4 5 6 7 10]" { + panic(x) + } + s := "" + for _, r := range runes { + s = fmt.Sprintf("%s%c", s, r) + } + if s != "Hello, 世界" { + panic(s) + } +} + +func main() { + if counter != 2*3*5 { + panic(counter) + } + + // Test builtins (e.g. complex) preserve named argument types. + type N complex128 + var n N + n = complex(1.0, 2.0) + if n != complex(1.0, 2.0) { + panic(n) + } + if x := reflect.TypeOf(n).String(); x != "main.N" { + panic(x) + } + if real(n) != 1.0 || imag(n) != 2.0 { + panic(n) + } + + // Channel + select. + ch := make(chan int, 1) + select { + case ch <- 1: + // ok + default: + panic("couldn't send") + } + if <-ch != 1 { + panic("couldn't receive") + } + + // Anon structs with methods. + anon := struct{ T }{T: T{z: 1}} + if x := anon.f(); x != 1 { + panic(x) + } + var i I = anon + if x := i.f(); x != 1 { + panic(x) + } + // NB. precise output of reflect.Type.String is undefined. + if x := reflect.TypeOf(i).String(); x != "struct { main.T }" && x != "struct{main.T}" { + panic(x) + } + + // fmt. + const message = "Hello, World!" + if fmt.Sprintf("%s, %s!", "Hello", "World") != message { + panic("oops") + } + + // Type assertion. + type S struct { + f int + } + var e empty = S{f: 42} + switch v := e.(type) { + case S: + if v.f != 42 { + panic(v.f) + } + default: + panic(reflect.TypeOf(v)) + } + if i, ok := e.(I); ok { + panic(i) + } + + // Switch. + var x int + switch x { + case 1: + panic(x) + fallthrough + case 2, 3: + panic(x) + default: + // ok + } + // empty switch + switch { + } + // empty switch + switch { + default: + } + // empty switch + switch { + default: + fallthrough + } + + // string -> []rune conversion. + use([]rune("foo")) + + // Calls of form x.f(). + type S2 struct { + f func() int + } + S2{f: func() int { return 1 }}.f() // field is a func value + T{}.f() // method call + i.f() // interface method invocation + (interface { + f() int + }(T{})).f() // anon interface method invocation + + // Map lookup. + if v, ok := map[string]string{}["foo5"]; v != "" || ok { + panic("oops") + } +} + +// Simple closures. +func init() { + b := 3 + f := func(a int) int { + return a + b + } + b++ + if x := f(1); x != 5 { // 1+4 == 5 + panic(x) + } + b++ + if x := f(2); x != 7 { // 2+5 == 7 + panic(x) + } + if b := f(1) < 16 || f(2) < 17; !b { + panic("oops") + } +} + +var order []int + +func create(x int) int { + order = append(order, x) + return x +} + +var c = create(b + 1) +var a, b = create(1), create(2) + +// Initialization order of package-level value specs. +func init() { + if x := fmt.Sprint(order); x != "[2 3 1]" { + panic(x) + } + if c != 3 { + panic(c) + } +} + +// Shifts. +func init() { + var i int64 = 1 + var u uint64 = 1 << 32 + if x := i << uint32(u); x != 1 { + panic(x) + } + if x := i << uint64(u); x != 0 { + panic(x) + } +} + +// Implicit conversion of delete() key operand. +func init() { + type I interface{} + m := make(map[I]bool) + m[1] = true + m[I(2)] = true + if len(m) != 2 { + panic(m) + } + delete(m, I(1)) + delete(m, 2) + if len(m) != 0 { + panic(m) + } +} + +////////////////////////////////////////////////////////////////////// +// Variadic bridge methods and interface thunks. + +type VT int + +var vcount = 0 + +func (VT) f(x int, y ...string) { + vcount++ + if x != 1 { + panic(x) + } + if len(y) != 2 || y[0] != "foo" || y[1] != "bar" { + panic(y) + } +} + +type VS struct { + VT +} + +type VI interface { + f(x int, y ...string) +} + +func init() { + foobar := []string{"foo", "bar"} + var s VS + s.f(1, "foo", "bar") + s.f(1, foobar...) + if vcount != 2 { + panic("s.f not called twice") + } + + fn := VI.f + fn(s, 1, "foo", "bar") + fn(s, 1, foobar...) + if vcount != 4 { + panic("I.f not called twice") + } +} + +// Multiple labels on same statement. +func multipleLabels() { + var trace []int + i := 0 +one: +two: + for ; i < 3; i++ { + trace = append(trace, i) + switch i { + case 0: + continue two + case 1: + i++ + goto one + case 2: + break two + } + } + if x := fmt.Sprint(trace); x != "[0 1 2]" { + panic(x) + } +} + +func init() { + multipleLabels() +} + +//////////////////////////////////////////////////////////////////////// +// Defer + +func deferMutatesResults(noArgReturn bool) (a, b int) { + defer func() { + if a != 1 || b != 2 { + panic(fmt.Sprint(a, b)) + } + a, b = 3, 4 + }() + if noArgReturn { + a, b = 1, 2 + return + } + return 1, 2 +} + +func init() { + a, b := deferMutatesResults(true) + if a != 3 || b != 4 { + panic(fmt.Sprint(a, b)) + } + a, b = deferMutatesResults(false) + if a != 3 || b != 4 { + panic(fmt.Sprint(a, b)) + } +} + +// We concatenate init blocks to make a single function, but we must +// run defers at the end of each block, not the combined function. +var deferCount = 0 + +func init() { + deferCount = 1 + defer func() { + deferCount++ + }() + // defer runs HERE +} + +func init() { + // Strictly speaking the spec says deferCount may be 0 or 2 + // since the relative order of init blocks is unspecified. + if deferCount != 2 { + panic(deferCount) // defer call has not run! + } +} diff --git a/ssa/interp/testdata/mrvchain.go b/ssa/interp/testdata/mrvchain.go new file mode 100644 index 0000000000..1c588bcf3b --- /dev/null +++ b/ssa/interp/testdata/mrvchain.go @@ -0,0 +1,65 @@ +// Tests of call chaining f(g()) when g has multiple return values (MRVs). +// See https://code.google.com/p/go/issues/detail?id=4573. + +package main + +func assert(actual, expected int) { + if actual != expected { + panic(actual) + } +} + +func g() (int, int) { + return 5, 7 +} + +func g2() (float64, float64) { + return 5, 7 +} + +func f1v(x int, v ...int) { + assert(x, 5) + assert(v[0], 7) +} + +func f2(x, y int) { + assert(x, 5) + assert(y, 7) +} + +func f2v(x, y int, v ...int) { + assert(x, 5) + assert(y, 7) + assert(len(v), 0) +} + +func complexArgs() (float64, float64) { + return 5, 7 +} + +func appendArgs() ([]string, string) { + return []string{"foo"}, "bar" +} + +func h() (interface{}, bool) { + m := map[int]string{1: "hi"} + return m[1] // string->interface{} conversion within multi-valued expression +} + +func main() { + f1v(g()) + f2(g()) + f2v(g()) + // TODO(gri): the typechecker still doesn't support these cases correctly. + // if c := complex(complexArgs()); c != 5+7i { + // panic(c) + // } + // if s := append(appendArgs()); len(s) != 2 || s[0] != "foo" || s[1] != "bar" { + // panic(s) + // } + + i, ok := h() + if !ok || i.(string) != "hi" { + panic(i) + } +} diff --git a/ssa/interp/value.go b/ssa/interp/value.go new file mode 100644 index 0000000000..f66392c6d5 --- /dev/null +++ b/ssa/interp/value.go @@ -0,0 +1,466 @@ +package interp + +// Values +// +// All interpreter values are "boxed" in the empty interface, value. +// The range of possible dynamic types within value are: +// +// - bool +// - numbers (all built-in int/float/complex types are distinguished) +// - string +// - map[value]value --- maps for which usesBuiltinMap(keyType) +// *hashmap --- maps for which !usesBuiltinMap(keyType) +// - chan value +// - []value --- slices +// - iface --- interfaces. +// - structure --- structs. Fields are ordered and accessed by numeric indices. +// - array --- arrays. +// - *value --- pointers. Careful: *value is a distinct type from *array etc. +// - *ssa.Function \ +// *ssa.Builtin } --- functions. +// *closure / +// - tuple --- as returned by Ret, Next, "value,ok" modes, etc. +// - iter --- iterators from 'range' over map or string. +// - bad --- a poison pill for locals that have gone out of scope. +// - rtype -- the interpreter's concrete implementation of reflect.Type +// +// Note that nil is not on this list. +// +// Pay close attention to whether or not the dynamic type is a pointer. +// The compiler cannot help you since value is an empty interface. + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strings" + "unsafe" + + "code.google.com/p/go.tools/go/types" + "code.google.com/p/go.tools/ssa" +) + +type value interface{} + +type tuple []value + +type array []value + +type iface struct { + t types.Type // never an "untyped" type + v value +} + +type structure []value + +// For map, array, *array, slice, string or channel. +type iter interface { + // next returns a Tuple (key, value, ok). + // key and value are unaliased, e.g. copies of the sequence element. + next() tuple +} + +type closure struct { + Fn *ssa.Function + Env []value +} + +type bad struct{} + +type rtype struct { + t types.Type +} + +// Hash functions and equivalence relation: + +// hashString computes the FNV hash of s. +func hashString(s string) int { + var h uint32 + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return int(h) +} + +// hashType returns a hash for t such that +// types.IsIdentical(x, y) => hashType(x) == hashType(y). +func hashType(t types.Type) int { + return hashString(t.String()) // TODO(gri): provide a better hash +} + +// usesBuiltinMap returns true if the built-in hash function and +// equivalence relation for type t are consistent with those of the +// interpreter's representation of type t. Such types are: all basic +// types (bool, numbers, string), pointers and channels. +// +// usesBuiltinMap returns false for types that require a custom map +// implementation: interfaces, arrays and structs. +// +// Panic ensues if t is an invalid map key type: function, map or slice. +func usesBuiltinMap(t types.Type) bool { + switch t := t.(type) { + case *types.Basic, *types.Chan, *types.Pointer: + return true + case *types.NamedType: + return usesBuiltinMap(t.Underlying) + case *types.Interface, *types.Array, *types.Struct: + return false + } + panic(fmt.Sprintf("invalid map key type: %T", t)) +} + +func (x array) eq(_y interface{}) bool { + y := _y.(array) + for i, xi := range x { + if !equals(xi, y[i]) { + return false + } + } + return true +} + +func (x array) hash() int { + h := 0 + for _, xi := range x { + h += hash(xi) + } + return h +} + +func (x structure) eq(_y interface{}) bool { + y := _y.(structure) + // TODO(adonovan): fix: only non-blank fields should be + // compared. This requires that we have type information + // available from the enclosing == operation or map access; + // the value is not sufficient. + for i, xi := range x { + if !equals(xi, y[i]) { + return false + } + } + return true +} + +func (x structure) hash() int { + h := 0 + for _, xi := range x { + h += hash(xi) + } + return h +} + +func (x iface) eq(_y interface{}) bool { + y := _y.(iface) + return types.IsIdentical(x.t, y.t) && (x.t == nil || equals(x.v, y.v)) +} + +func (x iface) hash() int { + return hashType(x.t)*8581 + hash(x.v) +} + +func (x rtype) hash() int { + return hashType(x.t) +} + +func (x rtype) eq(y interface{}) bool { + return types.IsIdentical(x.t, y.(rtype).t) +} + +// equals returns true iff x and y are equal according to Go's +// linguistic equivalence relation. In a well-typed program, the +// types of x and y are guaranteed equal. +func equals(x, y value) bool { + switch x := x.(type) { + case bool: + return x == y.(bool) + case int: + return x == y.(int) + case int8: + return x == y.(int8) + case int16: + return x == y.(int16) + case int32: + return x == y.(int32) + case int64: + return x == y.(int64) + case uint: + return x == y.(uint) + case uint8: + return x == y.(uint8) + case uint16: + return x == y.(uint16) + case uint32: + return x == y.(uint32) + case uint64: + return x == y.(uint64) + case uintptr: + return x == y.(uintptr) + case float32: + return x == y.(float32) + case float64: + return x == y.(float64) + case complex64: + return x == y.(complex64) + case complex128: + return x == y.(complex128) + case string: + return x == y.(string) + case *value: + return x == y.(*value) + case chan value: + return x == y.(chan value) + case structure: + return x.eq(y) + case array: + return x.eq(y) + case iface: + return x.eq(y) + case rtype: + return x.eq(y) + + // Since the following types don't support comparison, + // these cases are only reachable if one of x or y is + // (literally) nil. + case *hashmap: + return x == y.(*hashmap) + case map[value]value: + return (x != nil) == (y.(map[value]value) != nil) + case *ssa.Function, *closure: + return x == y + case []value: + return (x != nil) == (y.([]value) != nil) + } + panic(fmt.Sprintf("comparing incomparable type %T", x)) +} + +// Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y). +func hash(x value) int { + switch x := x.(type) { + case bool: + if x { + return 1 + } + return 0 + case int: + return x + case int8: + return int(x) + case int16: + return int(x) + case int32: + return int(x) + case int64: + return int(x) + case uint: + return int(x) + case uint8: + return int(x) + case uint16: + return int(x) + case uint32: + return int(x) + case uint64: + return int(x) + case uintptr: + return int(x) + case float32: + return int(x) + case float64: + return int(x) + case complex64: + return int(real(x)) + case complex128: + return int(real(x)) + case string: + return hashString(x) + case *value: + return int(uintptr(unsafe.Pointer(x))) + case chan value: + return int(uintptr(reflect.ValueOf(x).Pointer())) + case structure: + return x.hash() + case array: + return x.hash() + case iface: + return x.hash() + case rtype: + return x.hash() + } + panic(fmt.Sprintf("%T is unhashable", x)) +} + +// copyVal returns a copy of value v. +// TODO(adonovan): add tests of aliasing and mutation. +func copyVal(v value) value { + if v == nil { + panic("copyVal(nil)") + } + switch v := v.(type) { + case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string, unsafe.Pointer: + return v + case map[value]value: + return v + case *hashmap: + return v + case chan value: + return v + case *value: + return v + case *ssa.Function, *ssa.Builtin, *closure: + return v + case iface: + return v + case []value: + return v + case structure: + a := make(structure, len(v)) + copy(a, v) + return a + case array: + a := make(array, len(v)) + copy(a, v) + return a + case tuple: + break + case rtype: + return v + } + panic(fmt.Sprintf("cannot copy %T", v)) +} + +// Prints in the style of built-in println. +// (More or less; in gc println is actually a compiler intrinsic and +// can distinguish println(1) from println(interface{}(1)).) +func toWriter(w io.Writer, v value) { + switch v := v.(type) { + case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, float32, float64, complex64, complex128, string: + fmt.Fprintf(w, "%v", v) + + case map[value]value: + io.WriteString(w, "map[") + sep := " " + for k, e := range v { + io.WriteString(w, sep) + sep = " " + toWriter(w, k) + io.WriteString(w, ":") + toWriter(w, e) + } + io.WriteString(w, "]") + + case *hashmap: + io.WriteString(w, "map[") + sep := " " + for _, e := range v.table { + for e != nil { + io.WriteString(w, sep) + sep = " " + toWriter(w, e.key) + io.WriteString(w, ":") + toWriter(w, e.value) + e = e.next + } + } + io.WriteString(w, "]") + + case chan value: + fmt.Fprintf(w, "%v", v) // (an address) + + case *value: + if v == nil { + io.WriteString(w, "") + } else { + fmt.Fprintf(w, "%p", v) + } + + case iface: + toWriter(w, v.v) + + case structure: + io.WriteString(w, "{") + for i, e := range v { + if i > 0 { + io.WriteString(w, " ") + } + toWriter(w, e) + } + io.WriteString(w, "}") + + case array: + io.WriteString(w, "[") + for i, e := range v { + if i > 0 { + io.WriteString(w, " ") + } + toWriter(w, e) + } + io.WriteString(w, "]") + + case []value: + io.WriteString(w, "[") + for i, e := range v { + if i > 0 { + io.WriteString(w, " ") + } + toWriter(w, e) + } + io.WriteString(w, "]") + + case *ssa.Function, *ssa.Builtin, *closure: + fmt.Fprintf(w, "%p", v) // (an address) + + case rtype: + io.WriteString(w, v.t.String()) + + case tuple: + // Unreachable in well-formed Go programs + io.WriteString(w, "(") + for i, e := range v { + if i > 0 { + io.WriteString(w, ", ") + } + toWriter(w, e) + } + io.WriteString(w, ")") + + default: + fmt.Fprintf(w, "<%T>", v) + } +} + +// Implements printing of Go values in the style of built-in println. +func toString(v value) string { + var b bytes.Buffer + toWriter(&b, v) + return b.String() +} + +// ------------------------------------------------------------------------ +// Iterators + +type stringIter struct { + *strings.Reader + i int +} + +func (it *stringIter) next() tuple { + okv := make(tuple, 3) + ch, n, err := it.ReadRune() + ok := err != io.EOF + okv[0] = ok + if ok { + okv[1] = it.i + okv[2] = ch + } + it.i += n + return okv +} + +type mapIter chan [2]value + +func (it mapIter) next() tuple { + kv, ok := <-it + return tuple{ok, kv[0], kv[1]} +}