| // 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 |
| } |