| | |
| | |
| | |
| |
|
| | package syntax |
| |
|
| | import "fmt" |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func checkBranches(body *BlockStmt, errh ErrorHandler) { |
| | if body == nil { |
| | return |
| | } |
| |
|
| | |
| | ls := &labelScope{errh: errh} |
| | fwdGotos := ls.blockBranches(nil, targets{}, nil, body.Pos(), body.List) |
| |
|
| | |
| | |
| | |
| | for _, fwd := range fwdGotos { |
| | name := fwd.Label.Value |
| | if l := ls.labels[name]; l != nil { |
| | l.used = true |
| | 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) |
| | } |
| | } |
| |
|
| | |
| | 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 |
| | } |
| |
|
| | type label struct { |
| | parent *block |
| | lstmt *LabeledStmt |
| | used bool |
| | } |
| |
|
| | type block struct { |
| | parent *block |
| | start Pos |
| | lstmt *LabeledStmt |
| | } |
| |
|
| | func (ls *labelScope) errf(pos Pos, format string, args ...any) { |
| | ls.errh(Error{pos, fmt.Sprintf(format, args...)}) |
| | } |
| |
|
| | |
| | |
| | |
| | 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 |
| | } |
| |
|
| | |
| | |
| | |
| | func (ls *labelScope) gotoTarget(b *block, name string) *LabeledStmt { |
| | if l := ls.labels[name]; l != nil { |
| | l.used = true |
| | for ; b != nil; b = b.parent { |
| | if l.parent == b { |
| | return l.lstmt |
| | } |
| | } |
| | } |
| | return nil |
| | } |
| |
|
| | var invalid = new(LabeledStmt) |
| |
|
| | |
| | |
| | |
| | func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt { |
| | if l := ls.labels[name]; l != nil { |
| | l.used = true |
| | for ; b != nil; b = b.parent { |
| | if l.lstmt == b.lstmt { |
| | return l.lstmt |
| | } |
| | } |
| | return invalid |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | |
| | type targets struct { |
| | breaks Stmt |
| | continues *ForStmt |
| | caseIndex int |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | 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 |
| | |
| | |
| | |
| | |
| | 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) { |
| | |
| | |
| | fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...) |
| | } |
| |
|
| | |
| | |
| | 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 |
| | } |
| | } |
| |
|
| | case *LabeledStmt: |
| | |
| | if name := s.Label.Value; name != "_" { |
| | l := ls.declare(b, s) |
| | |
| | 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 { |
| | |
| | fwdGotos[i] = fwd |
| | i++ |
| | } |
| | } |
| | fwdGotos = fwdGotos[:i] |
| | lstmt = s |
| | } |
| | |
| | stmt = s.Stmt |
| | goto L |
| |
|
| | case *BranchStmt: |
| | |
| | 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) { |
| | |
| | |
| | } else if ctxt.caseIndex+1 == len(t.Body) { |
| | msg = "cannot fallthrough final case in switch" |
| | } else { |
| | break |
| | } |
| | } |
| | ls.errf(s.Pos(), "%s", msg) |
| | case _Goto: |
| | fallthrough |
| | default: |
| | panic("invalid BranchStmt") |
| | } |
| | break |
| | } |
| |
|
| | |
| | name := s.Label.Value |
| | switch s.Tok { |
| | case _Break: |
| | |
| | |
| | |
| | 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: |
| | |
| | |
| | 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 { |
| | |
| | fwdGotos = append(fwdGotos, s) |
| | } |
| |
|
| | case _Fallthrough: |
| | fallthrough |
| | 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 |
| | } |
| |
|