blob: bbb93a60c3b9b811617f4036881079978824362f [file] [log] [blame]
// 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
}