Commit a5096213 authored by Robert Griesemer's avatar Robert Griesemer

cmd/compile/internal/syntax: compute BranchStmt.Target statements

- Add new BranchStmt.Target field: It's the destination for break,
  continue, or goto statements.

- When parsing with CheckBranches enabled, set the BranchStmt.Target
  field. We get the information practically for free from the branch
  checker, so keep it for further use.

- Fix a couple of comments.

- This could use a test, but the new Target field is currently not
  used, and writing a test is tedious w/o a general tree visitor.
  Do it later. For now, visually verified output from syntax dump.

Change-Id: Id691d89efab514ad885e19ac9759506106579520
Reviewed-on: https://go-review.googlesource.com/40988Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
parent 7b0b52ef
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"fmt" "fmt"
) )
// TODO(gri) do this while parsing instead of in a separate pass? // TODO(gri) consider making this part of the parser code
// checkBranches checks correct use of labels and branch // checkBranches checks correct use of labels and branch
// statements (break, continue, goto) in a function body. // statements (break, continue, goto) in a function body.
...@@ -25,7 +25,7 @@ func checkBranches(body *BlockStmt, errh ErrorHandler) { ...@@ -25,7 +25,7 @@ func checkBranches(body *BlockStmt, errh ErrorHandler) {
// scope of all labels in this body // scope of all labels in this body
ls := &labelScope{errh: errh} ls := &labelScope{errh: errh}
fwdGotos := ls.blockBranches(nil, 0, nil, body.Pos(), body.List) fwdGotos := ls.blockBranches(nil, targets{}, nil, body.Pos(), body.List)
// If there are any forward gotos left, no matching label was // If there are any forward gotos left, no matching label was
// found for them. Either those labels were never defined, or // found for them. Either those labels were never defined, or
...@@ -91,12 +91,12 @@ func (ls *labelScope) declare(b *block, s *LabeledStmt) *label { ...@@ -91,12 +91,12 @@ func (ls *labelScope) declare(b *block, s *LabeledStmt) *label {
// gotoTarget returns the labeled statement matching the given name and // gotoTarget returns the labeled statement matching the given name and
// declared in block b or any of its enclosing blocks. The result is nil // 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. // if the label is not defined, or doesn't match a valid labeled statement.
func (ls *labelScope) gotoTarget(b *block, name string) *label { func (ls *labelScope) gotoTarget(b *block, name string) *LabeledStmt {
if l := ls.labels[name]; l != nil { if l := ls.labels[name]; l != nil {
l.used = true // even if it's not a valid target l.used = true // even if it's not a valid target
for ; b != nil; b = b.parent { for ; b != nil; b = b.parent {
if l.parent == b { if l.parent == b {
return l return l.lstmt
} }
} }
} }
...@@ -121,17 +121,18 @@ func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt { ...@@ -121,17 +121,18 @@ func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt {
return nil return nil
} }
// context flags // targets describes the target statements within which break
const ( // or continue statements are valid.
breakOk = 1 << iota type targets struct {
continueOk breaks Stmt // *ForStmt, *SwitchStmt, *SelectStmt, or nil
) continues *ForStmt // or nil
}
// blockBranches processes a block's body starting at start and returns the // blockBranches processes a block's body starting at start and returns the
// list of unresolved (forward) gotos. parent is the immediately enclosing // list of unresolved (forward) gotos. parent is the immediately enclosing
// block (or nil), context provides information about the enclosing statements, // block (or nil), ctxt provides information about the enclosing statements,
// and lstmt is the labeled statement asociated with this block, or nil. // and lstmt is the labeled statement asociated with this block, or nil.
func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledStmt, start src.Pos, body []Stmt) []*BranchStmt { func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start src.Pos, body []Stmt) []*BranchStmt {
b := &block{parent: parent, start: start, lstmt: lstmt} b := &block{parent: parent, start: start, lstmt: lstmt}
var varPos src.Pos var varPos src.Pos
...@@ -159,8 +160,10 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -159,8 +160,10 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
return false return false
} }
innerBlock := func(flags uint, start src.Pos, body []Stmt) { innerBlock := func(ctxt targets, start src.Pos, body []Stmt) {
fwdGotos = append(fwdGotos, ls.blockBranches(b, context|flags, lstmt, start, body)...) // Unresolved forward gotos from the inner block
// become forward gotos for the current block.
fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...)
} }
for _, stmt := range body { for _, stmt := range body {
...@@ -183,6 +186,7 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -183,6 +186,7 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
i := 0 i := 0
for _, fwd := range fwdGotos { for _, fwd := range fwdGotos {
if fwd.Label.Value == name { if fwd.Label.Value == name {
fwd.Target = s
l.used = true l.used = true
if jumpsOverVarDecl(fwd) { if jumpsOverVarDecl(fwd) {
ls.err( ls.err(
...@@ -209,11 +213,15 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -209,11 +213,15 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
if s.Label == nil { if s.Label == nil {
switch s.Tok { switch s.Tok {
case _Break: case _Break:
if context&breakOk == 0 { if t := ctxt.breaks; t != nil {
s.Target = t
} else {
ls.err(s.Pos(), "break is not in a loop, switch, or select") ls.err(s.Pos(), "break is not in a loop, switch, or select")
} }
case _Continue: case _Continue:
if context&continueOk == 0 { if t := ctxt.continues; t != nil {
s.Target = t
} else {
ls.err(s.Pos(), "continue is not in a loop") ls.err(s.Pos(), "continue is not in a loop")
} }
case _Fallthrough: case _Fallthrough:
...@@ -234,12 +242,10 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -234,12 +242,10 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
// "for", "switch", or "select" statement, and that is the one // "for", "switch", or "select" statement, and that is the one
// whose execution terminates." // whose execution terminates."
if t := ls.enclosingTarget(b, name); t != nil { if t := ls.enclosingTarget(b, name); t != nil {
valid := false switch t := t.Stmt.(type) {
switch t.Stmt.(type) {
case *SwitchStmt, *SelectStmt, *ForStmt: case *SwitchStmt, *SelectStmt, *ForStmt:
valid = true s.Target = t
} default:
if !valid {
ls.err(s.Label.Pos(), "invalid break label %s", name) ls.err(s.Label.Pos(), "invalid break label %s", name)
} }
} else { } else {
...@@ -250,7 +256,9 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -250,7 +256,9 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
// spec: "If there is a label, it must be that of an enclosing // spec: "If there is a label, it must be that of an enclosing
// "for" statement, and that is the one whose execution advances." // "for" statement, and that is the one whose execution advances."
if t := ls.enclosingTarget(b, name); t != nil { if t := ls.enclosingTarget(b, name); t != nil {
if _, ok := t.Stmt.(*ForStmt); !ok { if t, ok := t.Stmt.(*ForStmt); ok {
s.Target = t
} else {
ls.err(s.Label.Pos(), "invalid continue label %s", name) ls.err(s.Label.Pos(), "invalid continue label %s", name)
} }
} else { } else {
...@@ -258,7 +266,9 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -258,7 +266,9 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
} }
case _Goto: case _Goto:
if ls.gotoTarget(b, name) == nil { if t := ls.gotoTarget(b, name); t != nil {
s.Target = t
} else {
// label may be declared later - add goto to forward gotos // label may be declared later - add goto to forward gotos
fwdGotos = append(fwdGotos, s) fwdGotos = append(fwdGotos, s)
} }
...@@ -275,27 +285,27 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS ...@@ -275,27 +285,27 @@ func (ls *labelScope) blockBranches(parent *block, context uint, lstmt *LabeledS
} }
case *BlockStmt: case *BlockStmt:
// Unresolved forward gotos from the nested block innerBlock(ctxt, s.Pos(), s.List)
// become forward gotos for the current block.
innerBlock(0, s.Pos(), s.List)
case *IfStmt: case *IfStmt:
innerBlock(0, s.Then.Pos(), s.Then.List) innerBlock(ctxt, s.Then.Pos(), s.Then.List)
if s.Else != nil { if s.Else != nil {
innerBlock(0, s.Else.Pos(), []Stmt{s.Else}) innerBlock(ctxt, s.Else.Pos(), []Stmt{s.Else})
} }
case *ForStmt: case *ForStmt:
innerBlock(breakOk|continueOk, s.Body.Pos(), s.Body.List) innerBlock(targets{s, s}, s.Body.Pos(), s.Body.List)
case *SwitchStmt: case *SwitchStmt:
inner := targets{s, ctxt.continues}
for _, cc := range s.Body { for _, cc := range s.Body {
innerBlock(breakOk, cc.Pos(), cc.Body) innerBlock(inner, cc.Pos(), cc.Body)
} }
case *SelectStmt: case *SelectStmt:
inner := targets{s, ctxt.continues}
for _, cc := range s.Body { for _, cc := range s.Body {
innerBlock(breakOk, cc.Pos(), cc.Body) innerBlock(inner, cc.Pos(), cc.Body)
} }
} }
} }
......
...@@ -14,7 +14,7 @@ func TestDump(t *testing.T) { ...@@ -14,7 +14,7 @@ func TestDump(t *testing.T) {
t.Skip("skipping test in short mode") t.Skip("skipping test in short mode")
} }
ast, err := ParseFile(*src_, nil, nil, 0) ast, err := ParseFile(*src_, nil, nil, CheckBranches)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -356,6 +356,12 @@ type ( ...@@ -356,6 +356,12 @@ type (
BranchStmt struct { BranchStmt struct {
Tok token // Break, Continue, Fallthrough, or Goto Tok token // Break, Continue, Fallthrough, or Goto
Label *Name Label *Name
// Target is the continuation of the control flow after executing
// the branch; it is computed by the parser if CheckBranches is set.
// Target is a *LabeledStmt for gotos, and a *SwitchStmt, *SelectStmt,
// or *ForStmt for breaks and continues, depending on the context of
// the branch. Target is not set for fallthroughs.
Target Stmt
stmt stmt
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment