// 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 syntax import "fmt" // checkBranches checks correct use of labels and branch // statements (break, continue, fallthrough, goto) in a function body. // It catches: // - misplaced breaks, continues, and fallthroughs // - bad labeled breaks and continues // - invalid, unused, duplicate, and missing labels // - gotos jumping over variable declarations and into blocks func checkBranches(body *BlockStmt, errh ErrorHandler) { if body == nil { return } // scope of all labels in this body ls := &labelScope{errh: errh} fwdGotos := ls.blockBranches(nil, targets{}, nil, body.Pos(), body.List) // If there are any forward gotos left, no matching label was // found for them. Either those labels were never defined, or // they are inside blocks and not reachable from the gotos. for _, fwd := range fwdGotos { name := fwd.Label.Value if l := ls.labels[name]; l != nil { l.used = true // avoid "defined and not used" error ls.errf(fwd.Label.Pos(), "goto %s jumps into block starting at %s", name, l.parent.start) } else { ls.errf(fwd.Label.Pos(), "label %s not defined", name) } } // spec: "It is illegal to define a label that is never used." for _, l := range ls.labels { if !l.used { l := l.lstmt.Label ls.errf(l.Pos(), "label %s defined and not used", l.Value) } } } type labelScope struct { errh ErrorHandler labels map[string]*label // all label declarations inside the function; allocated lazily } type label struct { parent *block // block containing this label declaration lstmt *LabeledStmt // statement declaring the label used bool // whether the label is used or not } type block struct { parent *block // immediately enclosing block, or nil start Pos // start of block lstmt *LabeledStmt // labeled statement associated with this block, or nil } func (ls *labelScope) errf(pos Pos, format string, args ...interface{}) { ls.errh(Error{pos, fmt.Sprintf(format, args...)}) } // declare declares the label introduced by s in block b and returns // the new label. If the label was already declared, declare reports // and error and the existing label is returned instead. func (ls *labelScope) declare(b *block, s *LabeledStmt) *label { name := s.Label.Value labels := ls.labels if labels == nil { labels = make(map[string]*label) ls.labels = labels } else if alt := labels[name]; alt != nil { ls.errf(s.Label.Pos(), "label %s already defined at %s", name, alt.lstmt.Label.Pos().String()) return alt } l := &label{b, s, false} labels[name] = l return l } // gotoTarget returns the labeled statement matching the given name and // declared in block b or any of its enclosing blocks. The result is nil // if the label is not defined, or doesn't match a valid labeled statement. func (ls *labelScope) gotoTarget(b *block, name string) *LabeledStmt { if l := ls.labels[name]; l != nil { l.used = true // even if it's not a valid target for ; b != nil; b = b.parent { if l.parent == b { return l.lstmt } } } return nil } var invalid = new(LabeledStmt) // singleton to signal invalid enclosing target // enclosingTarget returns the innermost enclosing labeled statement matching // the given name. The result is nil if the label is not defined, and invalid // if the label is defined but doesn't label a valid labeled statement. func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt { if l := ls.labels[name]; l != nil { l.used = true // even if it's not a valid target (see e.g., test/fixedbugs/bug136.go) for ; b != nil; b = b.parent { if l.lstmt == b.lstmt { return l.lstmt } } return invalid } return nil } // targets describes the target statements within which break // or continue statements are valid. type targets struct { breaks Stmt // *ForStmt, *SwitchStmt, *SelectStmt, or nil continues *ForStmt // or nil caseIndex int // case index of immediately enclosing switch statement, or < 0 } // blockBranches processes a block's body starting at start and returns the // list of unresolved (forward) gotos. parent is the immediately enclosing // block (or nil), ctxt provides information about the enclosing statements, // and lstmt is the labeled statement associated with this block, or nil. func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start Pos, body []Stmt) []*BranchStmt { b := &block{parent: parent, start: start, lstmt: lstmt} var varPos Pos var varName Expr var fwdGotos, badGotos []*BranchStmt recordVarDecl := func(pos Pos, name Expr) { varPos = pos varName = name // Any existing forward goto jumping over the variable // declaration is invalid. The goto may still jump out // of the block and be ok, but we don't know that yet. // Remember all forward gotos as potential bad gotos. badGotos = append(badGotos[:0], fwdGotos...) } jumpsOverVarDecl := func(fwd *BranchStmt) bool { if varPos.IsKnown() { for _, bad := range badGotos { if fwd == bad { return true } } } return false } innerBlock := func(ctxt targets, start Pos, body []Stmt) { // Unresolved forward gotos from the inner block // become forward gotos for the current block. fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...) } // A fallthrough statement counts as last statement in a statement // list even if there are trailing empty statements; remove them. stmtList := trimTrailingEmptyStmts(body) for stmtIndex, stmt := range stmtList { lstmt = nil L: switch s := stmt.(type) { case *DeclStmt: for _, d := range s.DeclList { if v, ok := d.(*VarDecl); ok { recordVarDecl(v.Pos(), v.NameList[0]) break // the first VarDecl will do } } case *LabeledStmt: // declare non-blank label if name := s.Label.Value; name != "_" { l := ls.declare(b, s) // resolve matching forward gotos i := 0 for _, fwd := range fwdGotos { if fwd.Label.Value == name { fwd.Target = s l.used = true if jumpsOverVarDecl(fwd) { ls.errf( fwd.Label.Pos(), "goto %s jumps over declaration of %s at %s", name, String(varName), varPos, ) } } else { // no match - keep forward goto fwdGotos[i] = fwd i++ } } fwdGotos = fwdGotos[:i] lstmt = s } // process labeled statement stmt = s.Stmt goto L case *BranchStmt: // unlabeled branch statement if s.Label == nil { switch s.Tok { case _Break: if t := ctxt.breaks; t != nil { s.Target = t } else { ls.errf(s.Pos(), "break is not in a loop, switch, or select") } case _Continue: if t := ctxt.continues; t != nil { s.Target = t } else { ls.errf(s.Pos(), "continue is not in a loop") } case _Fallthrough: msg := "fallthrough statement out of place" if t, _ := ctxt.breaks.(*SwitchStmt); t != nil { if _, ok := t.Tag.(*TypeSwitchGuard); ok { msg = "cannot fallthrough in type switch" } else if ctxt.caseIndex < 0 || stmtIndex+1 < len(stmtList) { // fallthrough nested in a block or not the last statement // use msg as is } else if ctxt.caseIndex+1 == len(t.Body) { msg = "cannot fallthrough final case in switch" } else { break // fallthrough ok } } ls.errf(s.Pos(), "%s", msg) case _Goto: fallthrough // should always have a label default: panic("invalid BranchStmt") } break } // labeled branch statement name := s.Label.Value switch s.Tok { case _Break: // spec: "If there is a label, it must be that of an enclosing // "for", "switch", or "select" statement, and that is the one // whose execution terminates." if t := ls.enclosingTarget(b, name); t != nil { switch t := t.Stmt.(type) { case *SwitchStmt, *SelectStmt, *ForStmt: s.Target = t default: ls.errf(s.Label.Pos(), "invalid break label %s", name) } } else { ls.errf(s.Label.Pos(), "break label not defined: %s", name) } case _Continue: // spec: "If there is a label, it must be that of an enclosing // "for" statement, and that is the one whose execution advances." if t := ls.enclosingTarget(b, name); t != nil { if t, ok := t.Stmt.(*ForStmt); ok { s.Target = t } else { ls.errf(s.Label.Pos(), "invalid continue label %s", name) } } else { ls.errf(s.Label.Pos(), "continue label not defined: %s", name) } case _Goto: if t := ls.gotoTarget(b, name); t != nil { s.Target = t } else { // label may be declared later - add goto to forward gotos fwdGotos = append(fwdGotos, s) } case _Fallthrough: fallthrough // should never have a label default: panic("invalid BranchStmt") } case *AssignStmt: if s.Op == Def { recordVarDecl(s.Pos(), s.Lhs) } case *BlockStmt: inner := targets{ctxt.breaks, ctxt.continues, -1} innerBlock(inner, s.Pos(), s.List) case *IfStmt: inner := targets{ctxt.breaks, ctxt.continues, -1} innerBlock(inner, s.Then.Pos(), s.Then.List) if s.Else != nil { innerBlock(inner, s.Else.Pos(), []Stmt{s.Else}) } case *ForStmt: inner := targets{s, s, -1} innerBlock(inner, s.Body.Pos(), s.Body.List) case *SwitchStmt: inner := targets{s, ctxt.continues, -1} for i, cc := range s.Body { inner.caseIndex = i innerBlock(inner, cc.Pos(), cc.Body) } case *SelectStmt: inner := targets{s, ctxt.continues, -1} for _, cc := range s.Body { innerBlock(inner, cc.Pos(), cc.Body) } } } return fwdGotos } func trimTrailingEmptyStmts(list []Stmt) []Stmt { for i := len(list); i > 0; i-- { if _, ok := list[i-1].(*EmptyStmt); !ok { return list[:i] } } return nil }