Skip to content

Commit

Permalink
fix: correct control flow graph for switch statement
Browse files Browse the repository at this point in the history
  • Loading branch information
mvertes committed Mar 25, 2020
1 parent 4995654 commit ebde09b
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 17 deletions.
16 changes: 16 additions & 0 deletions _test/switch27.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

func main() {
//a := false
switch false {
case true:
println("true")
case false:
println("false")
}
println("bye")
}

// Output:
// false
// bye
15 changes: 15 additions & 0 deletions _test/switch28.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package main

func main() {
switch {
case true:
println("true")
case false:
println("false")
}
println("bye")
}

// Output:
// true
// bye
14 changes: 14 additions & 0 deletions _test/switch29.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

func main() {
a := 3
switch a {
case 3:
println("three")
}
println("bye")
}

// Output:
// three
// bye
16 changes: 16 additions & 0 deletions _test/switch30.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

func main() {
a := 3
switch a {
default:
//println("default")
case 3:
println("three")
}
println("bye")
}

// Output:
// three
// bye
10 changes: 10 additions & 0 deletions _test/switch31.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

func main() {
switch {
}
println("bye")
}

// Output:
// bye
11 changes: 11 additions & 0 deletions _test/switch32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

func main() {
a := 1
switch a {
}
println("bye", a)
}

// Output:
// bye 1
11 changes: 11 additions & 0 deletions _test/switch33.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

func main() {
var a interface{}
switch a.(type) {
}
println("bye")
}

// Output:
// bye
61 changes: 44 additions & 17 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sc = sc.pushBloc()

case switchStmt, switchIfStmt, typeSwitch:
// Make sure default clause is in last position
// Make sure default clause is in last position.
c := n.lastChild().child
if i, l := getDefault(n), len(c)-1; i >= 0 && i != l {
c[i], c[l] = c[l], c[i]
Expand Down Expand Up @@ -1378,33 +1378,56 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
fallthrough

case switchStmt:
sc = sc.pop()
sbn := n.lastChild() // switch block node
clauses := sbn.child
l := len(clauses)
// Chain case clauses
if l == 0 {
// Switch is empty
break
}
// Chain case clauses.
for i, c := range clauses[:l-1] {
c.fnext = clauses[i+1] // chain to next clause
body := c.lastChild()
c.tnext = body.start
if len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt {
if n.kind == typeSwitch {
err = body.lastChild().cfgErrorf("cannot fallthrough in type switch")
}
body.tnext = clauses[i+1].lastChild().start
c.fnext = clauses[i+1] // Chain to next clause.
if len(c.child) == 0 {
c.tnext = n // Clause body is empty, exit.
} else {
body.tnext = n
body := c.lastChild()
c.tnext = body.start
if len(body.child) > 0 && body.lastChild().kind == fallthroughtStmt {
if n.kind == typeSwitch {
err = body.lastChild().cfgErrorf("cannot fallthrough in type switch")
}
if len(clauses[i+1].child) == 0 {
body.tnext = n // Fallthrough to next with empty body, just exit.
} else {
body.tnext = clauses[i+1].lastChild().start
}
} else {
body.tnext = n // Exit switch at end of clause body.
}
}
}
c := clauses[l-1]
c.tnext = c.lastChild().start
c := clauses[l-1] // Last clause.
if len(c.child) == 0 {
c.tnext = n // Clause body is empty, exit.
} else {
body := c.lastChild()
c.tnext = body.start
body.tnext = n
}
n.start = n.child[0].start
n.child[0].tnext = sbn.start
sc = sc.pop()

case switchIfStmt: // like an if-else chain
sc = sc.pop()
sbn := n.lastChild() // switch block node
clauses := sbn.child
l := len(clauses)
if l == 0 {
// Switch is empty
break
}
// Wire case clauses in reverse order so the next start node is already resolved when used.
for i := l - 1; i >= 0; i-- {
c := clauses[i]
Expand Down Expand Up @@ -1432,7 +1455,6 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
sbn.start = clauses[0].start
n.start = n.child[0].start
n.child[0].tnext = sbn.start
sc = sc.pop()

case typeAssertExpr:
if len(n.child) > 1 {
Expand Down Expand Up @@ -1646,11 +1668,16 @@ func genRun(nod *node) error {
return err
}

// Find default case clause index of a switch statement, if any
// GetDefault return the index of default case clause in a switch statement, or -1.
func getDefault(n *node) int {
for i, c := range n.lastChild().child {
if len(c.child) == 1 {
switch len(c.child) {
case 0:
return i
case 1:
if c.child[0].kind == caseBody {
return i
}
}
}
return -1
Expand Down

0 comments on commit ebde09b

Please sign in to comment.