--- /dev/null
+// 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
+}
 
                s.popLine()
        }
 
-       // Check that we used all labels
-       for name, lab := range s.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 s.fwdGotos {
-               lab := s.labels[n.Left.Sym.Name]
-               // If the label is undefined, we have already have printed an error.
-               if lab.defined() {
-                       s.checkgoto(n, lab.defNode)
-               }
-       }
-
        if nerrors > 0 {
                s.f.Free()
                return nil
        labels       map[string]*ssaLabel
        labeledNodes map[*Node]*ssaLabel
 
-       // gotos that jump forward; required for deferred checkgoto calls
-       fwdGotos []*Node
        // Code that must precede any return
        // (e.g., copying value of heap-escaped paramout back to true paramout)
        exitCode Nodes
        target         *ssa.Block // block identified by this label
        breakTarget    *ssa.Block // block to break to in control flow node identified by this label
        continueTarget *ssa.Block // block to continue to in control flow node identified by this label
-       defNode        *Node      // label definition Node (OLABEL)
-       // Label use Node (OGOTO, OBREAK, OCONTINUE).
-       // Used only for error detection and reporting.
-       // 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 *ssaLabel) defined() bool { return l.defNode != nil }
-
-// used reports whether the label has a use (OGOTO, OBREAK, or OCONTINUE node).
-func (l *ssaLabel) used() bool { return l.useNode != nil }
-
 // label returns the label associated with sym, creating it if necessary.
 func (s *state) label(sym *Sym) *ssaLabel {
        lab := s.labels[sym.Name]
        s.pushLine(n.Pos)
        defer s.popLine()
 
-       // If s.curBlock is nil, then we're about to generate dead code.
-       // We can't just short-circuit here, though,
-       // because we check labels and gotos as part of SSA generation.
-       // Provide a block for the dead code so that we don't have
-       // to add special cases everywhere else.
-       if s.curBlock == nil {
-               switch n.Op {
-               case OLABEL, OBREAK, OCONTINUE:
-                       // These statements don't need a block,
-                       // and they commonly occur without one.
-               default:
-                       dead := s.f.NewBlock(ssa.BlockPlain)
-                       s.startBlock(dead)
-               }
+       // If s.curBlock is nil, and n isn't a label (which might have an associated goto somewhere),
+       // then this code is dead. Stop here.
+       if s.curBlock == nil && n.Op != OLABEL {
+               return
        }
 
        s.stmtList(n.Ninit)
 
        case OLABEL:
                sym := n.Left.Sym
-
-               if isblanksym(sym) {
-                       // Empty identifier is valid but useless.
-                       // See issues 11589, 11593.
-                       return
-               }
-
                lab := s.label(sym)
 
                // Associate label with its control flow node, if any
-               if ctl := n.Name.Defn; ctl != nil {
-                       switch ctl.Op {
-                       case OFOR, OFORUNTIL, OSWITCH, OSELECT:
-                               s.labeledNodes[ctl] = lab
-                       }
+               if ctl := n.labeledControl(); ctl != nil {
+                       s.labeledNodes[ctl] = lab
                }
 
-               if !lab.defined() {
-                       lab.defNode = n
-               } else {
-                       s.Error("label %v already defined at %v", sym, linestr(lab.defNode.Pos))
-                       lab.reported = true
-               }
                // The label might already have a target block via a goto.
                if lab.target == nil {
                        lab.target = s.f.NewBlock(ssa.BlockPlain)
                if lab.target == nil {
                        lab.target = s.f.NewBlock(ssa.BlockPlain)
                }
-               if !lab.used() {
-                       lab.useNode = n
-               }
-
-               if lab.defined() {
-                       s.checkgoto(n, lab.defNode)
-               } else {
-                       s.fwdGotos = append(s.fwdGotos, n)
-               }
 
                b := s.endBlock()
                b.AddEdgeTo(lab.target)
                b.Aux = Linksym(n.Left.Sym)
 
        case OCONTINUE, OBREAK:
-               var op string
                var to *ssa.Block
-               switch n.Op {
-               case OCONTINUE:
-                       op = "continue"
-                       to = s.continueTo
-               case OBREAK:
-                       op = "break"
-                       to = s.breakTo
-               }
                if n.Left == nil {
                        // plain break/continue
-                       if to == nil {
-                               s.Error("%s is not in a loop", op)
-                               return
+                       switch n.Op {
+                       case OCONTINUE:
+                               to = s.continueTo
+                       case OBREAK:
+                               to = s.breakTo
                        }
-                       // nothing to do; "to" is already the correct target
                } else {
                        // labeled break/continue; look up the target
                        sym := n.Left.Sym
                        lab := s.label(sym)
-                       if !lab.used() {
-                               lab.useNode = n.Left
-                       }
-                       if !lab.defined() {
-                               s.Error("%s label not defined: %v", op, sym)
-                               lab.reported = true
-                               return
-                       }
                        switch n.Op {
                        case OCONTINUE:
                                to = lab.continueTarget
                        case OBREAK:
                                to = lab.breakTarget
                        }
-                       if to == nil {
-                               // Valid label but not usable with a break/continue here, e.g.:
-                               // for {
-                               //      continue abc
-                               // }
-                               // abc:
-                               // for {}
-                               s.Error("invalid %s label %v", op, sym)
-                               lab.reported = true
-                               return
-                       }
                }
 
-               if s.curBlock != nil {
-                       b := s.endBlock()
-                       b.AddEdgeTo(to)
-               }
+               b := s.endBlock()
+               b.AddEdgeTo(to)
 
        case OFOR, OFORUNTIL:
                // OFOR: for Ninit; Left; Right { Nbody }
        return res, resok
 }
 
-// checkgoto checks that a goto from from to to does not
-// jump into a block or jump over variable declarations.
-func (s *state) 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.
-       lno := from.Left.Pos
-       if block != nil {
-               yyerrorl(lno, "goto %v jumps into block starting at %v", from.Left.Sym, linestr(block.Lastlineno))
-       } else {
-               yyerrorl(lno, "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
-}
-
 // variable returns the value of a variable at the current location.
 func (s *state) variable(name *Node, t ssa.Type) *ssa.Value {
        v := s.vars[name]