From 01f8cd246d3de57dc580b56c610c5e73c591c59c Mon Sep 17 00:00:00 2001 From: Rob Pike Date: Fri, 17 May 2013 13:20:39 -0700 Subject: [PATCH] go.tools: add go/types, ssa, and cmd/vet They will be deleted from their current homes once this has landed. Changes made to import paths to make the code compile, and to find errchk in the right place in cmd/vet's Makefile. TODO in a later CL: tidy up vet. R=golang-dev, gri CC=golang-dev https://golang.org/cl/9495043 --- cmd/vet/Makefile | 14 + cmd/vet/asmdecl.go | 533 ++++++++ cmd/vet/assign.go | 44 + cmd/vet/atomic.go | 59 + cmd/vet/buildtag.go | 91 ++ cmd/vet/deadcode.go | 280 ++++ cmd/vet/doc.go | 76 ++ cmd/vet/main.go | 422 ++++++ cmd/vet/method.go | 162 +++ cmd/vet/print.go | 351 +++++ cmd/vet/rangeloop.go | 65 + cmd/vet/structtag.go | 37 + cmd/vet/taglit.go | 164 +++ cmd/vet/test_asm.go | 24 + cmd/vet/test_asm1.s | 247 ++++ cmd/vet/test_asm2.s | 251 ++++ cmd/vet/test_asm3.s | 166 +++ cmd/vet/test_assign.go | 20 + cmd/vet/test_atomic.go | 43 + cmd/vet/test_buildtag.go | 15 + cmd/vet/test_buildtag_bad.go | 15 + cmd/vet/test_deadcode.go | 2121 +++++++++++++++++++++++++++++ cmd/vet/test_method.go | 24 + cmd/vet/test_print.go | 153 +++ cmd/vet/test_rangeloop.go | 61 + cmd/vet/test_structtag.go | 15 + cmd/vet/test_taglit.go | 65 + cmd/vet/types.go | 185 +++ cmd/vet/typestub.go | 50 + go/exact/exact.go | 748 ++++++++++ go/exact/exact_test.go | 186 +++ go/types/api.go | 122 ++ go/types/builtins.go | 463 +++++++ go/types/check.go | 511 +++++++ go/types/check_test.go | 244 ++++ go/types/conversions.go | 158 +++ go/types/errors.go | 335 +++++ go/types/exportdata.go | 111 ++ go/types/expr.go | 1758 ++++++++++++++++++++++++ go/types/gcimporter.go | 945 +++++++++++++ go/types/gcimporter_test.go | 180 +++ go/types/objects.go | 193 +++ go/types/operand.go | 406 ++++++ go/types/predicates.go | 303 +++++ go/types/resolve.go | 197 +++ go/types/resolver_test.go | 167 +++ go/types/return.go | 189 +++ go/types/scope.go | 78 ++ go/types/sizes.go | 162 +++ go/types/stdlib_test.go | 133 ++ go/types/stmt.go | 724 ++++++++++ go/types/testdata/builtins.src | 432 ++++++ go/types/testdata/const0.src | 215 +++ go/types/testdata/conversions.src | 36 + go/types/testdata/decls0.src | 187 +++ go/types/testdata/decls1.src | 132 ++ go/types/testdata/decls2a.src | 66 + go/types/testdata/decls2b.src | 28 + go/types/testdata/decls3.src | 253 ++++ go/types/testdata/exports.go | 89 ++ go/types/testdata/expr0.src | 161 +++ go/types/testdata/expr1.src | 7 + go/types/testdata/expr2.src | 23 + go/types/testdata/expr3.src | 338 +++++ go/types/testdata/shifts.src | 293 ++++ go/types/testdata/stmt0.src | 288 ++++ go/types/testdata/stmt1.src | 164 +++ go/types/types.go | 236 ++++ go/types/types_test.go | 171 +++ go/types/universe.go | 148 ++ gotype/doc.go | 63 + gotype/gotype.go | 206 +++ ssa/interp/external.go | 344 +++++ ssa/interp/external_plan9.go | 49 + ssa/interp/external_unix.go | 137 ++ ssa/interp/external_windows.go | 42 + ssa/interp/interp.go | 622 +++++++++ ssa/interp/interp_test.go | 208 +++ ssa/interp/map.go | 101 ++ ssa/interp/ops.go | 1375 +++++++++++++++++++ ssa/interp/reflect.go | 441 ++++++ ssa/interp/testdata/coverage.go | 447 ++++++ ssa/interp/testdata/mrvchain.go | 65 + ssa/interp/value.go | 466 +++++++ 84 files changed, 21899 insertions(+) create mode 100644 cmd/vet/Makefile create mode 100644 cmd/vet/asmdecl.go create mode 100644 cmd/vet/assign.go create mode 100644 cmd/vet/atomic.go create mode 100644 cmd/vet/buildtag.go create mode 100644 cmd/vet/deadcode.go create mode 100644 cmd/vet/doc.go create mode 100644 cmd/vet/main.go create mode 100644 cmd/vet/method.go create mode 100644 cmd/vet/print.go create mode 100644 cmd/vet/rangeloop.go create mode 100644 cmd/vet/structtag.go create mode 100644 cmd/vet/taglit.go create mode 100644 cmd/vet/test_asm.go create mode 100644 cmd/vet/test_asm1.s create mode 100644 cmd/vet/test_asm2.s create mode 100644 cmd/vet/test_asm3.s create mode 100644 cmd/vet/test_assign.go create mode 100644 cmd/vet/test_atomic.go create mode 100644 cmd/vet/test_buildtag.go create mode 100644 cmd/vet/test_buildtag_bad.go create mode 100644 cmd/vet/test_deadcode.go create mode 100644 cmd/vet/test_method.go create mode 100644 cmd/vet/test_print.go create mode 100644 cmd/vet/test_rangeloop.go create mode 100644 cmd/vet/test_structtag.go create mode 100644 cmd/vet/test_taglit.go create mode 100644 cmd/vet/types.go create mode 100644 cmd/vet/typestub.go create mode 100644 go/exact/exact.go create mode 100644 go/exact/exact_test.go create mode 100644 go/types/api.go create mode 100644 go/types/builtins.go create mode 100644 go/types/check.go create mode 100644 go/types/check_test.go create mode 100644 go/types/conversions.go create mode 100644 go/types/errors.go create mode 100644 go/types/exportdata.go create mode 100644 go/types/expr.go create mode 100644 go/types/gcimporter.go create mode 100644 go/types/gcimporter_test.go create mode 100644 go/types/objects.go create mode 100644 go/types/operand.go create mode 100644 go/types/predicates.go create mode 100644 go/types/resolve.go create mode 100644 go/types/resolver_test.go create mode 100644 go/types/return.go create mode 100644 go/types/scope.go create mode 100644 go/types/sizes.go create mode 100644 go/types/stdlib_test.go create mode 100644 go/types/stmt.go create mode 100644 go/types/testdata/builtins.src create mode 100644 go/types/testdata/const0.src create mode 100644 go/types/testdata/conversions.src create mode 100644 go/types/testdata/decls0.src create mode 100644 go/types/testdata/decls1.src create mode 100644 go/types/testdata/decls2a.src create mode 100644 go/types/testdata/decls2b.src create mode 100644 go/types/testdata/decls3.src create mode 100644 go/types/testdata/exports.go create mode 100644 go/types/testdata/expr0.src create mode 100644 go/types/testdata/expr1.src create mode 100644 go/types/testdata/expr2.src create mode 100644 go/types/testdata/expr3.src create mode 100644 go/types/testdata/shifts.src create mode 100644 go/types/testdata/stmt0.src create mode 100644 go/types/testdata/stmt1.src create mode 100644 go/types/types.go create mode 100644 go/types/types_test.go create mode 100644 go/types/universe.go create mode 100644 gotype/doc.go create mode 100644 gotype/gotype.go create mode 100644 ssa/interp/external.go create mode 100644 ssa/interp/external_plan9.go create mode 100644 ssa/interp/external_unix.go create mode 100644 ssa/interp/external_windows.go create mode 100644 ssa/interp/interp.go create mode 100644 ssa/interp/interp_test.go create mode 100644 ssa/interp/map.go create mode 100644 ssa/interp/ops.go create mode 100644 ssa/interp/reflect.go create mode 100644 ssa/interp/testdata/coverage.go create mode 100644 ssa/interp/testdata/mrvchain.go create mode 100644 ssa/interp/value.go 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]} +}