1
0
mirror of https://github.com/golang/go synced 2024-11-05 17:26:11 -07:00

go.tools/go/types: first cut at checking variable initialization cycles

Missing:
- dependencies via functions (incl. closures and methods)
- more tests (incl. API test)

R=adonovan
CC=golang-dev
https://golang.org/cl/20510043
This commit is contained in:
Robert Griesemer 2013-10-31 21:47:35 -07:00
parent 5879669d9d
commit d259ea48d1
6 changed files with 148 additions and 22 deletions

View File

@ -161,6 +161,12 @@ type Info struct {
// *ast.RangeStmt
//
Scopes map[ast.Node]*Scope
// InitOrder is the list of package-level variables in the order in which
// they must be initialized. Variables related by an initialization dependency
// appear in topological order, and unrelated variables appear in source order.
// TODO(gri) find a better name
InitOrder []*Var
}
// Check type-checks a package and returns the resulting package object,

View File

@ -36,6 +36,7 @@ type checker struct {
methods map[string][]*Func // maps type names to associated methods
conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls)
dependencies map[Object]map[Object]bool // set of object initialization dependencies (obj depends on {obj1, obj2, ...})
untyped map[ast.Expr]exprInfo // map of expressions without final type
lhsVarsList [][]*Var // type switch lhs variable sets, for 'declared but not used' errors
delayed []func() // delayed checks that require fully setup types
@ -45,7 +46,8 @@ type checker struct {
objMap map[Object]declInfo // if set we are in the package-level declaration phase (otherwise all objects seen must be declared)
topScope *Scope // current topScope for lookups
iota exact.Value // value of iota in a constant declaration; nil otherwise
iota exact.Value // current value of iota in a constant declaration; nil otherwise
init Object // current variable or function for which init expression or body is type-checked
// functions
funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
@ -63,10 +65,40 @@ func newChecker(conf *Config, fset *token.FileSet, pkg *Package) *checker {
pkg: pkg,
methods: make(map[string][]*Func),
conversions: make(map[*ast.CallExpr]bool),
dependencies: make(map[Object]map[Object]bool),
untyped: make(map[ast.Expr]exprInfo),
}
}
func isVarOrFunc(obj Object) bool {
switch obj.(type) {
case *Var, *Func:
return true
}
return false
}
// addInitDep adds the dependency edge (to -> check.init)
// if check.init is a package-level variable or function.
func (check *checker) addInitDep(to Object) {
from := check.init
if from == nil || from.Parent() == nil {
return // not a package-level variable or function
}
if debug {
assert(isVarOrFunc(from))
assert(isVarOrFunc(to))
}
m := check.dependencies[from] // lazily allocated
if m == nil {
m = make(map[Object]bool)
check.dependencies[from] = m
}
m[to] = true
}
func (check *checker) delay(f func()) {
check.delayed = append(check.delayed, f)
}
@ -227,5 +259,11 @@ func (conf *Config) check(pkgPath string, fset *token.FileSet, files []*ast.File
}
}
// copy check.InitOrder back to incoming *info if necessary
// (In case of early (error) bailout, this is not done, but we don't care in that case.)
if info != nil {
info.InitOrder = check.InitOrder
}
return
}

View File

@ -52,6 +52,7 @@ var tests = [][]string{
{"testdata/cycles2.src"},
{"testdata/cycles3.src"},
{"testdata/cycles4.src"},
{"testdata/init0.src"},
{"testdata/decls0.src"},
{"testdata/decls1.src"},
{"testdata/decls2a.src", "testdata/decls2b.src"},

View File

@ -353,16 +353,13 @@ func (check *checker) resolveFiles(files []*ast.File) {
// Phase 3: Typecheck all objects in objList, but not function bodies.
check.objMap = objMap // indicate that we are checking global declarations (objects may not have a type yet)
check.objMap = objMap // indicate that we are checking package-level declarations (objects may not have a type yet)
for _, obj := range objList {
if obj.Type() == nil {
check.objDecl(obj, nil, false)
}
}
// done with package-level declarations
check.objMap = nil
objList = nil
check.objMap = nil // done with package-level declarations
// At this point we may have a non-empty check.methods map; this means that not all
// entries were deleted at the end of typeDecl because the respective receiver base
@ -401,7 +398,19 @@ func (check *checker) resolveFiles(files []*ast.File) {
}
}
// Phase 5: Check for declared but not used packages and variables.
// Phase 5: Check initialization dependencies.
// Note: must happen after checking all functions because function bodies
// may introduce dependencies
state := make(map[Object]uint8)
for _, obj := range objList {
if isVarOrFunc(obj) && state[obj] == 0 {
check.objDependencies(obj, state)
}
}
objList = nil // not needed anymore
// Phase 6: Check for declared but not used packages and variables.
// Note: must happen after checking all functions because closures may affect outer scopes
// spec: "It is illegal (...) to directly import a package without referring to
@ -464,6 +473,26 @@ func (check *checker) resolveFiles(files []*ast.File) {
}
}
func (check *checker) objDependencies(obj Object, state map[Object]uint8) {
const inProgress, done = 1, 2
if state[obj] == inProgress {
check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name())
// TODO(gri) print the cycle
state[obj] = done // avoid further errors
return
}
state[obj] = inProgress
for dep := range check.dependencies[obj] {
check.objDependencies(dep, state)
}
state[obj] = done
if v, _ := obj.(*Var); v != nil {
check.Info.InitOrder = append(check.Info.InitOrder, v)
}
}
func (check *checker) usage(scope *Scope) {
for _, obj := range scope.elems {
if v, _ := obj.(*Var); v != nil && !v.used {
@ -557,16 +586,21 @@ func (check *checker) varDecl(obj *Var, typ, init ast.Expr) {
return
}
// save current init object
oldInit := check.init
check.init = obj
if m, _ := init.(*multiExpr); m != nil {
check.initVars(m.lhs, m.rhs, token.NoPos)
return
}
} else {
var x operand
check.expr(&x, init)
check.initVar(obj, &x)
}
check.init = oldInit
}
func (check *checker) typeDecl(obj *TypeName, typ ast.Expr, def *Named, cycleOk bool) {
assert(obj.Type() == nil)

42
go/types/testdata/init0.src vendored Normal file
View File

@ -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.
// initialization cycles
package init0
// type-checking, not initialization cycles (we don't know the types)
// (avoid duplicate errors)
var (
x1 /* ERROR cycle */ = y1
y1 = x1
a1 = b1
b1 /* ERROR cycle */ = c1
c1 = d1
d1 = b1
)
// initialization cycles (we know the types)
var (
x2 /* ERROR initialization cycle */ int = y2
y2 = x2
a2 = b2
b2 /* ERROR initialization cycle */ int = c2
c2 = d2
d2 = b2
)
// cycles via struct fields
type S1 struct {
f int
}
var x3 /* ERROR initialization cycle */ S1 = S1{x3.f}
// cycles via functions
var x4 = f1() // TODO(gri) recognize cycle
func f1() int {
return x4
}

View File

@ -89,6 +89,11 @@ func (check *checker) ident(x *operand, e *ast.Ident, def *Named, cycleOk bool)
case *Var:
obj.used = true
x.mode = variable
if obj.parent.parent == Universe && typ != Typ[Invalid] {
// obj is a package-level variable, and there are no other errors
// (such as a type-checking cycle)
check.addInitDep(obj)
}
case *Func:
obj.used = true