mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
300 lines
7.4 KiB
Go
300 lines
7.4 KiB
Go
|
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||
|
|
// Use of this source code is governed by a BSD-style
|
||
|
|
// license that can be found in the LICENSE file.
|
||
|
|
|
||
|
|
package gc
|
||
|
|
|
||
|
|
import (
|
||
|
|
"cmd/internal/src"
|
||
|
|
)
|
||
|
|
|
||
|
|
// checkcontrolflow checks fn's control flow structures for correctness.
|
||
|
|
// It catches:
|
||
|
|
// * misplaced breaks and continues
|
||
|
|
// * bad labeled break and continues
|
||
|
|
// * invalid, unused, duplicate, and missing labels
|
||
|
|
// * gotos jumping over declarations and into blocks
|
||
|
|
func checkcontrolflow(fn *Node) {
|
||
|
|
c := controlflow{
|
||
|
|
labels: make(map[string]*cfLabel),
|
||
|
|
labeledNodes: make(map[*Node]*cfLabel),
|
||
|
|
}
|
||
|
|
c.pushPos(fn.Pos)
|
||
|
|
c.stmtList(fn.Nbody)
|
||
|
|
|
||
|
|
// Check that we used all labels.
|
||
|
|
for name, lab := range c.labels {
|
||
|
|
if !lab.used() && !lab.reported && !lab.defNode.Used() {
|
||
|
|
yyerrorl(lab.defNode.Pos, "label %v defined and not used", name)
|
||
|
|
lab.reported = true
|
||
|
|
}
|
||
|
|
if lab.used() && !lab.defined() && !lab.reported {
|
||
|
|
yyerrorl(lab.useNode.Pos, "label %v not defined", name)
|
||
|
|
lab.reported = true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check any forward gotos. Non-forward gotos have already been checked.
|
||
|
|
for _, n := range c.fwdGotos {
|
||
|
|
lab := c.labels[n.Left.Sym.Name]
|
||
|
|
// If the label is undefined, we have already have printed an error.
|
||
|
|
if lab.defined() {
|
||
|
|
c.checkgoto(n, lab.defNode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
type controlflow struct {
|
||
|
|
// Labels and labeled control flow nodes (OFOR, OFORUNTIL, OSWITCH, OSELECT) in f.
|
||
|
|
labels map[string]*cfLabel
|
||
|
|
labeledNodes map[*Node]*cfLabel
|
||
|
|
|
||
|
|
// Gotos that jump forward; required for deferred checkgoto calls.
|
||
|
|
fwdGotos []*Node
|
||
|
|
|
||
|
|
// Unlabeled break and continue statement tracking.
|
||
|
|
innerloop *Node
|
||
|
|
|
||
|
|
// Position stack. The current position is top of stack.
|
||
|
|
pos []src.XPos
|
||
|
|
}
|
||
|
|
|
||
|
|
// cfLabel is a label tracked by a controlflow.
|
||
|
|
type cfLabel struct {
|
||
|
|
ctlNode *Node // associated labeled control flow node
|
||
|
|
defNode *Node // label definition Node (OLABEL)
|
||
|
|
// Label use Node (OGOTO, OBREAK, OCONTINUE).
|
||
|
|
// There might be multiple uses, but we only need to track one.
|
||
|
|
useNode *Node
|
||
|
|
reported bool // reported indicates whether an error has already been reported for this label
|
||
|
|
}
|
||
|
|
|
||
|
|
// defined reports whether the label has a definition (OLABEL node).
|
||
|
|
func (l *cfLabel) defined() bool { return l.defNode != nil }
|
||
|
|
|
||
|
|
// used reports whether the label has a use (OGOTO, OBREAK, or OCONTINUE node).
|
||
|
|
func (l *cfLabel) used() bool { return l.useNode != nil }
|
||
|
|
|
||
|
|
// label returns the label associated with sym, creating it if necessary.
|
||
|
|
func (c *controlflow) label(sym *Sym) *cfLabel {
|
||
|
|
lab := c.labels[sym.Name]
|
||
|
|
if lab == nil {
|
||
|
|
lab = new(cfLabel)
|
||
|
|
c.labels[sym.Name] = lab
|
||
|
|
}
|
||
|
|
return lab
|
||
|
|
}
|
||
|
|
|
||
|
|
// stmtList checks l.
|
||
|
|
func (c *controlflow) stmtList(l Nodes) {
|
||
|
|
for _, n := range l.Slice() {
|
||
|
|
c.stmt(n)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// stmt checks n.
|
||
|
|
func (c *controlflow) stmt(n *Node) {
|
||
|
|
c.pushPos(n.Pos)
|
||
|
|
defer c.popPos()
|
||
|
|
c.stmtList(n.Ninit)
|
||
|
|
|
||
|
|
checkedNbody := false
|
||
|
|
|
||
|
|
switch n.Op {
|
||
|
|
case OLABEL:
|
||
|
|
sym := n.Left.Sym
|
||
|
|
lab := c.label(sym)
|
||
|
|
// Associate label with its control flow node, if any
|
||
|
|
if ctl := n.labeledControl(); ctl != nil {
|
||
|
|
c.labeledNodes[ctl] = lab
|
||
|
|
}
|
||
|
|
|
||
|
|
if !lab.defined() {
|
||
|
|
lab.defNode = n
|
||
|
|
} else {
|
||
|
|
c.err("label %v already defined at %v", sym, linestr(lab.defNode.Pos))
|
||
|
|
lab.reported = true
|
||
|
|
}
|
||
|
|
|
||
|
|
case OGOTO:
|
||
|
|
lab := c.label(n.Left.Sym)
|
||
|
|
if !lab.used() {
|
||
|
|
lab.useNode = n
|
||
|
|
}
|
||
|
|
if lab.defined() {
|
||
|
|
c.checkgoto(n, lab.defNode)
|
||
|
|
} else {
|
||
|
|
c.fwdGotos = append(c.fwdGotos, n)
|
||
|
|
}
|
||
|
|
|
||
|
|
case OCONTINUE, OBREAK:
|
||
|
|
if n.Left == nil {
|
||
|
|
// plain break/continue
|
||
|
|
if c.innerloop == nil {
|
||
|
|
c.err("%v is not in a loop", n.Op)
|
||
|
|
}
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
// labeled break/continue; look up the target
|
||
|
|
sym := n.Left.Sym
|
||
|
|
lab := c.label(sym)
|
||
|
|
if !lab.used() {
|
||
|
|
lab.useNode = n.Left
|
||
|
|
}
|
||
|
|
if !lab.defined() {
|
||
|
|
c.err("%v label not defined: %v", n.Op, sym)
|
||
|
|
lab.reported = true
|
||
|
|
break
|
||
|
|
}
|
||
|
|
ctl := lab.ctlNode
|
||
|
|
if n.Op == OCONTINUE && ctl != nil && (ctl.Op == OSWITCH || ctl.Op == OSELECT) {
|
||
|
|
// Cannot continue in a switch or select.
|
||
|
|
ctl = nil
|
||
|
|
}
|
||
|
|
if ctl == nil {
|
||
|
|
// Valid label but not usable with a break/continue here, e.g.:
|
||
|
|
// for {
|
||
|
|
// continue abc
|
||
|
|
// }
|
||
|
|
// abc:
|
||
|
|
// for {}
|
||
|
|
c.err("invalid %v label %v", n.Op, sym)
|
||
|
|
lab.reported = true
|
||
|
|
}
|
||
|
|
|
||
|
|
case OFOR, OFORUNTIL, OSWITCH, OSELECT:
|
||
|
|
// set up for continue/break in body
|
||
|
|
innerloop := c.innerloop
|
||
|
|
c.innerloop = n
|
||
|
|
lab := c.labeledNodes[n]
|
||
|
|
if lab != nil {
|
||
|
|
// labeled for loop
|
||
|
|
lab.ctlNode = n
|
||
|
|
}
|
||
|
|
|
||
|
|
// check body
|
||
|
|
c.stmtList(n.Nbody)
|
||
|
|
checkedNbody = true
|
||
|
|
|
||
|
|
// tear down continue/break
|
||
|
|
c.innerloop = innerloop
|
||
|
|
if lab != nil {
|
||
|
|
lab.ctlNode = nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if !checkedNbody {
|
||
|
|
c.stmtList(n.Nbody)
|
||
|
|
}
|
||
|
|
c.stmtList(n.List)
|
||
|
|
c.stmtList(n.Rlist)
|
||
|
|
}
|
||
|
|
|
||
|
|
// pushPos pushes a position onto the position stack.
|
||
|
|
func (c *controlflow) pushPos(pos src.XPos) {
|
||
|
|
if !pos.IsKnown() {
|
||
|
|
pos = c.peekPos()
|
||
|
|
if Debug['K'] != 0 {
|
||
|
|
Warn("controlflow: unknown position")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
c.pos = append(c.pos, pos)
|
||
|
|
}
|
||
|
|
|
||
|
|
// popLine pops the top of the position stack.
|
||
|
|
func (c *controlflow) popPos() { c.pos = c.pos[:len(c.pos)-1] }
|
||
|
|
|
||
|
|
// peekPos peeks at the top of the position stack.
|
||
|
|
func (c *controlflow) peekPos() src.XPos { return c.pos[len(c.pos)-1] }
|
||
|
|
|
||
|
|
// err reports a control flow error at the current position.
|
||
|
|
func (c *controlflow) err(msg string, args ...interface{}) {
|
||
|
|
yyerrorl(c.peekPos(), msg, args...)
|
||
|
|
}
|
||
|
|
|
||
|
|
// checkgoto checks that a goto from from to to does not
|
||
|
|
// jump into a block or jump over variable declarations.
|
||
|
|
func (c *controlflow) checkgoto(from *Node, to *Node) {
|
||
|
|
if from.Op != OGOTO || to.Op != OLABEL {
|
||
|
|
Fatalf("bad from/to in checkgoto: %v -> %v", from, to)
|
||
|
|
}
|
||
|
|
|
||
|
|
// from and to's Sym fields record dclstack's value at their
|
||
|
|
// position, which implicitly encodes their block nesting
|
||
|
|
// level and variable declaration position within that block.
|
||
|
|
//
|
||
|
|
// For valid gotos, to.Sym will be a tail of from.Sym.
|
||
|
|
// Otherwise, any link in to.Sym not also in from.Sym
|
||
|
|
// indicates a block/declaration being jumped into/over.
|
||
|
|
//
|
||
|
|
// TODO(mdempsky): We should only complain about jumping over
|
||
|
|
// variable declarations, but currently we reject type and
|
||
|
|
// constant declarations too (#8042).
|
||
|
|
|
||
|
|
if from.Sym == to.Sym {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
nf := dcldepth(from.Sym)
|
||
|
|
nt := dcldepth(to.Sym)
|
||
|
|
|
||
|
|
// Unwind from.Sym so it's no longer than to.Sym. It's okay to
|
||
|
|
// jump out of blocks or backwards past variable declarations.
|
||
|
|
fs := from.Sym
|
||
|
|
for ; nf > nt; nf-- {
|
||
|
|
fs = fs.Link
|
||
|
|
}
|
||
|
|
|
||
|
|
if fs == to.Sym {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// Decide what to complain about. Unwind to.Sym until where it
|
||
|
|
// forked from from.Sym, and keep track of the innermost block
|
||
|
|
// and declaration we jumped into/over.
|
||
|
|
var block *Sym
|
||
|
|
var dcl *Sym
|
||
|
|
|
||
|
|
// If to.Sym is longer, unwind until it's the same length.
|
||
|
|
ts := to.Sym
|
||
|
|
for ; nt > nf; nt-- {
|
||
|
|
if ts.Pkg == nil {
|
||
|
|
block = ts
|
||
|
|
} else {
|
||
|
|
dcl = ts
|
||
|
|
}
|
||
|
|
ts = ts.Link
|
||
|
|
}
|
||
|
|
|
||
|
|
// Same length; unwind until we find their common ancestor.
|
||
|
|
for ts != fs {
|
||
|
|
if ts.Pkg == nil {
|
||
|
|
block = ts
|
||
|
|
} else {
|
||
|
|
dcl = ts
|
||
|
|
}
|
||
|
|
ts = ts.Link
|
||
|
|
fs = fs.Link
|
||
|
|
}
|
||
|
|
|
||
|
|
// Prefer to complain about 'into block' over declarations.
|
||
|
|
pos := from.Left.Pos
|
||
|
|
if block != nil {
|
||
|
|
yyerrorl(pos, "goto %v jumps into block starting at %v", from.Left.Sym, linestr(block.Lastlineno))
|
||
|
|
} else {
|
||
|
|
yyerrorl(pos, "goto %v jumps over declaration of %v at %v", from.Left.Sym, dcl, linestr(dcl.Lastlineno))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// dcldepth returns the declaration depth for a dclstack Sym; that is,
|
||
|
|
// the sum of the block nesting level and the number of declarations
|
||
|
|
// in scope.
|
||
|
|
func dcldepth(s *Sym) int {
|
||
|
|
n := 0
|
||
|
|
for ; s != nil; s = s.Link {
|
||
|
|
n++
|
||
|
|
}
|
||
|
|
return n
|
||
|
|
}
|