├── flow ├── show ├── testdata │ ├── cfg-triv.go │ ├── cfg-return.go │ ├── cfg-assign.go │ ├── cfg-if.go │ ├── cfg-andand.go │ ├── cfg-addr.go │ ├── flowdef-loop.go │ ├── cfg-goto.go │ ├── cfg-for.go │ ├── cfg-triv.dot │ ├── cfg-range.go │ ├── cfg-select.go │ ├── cfg-typeswitch.go │ ├── cfg-bug2.go │ ├── cfg-switch.go │ ├── cfg-return.dot │ ├── cfg-goto.dot │ ├── cfg-if.dot │ ├── cfg-for.dot │ ├── cfg-andand.dot │ ├── cfg-range.dot │ ├── cfg-typeswitch.dot │ ├── cfg-addr.dot │ ├── cfg-assign.dot │ ├── cfg-select.dot │ ├── flowdef-loop.dot │ ├── cfg-bug2.dot │ ├── flowdef-loop.reach │ └── cfg-switch.dot ├── cfg_test.go └── cfg.go ├── README.md ├── vardecl ├── testdata │ ├── grind-bug3.go │ ├── grind-bug3.go.out │ ├── grind-bug1.go │ ├── grind-loopreuse.go │ ├── grind-loopreuse.go.out │ ├── grind-issue5.go │ ├── grind-addr1.go │ ├── grind-bug2.go │ ├── grind-bug5.go.out │ ├── grind-bug4.go.out │ ├── grind-loop.go.out │ ├── grind-bug5.go │ ├── grind-bug4.go │ ├── grind-addr2.go │ ├── grind-loop.go │ ├── grind-addr2.go.out │ ├── grind-overlap.go │ ├── grind-overlap.go.out │ ├── grind-init2decl.go.out │ ├── grind-init2decl.go │ ├── grind-switch.go.out │ ├── grind-switch.go │ ├── grind-goto.go.out │ ├── grind-goto.go │ ├── grind-addr.go.out │ └── grind-addr.go ├── vardecl_test.go └── vardecl.go ├── deadcode ├── testdata │ ├── grind-deadend.go.out │ ├── grind-deadend.go │ ├── grind-cplx.go.out │ ├── grind-dead.go.out │ ├── grind-cplx.go │ └── grind-dead.go ├── deadcode_test.go └── deadcode.go ├── block ├── testdata │ ├── block-basic.go │ ├── block-switch.go │ ├── block-basic.dump │ └── block-switch.dump ├── block_test.go └── block.go ├── testdata ├── grind-unusedlabel.go.out └── grind-unusedlabel.go ├── gotoinline ├── testdata │ ├── grind-fwd.go.out │ ├── grind-const.go.out │ ├── grind-fwd.go │ ├── grind-decl.go.out │ ├── grind-comment.go.out │ ├── grind-const.go │ ├── grind-decl.go │ ├── grind-comment.go │ ├── grind-basic.go.out │ ├── grind-basic.go │ ├── grind-return.go.out │ ├── grind-return.go │ └── grind-shadow.go ├── gotoinline_test.go └── gotoinline.go ├── grind_test.go ├── unusedlabel.go ├── LICENSE ├── grindtest └── test.go ├── main.go ├── doc.go └── grinder ├── ast.go ├── grind.go └── edit.go /flow/show: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dot -Tsvg $1 >x.svg && open x.svg 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go get [-u] rsc.io/grind 2 | 3 | http://godoc.org/rsc.io/grind 4 | -------------------------------------------------------------------------------- /flow/testdata/cfg-triv.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | g() 5 | h() 6 | } 7 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug3.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var x int 5 | { 6 | x = x + 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-deadend.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | f() 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug3.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | { 5 | var x int 6 | x = x + 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /block/testdata/block-basic.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if x { 5 | y 6 | } else { 7 | { 8 | z 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /flow/testdata/cfg-return.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() (int, int, int) { 4 | if ret { 5 | return r1, r2, r3 6 | } 7 | return g() 8 | } 9 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-deadend.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | f() 7 | return 8 | goto bad 9 | bad: 10 | } 11 | -------------------------------------------------------------------------------- /testdata/grind-unusedlabel.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | } 5 | 6 | func f1() { 7 | if b { 8 | println() 9 | } 10 | } 11 | 12 | var b bool 13 | -------------------------------------------------------------------------------- /flow/testdata/cfg-assign.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | x = y 5 | more() 6 | (x) = y + z 7 | more() 8 | x[1] = y + z 9 | more() 10 | *x = y + z 11 | } 12 | -------------------------------------------------------------------------------- /flow/testdata/cfg-if.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | f() 5 | if x { 6 | g() 7 | } 8 | h() 9 | if x { 10 | i() 11 | } else { 12 | j() 13 | } 14 | k() 15 | } 16 | -------------------------------------------------------------------------------- /testdata/grind-unusedlabel.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | bad: 5 | } 6 | 7 | func f1() { 8 | if b { 9 | bad: 10 | println() 11 | } 12 | } 13 | 14 | var b bool 15 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug1.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var y int 4 | 5 | func g() bool 6 | 7 | func f() int { 8 | var x int 9 | for g() { 10 | x = y 11 | } 12 | return x 13 | } 14 | -------------------------------------------------------------------------------- /flow/testdata/cfg-andand.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if x && y { 5 | g() 6 | } 7 | 8 | if x || y { 9 | h() 10 | } 11 | 12 | if !x || y { 13 | j() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-fwd.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | println(x) 8 | return 9 | } 10 | return 11 | } 12 | 13 | var x int 14 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-loopreuse.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var i int 5 | 6 | { 7 | for { 8 | use(i) 9 | i++ 10 | } 11 | } 12 | } 13 | 14 | func use(interface{}) 15 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-loopreuse.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | { 5 | var i int 6 | for { 7 | use(i) 8 | i++ 9 | } 10 | } 11 | } 12 | 13 | func use(interface{}) 14 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-issue5.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func test(s string) []string { 4 | var p []string 5 | switch s { 6 | case "test": 7 | p = append(p, "hello") 8 | } 9 | return p 10 | } 11 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-const.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | f() 8 | goto ret 9 | } 10 | goto ret 11 | 12 | ret: 13 | f() 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-fwd.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | goto bad 8 | } 9 | return 10 | 11 | bad: 12 | println(x) 13 | } 14 | 15 | var x int 16 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-cplx.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func dead() 4 | 5 | func f(x int) { 6 | for { 7 | switch x { 8 | case 1: 9 | break 10 | case 2: 11 | continue 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-decl.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | if cond { 8 | x := 1 9 | println(x) 10 | } 11 | return 12 | } 13 | return 14 | } 15 | -------------------------------------------------------------------------------- /flow/testdata/cfg-addr.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var ss serializedFileSet 5 | if err := decode(&ss); err != nil { 6 | return err 7 | } 8 | 9 | s.mutex.Lock() 10 | s.base = ss.Base 11 | } 12 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-comment.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if cond { 5 | // comment 6 | 7 | // more comment 8 | f() 9 | return 10 | } 11 | return 12 | } 13 | 14 | var cond bool 15 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-const.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | goto unary 8 | } 9 | goto ret 10 | 11 | unary: 12 | f() 13 | 14 | ret: 15 | f() 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-decl.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func f() { 6 | if cond { 7 | goto bad 8 | } 9 | return 10 | 11 | bad: 12 | if cond { 13 | x := 1 14 | println(x) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-comment.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if cond { 5 | goto bad 6 | } 7 | return 8 | 9 | // comment 10 | 11 | // more comment 12 | bad: 13 | f() 14 | } 15 | 16 | var cond bool 17 | -------------------------------------------------------------------------------- /flow/testdata/flowdef-loop.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var i int 5 | i++ 6 | 7 | for i = 0; i < 10; i++ { 8 | use(i) 9 | } 10 | 11 | for i = 0; i < 10; i++ { 12 | use(i) 13 | } 14 | 15 | use(i) 16 | } 17 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-dead.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var b bool 4 | 5 | func f(x int) { 6 | if b { 7 | if b { 8 | return 9 | } 10 | if b { 11 | goto Y 12 | } 13 | return 14 | } 15 | return 16 | Y: 17 | f(3) 18 | } 19 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-basic.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if b { 5 | f() 6 | f() 7 | f() 8 | goto bar 9 | } 10 | goto bar 11 | 12 | bar: 13 | baz() 14 | return 15 | } 16 | 17 | func baz() 18 | 19 | var b bool 20 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-cplx.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func dead() 4 | 5 | func f(x int) { 6 | for { 7 | switch x { 8 | case 1: 9 | break 10 | dead() 11 | case 2: 12 | continue 13 | dead() 14 | } 15 | } 16 | dead() 17 | } 18 | -------------------------------------------------------------------------------- /block/testdata/block-switch.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | for { 5 | x 6 | } 7 | 8 | switch x { 9 | case 1: 10 | y 11 | case 2: 12 | z 13 | } 14 | 15 | switch x.(type) { 16 | case T: 17 | y 18 | case T1: 19 | z 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /flow/testdata/cfg-goto.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | top() 5 | L1: 6 | l1() 7 | if gotoL1() { 8 | goto L1 9 | } 10 | if gotoL2() { 11 | goto L2 12 | } 13 | beforeL2() 14 | L2: 15 | l2() 16 | if gotoL1x() { 17 | goto L1 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-basic.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if b { 5 | goto foo 6 | } 7 | goto bar 8 | 9 | foo: 10 | f() 11 | f() 12 | f() 13 | goto bar 14 | 15 | bar: 16 | baz() 17 | return 18 | } 19 | 20 | func baz() 21 | 22 | var b bool 23 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-addr1.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | type T struct { 4 | X int 5 | } 6 | 7 | func decode(*T) error 8 | 9 | func f() error { 10 | var t T 11 | if err := decode(&t); err != nil { 12 | return err 13 | } 14 | print(t.X) 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug2.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() int { 4 | var x int 5 | fail := func() int { return 1 } 6 | for { 7 | x++ 8 | if x == 100 { 9 | break 10 | } 11 | if x == 99 { 12 | return fail() 13 | } 14 | } 15 | return 2 16 | } 17 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug5.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func oploop() { 6 | a1 := int(1) 7 | if cond { 8 | a1 = f() 9 | g = int8(a1) 10 | } 11 | a1-- 12 | _ = x[a1] 13 | } 14 | 15 | func f() int 16 | 17 | var g int8 18 | var x []int 19 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug4.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f(b bool) { 4 | var x [1]string 5 | 6 | if b { 7 | for { 8 | x[0] = "xxx" 9 | break 10 | } 11 | } 12 | 13 | for i := 0; i < 10; i++ { 14 | if x[i] == "yyy" { 15 | break 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-loop.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | for i := 0; i < 10; i++ { 5 | use(i) 6 | } 7 | 8 | for i := 0; i < 10; i++ { 9 | use(i) 10 | } 11 | 12 | var j int 13 | for j += 1; j < 10; j++ { 14 | } 15 | } 16 | 17 | func use(interface{}) 18 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug5.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var cond bool 4 | 5 | func oploop() { 6 | var a1 int 7 | a1 = int(1) 8 | if cond { 9 | a1 = f() 10 | g = int8(a1) 11 | } 12 | a1-- 13 | _ = x[a1] 14 | } 15 | 16 | func f() int 17 | 18 | var g int8 19 | var x []int 20 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-bug4.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f(b bool) { 4 | var x [1]string 5 | var i int 6 | 7 | if b { 8 | for { 9 | x[0] = "xxx" 10 | break 11 | } 12 | } 13 | 14 | for i = 0; i < 10; i++ { 15 | if x[i] == "yyy" { 16 | break 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-addr2.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var x int 5 | var z int 6 | { 7 | switch z { 8 | case 1: 9 | use(&x) 10 | use(x) 11 | use(x) 12 | case 2: 13 | use(&x) 14 | use(x) 15 | use(x) 16 | } 17 | } 18 | } 19 | 20 | func use(interface{}) 21 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-loop.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var i int 5 | 6 | for i = 0; i < 10; i++ { 7 | use(i) 8 | } 9 | 10 | for i = 0; i < 10; i++ { 11 | use(i) 12 | } 13 | 14 | var j int 15 | for j += 1; j < 10; j++ { 16 | } 17 | } 18 | 19 | func use(interface{}) 20 | -------------------------------------------------------------------------------- /deadcode/testdata/grind-dead.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | var b bool 4 | 5 | func f(x int) { 6 | if b { 7 | if b { 8 | return 9 | } 10 | if b { 11 | goto Y 12 | } 13 | return 14 | goto X 15 | goto Z 16 | } 17 | return 18 | 19 | X: 20 | f(1) 21 | Z: 22 | f(2) 23 | Y: 24 | f(3) 25 | } 26 | -------------------------------------------------------------------------------- /flow/testdata/cfg-for.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | L: 5 | for pre(); cond(); post() { 6 | body() 7 | if brk() { 8 | break 9 | } 10 | if cont() { 11 | continue 12 | } 13 | if brkL() { 14 | break L 15 | } 16 | if contL() { 17 | continue L 18 | } 19 | more() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /flow/testdata/cfg-triv.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n4 [label="_cfg_end_ *ast.Ident :0"]; 3 | n3 [label="h *ast.Ident testdata/cfg-triv.go:5"]; 4 | n3 -> n4 [label=""]; 5 | n2 [label="g *ast.Ident testdata/cfg-triv.go:4"]; 6 | n2 -> n3 [label=""]; 7 | n1 [label="_cfg_start_ *ast.Ident :0"]; 8 | n1 -> n2 [label=""]; 9 | } 10 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-addr2.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | { 5 | var z int 6 | switch z { 7 | case 1: 8 | var x int 9 | use(&x) 10 | use(x) 11 | use(x) 12 | case 2: 13 | var x int 14 | use(&x) 15 | use(x) 16 | use(x) 17 | } 18 | } 19 | } 20 | 21 | func use(interface{}) 22 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-return.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if b { 5 | return 6 | } 7 | if b { 8 | return 9 | } 10 | baz() 11 | } 12 | 13 | func f2() { 14 | if b { 15 | return 16 | } 17 | if b { 18 | return 19 | } 20 | baz() 21 | 22 | return 23 | } 24 | 25 | func baz() 26 | 27 | var b bool 28 | -------------------------------------------------------------------------------- /flow/testdata/cfg-range.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | before() 5 | L: 6 | for k, v = range expr { 7 | body() 8 | if brk() { 9 | break 10 | } 11 | if cont() { 12 | continue 13 | } 14 | if brkL() { 15 | break L 16 | } 17 | if contL() { 18 | continue L 19 | } 20 | more() 21 | } 22 | after() 23 | } 24 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-overlap.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var x int 5 | { 6 | if b1 { 7 | x = 2 8 | } else if b2 { 9 | x = 4 10 | x = 2 * x 11 | } else { 12 | x = 3 13 | } 14 | use(x) 15 | } 16 | { 17 | x = 2 18 | use(x) 19 | } 20 | } 21 | 22 | var b1, b2 bool 23 | 24 | func use(interface{}) 25 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-overlap.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | { 5 | var x int 6 | if b1 { 7 | x = 2 8 | } else if b2 { 9 | x = 4 10 | x = 2 * x 11 | } else { 12 | x = 3 13 | } 14 | use(x) 15 | } 16 | { 17 | x := 2 18 | use(x) 19 | } 20 | } 21 | 22 | var b1, b2 bool 23 | 24 | func use(interface{}) 25 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-return.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | if b { 5 | goto ret 6 | } 7 | if b { 8 | goto ret 9 | } 10 | baz() 11 | 12 | ret: 13 | } 14 | 15 | func f2() { 16 | if b { 17 | goto ret 18 | } 19 | if b { 20 | goto ret 21 | } 22 | baz() 23 | 24 | ret: 25 | return 26 | } 27 | 28 | func baz() 29 | 30 | var b bool 31 | -------------------------------------------------------------------------------- /flow/testdata/cfg-select.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | L: 5 | select { 6 | case lhs1 := <-rhs1: 7 | body1() 8 | if brk { 9 | break 10 | } 11 | more1() 12 | case <-rhs2: 13 | body2() 14 | if brk { 15 | break L 16 | } 17 | more2() 18 | case lhs3 = <-rhs3: 19 | body3() 20 | case lhs4 <- rhs4: 21 | body4() 22 | default: 23 | bodyDefault() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /block/testdata/block-basic.dump: -------------------------------------------------------------------------------- 1 | 0: depth=0 child=1 2 | 1: depth=1 parent=0 child=2 root=*ast.BlockStmt testdata/block-basic.go:3 3 | 2: depth=2 parent=1 child=3,4 root=*ast.IfStmt testdata/block-basic.go:4 4 | 3: depth=3 parent=2 root=*ast.BlockStmt testdata/block-basic.go:4 5 | 4: depth=3 parent=2 child=5 root=*ast.BlockStmt testdata/block-basic.go:6 6 | 5: depth=4 parent=4 root=*ast.BlockStmt testdata/block-basic.go:7 7 | -------------------------------------------------------------------------------- /flow/testdata/cfg-typeswitch.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | L: 5 | switch x := expr.(type) { 6 | case case1, case2, case3: 7 | body123() 8 | case case4, case5, case6: 9 | body456() 10 | case case7: 11 | body7() 12 | if brk { 13 | break 14 | } 15 | more7() 16 | default: 17 | bodyDefault() 18 | case case8: 19 | body8() 20 | if brkL { 21 | break L 22 | } 23 | more8() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-init2decl.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | type T map[int]int 4 | type X [10]int 5 | type S struct { 6 | X, Y, Z int 7 | } 8 | 9 | func f() { 10 | var x *T 11 | var y X 12 | var z [12]int 13 | var w struct{ X, Y int } 14 | var a S 15 | b := struct{ X, Y int }{1, 2} 16 | var c map[string]int 17 | var d chan int 18 | 19 | _ = x 20 | _ = y 21 | _ = z 22 | _ = w 23 | _ = a 24 | _ = b 25 | _ = c 26 | _ = d 27 | } 28 | -------------------------------------------------------------------------------- /deadcode/deadcode_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package deadcode 6 | 7 | import ( 8 | "testing" 9 | 10 | "rsc.io/grind/grinder" 11 | "rsc.io/grind/grindtest" 12 | ) 13 | 14 | func TestDeadcode(t *testing.T) { 15 | grindtest.TestGlob(t, "testdata/grind-*.go", []grinder.Func{Grind}) 16 | } 17 | -------------------------------------------------------------------------------- /gotoinline/gotoinline_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gotoinline 6 | 7 | import ( 8 | "testing" 9 | 10 | "rsc.io/grind/grinder" 11 | "rsc.io/grind/grindtest" 12 | ) 13 | 14 | func TestGrind(t *testing.T) { 15 | grindtest.TestGlob(t, "testdata/grind-*.go", []grinder.Func{Grind}) 16 | } 17 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-init2decl.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | type T map[int]int 4 | type X [10]int 5 | type S struct { 6 | X, Y, Z int 7 | } 8 | 9 | func f() { 10 | x := (*T)(nil) 11 | y := X{} 12 | z := [12]int{} 13 | w := struct{ X, Y int }{} 14 | a := S{} 15 | b := struct{ X, Y int }{1, 2} 16 | c := (map[string]int)(nil) 17 | d := (chan int)(nil) 18 | 19 | _ = x 20 | _ = y 21 | _ = z 22 | _ = w 23 | _ = a 24 | _ = b 25 | _ = c 26 | _ = d 27 | } 28 | -------------------------------------------------------------------------------- /grind_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "testing" 9 | 10 | "rsc.io/grind/grinder" 11 | "rsc.io/grind/grindtest" 12 | ) 13 | 14 | var builtins = []grinder.Func{ 15 | DeleteUnusedLabels, 16 | } 17 | 18 | func TestGrind(t *testing.T) { 19 | grindtest.TestGlob(t, "testdata/grind-*.go", builtins) 20 | } 21 | -------------------------------------------------------------------------------- /flow/testdata/cfg-bug2.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | import "encoding/pem" 4 | 5 | func f() { 6 | var cert Certificate 7 | var certDERBlock *pem.Block 8 | fail := func(err error) (Certificate, error) { return Certificate{}, err } 9 | for { 10 | certDERBlock, certPEMBlock = pem.Decode(certPEMBlock) 11 | if certDERBlock == nil { 12 | break 13 | } 14 | if certDERBlock.Type == "CERTIFICATE" { 15 | cert.Certificate = append(cert.Certificate, certDERBlock.Bytes) 16 | } 17 | break 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /gotoinline/testdata/grind-shadow.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f1() int { 4 | c := 1 5 | { 6 | c := 2 7 | use(c) 8 | goto ret 9 | } 10 | return 0 11 | 12 | ret: 13 | use(c) 14 | return 1 15 | } 16 | 17 | func f2() (c int) { 18 | c = 1 19 | { 20 | c := 2 21 | use(c) 22 | goto ret 23 | } 24 | return 0 25 | 26 | ret: 27 | return 28 | } 29 | 30 | func f3() (c int) { 31 | c = 1 32 | { 33 | c := 2 34 | use(c) 35 | goto ret 36 | } 37 | return 0 38 | 39 | ret: 40 | return c 41 | } 42 | 43 | func use(interface{}) 44 | -------------------------------------------------------------------------------- /flow/testdata/cfg-switch.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | L: 5 | switch expr { 6 | case case1, case2, case3: 7 | body123() 8 | case case4, case5, case6: 9 | body456() 10 | fall() 11 | fallthrough 12 | case case7: 13 | body7() 14 | if brk { 15 | break 16 | } 17 | more7() 18 | default: 19 | bodyDefault() 20 | case case8: 21 | body8() 22 | if brkL { 23 | break L 24 | } 25 | more8() 26 | } 27 | 28 | switch expr { 29 | default: 30 | bodyDefault() 31 | fallthrough 32 | case case1, case2, case3: 33 | body123() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /flow/testdata/cfg-return.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n6 [label="_cfg_end_ *ast.Ident :0"]; 3 | n5 [label="r3 *ast.Ident testdata/cfg-return.go:5"]; 4 | n5 -> n6 [label=""]; 5 | n4 [label="r2 *ast.Ident testdata/cfg-return.go:5"]; 6 | n4 -> n5 [label=""]; 7 | n3 [label="r1 *ast.Ident testdata/cfg-return.go:5"]; 8 | n3 -> n4 [label=""]; 9 | n7 [label="g *ast.Ident testdata/cfg-return.go:7"]; 10 | n7 -> n6 [label=""]; 11 | n2 [label="ret *ast.Ident testdata/cfg-return.go:4"]; 12 | n2 -> n3 [label=""]; 13 | n2 -> n7 [label=""]; 14 | n1 [label="_cfg_start_ *ast.Ident :0"]; 15 | n1 -> n2 [label=""]; 16 | } 17 | -------------------------------------------------------------------------------- /block/testdata/block-switch.dump: -------------------------------------------------------------------------------- 1 | 0: depth=0 child=1 2 | 1: depth=1 parent=0 child=2,4,7 root=*ast.BlockStmt testdata/block-switch.go:3 3 | 2: depth=2 parent=1 child=3 root=*ast.ForStmt testdata/block-switch.go:4 4 | 3: depth=3 parent=2 root=*ast.BlockStmt testdata/block-switch.go:4 5 | 4: depth=2 parent=1 child=5,6 root=*ast.SwitchStmt testdata/block-switch.go:8 6 | 5: depth=3 parent=4 root=*ast.CaseClause testdata/block-switch.go:9 7 | 6: depth=3 parent=4 root=*ast.CaseClause testdata/block-switch.go:11 8 | 7: depth=2 parent=1 child=8,9 root=*ast.TypeSwitchStmt testdata/block-switch.go:15 9 | 8: depth=3 parent=7 root=*ast.CaseClause testdata/block-switch.go:16 10 | 9: depth=3 parent=7 root=*ast.CaseClause testdata/block-switch.go:18 11 | -------------------------------------------------------------------------------- /flow/testdata/cfg-goto.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n8 [label="_cfg_end_ *ast.Ident :0"]; 3 | n7 [label="gotoL1x *ast.Ident testdata/cfg-goto.go:16"]; 4 | n7 -> n3 [label=""]; 5 | n7 -> n8 [label=""]; 6 | n6 [label="l2 *ast.Ident testdata/cfg-goto.go:15"]; 7 | n6 -> n7 [label=""]; 8 | n9 [label="beforeL2 *ast.Ident testdata/cfg-goto.go:13"]; 9 | n9 -> n6 [label=""]; 10 | n5 [label="gotoL2 *ast.Ident testdata/cfg-goto.go:10"]; 11 | n5 -> n6 [label=""]; 12 | n5 -> n9 [label=""]; 13 | n4 [label="gotoL1 *ast.Ident testdata/cfg-goto.go:7"]; 14 | n4 -> n3 [label=""]; 15 | n4 -> n5 [label=""]; 16 | n3 [label="l1 *ast.Ident testdata/cfg-goto.go:6"]; 17 | n3 -> n4 [label=""]; 18 | n2 [label="top *ast.Ident testdata/cfg-goto.go:4"]; 19 | n2 -> n3 [label=""]; 20 | n1 [label="_cfg_start_ *ast.Ident :0"]; 21 | n1 -> n2 [label=""]; 22 | } 23 | -------------------------------------------------------------------------------- /flow/testdata/cfg-if.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n9 [label="_cfg_end_ *ast.Ident :0"]; 3 | n8 [label="k *ast.Ident testdata/cfg-if.go:14"]; 4 | n8 -> n9 [label=""]; 5 | n7 [label="i *ast.Ident testdata/cfg-if.go:10"]; 6 | n7 -> n8 [label=""]; 7 | n10 [label="j *ast.Ident testdata/cfg-if.go:12"]; 8 | n10 -> n8 [label=""]; 9 | n6 [label="x *ast.Ident testdata/cfg-if.go:9"]; 10 | n6 -> n7 [label=""]; 11 | n6 -> n10 [label=""]; 12 | n5 [label="h *ast.Ident testdata/cfg-if.go:8"]; 13 | n5 -> n6 [label=""]; 14 | n4 [label="g *ast.Ident testdata/cfg-if.go:6"]; 15 | n4 -> n5 [label=""]; 16 | n3 [label="x *ast.Ident testdata/cfg-if.go:5"]; 17 | n3 -> n4 [label=""]; 18 | n3 -> n5 [label=""]; 19 | n2 [label="f *ast.Ident testdata/cfg-if.go:4"]; 20 | n2 -> n3 [label=""]; 21 | n1 [label="_cfg_start_ *ast.Ident :0"]; 22 | n1 -> n2 [label=""]; 23 | } 24 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-switch.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | switch x { 5 | case 1: 6 | for i := 0; i < 10; i++ { 7 | use(i) 8 | } 9 | case 2: 10 | for i := 0; i < 10; i++ { 11 | use(i) 12 | } 13 | } 14 | 15 | switch x { 16 | case 1: 17 | var j int 18 | for j = 0; j < 10; j++ { 19 | _ = &j 20 | use(j) 21 | } 22 | for j = 0; j < 10; j++ { 23 | _ = &j 24 | use(j) 25 | } 26 | case 2: 27 | for j := 0; j < 10; j++ { 28 | use(j) 29 | } 30 | } 31 | 32 | var k int 33 | switch x { 34 | case 1: 35 | for k = 0; k < 10; k++ { 36 | _ = &k 37 | use(k) 38 | } 39 | for k = 0; k < 10; k++ { 40 | _ = &k 41 | use(k) 42 | } 43 | case 2: 44 | for k = 0; k < 10; k++ { 45 | use(k) 46 | } 47 | } 48 | use(k) 49 | } 50 | 51 | var x int 52 | 53 | func use(interface{}) 54 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-switch.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func f() { 4 | var i int 5 | switch x { 6 | case 1: 7 | for i = 0; i < 10; i++ { 8 | use(i) 9 | } 10 | case 2: 11 | for i = 0; i < 10; i++ { 12 | use(i) 13 | } 14 | } 15 | 16 | var j int 17 | switch x { 18 | case 1: 19 | for j = 0; j < 10; j++ { 20 | _ = &j 21 | use(j) 22 | } 23 | for j = 0; j < 10; j++ { 24 | _ = &j 25 | use(j) 26 | } 27 | case 2: 28 | for j = 0; j < 10; j++ { 29 | use(j) 30 | } 31 | } 32 | 33 | var k int 34 | switch x { 35 | case 1: 36 | for k = 0; k < 10; k++ { 37 | _ = &k 38 | use(k) 39 | } 40 | for k = 0; k < 10; k++ { 41 | _ = &k 42 | use(k) 43 | } 44 | case 2: 45 | for k = 0; k < 10; k++ { 46 | use(k) 47 | } 48 | } 49 | use(k) 50 | } 51 | 52 | var x int 53 | 54 | func use(interface{}) 55 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-goto.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func x() 4 | 5 | var b bool 6 | 7 | func use(interface{}) 8 | 9 | func f() { 10 | { 11 | var i int 12 | 13 | x() 14 | i++ 15 | if b { 16 | L1: 17 | goto L1 18 | } 19 | use(i) 20 | } 21 | { 22 | var i int 23 | 24 | L2: 25 | x() 26 | i++ 27 | if b { 28 | goto L2 29 | } 30 | use(i) 31 | } 32 | 33 | { 34 | var i int 35 | 36 | if b { 37 | goto L3 38 | } 39 | i = 10 40 | use(i) 41 | L3: 42 | } 43 | { 44 | { 45 | if b { 46 | goto L4 47 | } 48 | i := 10 49 | use(i) 50 | } 51 | L4: 52 | } 53 | 54 | { 55 | L5: 56 | x() 57 | var i int 58 | i++ 59 | if b { 60 | goto L5 61 | } 62 | use(i) 63 | } 64 | 65 | { 66 | { 67 | if b { 68 | goto L7 69 | } 70 | var i int 71 | use(i) 72 | i = 10 73 | use(i) 74 | } 75 | L7: 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-goto.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | func x() 4 | 5 | var b bool 6 | 7 | func use(interface{}) 8 | 9 | func f() { 10 | { 11 | var i int 12 | 13 | x() 14 | i++ 15 | if b { 16 | L1: 17 | goto L1 18 | } 19 | use(i) 20 | } 21 | { 22 | var i int 23 | 24 | L2: 25 | x() 26 | i++ 27 | if b { 28 | goto L2 29 | } 30 | use(i) 31 | } 32 | 33 | { 34 | var i int 35 | 36 | if b { 37 | goto L3 38 | } 39 | i = 10 40 | use(i) 41 | L3: 42 | } 43 | { 44 | { 45 | var i int 46 | 47 | if b { 48 | goto L4 49 | } 50 | i = 10 51 | use(i) 52 | } 53 | L4: 54 | } 55 | 56 | { 57 | L5: 58 | x() 59 | var i int 60 | i++ 61 | if b { 62 | goto L5 63 | } 64 | use(i) 65 | } 66 | 67 | { 68 | { 69 | if b { 70 | goto L7 71 | } 72 | var i int 73 | use(i) 74 | i = 10 75 | use(i) 76 | } 77 | L7: 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /unusedlabel.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "go/ast" 9 | 10 | "rsc.io/grind/block" 11 | "rsc.io/grind/grinder" 12 | ) 13 | 14 | func DeleteUnusedLabels(ctxt *grinder.Context, pkg *grinder.Package) { 15 | grinder.GrindFuncDecls(ctxt, pkg, func(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { 16 | if fn.Body == nil { 17 | return 18 | } 19 | blocks := block.Build(pkg.FileSet, fn.Body) 20 | ast.Inspect(fn.Body, func(x ast.Node) bool { 21 | switch x := x.(type) { 22 | case *ast.LabeledStmt: 23 | if len(blocks.Goto[x.Label.Name])+len(blocks.Break[x.Label.Name])+len(blocks.Continue[x.Label.Name]) == 0 { 24 | edit.DeleteLine(x.Pos(), x.Colon+1) 25 | } 26 | case ast.Expr: 27 | return false 28 | } 29 | return true 30 | }) 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /flow/testdata/cfg-for.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n6 [label="_cfg_end_ *ast.Ident :0"]; 3 | n8 [label="post *ast.Ident testdata/cfg-for.go:5"]; 4 | n8 -> n3 [label=""]; 5 | n11 [label="more *ast.Ident testdata/cfg-for.go:19"]; 6 | n11 -> n8 [label=""]; 7 | n10 [label="contL *ast.Ident testdata/cfg-for.go:16"]; 8 | n10 -> n8 [label=""]; 9 | n10 -> n11 [label=""]; 10 | n9 [label="brkL *ast.Ident testdata/cfg-for.go:13"]; 11 | n9 -> n6 [label=""]; 12 | n9 -> n10 [label=""]; 13 | n7 [label="cont *ast.Ident testdata/cfg-for.go:10"]; 14 | n7 -> n8 [label=""]; 15 | n7 -> n9 [label=""]; 16 | n5 [label="brk *ast.Ident testdata/cfg-for.go:7"]; 17 | n5 -> n6 [label=""]; 18 | n5 -> n7 [label=""]; 19 | n4 [label="body *ast.Ident testdata/cfg-for.go:6"]; 20 | n4 -> n5 [label=""]; 21 | n3 [label="cond *ast.Ident testdata/cfg-for.go:5"]; 22 | n3 -> n4 [label=""]; 23 | n3 -> n6 [label=""]; 24 | n2 [label="pre *ast.Ident testdata/cfg-for.go:5"]; 25 | n2 -> n3 [label=""]; 26 | n1 [label="_cfg_start_ *ast.Ident :0"]; 27 | n1 -> n2 [label=""]; 28 | } 29 | -------------------------------------------------------------------------------- /flow/testdata/cfg-andand.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n10 [label="_cfg_end_ *ast.Ident :0"]; 3 | n9 [label="j *ast.Ident testdata/cfg-andand.go:13"]; 4 | n9 -> n10 [label=""]; 5 | n8 [label="y *ast.Ident testdata/cfg-andand.go:12"]; 6 | n8 -> n9 [label=""]; 7 | n8 -> n10 [label=""]; 8 | n7 [label="x *ast.Ident testdata/cfg-andand.go:12"]; 9 | n7 -> n8 [label=""]; 10 | n7 -> n9 [label=""]; 11 | n6 [label="h *ast.Ident testdata/cfg-andand.go:9"]; 12 | n6 -> n7 [label=""]; 13 | n11 [label="y *ast.Ident testdata/cfg-andand.go:8"]; 14 | n11 -> n6 [label=""]; 15 | n11 -> n7 [label=""]; 16 | n5 [label="x *ast.Ident testdata/cfg-andand.go:8"]; 17 | n5 -> n6 [label=""]; 18 | n5 -> n11 [label=""]; 19 | n4 [label="g *ast.Ident testdata/cfg-andand.go:5"]; 20 | n4 -> n5 [label=""]; 21 | n3 [label="y *ast.Ident testdata/cfg-andand.go:4"]; 22 | n3 -> n4 [label=""]; 23 | n3 -> n5 [label=""]; 24 | n2 [label="x *ast.Ident testdata/cfg-andand.go:4"]; 25 | n2 -> n3 [label=""]; 26 | n2 -> n5 [label=""]; 27 | n1 [label="_cfg_start_ *ast.Ident :0"]; 28 | n1 -> n2 [label=""]; 29 | } 30 | -------------------------------------------------------------------------------- /flow/testdata/cfg-range.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n9 [label="_cfg_end_ *ast.Ident :0"]; 3 | n8 [label="after *ast.Ident testdata/cfg-range.go:22"]; 4 | n8 -> n9 [label=""]; 5 | n13 [label="more *ast.Ident testdata/cfg-range.go:20"]; 6 | n13 -> n4 [label=""]; 7 | n13 -> n8 [label=""]; 8 | n12 [label="contL *ast.Ident testdata/cfg-range.go:17"]; 9 | n12 -> n4 [label=""]; 10 | n12 -> n8 [label=""]; 11 | n12 -> n13 [label=""]; 12 | n11 [label="brkL *ast.Ident testdata/cfg-range.go:14"]; 13 | n11 -> n8 [label=""]; 14 | n11 -> n12 [label=""]; 15 | n10 [label="cont *ast.Ident testdata/cfg-range.go:11"]; 16 | n10 -> n4 [label=""]; 17 | n10 -> n8 [label=""]; 18 | n10 -> n11 [label=""]; 19 | n7 [label="brk *ast.Ident testdata/cfg-range.go:8"]; 20 | n7 -> n8 [label=""]; 21 | n7 -> n10 [label=""]; 22 | n6 [label="body *ast.Ident testdata/cfg-range.go:7"]; 23 | n6 -> n7 [label=""]; 24 | n5 [label="v *ast.Ident testdata/cfg-range.go:6"]; 25 | n5 -> n6 [label=""]; 26 | n4 [label="k *ast.Ident testdata/cfg-range.go:6"]; 27 | n4 -> n5 [label=""]; 28 | n3 [label="expr *ast.Ident testdata/cfg-range.go:6"]; 29 | n3 -> n4 [label=""]; 30 | n3 -> n8 [label=""]; 31 | n2 [label="before *ast.Ident testdata/cfg-range.go:4"]; 32 | n2 -> n3 [label=""]; 33 | n1 [label="_cfg_start_ *ast.Ident :0"]; 34 | n1 -> n2 [label=""]; 35 | } 36 | -------------------------------------------------------------------------------- /flow/testdata/cfg-typeswitch.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n7 [label="_cfg_end_ *ast.Ident :0"]; 3 | n8 [label="more8 *ast.Ident testdata/cfg-typeswitch.go:23"]; 4 | n8 -> n7 [label=""]; 5 | n6 [label="brkL *ast.Ident testdata/cfg-typeswitch.go:20"]; 6 | n6 -> n7 [label=""]; 7 | n6 -> n8 [label=""]; 8 | n5 [label="body8 *ast.Ident testdata/cfg-typeswitch.go:19"]; 9 | n5 -> n6 [label=""]; 10 | n9 [label="bodyDefault *ast.Ident testdata/cfg-typeswitch.go:17"]; 11 | n9 -> n7 [label=""]; 12 | n12 [label="more7 *ast.Ident testdata/cfg-typeswitch.go:15"]; 13 | n12 -> n7 [label=""]; 14 | n11 [label="brk *ast.Ident testdata/cfg-typeswitch.go:12"]; 15 | n11 -> n7 [label=""]; 16 | n11 -> n12 [label=""]; 17 | n10 [label="body7 *ast.Ident testdata/cfg-typeswitch.go:11"]; 18 | n10 -> n11 [label=""]; 19 | n13 [label="body456 *ast.Ident testdata/cfg-typeswitch.go:9"]; 20 | n13 -> n7 [label=""]; 21 | n14 [label="body123 *ast.Ident testdata/cfg-typeswitch.go:7"]; 22 | n14 -> n7 [label=""]; 23 | n4 [label="x *ast.Ident testdata/cfg-typeswitch.go:5"]; 24 | n4 -> n5 [label=""]; 25 | n4 -> n9 [label=""]; 26 | n4 -> n10 [label=""]; 27 | n4 -> n13 [label=""]; 28 | n4 -> n14 [label=""]; 29 | n3 [label="*ast.AssignStmt testdata/cfg-typeswitch.go:5"]; 30 | n3 -> n4 [label=""]; 31 | n2 [label="expr *ast.Ident testdata/cfg-typeswitch.go:5"]; 32 | n2 -> n3 [label=""]; 33 | n1 [label="_cfg_start_ *ast.Ident :0"]; 34 | n1 -> n2 [label=""]; 35 | } 36 | -------------------------------------------------------------------------------- /block/block_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package block 6 | 7 | import ( 8 | "bytes" 9 | "go/ast" 10 | "go/parser" 11 | "go/token" 12 | "io/ioutil" 13 | "path/filepath" 14 | "strings" 15 | "testing" 16 | ) 17 | 18 | func TestBlockGolden(t *testing.T) { 19 | matches, err := filepath.Glob("testdata/block-*.go") 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | if len(matches) == 0 { 24 | t.Fatal("no testdata found") 25 | } 26 | fset := token.NewFileSet() 27 | for _, file := range matches { 28 | f, err := parser.ParseFile(fset, file, nil, 0) 29 | if err != nil { 30 | t.Error(err) 31 | continue 32 | } 33 | if len(f.Decls) == 0 { 34 | t.Errorf("%s: no decls", file) 35 | continue 36 | } 37 | fn, ok := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) 38 | if !ok { 39 | t.Errorf("%s: found %T, want *ast.FuncDecl", file, f.Decls[0]) 40 | continue 41 | } 42 | base := strings.TrimSuffix(file, ".go") 43 | g := Build(fset, fn.Body) 44 | dump := g.Dump() 45 | golden, _ := ioutil.ReadFile(base + ".dump") 46 | if !bytes.Equal(dump, golden) { 47 | ioutil.WriteFile(base+".dump.xxx", dump, 0666) 48 | t.Errorf("%s: wrong graph; have %s.dump.xxx, want %s.dump", file, base, base) 49 | continue 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /flow/testdata/cfg-addr.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n9 [label="_cfg_end_ *ast.Ident :0"]; 3 | n8 [label="err *ast.Ident testdata/cfg-addr.go:6"]; 4 | n8 -> n9 [label=""]; 5 | n17 [label="*ast.AssignStmt testdata/cfg-addr.go:10"]; 6 | n17 -> n9 [label=""]; 7 | n16 [label="Base *ast.Ident testdata/cfg-addr.go:10"]; 8 | n16 -> n17 [label=""]; 9 | n15 [label="ss *ast.Ident testdata/cfg-addr.go:10"]; 10 | n15 -> n16 [label=""]; 11 | n14 [label="base *ast.Ident testdata/cfg-addr.go:10"]; 12 | n14 -> n15 [label=""]; 13 | n13 [label="s *ast.Ident testdata/cfg-addr.go:10"]; 14 | n13 -> n14 [label=""]; 15 | n12 [label="Lock *ast.Ident testdata/cfg-addr.go:9"]; 16 | n12 -> n13 [label=""]; 17 | n11 [label="mutex *ast.Ident testdata/cfg-addr.go:9"]; 18 | n11 -> n12 [label=""]; 19 | n10 [label="s *ast.Ident testdata/cfg-addr.go:9"]; 20 | n10 -> n11 [label=""]; 21 | n7 [label="nil *ast.Ident testdata/cfg-addr.go:5"]; 22 | n7 -> n8 [label=""]; 23 | n7 -> n10 [label=""]; 24 | n6 [label="err *ast.Ident testdata/cfg-addr.go:5"]; 25 | n6 -> n7 [label=""]; 26 | n5 [label="err *ast.Ident testdata/cfg-addr.go:5"]; 27 | n5 -> n6 [label=""]; 28 | n4 [label="*ast.AssignStmt testdata/cfg-addr.go:5"]; 29 | n4 -> n5 [label=""]; 30 | n3 [label="ss *ast.Ident testdata/cfg-addr.go:5"]; 31 | n3 -> n4 [label=""]; 32 | n2 [label="decode *ast.Ident testdata/cfg-addr.go:5"]; 33 | n2 -> n3 [label=""]; 34 | n1 [label="_cfg_start_ *ast.Ident :0"]; 35 | n1 -> n2 [label=""]; 36 | } 37 | -------------------------------------------------------------------------------- /vardecl/vardecl_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package vardecl 6 | 7 | import ( 8 | "testing" 9 | 10 | "rsc.io/grind/grinder" 11 | "rsc.io/grind/grindtest" 12 | ) 13 | 14 | /* 15 | func TestVardeclGolden(t *testing.T) { 16 | matches, err := filepath.Glob("testdata/vardecl-*.go") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | if len(matches) == 0 { 21 | t.Fatal("no testdata found") 22 | } 23 | fset := token.NewFileSet() 24 | for _, file := range matches { 25 | f, err := parser.ParseFile(fset, file, nil, 0) 26 | if err != nil { 27 | t.Error(err) 28 | continue 29 | } 30 | if len(f.Decls) == 0 { 31 | t.Errorf("%s: no decls", file) 32 | continue 33 | } 34 | fn, ok := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) 35 | if !ok { 36 | t.Errorf("%s: found %T, want *ast.FuncDecl", file, f.Decls[0]) 37 | continue 38 | } 39 | vars := Analyze(fset, fn.Body) 40 | dump := PrintVars(fset, vars) 41 | base := strings.TrimSuffix(file, ".go") 42 | golden, _ := ioutil.ReadFile(base + ".dump") 43 | if !bytes.Equal(dump, golden) { 44 | ioutil.WriteFile(base+".dump.xxx", dump, 0666) 45 | t.Errorf("%s: wrong; diff %s.dump.xxx %s.dump", file, base, base) 46 | continue 47 | } 48 | } 49 | } 50 | */ 51 | 52 | func TestVardecl(t *testing.T) { 53 | grindtest.TestGlob(t, "testdata/grind-*.go", []grinder.Func{Grind}) 54 | } 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /flow/testdata/cfg-assign.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n20 [label="_cfg_end_ *ast.Ident :0"]; 3 | n19 [label="*ast.AssignStmt testdata/cfg-assign.go:10"]; 4 | n19 -> n20 [label=""]; 5 | n18 [label="z *ast.Ident testdata/cfg-assign.go:10"]; 6 | n18 -> n19 [label=""]; 7 | n17 [label="y *ast.Ident testdata/cfg-assign.go:10"]; 8 | n17 -> n18 [label=""]; 9 | n16 [label="x *ast.Ident testdata/cfg-assign.go:10"]; 10 | n16 -> n17 [label=""]; 11 | n15 [label="more *ast.Ident testdata/cfg-assign.go:9"]; 12 | n15 -> n16 [label=""]; 13 | n14 [label="*ast.AssignStmt testdata/cfg-assign.go:8"]; 14 | n14 -> n15 [label=""]; 15 | n13 [label="z *ast.Ident testdata/cfg-assign.go:8"]; 16 | n13 -> n14 [label=""]; 17 | n12 [label="y *ast.Ident testdata/cfg-assign.go:8"]; 18 | n12 -> n13 [label=""]; 19 | n11 [label="x *ast.Ident testdata/cfg-assign.go:8"]; 20 | n11 -> n12 [label=""]; 21 | n10 [label="more *ast.Ident testdata/cfg-assign.go:7"]; 22 | n10 -> n11 [label=""]; 23 | n9 [label="x *ast.Ident testdata/cfg-assign.go:6"]; 24 | n9 -> n10 [label=""]; 25 | n8 [label="*ast.AssignStmt testdata/cfg-assign.go:6"]; 26 | n8 -> n9 [label=""]; 27 | n7 [label="z *ast.Ident testdata/cfg-assign.go:6"]; 28 | n7 -> n8 [label=""]; 29 | n6 [label="y *ast.Ident testdata/cfg-assign.go:6"]; 30 | n6 -> n7 [label=""]; 31 | n5 [label="more *ast.Ident testdata/cfg-assign.go:5"]; 32 | n5 -> n6 [label=""]; 33 | n4 [label="x *ast.Ident testdata/cfg-assign.go:4"]; 34 | n4 -> n5 [label=""]; 35 | n3 [label="*ast.AssignStmt testdata/cfg-assign.go:4"]; 36 | n3 -> n4 [label=""]; 37 | n2 [label="y *ast.Ident testdata/cfg-assign.go:4"]; 38 | n2 -> n3 [label=""]; 39 | n1 [label="_cfg_start_ *ast.Ident :0"]; 40 | n1 -> n2 [label=""]; 41 | } 42 | -------------------------------------------------------------------------------- /flow/testdata/cfg-select.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n10 [label="_cfg_end_ *ast.Ident :0"]; 3 | n11 [label="more1 *ast.Ident testdata/cfg-select.go:11"]; 4 | n11 -> n10 [label=""]; 5 | n9 [label="brk *ast.Ident testdata/cfg-select.go:8"]; 6 | n9 -> n10 [label=""]; 7 | n9 -> n11 [label=""]; 8 | n8 [label="body1 *ast.Ident testdata/cfg-select.go:7"]; 9 | n8 -> n9 [label=""]; 10 | n7 [label="lhs1 *ast.Ident testdata/cfg-select.go:6"]; 11 | n7 -> n8 [label=""]; 12 | n14 [label="more2 *ast.Ident testdata/cfg-select.go:17"]; 13 | n14 -> n10 [label=""]; 14 | n13 [label="brk *ast.Ident testdata/cfg-select.go:14"]; 15 | n13 -> n10 [label=""]; 16 | n13 -> n14 [label=""]; 17 | n12 [label="body2 *ast.Ident testdata/cfg-select.go:13"]; 18 | n12 -> n13 [label=""]; 19 | n16 [label="body3 *ast.Ident testdata/cfg-select.go:19"]; 20 | n16 -> n10 [label=""]; 21 | n15 [label="lhs3 *ast.Ident testdata/cfg-select.go:18"]; 22 | n15 -> n16 [label=""]; 23 | n17 [label="body4 *ast.Ident testdata/cfg-select.go:21"]; 24 | n17 -> n10 [label=""]; 25 | n18 [label="bodyDefault *ast.Ident testdata/cfg-select.go:23"]; 26 | n18 -> n10 [label=""]; 27 | n6 [label="rhs4 *ast.Ident testdata/cfg-select.go:20"]; 28 | n6 -> n7 [label=""]; 29 | n6 -> n12 [label=""]; 30 | n6 -> n15 [label=""]; 31 | n6 -> n17 [label=""]; 32 | n6 -> n18 [label=""]; 33 | n5 [label="lhs4 *ast.Ident testdata/cfg-select.go:20"]; 34 | n5 -> n6 [label=""]; 35 | n4 [label="rhs3 *ast.Ident testdata/cfg-select.go:18"]; 36 | n4 -> n5 [label=""]; 37 | n3 [label="rhs2 *ast.Ident testdata/cfg-select.go:12"]; 38 | n3 -> n4 [label=""]; 39 | n2 [label="rhs1 *ast.Ident testdata/cfg-select.go:6"]; 40 | n2 -> n3 [label=""]; 41 | n1 [label="_cfg_start_ *ast.Ident :0"]; 42 | n1 -> n2 [label=""]; 43 | } 44 | -------------------------------------------------------------------------------- /flow/testdata/flowdef-loop.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n11 [label="i *ast.Ident testdata/flowdef-loop.go:7"]; 3 | n11 -> n7 [label=""]; 4 | n10 [label="*ast.IncDecStmt testdata/flowdef-loop.go:7"]; 5 | n10 -> n11 [label=""]; 6 | n9 [label="i *ast.Ident testdata/flowdef-loop.go:8"]; 7 | n9 -> n10 [label=""]; 8 | n8 [label="use *ast.Ident testdata/flowdef-loop.go:8"]; 9 | n8 -> n9 [label=""]; 10 | n18 [label="i *ast.Ident testdata/flowdef-loop.go:11"]; 11 | n18 -> n14 [label=""]; 12 | n17 [label="*ast.IncDecStmt testdata/flowdef-loop.go:11"]; 13 | n17 -> n18 [label=""]; 14 | n16 [label="i *ast.Ident testdata/flowdef-loop.go:12"]; 15 | n16 -> n17 [label=""]; 16 | n15 [label="use *ast.Ident testdata/flowdef-loop.go:12"]; 17 | n15 -> n16 [label=""]; 18 | n21 [label="_cfg_end_ *ast.Ident :0"]; 19 | n20 [label="i *ast.Ident testdata/flowdef-loop.go:15"]; 20 | n20 -> n21 [label=""]; 21 | n19 [label="use *ast.Ident testdata/flowdef-loop.go:15"]; 22 | n19 -> n20 [label=""]; 23 | n14 [label="i *ast.Ident testdata/flowdef-loop.go:11"]; 24 | n14 -> n15 [label=""]; 25 | n14 -> n19 [label=""]; 26 | n13 [label="i *ast.Ident testdata/flowdef-loop.go:11"]; 27 | n13 -> n14 [label=""]; 28 | n12 [label="*ast.AssignStmt testdata/flowdef-loop.go:11"]; 29 | n12 -> n13 [label=""]; 30 | n7 [label="i *ast.Ident testdata/flowdef-loop.go:7"]; 31 | n7 -> n8 [label=""]; 32 | n7 -> n12 [label=""]; 33 | n6 [label="i *ast.Ident testdata/flowdef-loop.go:7"]; 34 | n6 -> n7 [label=""]; 35 | n5 [label="*ast.AssignStmt testdata/flowdef-loop.go:7"]; 36 | n5 -> n6 [label=""]; 37 | n4 [label="i *ast.Ident testdata/flowdef-loop.go:5"]; 38 | n4 -> n5 [label=""]; 39 | n3 [label="*ast.IncDecStmt testdata/flowdef-loop.go:5"]; 40 | n3 -> n4 [label=""]; 41 | n2 [label="*ast.DeclStmt testdata/flowdef-loop.go:4"]; 42 | n2 -> n3 [label=""]; 43 | n1 [label="_cfg_start_ *ast.Ident :0"]; 44 | n1 -> n2 [label=""]; 45 | } 46 | -------------------------------------------------------------------------------- /deadcode/deadcode.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package deadcode 6 | 7 | import ( 8 | "go/ast" 9 | 10 | "rsc.io/grind/block" 11 | "rsc.io/grind/grinder" 12 | ) 13 | 14 | func Grind(ctxt *grinder.Context, pkg *grinder.Package) { 15 | grinder.GrindFuncDecls(ctxt, pkg, grindFunc) 16 | } 17 | 18 | func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { 19 | if fn.Body == nil { 20 | return 21 | } 22 | blocks := block.Build(pkg.FileSet, fn.Body) 23 | ast.Inspect(fn.Body, func(x ast.Node) bool { 24 | var list []ast.Stmt 25 | switch x := x.(type) { 26 | default: 27 | return true 28 | case *ast.BlockStmt: 29 | list = x.List 30 | case *ast.CommClause: 31 | list = x.Body 32 | case *ast.CaseClause: 33 | list = x.Body 34 | } 35 | 36 | for i := 0; i < len(list); i++ { 37 | x := list[i] 38 | if grinder.IsTerminatingStmt(blocks, x) { 39 | end := i + 1 40 | for end < len(list) && !isGotoTarget(blocks, list[end]) { 41 | end++ 42 | } 43 | if end > i+1 { 44 | edit.Delete(edit.End(x), edit.End(list[end-1])) 45 | i = end - 1 // after i++, next iteration starts at end 46 | } 47 | } 48 | } 49 | return true 50 | }) 51 | } 52 | 53 | func fallsThrough(x ast.Stmt) bool { 54 | switch x.(type) { 55 | case *ast.ReturnStmt, *ast.BranchStmt: 56 | return false 57 | } 58 | // TODO: for loop, switch etc with certain bodies 59 | return true 60 | } 61 | 62 | func isGotoTarget(blocks *block.Graph, x ast.Stmt) bool { 63 | for { 64 | y, ok := x.(*ast.LabeledStmt) 65 | if !ok { 66 | return false 67 | } 68 | if len(blocks.Goto[y.Label.Name]) > 0 { 69 | return true 70 | } 71 | x = y.Stmt 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /flow/testdata/cfg-bug2.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n12 [label="_cfg_end_ *ast.Ident :0"]; 3 | n22 [label="*ast.AssignStmt testdata/cfg-bug2.go:15"]; 4 | n22 -> n12 [label=""]; 5 | n21 [label="Bytes *ast.Ident testdata/cfg-bug2.go:15"]; 6 | n21 -> n22 [label=""]; 7 | n20 [label="certDERBlock *ast.Ident testdata/cfg-bug2.go:15"]; 8 | n20 -> n21 [label=""]; 9 | n19 [label="Certificate *ast.Ident testdata/cfg-bug2.go:15"]; 10 | n19 -> n20 [label=""]; 11 | n18 [label="cert *ast.Ident testdata/cfg-bug2.go:15"]; 12 | n18 -> n19 [label=""]; 13 | n17 [label="append *ast.Ident testdata/cfg-bug2.go:15"]; 14 | n17 -> n18 [label=""]; 15 | n16 [label="Certificate *ast.Ident testdata/cfg-bug2.go:15"]; 16 | n16 -> n17 [label=""]; 17 | n15 [label="cert *ast.Ident testdata/cfg-bug2.go:15"]; 18 | n15 -> n16 [label=""]; 19 | n14 [label="Type *ast.Ident testdata/cfg-bug2.go:14"]; 20 | n14 -> n15 [label=""]; 21 | n14 -> n12 [label=""]; 22 | n13 [label="certDERBlock *ast.Ident testdata/cfg-bug2.go:14"]; 23 | n13 -> n14 [label=""]; 24 | n11 [label="nil *ast.Ident testdata/cfg-bug2.go:11"]; 25 | n11 -> n12 [label=""]; 26 | n11 -> n13 [label=""]; 27 | n10 [label="certDERBlock *ast.Ident testdata/cfg-bug2.go:11"]; 28 | n10 -> n11 [label=""]; 29 | n9 [label="certPEMBlock *ast.Ident testdata/cfg-bug2.go:10"]; 30 | n9 -> n10 [label=""]; 31 | n8 [label="certDERBlock *ast.Ident testdata/cfg-bug2.go:10"]; 32 | n8 -> n9 [label=""]; 33 | n7 [label="*ast.AssignStmt testdata/cfg-bug2.go:10"]; 34 | n7 -> n8 [label=""]; 35 | n6 [label="certPEMBlock *ast.Ident testdata/cfg-bug2.go:10"]; 36 | n6 -> n7 [label=""]; 37 | n5 [label="Decode *ast.Ident testdata/cfg-bug2.go:10"]; 38 | n5 -> n6 [label=""]; 39 | n4 [label="pem *ast.Ident testdata/cfg-bug2.go:10"]; 40 | n4 -> n5 [label=""]; 41 | n3 [label="fail *ast.Ident testdata/cfg-bug2.go:8"]; 42 | n3 -> n4 [label=""]; 43 | n2 [label="*ast.AssignStmt testdata/cfg-bug2.go:8"]; 44 | n2 -> n3 [label=""]; 45 | n1 [label="_cfg_start_ *ast.Ident :0"]; 46 | n1 -> n2 [label=""]; 47 | } 48 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-addr.go.out: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | type T struct { 4 | X int 5 | Array [4]int 6 | Slice []int 7 | Ptr *T 8 | } 9 | 10 | func f() { 11 | { 12 | { 13 | i := 10 14 | use(i) 15 | } 16 | { 17 | i := 11 18 | use(i) 19 | } 20 | } 21 | { 22 | var i int 23 | 24 | { 25 | i = 10 26 | _ = &i 27 | } 28 | { 29 | i = 11 30 | use(i) 31 | } 32 | } 33 | { 34 | var i T 35 | { 36 | i = T{X: 1} 37 | _ = &i.Array 38 | } 39 | { 40 | i = T{X: 2} 41 | use(i) 42 | } 43 | } 44 | { 45 | var i T 46 | { 47 | i = T{X: 1} 48 | _ = &i.Slice 49 | } 50 | { 51 | i = T{X: 2} 52 | use(i) 53 | } 54 | } 55 | { 56 | { 57 | i := T{X: 1} 58 | _ = &i.Slice[0] 59 | } 60 | { 61 | i := T{X: 2} 62 | use(i) 63 | } 64 | } 65 | { 66 | var i [2]int 67 | { 68 | i = [2]int{1, 2} 69 | _ = &i[0] 70 | } 71 | { 72 | i = [2]int{3, 4} 73 | use(i) 74 | } 75 | } 76 | { 77 | { 78 | i := []int{1, 2} 79 | _ = &i[0] 80 | } 81 | { 82 | i := []int{3, 4} 83 | use(i) 84 | } 85 | } 86 | { 87 | var i T 88 | { 89 | i = T{X: 3} 90 | _ = &i.Array[0] 91 | } 92 | { 93 | i = T{X: 4} 94 | use(i) 95 | } 96 | } 97 | { 98 | { 99 | i := T{X: 3} 100 | _ = &i.Slice[0] 101 | } 102 | { 103 | i := T{X: 4} 104 | use(i) 105 | } 106 | } 107 | { 108 | var i [2]T 109 | { 110 | i = [2]T{{X: 5}, {X: 6}} 111 | _ = &i[0].X 112 | } 113 | { 114 | i = [2]T{{X: 7}, {X: 8}} 115 | use(i) 116 | } 117 | } 118 | { 119 | { 120 | i := [2]T{{X: 5}, {X: 6}} 121 | _ = &i[0].Ptr.X 122 | } 123 | { 124 | i := [2]T{{X: 7}, {X: 8}} 125 | use(i) 126 | } 127 | } 128 | { 129 | var i [2]T 130 | { 131 | i = [2]T{{X: 5}, {X: 6}} 132 | _ = (&(((i)[0]).X)) 133 | } 134 | { 135 | i = [2]T{{X: 7}, {X: 8}} 136 | use(i) 137 | } 138 | } 139 | } 140 | 141 | func use(interface{}) 142 | -------------------------------------------------------------------------------- /vardecl/testdata/grind-addr.go: -------------------------------------------------------------------------------- 1 | package p 2 | 3 | type T struct { 4 | X int 5 | Array [4]int 6 | Slice []int 7 | Ptr *T 8 | } 9 | 10 | func f() { 11 | { 12 | var i int 13 | 14 | { 15 | i = 10 16 | use(i) 17 | } 18 | { 19 | i = 11 20 | use(i) 21 | } 22 | } 23 | { 24 | var i int 25 | 26 | { 27 | i = 10 28 | _ = &i 29 | } 30 | { 31 | i = 11 32 | use(i) 33 | } 34 | } 35 | { 36 | var i T 37 | { 38 | i = T{X: 1} 39 | _ = &i.Array 40 | } 41 | { 42 | i = T{X: 2} 43 | use(i) 44 | } 45 | } 46 | { 47 | var i T 48 | { 49 | i = T{X: 1} 50 | _ = &i.Slice 51 | } 52 | { 53 | i = T{X: 2} 54 | use(i) 55 | } 56 | } 57 | { 58 | var i T 59 | { 60 | i = T{X: 1} 61 | _ = &i.Slice[0] 62 | } 63 | { 64 | i = T{X: 2} 65 | use(i) 66 | } 67 | } 68 | { 69 | var i [2]int 70 | { 71 | i = [2]int{1, 2} 72 | _ = &i[0] 73 | } 74 | { 75 | i = [2]int{3, 4} 76 | use(i) 77 | } 78 | } 79 | { 80 | var i []int 81 | { 82 | i = []int{1, 2} 83 | _ = &i[0] 84 | } 85 | { 86 | i = []int{3, 4} 87 | use(i) 88 | } 89 | } 90 | { 91 | var i T 92 | { 93 | i = T{X: 3} 94 | _ = &i.Array[0] 95 | } 96 | { 97 | i = T{X: 4} 98 | use(i) 99 | } 100 | } 101 | { 102 | var i T 103 | { 104 | i = T{X: 3} 105 | _ = &i.Slice[0] 106 | } 107 | { 108 | i = T{X: 4} 109 | use(i) 110 | } 111 | } 112 | { 113 | var i [2]T 114 | { 115 | i = [2]T{{X: 5}, {X: 6}} 116 | _ = &i[0].X 117 | } 118 | { 119 | i = [2]T{{X: 7}, {X: 8}} 120 | use(i) 121 | } 122 | } 123 | { 124 | var i [2]T 125 | { 126 | i = [2]T{{X: 5}, {X: 6}} 127 | _ = &i[0].Ptr.X 128 | } 129 | { 130 | i = [2]T{{X: 7}, {X: 8}} 131 | use(i) 132 | } 133 | } 134 | { 135 | var i [2]T 136 | { 137 | i = [2]T{{X: 5}, {X: 6}} 138 | _ = (&(((i)[0]).X)) 139 | } 140 | { 141 | i = [2]T{{X: 7}, {X: 8}} 142 | use(i) 143 | } 144 | } 145 | } 146 | 147 | func use(interface{}) 148 | -------------------------------------------------------------------------------- /grindtest/test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package grindtest 6 | 7 | import ( 8 | "bytes" 9 | "flag" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "regexp" 15 | "testing" 16 | 17 | "rsc.io/grind/grinder" 18 | ) 19 | 20 | var run = flag.String("grindrun", "", "only run golden tests for files with names matching this regexp") 21 | 22 | func TestGlob(t *testing.T, pattern string, grinders []grinder.Func) { 23 | matches, err := filepath.Glob(pattern) 24 | if err != nil { 25 | t.Errorf("%v", err) 26 | return 27 | } 28 | if len(matches) == 0 { 29 | t.Errorf("no matches for %s", pattern) 30 | return 31 | } 32 | var re *regexp.Regexp 33 | if *run != "" { 34 | var err error 35 | re, err = regexp.Compile(*run) 36 | if err != nil { 37 | t.Errorf("invalid regexp passed to -grindrun: %v", err) 38 | return 39 | } 40 | } 41 | for _, file := range matches { 42 | if re != nil && !re.MatchString(file) { 43 | continue 44 | } 45 | var buf bytes.Buffer 46 | ctxt := &grinder.Context{ 47 | Grinders: grinders, 48 | } 49 | ctxt.Logf = func(format string, args ...interface{}) { 50 | fmt.Fprintf(&buf, format, args...) 51 | if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] != '\n' { 52 | buf.WriteByte('\n') 53 | } 54 | } 55 | pkg := ctxt.GrindFiles(file) 56 | if pkg == nil || ctxt.Errors { 57 | t.Errorf("%s: grind failed:\n%s", file, buf.String()) 58 | continue 59 | } 60 | data, err := ioutil.ReadFile(file + ".out") 61 | if err != nil { 62 | if os.IsNotExist(err) { 63 | if pkg.Modified(file) { 64 | t.Errorf("%s: should not modify, but made changes:\n%s", file, grinder.Diff(pkg.OrigSrc(file), pkg.Src(file))) 65 | } 66 | continue 67 | } 68 | t.Errorf("%s: reading golden output: %v", file, err) 69 | continue 70 | } 71 | want := string(data) 72 | have := pkg.Src(file) 73 | if have != want { 74 | t.Errorf("%s: incorrect output\nhave:\n%s\nwant:\n%s\ndiff want have:\n%s", file, have, want, grinder.Diff(want, have)) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /flow/testdata/flowdef-loop.reach: -------------------------------------------------------------------------------- 1 | _cfg_start_ *ast.Ident :0: 2 | *ast.DeclStmt testdata/flowdef-loop.go:4: 3 | *ast.IncDecStmt testdata/flowdef-loop.go:5: *ast.DeclStmt testdata/flowdef-loop.go:4 4 | i *ast.Ident testdata/flowdef-loop.go:5: *ast.IncDecStmt testdata/flowdef-loop.go:5 5 | *ast.AssignStmt testdata/flowdef-loop.go:7: *ast.IncDecStmt testdata/flowdef-loop.go:5 6 | i *ast.Ident testdata/flowdef-loop.go:7: *ast.AssignStmt testdata/flowdef-loop.go:7 7 | i *ast.Ident testdata/flowdef-loop.go:7: *ast.AssignStmt testdata/flowdef-loop.go:7 *ast.IncDecStmt testdata/flowdef-loop.go:7 8 | use *ast.Ident testdata/flowdef-loop.go:8: *ast.AssignStmt testdata/flowdef-loop.go:7 *ast.IncDecStmt testdata/flowdef-loop.go:7 9 | *ast.AssignStmt testdata/flowdef-loop.go:11: *ast.AssignStmt testdata/flowdef-loop.go:7 *ast.IncDecStmt testdata/flowdef-loop.go:7 10 | i *ast.Ident testdata/flowdef-loop.go:8: *ast.AssignStmt testdata/flowdef-loop.go:7 *ast.IncDecStmt testdata/flowdef-loop.go:7 11 | i *ast.Ident testdata/flowdef-loop.go:11: *ast.AssignStmt testdata/flowdef-loop.go:11 12 | *ast.IncDecStmt testdata/flowdef-loop.go:7: *ast.AssignStmt testdata/flowdef-loop.go:7 *ast.IncDecStmt testdata/flowdef-loop.go:7 13 | i *ast.Ident testdata/flowdef-loop.go:11: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 14 | i *ast.Ident testdata/flowdef-loop.go:7: *ast.IncDecStmt testdata/flowdef-loop.go:7 15 | use *ast.Ident testdata/flowdef-loop.go:12: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 16 | use *ast.Ident testdata/flowdef-loop.go:15: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 17 | i *ast.Ident testdata/flowdef-loop.go:12: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 18 | i *ast.Ident testdata/flowdef-loop.go:15: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 19 | *ast.IncDecStmt testdata/flowdef-loop.go:11: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 20 | _cfg_end_ *ast.Ident :0: *ast.AssignStmt testdata/flowdef-loop.go:11 *ast.IncDecStmt testdata/flowdef-loop.go:11 21 | i *ast.Ident testdata/flowdef-loop.go:11: *ast.IncDecStmt testdata/flowdef-loop.go:11 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "os" 13 | "os/exec" 14 | "strings" 15 | 16 | _ "golang.org/x/tools/go/gcimporter" 17 | "rsc.io/grind/deadcode" 18 | "rsc.io/grind/gotoinline" 19 | "rsc.io/grind/grinder" 20 | "rsc.io/grind/vardecl" 21 | ) 22 | 23 | var diff = flag.Bool("diff", false, "print diffs") 24 | var verbose = flag.Bool("v", false, "verbose") 25 | 26 | func usage() { 27 | fmt.Fprintf(os.Stderr, "usage: grind [-diff] [-v] packagepath... (or file...)\n") 28 | os.Exit(2) 29 | } 30 | 31 | var ctxt = grinder.Context{ 32 | Logf: log.Printf, 33 | } 34 | 35 | func main() { 36 | flag.Usage = usage 37 | flag.Parse() 38 | if flag.NArg() == 0 { 39 | usage() 40 | } 41 | 42 | ctxt.Grinders = []grinder.Func{ 43 | deadcode.Grind, 44 | gotoinline.Grind, 45 | vardecl.Grind, 46 | DeleteUnusedLabels, 47 | } 48 | 49 | defer func() { 50 | if ctxt.Errors { 51 | os.Exit(1) 52 | } 53 | }() 54 | 55 | if strings.HasSuffix(flag.Arg(0), ".go") { 56 | grind(ctxt.GrindFiles(flag.Args()...)) 57 | return 58 | } 59 | 60 | for _, path := range flag.Args() { 61 | grind(ctxt.GrindPackage(path)) 62 | } 63 | } 64 | 65 | func grind(pkg *grinder.Package) { 66 | for _, name := range pkg.Filenames { 67 | if !pkg.Modified(name) { 68 | continue 69 | } 70 | 71 | if *diff { 72 | diffText, err := runDiff([]byte(pkg.OrigSrc(name)), []byte(pkg.Src(name))) 73 | if err != nil { 74 | ctxt.Errorf("%v", err) 75 | } 76 | os.Stdout.Write(diffText) 77 | continue 78 | } 79 | 80 | if err := ioutil.WriteFile(name, []byte(pkg.Src(name)), 0666); err != nil { 81 | ctxt.Errorf("%v", err) 82 | } 83 | } 84 | } 85 | 86 | func runDiff(b1, b2 []byte) (data []byte, err error) { 87 | f1, err := ioutil.TempFile("", "grind-") 88 | if err != nil { 89 | return nil, err 90 | } 91 | defer os.Remove(f1.Name()) 92 | defer f1.Close() 93 | 94 | f2, err := ioutil.TempFile("", "grind-") 95 | if err != nil { 96 | return nil, err 97 | } 98 | defer os.Remove(f2.Name()) 99 | defer f2.Close() 100 | 101 | f1.Write(b1) 102 | f2.Write(b2) 103 | 104 | data, err = exec.Command("git", "diff", f1.Name(), f2.Name()).CombinedOutput() 105 | if len(data) > 0 { 106 | // diff exits with a non-zero status when the files don't match. 107 | // Ignore that failure as long as we get output. 108 | err = nil 109 | } 110 | return 111 | } 112 | -------------------------------------------------------------------------------- /flow/testdata/cfg-switch.dot: -------------------------------------------------------------------------------- 1 | digraph cfg { 2 | n18 [label="_cfg_end_ *ast.Ident :0"]; 3 | n17 [label="body123 *ast.Ident testdata/cfg-switch.go:33"]; 4 | n17 -> n18 [label=""]; 5 | n16 [label="bodyDefault *ast.Ident testdata/cfg-switch.go:30"]; 6 | n16 -> n17 [label=""]; 7 | n15 [label="case3 *ast.Ident testdata/cfg-switch.go:32"]; 8 | n15 -> n16 [label=""]; 9 | n15 -> n17 [label=""]; 10 | n14 [label="case2 *ast.Ident testdata/cfg-switch.go:32"]; 11 | n14 -> n15 [label=""]; 12 | n14 -> n17 [label=""]; 13 | n13 [label="case1 *ast.Ident testdata/cfg-switch.go:32"]; 14 | n13 -> n14 [label=""]; 15 | n13 -> n17 [label=""]; 16 | n12 [label="expr *ast.Ident testdata/cfg-switch.go:28"]; 17 | n12 -> n13 [label=""]; 18 | n11 [label="bodyDefault *ast.Ident testdata/cfg-switch.go:19"]; 19 | n11 -> n12 [label=""]; 20 | n21 [label="more8 *ast.Ident testdata/cfg-switch.go:25"]; 21 | n21 -> n12 [label=""]; 22 | n20 [label="brkL *ast.Ident testdata/cfg-switch.go:22"]; 23 | n20 -> n12 [label=""]; 24 | n20 -> n21 [label=""]; 25 | n19 [label="body8 *ast.Ident testdata/cfg-switch.go:21"]; 26 | n19 -> n20 [label=""]; 27 | n10 [label="case8 *ast.Ident testdata/cfg-switch.go:20"]; 28 | n10 -> n11 [label=""]; 29 | n10 -> n19 [label=""]; 30 | n24 [label="more7 *ast.Ident testdata/cfg-switch.go:17"]; 31 | n24 -> n12 [label=""]; 32 | n23 [label="brk *ast.Ident testdata/cfg-switch.go:14"]; 33 | n23 -> n12 [label=""]; 34 | n23 -> n24 [label=""]; 35 | n22 [label="body7 *ast.Ident testdata/cfg-switch.go:13"]; 36 | n22 -> n23 [label=""]; 37 | n9 [label="case7 *ast.Ident testdata/cfg-switch.go:12"]; 38 | n9 -> n10 [label=""]; 39 | n9 -> n22 [label=""]; 40 | n26 [label="fall *ast.Ident testdata/cfg-switch.go:10"]; 41 | n26 -> n22 [label=""]; 42 | n25 [label="body456 *ast.Ident testdata/cfg-switch.go:9"]; 43 | n25 -> n26 [label=""]; 44 | n8 [label="case6 *ast.Ident testdata/cfg-switch.go:8"]; 45 | n8 -> n9 [label=""]; 46 | n8 -> n25 [label=""]; 47 | n7 [label="case5 *ast.Ident testdata/cfg-switch.go:8"]; 48 | n7 -> n8 [label=""]; 49 | n7 -> n25 [label=""]; 50 | n6 [label="case4 *ast.Ident testdata/cfg-switch.go:8"]; 51 | n6 -> n7 [label=""]; 52 | n6 -> n25 [label=""]; 53 | n27 [label="body123 *ast.Ident testdata/cfg-switch.go:7"]; 54 | n27 -> n12 [label=""]; 55 | n5 [label="case3 *ast.Ident testdata/cfg-switch.go:6"]; 56 | n5 -> n6 [label=""]; 57 | n5 -> n27 [label=""]; 58 | n4 [label="case2 *ast.Ident testdata/cfg-switch.go:6"]; 59 | n4 -> n5 [label=""]; 60 | n4 -> n27 [label=""]; 61 | n3 [label="case1 *ast.Ident testdata/cfg-switch.go:6"]; 62 | n3 -> n4 [label=""]; 63 | n3 -> n27 [label=""]; 64 | n2 [label="expr *ast.Ident testdata/cfg-switch.go:5"]; 65 | n2 -> n3 [label=""]; 66 | n1 [label="_cfg_start_ *ast.Ident :0"]; 67 | n1 -> n2 [label=""]; 68 | } 69 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Grind polishes Go programs. 7 | 8 | Usage: 9 | grind [-diff] [-v] packagepath... 10 | 11 | Grind rewrites the source files in the named packages. 12 | When grind rewrites a file, it prints a line to standard 13 | error giving the name of the file and the rewrites applied. 14 | 15 | As a special case, if the arguments are a list of Go source files, 16 | they are considered to make up a single package, which 17 | is then rewritten. 18 | 19 | If the -diff flag is set, no files are rewritten. 20 | Instead grind prints the differences a rewrite would introduce. 21 | 22 | Grind does not make backup copies of the files that it edits. 23 | Instead, use a version control system's ``diff'' functionality to inspect 24 | the changes that grind makes before committing them. 25 | 26 | Grind is a work in progress. More rewrites are planned. 27 | The initial use case for grind is cleaning up Go code that looks 28 | like C code. 29 | 30 | Dead Code Elimination 31 | 32 | Grind removes unreachable (dead) code. Code is considered 33 | unreachable if it is not the target of a goto statement and is 34 | preceded by a terminating statement 35 | (see golang.org/ref/spec#Terminating_statements), 36 | or a break or continue statement. 37 | 38 | Goto Inlining 39 | 40 | If the target of a goto is a block of code that is only reachable 41 | by following that goto statement, grind replaces the goto with 42 | a copy of the target code and deletes the original target code. 43 | 44 | If the target of a goto is an explicit or implicit return statement, 45 | replaces the goto with a copy of the return statement. 46 | 47 | Unused Label Removal 48 | 49 | Grind removes unused labels. 50 | 51 | Var Declaration Insertion 52 | 53 | Grind rewrites := declarations of complex zero types, such as: 54 | 55 | x := (*T)(nil) 56 | y := T{} // T a struct or array type 57 | 58 | to use plain var declarations, as in: 59 | 60 | var x *T 61 | var y T 62 | 63 | Var Declaration Placement 64 | 65 | Grind moves var declarations as close as possible to their uses. 66 | When possible, it combines the declaration with the initialization, 67 | and it splits disjoint uses of a single variable into multiple variables. 68 | 69 | For example, consider: 70 | 71 | var i int 72 | 73 | ... 74 | 75 | for i = 0; i < 10; i++ { 76 | use(i) 77 | } 78 | 79 | ... 80 | 81 | for i = 0; i < 10; i++ { 82 | otherUse(i) 83 | } 84 | 85 | Grind deletes the declaration and rewrites both loop initializers 86 | to use a combined declaration and assignment (i := 0). 87 | 88 | Limitation: Grind does not move variable declarations into loop bodies. 89 | It may in the future, provided it can determine that the variable 90 | is dead on entry to the body and that the variable does not 91 | escape (causing heap allocation inside the loop). 92 | 93 | Limitation: Grind considers variables that appear in closures off limits. 94 | A more sophisticated analysis might consider them in the future. 95 | 96 | */ 97 | package main 98 | -------------------------------------------------------------------------------- /block/block.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package block 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "go/ast" 11 | "go/token" 12 | ) 13 | 14 | type Graph struct { 15 | Start *Block 16 | Map map[ast.Node]*Block 17 | Label map[string]*ast.LabeledStmt 18 | Goto map[string][]*ast.BranchStmt 19 | Break map[string][]*ast.BranchStmt 20 | Continue map[string][]*ast.BranchStmt 21 | FileSet *token.FileSet 22 | } 23 | 24 | type Block struct { 25 | Depth int 26 | ID int 27 | Parent *Block 28 | Child []*Block 29 | Root ast.Node 30 | } 31 | 32 | type builder struct { 33 | g *Graph 34 | nblock int 35 | } 36 | 37 | type builderVisitor struct { 38 | builder *builder 39 | current *Block 40 | skipBlock bool 41 | } 42 | 43 | func (v *builderVisitor) Visit(x ast.Node) ast.Visitor { 44 | v.builder.g.Map[x] = v.current 45 | if v.skipBlock { 46 | if _, ok := x.(*ast.BlockStmt); ok { 47 | return &builderVisitor{builder: v.builder, current: v.current} 48 | } 49 | } 50 | switch x := x.(type) { 51 | case *ast.BlockStmt, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.CaseClause, *ast.CommClause: 52 | b := &Block{ 53 | Depth: v.current.Depth + 1, 54 | ID: v.builder.nblock, 55 | Parent: v.current, 56 | Root: x, 57 | } 58 | v.builder.nblock++ 59 | v.current.Child = append(v.current.Child, b) 60 | skipBlock := false 61 | switch x.(type) { 62 | case *ast.SwitchStmt, *ast.TypeSwitchStmt: 63 | // The braces in the inner BlockStmt do NOT mark a new scope 64 | // where variables can be declared. Elide. 65 | skipBlock = true 66 | } 67 | return &builderVisitor{builder: v.builder, current: b, skipBlock: skipBlock} 68 | case *ast.LabeledStmt: 69 | v.builder.g.Label[x.Label.Name] = x 70 | case *ast.BranchStmt: 71 | if x.Label == nil { 72 | break 73 | } 74 | switch x.Tok { 75 | case token.GOTO: 76 | v.builder.g.Goto[x.Label.Name] = append(v.builder.g.Goto[x.Label.Name], x) 77 | case token.BREAK: 78 | v.builder.g.Break[x.Label.Name] = append(v.builder.g.Break[x.Label.Name], x) 79 | case token.CONTINUE: 80 | v.builder.g.Continue[x.Label.Name] = append(v.builder.g.Continue[x.Label.Name], x) 81 | } 82 | } 83 | return v 84 | } 85 | 86 | func Build(fset *token.FileSet, x ast.Node) *Graph { 87 | g := &Graph{ 88 | Start: &Block{}, 89 | Map: make(map[ast.Node]*Block), 90 | Label: make(map[string]*ast.LabeledStmt), 91 | Goto: make(map[string][]*ast.BranchStmt), 92 | Break: make(map[string][]*ast.BranchStmt), 93 | Continue: make(map[string][]*ast.BranchStmt), 94 | FileSet: fset, 95 | } 96 | v := &builderVisitor{builder: &builder{g, 1}, current: g.Start} 97 | ast.Walk(v, x) 98 | return g 99 | } 100 | 101 | func (g *Graph) Dump() []byte { 102 | var buf bytes.Buffer 103 | var dump func(*Block) 104 | dump = func(b *Block) { 105 | fmt.Fprintf(&buf, "%d: depth=%d", b.ID, b.Depth) 106 | if b.Parent != nil { 107 | fmt.Fprintf(&buf, " parent=%d", b.Parent.ID) 108 | } 109 | if len(b.Child) > 0 { 110 | fmt.Fprintf(&buf, " child=") 111 | for i, c := range b.Child { 112 | if i > 0 { 113 | fmt.Fprintf(&buf, ",") 114 | } 115 | fmt.Fprintf(&buf, "%d", c.ID) 116 | } 117 | } 118 | if b.Root != nil { 119 | pos := g.FileSet.Position(b.Root.Pos()) 120 | fmt.Fprintf(&buf, " root=%T %s:%d", b.Root, pos.Filename, pos.Line) 121 | } 122 | fmt.Fprintf(&buf, "\n") 123 | 124 | for _, c := range b.Child { 125 | dump(c) 126 | } 127 | } 128 | dump(g.Start) 129 | return buf.Bytes() 130 | } 131 | -------------------------------------------------------------------------------- /grinder/ast.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package grinder 6 | 7 | import ( 8 | "go/ast" 9 | "go/token" 10 | 11 | "golang.org/x/tools/go/types" 12 | 13 | "rsc.io/grind/block" 14 | ) 15 | 16 | func Unlabel(x ast.Stmt) ast.Stmt { 17 | for { 18 | y, ok := x.(*ast.LabeledStmt) 19 | if !ok { 20 | return x 21 | } 22 | x = y.Stmt 23 | } 24 | } 25 | 26 | func IsGotoTarget(blocks *block.Graph, x ast.Stmt) bool { 27 | for { 28 | y, ok := x.(*ast.LabeledStmt) 29 | if !ok { 30 | return false 31 | } 32 | if len(blocks.Goto[y.Label.Name]) > 0 { 33 | return true 34 | } 35 | x = y.Stmt 36 | } 37 | } 38 | 39 | func IsTerminatingStmt(blocks *block.Graph, x ast.Stmt) bool { 40 | // Like http://golang.org/ref/spec#Terminating_statements 41 | // but added break and continue for use in non-end-of-function 42 | // contexts. 43 | label := "" 44 | for { 45 | y, ok := x.(*ast.LabeledStmt) 46 | if !ok { 47 | break 48 | } 49 | label = y.Label.Name 50 | x = y.Stmt 51 | } 52 | 53 | switch x := x.(type) { 54 | case *ast.ReturnStmt: 55 | return true 56 | case *ast.BranchStmt: 57 | switch x.Tok { 58 | case token.BREAK, token.CONTINUE, token.GOTO: 59 | return true 60 | } 61 | case *ast.IfStmt: 62 | return x.Else != nil && IsTerminatingStmt(blocks, x.Body) && IsTerminatingStmt(blocks, x.Else) 63 | case *ast.ForStmt: 64 | return x.Cond == nil && len(blocks.Break[label]) == 0 && !hasBreak(x.Body) 65 | case *ast.SwitchStmt: 66 | if len(blocks.Break[label]) > 0 || hasBreak(x.Body) { 67 | return false 68 | } 69 | hasDefault := false 70 | for _, cas := range x.Body.List { 71 | cas := cas.(*ast.CaseClause) 72 | if cas.List == nil { 73 | hasDefault = true 74 | } 75 | if len(cas.Body) == 0 { 76 | return false 77 | } 78 | last := cas.Body[len(cas.Body)-1] 79 | if !IsTerminatingStmt(blocks, last) && !isFallthrough(last) { 80 | return false 81 | } 82 | } 83 | if !hasDefault { 84 | return false 85 | } 86 | return true 87 | case *ast.TypeSwitchStmt: 88 | if len(blocks.Break[label]) > 0 || hasBreak(x.Body) { 89 | return false 90 | } 91 | hasDefault := false 92 | for _, cas := range x.Body.List { 93 | cas := cas.(*ast.CaseClause) 94 | if cas.List == nil { 95 | hasDefault = true 96 | } 97 | if len(cas.Body) == 0 { 98 | return false 99 | } 100 | last := cas.Body[len(cas.Body)-1] 101 | if !IsTerminatingStmt(blocks, last) && !isFallthrough(last) { 102 | return false 103 | } 104 | } 105 | if !hasDefault { 106 | return false 107 | } 108 | return true 109 | case *ast.SelectStmt: 110 | if len(blocks.Break[label]) > 0 || hasBreak(x.Body) { 111 | return false 112 | } 113 | for _, cas := range x.Body.List { 114 | cas := cas.(*ast.CommClause) 115 | if len(cas.Body) == 0 { 116 | return false 117 | } 118 | last := cas.Body[len(cas.Body)-1] 119 | if !IsTerminatingStmt(blocks, last) && !isFallthrough(last) { 120 | return false 121 | } 122 | } 123 | return true 124 | } 125 | return false 126 | } 127 | 128 | func isFallthrough(x ast.Stmt) bool { 129 | xx, ok := x.(*ast.BranchStmt) 130 | return ok && xx.Tok == token.FALLTHROUGH 131 | } 132 | 133 | func hasBreak(x ast.Stmt) bool { 134 | found := false 135 | ast.Inspect(x, func(x ast.Node) bool { 136 | switch x := x.(type) { 137 | case *ast.ForStmt, *ast.RangeStmt, *ast.SelectStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt: 138 | return false 139 | case *ast.BranchStmt: 140 | if x.Tok == token.BREAK && x.Label == nil { 141 | found = true 142 | } 143 | case ast.Expr: 144 | return false 145 | } 146 | return !found 147 | }) 148 | return found 149 | } 150 | 151 | func (pkg *Package) LookupAtPos(fn *ast.FuncDecl, pos token.Pos, name string) types.Object { 152 | scope := pkg.Info.Scopes[fn.Type] 153 | ast.Inspect(fn.Body, func(x ast.Node) bool { 154 | if x == nil { 155 | return false 156 | } 157 | if pos < x.Pos() || x.End() <= pos { 158 | return false 159 | } 160 | s := pkg.Info.Scopes[x] 161 | if s != nil { 162 | scope = s 163 | } 164 | return true 165 | }) 166 | 167 | pkgScope := pkg.Types.Scope() 168 | for s := scope; s != nil; s = s.Parent() { 169 | obj := s.Lookup(name) 170 | if obj != nil && (s == pkgScope || obj.Pos() < pos) { 171 | return obj 172 | } 173 | } 174 | 175 | return nil 176 | } 177 | 178 | // BlockList returns the list of statements contained by the block x, 179 | // when x is an *ast.BlockStmt, *ast.CommClause, or *ast.CaseClause. 180 | // Otherwise BlockList returns nil. 181 | func BlockList(x ast.Node) []ast.Stmt { 182 | switch x := x.(type) { 183 | case *ast.BlockStmt: 184 | return x.List 185 | case *ast.CommClause: 186 | return x.Body 187 | case *ast.CaseClause: 188 | return x.Body 189 | } 190 | return nil 191 | } 192 | -------------------------------------------------------------------------------- /flow/cfg_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package flow 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "go/ast" 11 | "go/parser" 12 | "go/token" 13 | "io/ioutil" 14 | "path/filepath" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | func TestBuildGolden(t *testing.T) { 20 | matches, err := filepath.Glob("testdata/cfg-*.go") 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | if len(matches) == 0 { 25 | t.Fatal("no testdata found") 26 | } 27 | fset := token.NewFileSet() 28 | for _, file := range matches { 29 | f, err := parser.ParseFile(fset, file, nil, 0) 30 | if err != nil { 31 | t.Error(err) 32 | continue 33 | } 34 | if len(f.Decls) == 0 { 35 | t.Errorf("%s: no decls", file) 36 | continue 37 | } 38 | fn, ok := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) 39 | if !ok { 40 | t.Errorf("%s: found %T, want *ast.FuncDecl", file, f.Decls[0]) 41 | continue 42 | } 43 | 44 | g := Build(fset, fn.Body, isIdentOrAssign) 45 | dot := g.Dot(nil) 46 | base := strings.TrimSuffix(file, ".go") 47 | golden, _ := ioutil.ReadFile(base + ".dot") 48 | if bytes.Equal(dot, golden) { 49 | continue 50 | } 51 | ioutil.WriteFile(base+".dot.xxx", dot, 0666) 52 | t.Errorf("%s: wrong graph; have %s.dot.xxx, want %s.dot", file, base, base) 53 | } 54 | } 55 | 56 | func isIdentOrAssign(x ast.Node) bool { 57 | switch x.(type) { 58 | case *ast.Ident, *ast.AssignStmt: 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | func TestFlowdef(t *testing.T) { 65 | matches, err := filepath.Glob("testdata/flowdef-*.go") 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | if len(matches) == 0 { 70 | t.Fatal("no testdata found") 71 | } 72 | fset := token.NewFileSet() 73 | for _, file := range matches { 74 | f, err := parser.ParseFile(fset, file, nil, 0) 75 | if err != nil { 76 | t.Error(err) 77 | continue 78 | } 79 | if len(f.Decls) == 0 { 80 | t.Errorf("%s: no decls", file) 81 | continue 82 | } 83 | fn, ok := f.Decls[len(f.Decls)-1].(*ast.FuncDecl) 84 | if !ok { 85 | t.Errorf("%s: found %T, want *ast.FuncDecl", file, f.Decls[0]) 86 | continue 87 | } 88 | 89 | g := Build(fset, fn.Body, identUpdate) 90 | dot := g.Dot(nil) 91 | base := strings.TrimSuffix(file, ".go") 92 | golden, _ := ioutil.ReadFile(base + ".dot") 93 | if !bytes.Equal(dot, golden) { 94 | ioutil.WriteFile(base+".dot.xxx", dot, 0666) 95 | t.Errorf("%s: wrong graph; have %s.dot.xxx, want %s.dot", file, base, base) 96 | continue 97 | } 98 | 99 | m := newIdentMatcher(fset) 100 | g.Dataflow(m) 101 | 102 | var buf bytes.Buffer 103 | for _, x := range m.list { 104 | fmt.Fprintf(&buf, "%s\n", m.nodeIn(x)) 105 | } 106 | eq := buf.Bytes() 107 | golden, _ = ioutil.ReadFile(base + ".reach") 108 | if !bytes.Equal(eq, golden) { 109 | ioutil.WriteFile(base+".reach.xxx", eq, 0666) 110 | t.Errorf("%s: wrong dataflow; have %s.reach.xxx, want %s.reach", file, base, base) 111 | continue 112 | } 113 | } 114 | } 115 | 116 | func identUpdate(x ast.Node) bool { 117 | switch x := x.(type) { 118 | case *ast.DeclStmt: 119 | return true 120 | case *ast.ParenExpr: 121 | return identUpdate(x.X) 122 | case *ast.Ident: 123 | return true 124 | case *ast.AssignStmt: 125 | for _, y := range x.Lhs { 126 | if identUpdate(y) { 127 | return true 128 | } 129 | } 130 | case *ast.IncDecStmt: 131 | if identUpdate(x.X) { 132 | return true 133 | } 134 | } 135 | return false 136 | } 137 | 138 | type identMatcher struct { 139 | in map[ast.Node][]ast.Node 140 | out map[ast.Node][]ast.Node 141 | list []ast.Node 142 | fset *token.FileSet 143 | } 144 | 145 | func newIdentMatcher(fset *token.FileSet) *identMatcher { 146 | return &identMatcher{ 147 | in: make(map[ast.Node][]ast.Node), 148 | out: make(map[ast.Node][]ast.Node), 149 | fset: fset, 150 | } 151 | } 152 | 153 | func (m *identMatcher) nodeIn(x ast.Node) string { 154 | var buf bytes.Buffer 155 | 156 | fmt.Fprintf(&buf, "%s:", nodeLabel(m.fset, x)) 157 | for _, y := range m.in[x] { 158 | fmt.Fprintf(&buf, " %s", nodeLabel(m.fset, y)) 159 | } 160 | return buf.String() 161 | } 162 | 163 | func (m *identMatcher) nodeOut(x ast.Node) string { 164 | var buf bytes.Buffer 165 | 166 | fmt.Fprintf(&buf, "%s:", nodeLabel(m.fset, x)) 167 | for _, y := range m.out[x] { 168 | fmt.Fprintf(&buf, " %s", nodeLabel(m.fset, y)) 169 | } 170 | return buf.String() 171 | } 172 | 173 | func (m *identMatcher) Init(x ast.Node) { 174 | m.in[x] = nil 175 | } 176 | 177 | func (m *identMatcher) Transfer(x ast.Node) { 178 | _, ok := m.out[x] 179 | if !ok { 180 | m.list = append(m.list, x) 181 | } 182 | 183 | switch x := x.(type) { 184 | case *ast.DeclStmt, *ast.IncDecStmt: 185 | m.out[x] = []ast.Node{x} 186 | return 187 | case *ast.AssignStmt: 188 | for _, y := range x.Lhs { 189 | for { 190 | yy, ok := y.(*ast.ParenExpr) 191 | if !ok { 192 | break 193 | } 194 | y = yy.X 195 | } 196 | _, ok := y.(*ast.Ident) 197 | if !ok { 198 | continue 199 | } 200 | m.out[x] = []ast.Node{x} 201 | return 202 | } 203 | } 204 | m.out[x] = m.in[x] 205 | } 206 | 207 | func (m *identMatcher) Join(x, y ast.Node) bool { 208 | new := mergef(m.in[x], m.out[y]) 209 | if len(new) > len(m.in[x]) { 210 | m.in[x] = new 211 | return true 212 | } 213 | return false 214 | } 215 | -------------------------------------------------------------------------------- /grinder/grind.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package grinder defines the API for individual grinding bits. 6 | package grinder 7 | 8 | import ( 9 | "bytes" 10 | "fmt" 11 | "go/ast" 12 | "go/build" 13 | "go/format" 14 | "go/parser" 15 | "go/token" 16 | "io/ioutil" 17 | "path/filepath" 18 | "strings" 19 | 20 | _ "golang.org/x/tools/go/gcimporter" 21 | "golang.org/x/tools/go/types" 22 | ) 23 | 24 | type Package struct { 25 | ImportPath string 26 | Files []*ast.File 27 | Filenames []string 28 | FileSet *token.FileSet 29 | Types *types.Package 30 | TypesError error 31 | Info types.Info 32 | 33 | clean bool 34 | oldSrc map[string]string 35 | newSrc map[string]string 36 | } 37 | 38 | func (p *Package) Src(name string) string { 39 | if content := p.newSrc[name]; content != "" { 40 | return content 41 | } 42 | return p.oldSrc[name] 43 | } 44 | 45 | func (p *Package) OrigSrc(name string) string { 46 | return p.oldSrc[name] 47 | } 48 | 49 | func (p *Package) Modified(name string) bool { 50 | _, ok := p.newSrc[name] 51 | return ok 52 | } 53 | 54 | func (p *Package) Rewrite(name, content string) { 55 | gofmt, err := format.Source([]byte(content)) 56 | if err != nil { 57 | panic("rewrite " + name + " with bad source: " + err.Error() + "\n" + content) 58 | } 59 | // Cut blank lines at top of blocks declarations. 60 | gofmt = bytes.Replace(gofmt, []byte("{\n\n"), []byte("{\n"), -1) 61 | gofmt = bytes.Replace(gofmt, []byte("\n\n}"), []byte("\n}"), -1) 62 | p.newSrc[name] = string(gofmt) 63 | p.clean = false 64 | } 65 | 66 | type Func func(*Context, *Package) 67 | 68 | type Context struct { 69 | Logf func(format string, args ...interface{}) 70 | Errors bool 71 | Grinders []Func 72 | } 73 | 74 | func (ctxt *Context) Errorf(format string, args ...interface{}) { 75 | ctxt.Logf(format, args...) 76 | ctxt.Errors = true 77 | } 78 | 79 | func (ctxt *Context) GrindFiles(files ...string) *Package { 80 | pkg := &Package{ 81 | ImportPath: ".", 82 | Filenames: files, 83 | oldSrc: make(map[string]string), 84 | newSrc: make(map[string]string), 85 | } 86 | 87 | for _, file := range files { 88 | data, err := ioutil.ReadFile(file) 89 | if err != nil { 90 | ctxt.Errorf("%v", err) 91 | return nil 92 | } 93 | src := string(data) 94 | pkg.oldSrc[file] = src 95 | } 96 | 97 | ctxt.grind(pkg) 98 | return pkg 99 | } 100 | 101 | func (ctxt *Context) GrindPackage(path string) *Package { 102 | buildCtxt := build.Default 103 | 104 | buildPkg, err := buildCtxt.Import(path, ".", 0) 105 | if err != nil { 106 | ctxt.Errorf("%v", err) 107 | return nil 108 | } 109 | if len(buildPkg.CgoFiles) > 0 { 110 | ctxt.Errorf("%s: packages using cgo not supported", path) 111 | return nil 112 | } 113 | 114 | pkg := &Package{ 115 | ImportPath: path, 116 | oldSrc: make(map[string]string), 117 | newSrc: make(map[string]string), 118 | } 119 | 120 | for _, name := range buildPkg.GoFiles { 121 | filename := filepath.Join(buildPkg.Dir, name) 122 | pkg.Filenames = append(pkg.Filenames, filename) 123 | data, err := ioutil.ReadFile(filename) 124 | if err != nil { 125 | ctxt.Errorf("%s: %v", path, err) 126 | return nil 127 | } 128 | src := string(data) 129 | pkg.oldSrc[filename] = src 130 | } 131 | 132 | ctxt.grind(pkg) 133 | return pkg 134 | } 135 | 136 | func (ctxt *Context) grind(pkg *Package) { 137 | Loop: 138 | for loop := 0; ; loop++ { 139 | println(loop) 140 | pkg.FileSet = token.NewFileSet() 141 | 142 | pkg.Files = nil 143 | for _, name := range pkg.Filenames { 144 | f, err := parser.ParseFile(pkg.FileSet, name, pkg.Src(name), 0) 145 | if err != nil { 146 | if loop > 0 { 147 | ctxt.Errorf("%s: error parsing rewritten file: %v", pkg.ImportPath, err) 148 | return 149 | } 150 | ctxt.Errorf("%s: %v", pkg.ImportPath, err) 151 | return 152 | } 153 | pkg.Files = append(pkg.Files, f) 154 | } 155 | 156 | conf := new(types.Config) 157 | // conf.DisableUnusedImportCheck = true 158 | pkg.Info = types.Info{} 159 | pkg.Info.Types = make(map[ast.Expr]types.TypeAndValue) 160 | pkg.Info.Scopes = make(map[ast.Node]*types.Scope) 161 | pkg.Info.Defs = make(map[*ast.Ident]types.Object) 162 | pkg.Info.Uses = make(map[*ast.Ident]types.Object) 163 | typesPkg, err := conf.Check(pkg.ImportPath, pkg.FileSet, pkg.Files, &pkg.Info) 164 | if err != nil && typesPkg == nil { 165 | if loop > 0 { 166 | ctxt.Errorf("%s: error type checking rewritten package: %v", pkg.ImportPath, err) 167 | for _, name := range pkg.Filenames { 168 | if pkg.Modified(name) { 169 | ctxt.Errorf("%s <<<\n%s\n>>>", name, pkg.Src(name)) 170 | } 171 | } 172 | return 173 | } 174 | ctxt.Errorf("%s: %v", pkg.ImportPath, err) 175 | return 176 | } 177 | pkg.Types = typesPkg 178 | pkg.TypesError = err 179 | 180 | for _, g := range ctxt.Grinders { 181 | pkg.clean = true 182 | g(ctxt, pkg) 183 | if !pkg.clean { 184 | continue Loop 185 | } 186 | } 187 | break 188 | } 189 | } 190 | 191 | func GrindFuncDecls(ctxt *Context, pkg *Package, fn func(ctxt *Context, pkg *Package, edit *EditBuffer, decl *ast.FuncDecl)) { 192 | for i, filename := range pkg.Filenames { 193 | file := pkg.Files[i] 194 | if strings.Contains(pkg.Src(filename), "\n//line ") { 195 | // Don't bother cleaning generated code. 196 | continue 197 | } 198 | edit := NewEditBuffer(pkg, filename, file) 199 | for _, decl := range file.Decls { 200 | decl, ok := decl.(*ast.FuncDecl) 201 | if !ok || decl.Body == nil { 202 | continue 203 | } 204 | fn(ctxt, pkg, edit, decl) 205 | } 206 | if edit.NumEdits() > 0 { 207 | old := pkg.Src(filename) 208 | new := edit.Apply() 209 | if old != new { 210 | // TODO(rsc): It should not happen that old != new, 211 | // but sometimes we delete a var declaration only to 212 | // put it right back where we started. 213 | // Hopefully there are no cycles. Ugh. 214 | fmt.Printf("EDIT: %s\n%s\n", filename, Diff(old, new)) 215 | pkg.Rewrite(filename, new) 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /grinder/edit.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package grinder 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "go/ast" 11 | "go/token" 12 | "io/ioutil" 13 | "os" 14 | "os/exec" 15 | "sort" 16 | "strings" 17 | ) 18 | 19 | type EditBuffer struct { 20 | edits []edit 21 | src *token.File 22 | text string 23 | } 24 | 25 | func (b *EditBuffer) NumEdits() int { 26 | return len(b.edits) 27 | } 28 | 29 | func NewEditBuffer(pkg *Package, filename string, f *ast.File) *EditBuffer { 30 | // Find *token.File via package clause. Must match expected file name. 31 | // TODO(rsc): Handle mismatch gracefully (think yacc etc). 32 | src := pkg.FileSet.File(f.Package) 33 | if src.Name() != filename { 34 | panic("package statement not in source file") 35 | } 36 | 37 | return &EditBuffer{src: src, text: pkg.Src(filename)} 38 | } 39 | 40 | func (b *EditBuffer) tx(p token.Pos) int { 41 | return b.src.Offset(p) 42 | } 43 | 44 | const ( 45 | Insert = 1 + iota 46 | Delete 47 | ) 48 | 49 | type edit struct { 50 | start int 51 | end int 52 | text string 53 | } 54 | 55 | // End returns x.End() except that it works around buggy results from 56 | // the implementation of *ast.LabeledStmt and *ast.EmptyStmt. 57 | // The node x must be located within b's source file. 58 | // See golang.org/issue/9979. 59 | func (b *EditBuffer) End(x ast.Node) token.Pos { 60 | switch x := x.(type) { 61 | case *ast.LabeledStmt: 62 | if _, ok := x.Stmt.(*ast.EmptyStmt); ok { 63 | return x.Colon + 1 64 | } 65 | return b.End(x.Stmt) 66 | case *ast.EmptyStmt: 67 | i := b.tx(x.Semicolon) 68 | if strings.HasPrefix(b.text[i:], ";") { 69 | return x.Semicolon + 1 70 | } 71 | return x.Semicolon 72 | } 73 | return x.End() 74 | } 75 | 76 | // BeforeComments rewinds start past any blank lines or line comments 77 | // and return the result. It does not rewind past leading blank lines: 78 | // the returned position, if changed, is always the start of a non-blank line. 79 | func (b *EditBuffer) BeforeComments(start token.Pos) token.Pos { 80 | i := b.tx(start) 81 | // Back up to newline. 82 | for i > 0 && (b.text[i-1] == ' ' || b.text[i-1] == '\t') { 83 | i-- 84 | } 85 | if i > 0 && b.text[i-1] != '\n' { 86 | return start 87 | } 88 | 89 | // Go backward by lines. 90 | lastNonBlank := i 91 | for i > 0 { 92 | j := i - 1 93 | for j > 0 && b.text[j-1] != '\n' { 94 | j-- 95 | } 96 | trim := strings.TrimSpace(b.text[j:i]) 97 | if len(trim) > 0 && !strings.HasPrefix(trim, "//") { 98 | break 99 | } 100 | if len(trim) > 0 { 101 | lastNonBlank = j 102 | } 103 | i = j 104 | } 105 | return start - token.Pos(b.tx(start)-lastNonBlank) 106 | } 107 | 108 | func (b *EditBuffer) TextAt(start, end token.Pos) string { 109 | return string(b.text[b.tx(start):b.tx(end)]) 110 | } 111 | 112 | func (b *EditBuffer) Insert(p token.Pos, text string) { 113 | b.edits = append(b.edits, edit{b.tx(p), b.tx(p), text}) 114 | } 115 | 116 | func (b *EditBuffer) Replace(start, end token.Pos, text string) { 117 | b.edits = append(b.edits, edit{b.tx(start), b.tx(end), text}) 118 | } 119 | 120 | func (b *EditBuffer) Delete(startp, endp token.Pos) { 121 | b.edits = append(b.edits, edit{b.tx(startp), b.tx(endp), ""}) 122 | } 123 | 124 | func (b *EditBuffer) DeleteLine(startp, endp token.Pos) { 125 | start := b.tx(startp) 126 | end := b.tx(endp) 127 | i := end 128 | for i < len(b.text) && (b.text[i] == ' ' || b.text[i] == '\t' || b.text[i] == '\r') { 129 | i++ 130 | } 131 | // delete comment too 132 | if i+2 < len(b.text) && b.text[i] == '/' && b.text[i+1] == '/' { 133 | j := strings.Index(b.text[i:], "\n") 134 | if j >= 0 { 135 | i += j 136 | } 137 | } 138 | if i == len(b.text) || b.text[i] == '\n' { 139 | end = i + 1 140 | i := start 141 | for i > 0 && (b.text[i-1] == ' ' || b.text[i-1] == '\t') { 142 | i-- 143 | } 144 | if i == 0 || b.text[i-1] == '\n' { 145 | start = i 146 | } 147 | } 148 | b.edits = append(b.edits, edit{start, end, ""}) 149 | } 150 | 151 | func (b *EditBuffer) CopyLine(startp, endp, insertp token.Pos) { 152 | start := b.tx(startp) 153 | end := b.tx(endp) 154 | i := end 155 | for i < len(b.text) && (b.text[i] == ' ' || b.text[i] == '\t' || b.text[i] == '\r') { 156 | i++ 157 | } 158 | // copy comment too 159 | if i+2 < len(b.text) && b.text[i] == '/' && b.text[i+1] == '/' { 160 | j := strings.Index(b.text[i:], "\n") 161 | if j >= 0 { 162 | i += j 163 | } 164 | } 165 | if i == len(b.text) || b.text[i] == '\n' { 166 | end = i + 1 167 | } 168 | text := string(b.text[start:end]) 169 | if !strings.HasSuffix(text, "\n") { 170 | text += "\n" 171 | } 172 | 173 | insert := b.tx(insertp) 174 | j := insert 175 | for j > 0 && (b.text[j-1] == ' ' || b.text[j-1] == '\t') { 176 | j-- 177 | } 178 | if j == 0 || b.text[j-1] == '\n' { 179 | text = string(b.text[j:insert]) + text 180 | insert = j 181 | } 182 | b.edits = append(b.edits, edit{insert, insert, text}) 183 | } 184 | 185 | func (b *EditBuffer) Apply() string { 186 | sort.Sort(editsByStart(b.edits)) 187 | var out []byte 188 | last := 0 189 | for _, e := range b.edits { 190 | //fmt.Printf("EDIT: %+v\n", e) 191 | if e.start < last { 192 | for _, e := range b.edits { 193 | fmt.Printf("%d,%d %q\n", e.start, e.end, e.text) 194 | } 195 | panic("overlapping edits") 196 | } 197 | out = append(out, b.text[last:e.start]...) 198 | out = append(out, e.text...) 199 | last = e.end 200 | } 201 | out = append(out, b.text[last:]...) 202 | return string(out) 203 | } 204 | 205 | type editsByStart []edit 206 | 207 | func (x editsByStart) Len() int { return len(x) } 208 | func (x editsByStart) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 209 | func (x editsByStart) Less(i, j int) bool { 210 | if x[i].start != x[j].start { 211 | return x[i].start < x[j].start 212 | } 213 | if x[i].end != x[j].end { 214 | return x[i].end < x[j].end 215 | } 216 | return x[i].text < x[j].text 217 | } 218 | 219 | func Diff(old, new string) []byte { 220 | f1, err := ioutil.TempFile("", "go-fix") 221 | if err != nil { 222 | return []byte(fmt.Sprintf("writing temp file: %v\n", err)) 223 | } 224 | defer os.Remove(f1.Name()) 225 | defer f1.Close() 226 | 227 | f2, err := ioutil.TempFile("", "go-fix") 228 | if err != nil { 229 | return []byte(fmt.Sprintf("writing temp file: %v\n", err)) 230 | } 231 | defer os.Remove(f2.Name()) 232 | defer f2.Close() 233 | 234 | f1.Write([]byte(old)) 235 | f2.Write([]byte(new)) 236 | 237 | // Use git diff to get consistent output and also for the context after @@ lines. 238 | data, err := exec.Command("git", "diff", f1.Name(), f2.Name()).CombinedOutput() 239 | if len(data) > 0 { 240 | // diff exits with a non-zero status when the files don't match. 241 | // Ignore that failure as long as we get output. 242 | err = nil 243 | } 244 | if err != nil { 245 | return []byte(fmt.Sprintf("invoking git diff: %v\n%s", err, data)) 246 | } 247 | // skip over diff header, since it is showing temporary file names 248 | i := bytes.Index(data, []byte("\n@@")) 249 | if i >= 0 { 250 | data = data[i+1:] 251 | } 252 | return data 253 | } 254 | -------------------------------------------------------------------------------- /gotoinline/gotoinline.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package gotoinline 6 | 7 | import ( 8 | "fmt" 9 | "go/ast" 10 | "go/token" 11 | "strings" 12 | 13 | "golang.org/x/tools/go/types" 14 | 15 | "rsc.io/grind/block" 16 | "rsc.io/grind/grinder" 17 | ) 18 | 19 | var debug = false 20 | 21 | func Grind(ctxt *grinder.Context, pkg *grinder.Package) { 22 | grinder.GrindFuncDecls(ctxt, pkg, grindFunc) 23 | } 24 | 25 | type targetBlock struct { 26 | comment token.Pos 27 | start token.Pos 28 | endLabel token.Pos 29 | end token.Pos 30 | code string 31 | needReturn bool 32 | needGoto string 33 | short bool 34 | dead bool 35 | objs []types.Object 36 | } 37 | 38 | func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { 39 | if fn.Name.Name == "evconst" { 40 | old := debug 41 | debug = true 42 | defer func() { debug = old }() 43 | } 44 | 45 | if pkg.TypesError != nil { 46 | // Without scoping information, we can't be sure code moves are okay. 47 | fmt.Printf("%s: cannot inline gotos without type information\n", fn.Name) 48 | return 49 | } 50 | 51 | if fn.Body == nil { 52 | return 53 | } 54 | blocks := block.Build(pkg.FileSet, fn.Body) 55 | for labelname, gotos := range blocks.Goto { 56 | target, ok := findTargetBlock(pkg, edit, fn, blocks, labelname) 57 | if debug { 58 | println("TARGET", ok, labelname, len(gotos), target.dead, target.short) 59 | } 60 | if ok && (len(gotos) == 1 && target.dead || target.short) { 61 | numReplaced := 0 62 | for _, g := range gotos { 63 | code := edit.TextAt(target.comment, target.start) + target.code 64 | if !objsMatch(pkg, fn, g.Pos(), target.objs, target.start, target.end) { 65 | if debug { 66 | println("OBJS DO NOT MATCH") 67 | } 68 | // Cannot inline code here; needed identifiers have different meanings. 69 | continue 70 | } 71 | if target.needReturn { 72 | // NOTE: Should really check to see if function results are shadowed. 73 | // If we screw up, the code won't compile, so we can put it off. 74 | code += "; return" 75 | } 76 | if target.needGoto != "" { 77 | code += "; goto " + target.needGoto 78 | } 79 | edit.Replace(g.Pos(), g.End(), code) 80 | numReplaced++ 81 | } 82 | if numReplaced == len(gotos) { 83 | if len(gotos) == 1 && target.dead { 84 | edit.Delete(target.comment, target.end) 85 | } else { 86 | edit.DeleteLine(target.start, target.endLabel) 87 | } 88 | } 89 | // The code we move might itself have gotos to inline, 90 | // and we can't make that change until we get new line 91 | // number position, so return after each label change. 92 | if numReplaced > 0 { 93 | return 94 | } 95 | } 96 | } 97 | } 98 | 99 | func findTargetBlock(pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl, blocks *block.Graph, labelname string) (target targetBlock, ok bool) { 100 | if debug { 101 | println("FINDTARGET", labelname) 102 | } 103 | lstmt := blocks.Label[labelname] 104 | if lstmt == nil { 105 | return 106 | } 107 | 108 | list := grinder.BlockList(blocks.Map[lstmt].Root) 109 | if list == nil { 110 | return 111 | } 112 | 113 | ulstmt := grinder.Unlabel(lstmt) 114 | for i := 0; i < len(list); i++ { 115 | if grinder.Unlabel(list[i]) == ulstmt { 116 | // Found statement. Find extent of block. 117 | if debug { 118 | println("FOUND") 119 | } 120 | end := i 121 | for ; ; end++ { 122 | if end >= len(list) { 123 | if debug { 124 | println("EARLY END") 125 | } 126 | // List ended without terminating statement. 127 | // Unless this is the top-most block, we can't hoist this code. 128 | if blocks.Map[lstmt].Root != fn.Body { 129 | return 130 | } 131 | // Top-most block. Implicit return at end of list. 132 | target.needReturn = true 133 | break 134 | } 135 | if end > i && grinder.IsGotoTarget(blocks, list[end]) { 136 | if debug { 137 | println("FOUND TARGET") 138 | } 139 | target.needGoto = list[end].(*ast.LabeledStmt).Label.Name 140 | break 141 | } 142 | if grinder.IsTerminatingStmt(blocks, list[end]) { 143 | if debug { 144 | println("TERMINATING") 145 | } 146 | end++ 147 | break 148 | } 149 | } 150 | if end <= i { 151 | if debug { 152 | println("NOTHING") 153 | } 154 | return 155 | } 156 | if debug { 157 | println("OK") 158 | } 159 | target.dead = i > 0 && grinder.IsTerminatingStmt(blocks, list[i-1]) 160 | target.start = lstmt.Pos() 161 | target.comment = edit.BeforeComments(target.start) 162 | target.endLabel = lstmt.Colon + 1 163 | target.end = edit.End(list[end-1]) 164 | target.code = strings.TrimSpace(edit.TextAt(lstmt.Colon+1, target.end)) 165 | target.short = end == i+1 && (isReturn(grinder.Unlabel(list[i])) || isEmpty(grinder.Unlabel(list[i])) && target.needReturn) 166 | target.objs = gatherObjs(pkg, fn, lstmt.Pos(), list[i:end]) 167 | return target, true 168 | } 169 | } 170 | return 171 | } 172 | 173 | func isReturn(x ast.Stmt) bool { 174 | _, ok := x.(*ast.ReturnStmt) 175 | return ok 176 | } 177 | 178 | func isEmpty(x ast.Stmt) bool { 179 | _, ok := x.(*ast.EmptyStmt) 180 | return ok 181 | } 182 | 183 | func gatherObjs(pkg *grinder.Package, fn *ast.FuncDecl, start token.Pos, list []ast.Stmt) []types.Object { 184 | seen := make(map[types.Object]bool) 185 | var objs []types.Object 186 | addObj := func(obj types.Object) { 187 | if obj == nil || seen[obj] { 188 | return 189 | } 190 | switch obj := obj.(type) { 191 | case *types.Label: 192 | return 193 | case *types.Var: 194 | if obj.IsField() { 195 | return 196 | } 197 | } 198 | seen[obj] = true 199 | objs = append(objs, obj) 200 | } 201 | ignore := make(map[*ast.Ident]bool) 202 | for _, stmt := range list { 203 | ast.Inspect(stmt, func(x ast.Node) bool { 204 | switch x := x.(type) { 205 | case *ast.SelectorExpr: 206 | ignore[x.Sel] = true 207 | case *ast.Ident: 208 | if !ignore[x] { 209 | addObj(pkg.Info.Uses[x]) 210 | } 211 | case *ast.ReturnStmt: 212 | if len(x.Results) == 0 && fn.Type.Results != nil { 213 | for _, field := range fn.Type.Results.List { 214 | for _, id := range field.Names { 215 | if pkg.Info.Defs[id] == nil { 216 | break 217 | } 218 | addObj(pkg.Info.Defs[id]) 219 | } 220 | } 221 | } 222 | } 223 | return true 224 | }) 225 | } 226 | return objs 227 | } 228 | 229 | func objsMatch(pkg *grinder.Package, fn *ast.FuncDecl, pos token.Pos, objs []types.Object, start, end token.Pos) bool { 230 | for _, obj := range objs { 231 | if start < obj.Pos() && obj.Pos() < end { 232 | // declaration is in code being moved 233 | return true 234 | } 235 | if pkg.LookupAtPos(fn, pos, obj.Name()) != obj { 236 | if debug { 237 | println("OBJ MISMATCH", obj.Name(), pkg.LookupAtPos(fn, pos, obj.Name()), obj) 238 | } 239 | return false 240 | } 241 | } 242 | return true 243 | } 244 | -------------------------------------------------------------------------------- /flow/cfg.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Derived from http://pdos.csail.mit.edu/xoc/xoc-unstable.tgz 6 | // xoc-unstable/zeta/xoc/flow.zeta. 7 | // 8 | // Copyright (c) 2003-2007 Russ Cox, Tom Bergan, Austin Clements, 9 | // Massachusetts Institute of Technology 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining 12 | // a copy of this software and associated documentation files (the 13 | // "Software"), to deal in the Software without restriction, including 14 | // without limitation the rights to use, copy, modify, merge, publish, 15 | // distribute, sublicense, and/or sell copies of the Software, and to 16 | // permit persons to whom the Software is furnished to do so, subject to 17 | // the following conditions: 18 | // 19 | // The above copyright notice and this permission notice shall be 20 | // included in all copies or substantial portions of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | //go:generate ./mksplit.sh 31 | 32 | // Package flow provides access to control flow graph computation 33 | // and analysis for Go programs. 34 | package flow 35 | 36 | import ( 37 | "bytes" 38 | "fmt" 39 | "go/ast" 40 | "go/token" 41 | "strings" 42 | ) 43 | 44 | type Graph struct { 45 | FileSet *token.FileSet 46 | Start ast.Node 47 | End ast.Node 48 | Follow map[ast.Node][]ast.Node 49 | } 50 | 51 | type Computation interface { 52 | Init(ast.Node) 53 | Transfer(ast.Node) 54 | Join(ast.Node, ast.Node) bool 55 | } 56 | 57 | type builder struct { 58 | interesting func(ast.Node) bool 59 | followCache map[ast.Node][]ast.Node 60 | end ast.Node 61 | need map[ast.Node]bool 62 | trimmed map[ast.Node]bool 63 | followed map[ast.Node]bool 64 | brk []ast.Node 65 | cont []ast.Node 66 | fall []ast.Node 67 | brkLabel map[string][]ast.Node 68 | contLabel map[string][]ast.Node 69 | gotoLabel map[string]ast.Node 70 | isGotoTarget map[string]bool 71 | stmtLabel map[ast.Stmt]string 72 | } 73 | 74 | // Build constructs a control flow graph, 75 | // filtered to include only interesting nodes. 76 | func Build(fset *token.FileSet, body *ast.BlockStmt, interesting func(ast.Node) bool) *Graph { 77 | start := &ast.Ident{Name: "_cfg_start_"} 78 | end := &ast.Ident{Name: "_cfg_end_"} 79 | b := &builder{ 80 | interesting: interesting, 81 | followCache: make(map[ast.Node][]ast.Node), 82 | end: end, 83 | need: make(map[ast.Node]bool), 84 | trimmed: make(map[ast.Node]bool), 85 | followed: make(map[ast.Node]bool), 86 | brkLabel: make(map[string][]ast.Node), 87 | contLabel: make(map[string][]ast.Node), 88 | gotoLabel: make(map[string]ast.Node), 89 | isGotoTarget: make(map[string]bool), 90 | stmtLabel: make(map[ast.Stmt]string), 91 | } 92 | 93 | ast.Inspect(body, b.scanGoto) 94 | b.followCache[start] = b.trimList(b.follow(body, []ast.Node{end})) 95 | return &Graph{ 96 | FileSet: fset, 97 | Start: start, 98 | End: end, 99 | Follow: b.followCache, 100 | } 101 | } 102 | 103 | func (b *builder) trimList(list []ast.Node) []ast.Node { 104 | if len(list) == 0 { 105 | return list 106 | } 107 | return mergef(b.trim(list[0]), b.trimList(list[1:])) 108 | } 109 | 110 | func (b *builder) trim(x ast.Node) []ast.Node { 111 | if x == nil { 112 | return nil 113 | } 114 | if !b.trimmed[x] { 115 | b.trimmed[x] = true 116 | fol := b.followCache[x] 117 | b.followCache[x] = []ast.Node{x} // during recursion 118 | b.followCache[x] = b.trimList(fol) 119 | } 120 | if !b.need[x] && len(b.followCache[x]) > 0 { 121 | return b.followCache[x] 122 | } 123 | return []ast.Node{x} 124 | } 125 | 126 | func mergef(l1, l2 []ast.Node) []ast.Node { 127 | if l1 == nil { 128 | return l2 129 | } 130 | if l2 == nil { 131 | return l1 132 | } 133 | var out []ast.Node 134 | seen := map[ast.Node]bool{} 135 | for _, x := range l1 { 136 | out = append(out, x) 137 | seen[x] = true 138 | } 139 | for _, x := range l2 { 140 | if !seen[x] { 141 | out = append(out, x) 142 | } 143 | } 144 | return out 145 | } 146 | 147 | func (b *builder) scanGoto(x ast.Node) bool { 148 | switch x := x.(type) { 149 | case *ast.LabeledStmt: 150 | b.gotoLabel[x.Label.Name] = x 151 | case *ast.BranchStmt: 152 | if x.Tok == token.GOTO { 153 | b.isGotoTarget[x.Label.Name] = true 154 | } 155 | } 156 | return true // scan children 157 | } 158 | 159 | func (b *builder) followCond(cond ast.Expr, btrue, bfalse []ast.Node) []ast.Node { 160 | // Could be more precise by looking at cond to see if it is 161 | // a constant true or false, but that might lead to code 162 | // rewrites that make dead code no longer compile. 163 | switch x := cond.(type) { 164 | case *ast.BinaryExpr: 165 | switch x.Op { 166 | case token.LAND: 167 | return b.followCond(x.X, b.followCond(x.Y, btrue, bfalse), bfalse) 168 | case token.LOR: 169 | return b.followCond(x.X, btrue, b.followCond(x.Y, btrue, bfalse)) 170 | } 171 | case *ast.UnaryExpr: 172 | switch x.Op { 173 | case token.NOT: 174 | return b.followCond(x.X, bfalse, btrue) 175 | } 176 | case *ast.ParenExpr: 177 | return b.followCond(x.X, btrue, bfalse) 178 | } 179 | return b.follow(cond, mergef(btrue, bfalse)) 180 | } 181 | 182 | func (b *builder) addNode(x ast.Node, out []ast.Node) []ast.Node { 183 | b.followCache[x] = out 184 | if !b.need[x] && !b.interesting(x) { 185 | return out 186 | } 187 | b.need[x] = true 188 | return []ast.Node{x} 189 | } 190 | 191 | func astVisit(x ast.Node, f visitFunc) { 192 | ast.Walk(f, x) 193 | } 194 | 195 | type visitFunc func(ast.Node) visitFunc 196 | 197 | func (f visitFunc) Visit(x ast.Node) ast.Visitor { 198 | v := f(x) 199 | if v == nil { 200 | return nil 201 | } 202 | return v 203 | } 204 | 205 | func astVisitChildren(x ast.Node, f func(ast.Node)) { 206 | astVisit(x, func(ast.Node) visitFunc { 207 | return func(x ast.Node) visitFunc { 208 | f(x) 209 | return nil 210 | } 211 | }) 212 | } 213 | 214 | func astSplit(x ast.Node) []ast.Node { 215 | var list []ast.Node 216 | astVisitChildren(x, func(x ast.Node) { 217 | list = append(list, x) 218 | }) 219 | return list 220 | } 221 | 222 | func (b *builder) previsit(x ast.Node, out []ast.Node) []ast.Node { 223 | list := astSplit(x) 224 | for i := len(list) - 1; i >= 0; i-- { 225 | out = b.follow(list[i], out) 226 | } 227 | out = b.addNode(x, out) 228 | return out 229 | } 230 | 231 | func (b *builder) postvisit(x ast.Node, out []ast.Node) []ast.Node { 232 | out = b.addNode(x, out) 233 | list := astSplit(x) 234 | for i := len(list) - 1; i >= 0; i-- { 235 | out = b.follow(list[i], out) 236 | } 237 | return out 238 | } 239 | 240 | func (b *builder) follow(x ast.Node, out []ast.Node) []ast.Node { 241 | switch x.(type) { 242 | case ast.Expr, ast.Stmt: 243 | // ok 244 | default: 245 | return out 246 | } 247 | 248 | if b.followed[x] { 249 | panic("flow: already followed") 250 | } 251 | b.followed[x] = true 252 | 253 | if x, ok := x.(ast.Expr); ok { 254 | switch x := x.(type) { 255 | case *ast.BinaryExpr: 256 | switch x.Op { 257 | case token.LAND: 258 | return b.followCond(x.X, b.follow(x.Y, out), out) 259 | case token.LOR: 260 | return b.followCond(x.X, out, b.follow(x.Y, out)) 261 | } 262 | case *ast.FuncLit: 263 | return b.addNode(x, out) 264 | } 265 | return b.postvisit(x, out) 266 | } 267 | 268 | switch x := x.(type) { 269 | case *ast.AssignStmt: 270 | var assigned []ast.Expr 271 | var eval []ast.Expr 272 | for _, y := range x.Lhs { 273 | for { 274 | yy, ok := y.(*ast.ParenExpr) 275 | if !ok { 276 | break 277 | } 278 | y = yy.X 279 | } 280 | id, ok := y.(*ast.Ident) 281 | if ok { 282 | assigned = append(assigned, id) 283 | } else { 284 | eval = append(eval, y) 285 | } 286 | } 287 | return b.followExprs(eval, b.followExprs(x.Rhs, b.addNode(x, b.followExprs(assigned, out)))) 288 | 289 | case *ast.BranchStmt: 290 | switch x.Tok { 291 | case token.BREAK: 292 | if x.Label != nil { 293 | return b.brkLabel[x.Label.Name] 294 | } 295 | return b.brk 296 | 297 | case token.CONTINUE: 298 | if x.Label != nil { 299 | return b.contLabel[x.Label.Name] 300 | } 301 | return b.cont 302 | 303 | case token.GOTO: 304 | return []ast.Node{b.gotoLabel[x.Label.Name]} 305 | 306 | case token.FALLTHROUGH: 307 | return b.fall 308 | } 309 | 310 | case *ast.LabeledStmt: 311 | b.stmtLabel[x.Stmt] = x.Label.Name 312 | out = b.follow(x.Stmt, out) 313 | if b.isGotoTarget[x.Label.Name] { 314 | out = b.addNode(x, out) 315 | } 316 | return out 317 | 318 | case *ast.ForStmt: 319 | oldBrk := b.brk 320 | b.brk = out 321 | oldCont := b.cont 322 | b.cont = b.follow(x.Post, []ast.Node{x}) // note: x matches b.addNode below, cleaned up by trim 323 | if label := b.stmtLabel[x]; label != "" { 324 | b.brkLabel[label] = b.brk 325 | b.contLabel[label] = b.cont 326 | } 327 | bin := b.follow(x.Body, b.cont) 328 | var condOut []ast.Node 329 | if x.Cond == nil { 330 | condOut = bin 331 | } else { 332 | condOut = mergef(bin, out) 333 | } 334 | b.brk = oldBrk 335 | b.cont = oldCont 336 | return b.follow(x.Init, b.addNode(x, b.follow(x.Cond, condOut))) 337 | 338 | case *ast.IfStmt: 339 | return b.follow(x.Init, b.followCond(x.Cond, b.follow(x.Body, out), b.follow(x.Else, out))) 340 | 341 | case *ast.RangeStmt: 342 | oldBrk := b.brk 343 | b.brk = out 344 | oldCont := b.cont 345 | b.cont = []ast.Node{x} // note: x matches b.addNode below, cleaned up by trim 346 | if label := b.stmtLabel[x]; label != "" { 347 | b.brkLabel[label] = b.brk 348 | b.contLabel[label] = b.cont 349 | } 350 | out = b.addNode(x, mergef(b.follow(x.Key, b.follow(x.Value, b.follow(x.Body, b.cont))), out)) 351 | b.brk = oldBrk 352 | b.cont = oldCont 353 | return b.follow(x.X, out) 354 | 355 | case *ast.ReturnStmt: 356 | return b.followExprs(x.Results, []ast.Node{b.end}) 357 | 358 | case *ast.SelectStmt: 359 | oldBrk := b.brk 360 | b.brk = out 361 | if label := b.stmtLabel[x]; label != "" { 362 | b.brkLabel[label] = b.brk 363 | } 364 | var allCasOut []ast.Node 365 | for _, xcas := range x.Body.List { 366 | cas := xcas.(*ast.CommClause) 367 | casOut := b.followStmts(cas.Body, out) 368 | switch comm := cas.Comm.(type) { 369 | case *ast.AssignStmt: 370 | for i := len(comm.Lhs) - 1; i >= 0; i-- { 371 | casOut = b.follow(comm.Lhs[i], casOut) 372 | } 373 | } 374 | allCasOut = mergef(allCasOut, casOut) 375 | } 376 | out = allCasOut 377 | for i := len(x.Body.List) - 1; i >= 0; i-- { 378 | cas := x.Body.List[i].(*ast.CommClause) 379 | switch comm := cas.Comm.(type) { 380 | case *ast.SendStmt: 381 | out = b.follow(comm.Value, out) 382 | out = b.follow(comm.Chan, out) 383 | case *ast.AssignStmt: 384 | out = b.follow(comm.Rhs[0], out) 385 | // Lhs evaluated when case is selected; see above. 386 | case *ast.ExprStmt: 387 | out = b.follow(comm.X, out) 388 | } 389 | } 390 | b.brk = oldBrk 391 | return out 392 | 393 | case *ast.SwitchStmt: 394 | oldBrk := b.brk 395 | b.brk = out 396 | oldFall := b.fall 397 | b.fall = nil 398 | if label := b.stmtLabel[x]; label != "" { 399 | b.brkLabel[label] = b.brk 400 | } 401 | 402 | // default is last resort, after all switch expressions are evaluated 403 | var needFall *ast.CaseClause 404 | nextCase := out 405 | for i := len(x.Body.List) - 1; i >= 0; i-- { 406 | cas := x.Body.List[i].(*ast.CaseClause) 407 | if cas.List == nil { 408 | // Found default. 409 | // Set up fallthrough link if needed. 410 | if len(cas.Body) > 0 && isFallthrough(cas.Body[len(cas.Body)-1]) { 411 | if i+1 < len(x.Body.List) { 412 | needFall = x.Body.List[i+1].(*ast.CaseClause) 413 | b.fall = []ast.Node{needFall} 414 | } 415 | } 416 | nextCase = b.followStmts(cas.Body, out) 417 | } 418 | } 419 | 420 | // non-default cases 421 | for i := len(x.Body.List) - 1; i >= 0; i-- { 422 | cas := x.Body.List[i].(*ast.CaseClause) 423 | if cas.List == nil { 424 | continue 425 | } 426 | casOut := b.followStmts(cas.Body, out) 427 | if cas == needFall { 428 | casOut = b.addNode(cas, casOut) 429 | } 430 | b.fall = casOut 431 | for j := len(cas.List) - 1; j >= 0; j-- { 432 | nextCase = b.follow(cas.List[j], mergef(nextCase, casOut)) 433 | } 434 | } 435 | 436 | b.brk = oldBrk 437 | b.fall = oldFall 438 | return b.follow(x.Init, b.follow(x.Tag, nextCase)) 439 | 440 | case *ast.TypeSwitchStmt: 441 | // Easier than switch: no fallthrough, case values are not executable. 442 | oldBrk := b.brk 443 | b.brk = out 444 | if label := b.stmtLabel[x]; label != "" { 445 | b.brkLabel[label] = b.brk 446 | } 447 | 448 | var allCasOut []ast.Node 449 | defaultOut := out 450 | for i := len(x.Body.List) - 1; i >= 0; i-- { 451 | cas := x.Body.List[i].(*ast.CaseClause) 452 | if cas.List == nil { 453 | defaultOut = nil 454 | } 455 | allCasOut = mergef(allCasOut, b.followStmts(cas.Body, out)) 456 | } 457 | b.brk = oldBrk 458 | return b.follow(x.Init, b.follow(x.Assign, mergef(allCasOut, defaultOut))) 459 | } 460 | 461 | return b.previsit(x, out) 462 | } 463 | 464 | func (b *builder) followExprs(x []ast.Expr, out []ast.Node) []ast.Node { 465 | for i := len(x) - 1; i >= 0; i-- { 466 | out = b.follow(x[i], out) 467 | } 468 | return out 469 | } 470 | 471 | func (b *builder) followStmts(x []ast.Stmt, out []ast.Node) []ast.Node { 472 | for i := len(x) - 1; i >= 0; i-- { 473 | out = b.follow(x[i], out) 474 | } 475 | return out 476 | } 477 | 478 | func isFallthrough(x ast.Stmt) bool { 479 | br, ok := x.(*ast.BranchStmt) 480 | return ok && br.Tok == token.FALLTHROUGH 481 | } 482 | 483 | func (g *Graph) Dataflow(compute Computation) { 484 | if g == nil || g.Start == nil { 485 | return 486 | } 487 | 488 | visited := make(map[ast.Node]bool) 489 | compute.Init(g.Start) 490 | var workq, nextq []ast.Node 491 | workq = append(workq, g.Start) 492 | visited[g.Start] = true 493 | for len(workq) > 0 { 494 | for _, x := range workq { 495 | compute.Transfer(x) 496 | for _, y := range g.Follow[x] { 497 | if compute.Join(y, x) || !visited[y] { 498 | visited[y] = true 499 | nextq = append(nextq, y) 500 | } 501 | } 502 | } 503 | workq, nextq = nextq, workq[:0] 504 | } 505 | } 506 | 507 | type printer struct { 508 | visited map[ast.Node]bool 509 | id map[ast.Node]int 510 | buf bytes.Buffer 511 | edge func(ast.Node, ast.Node) string 512 | g *Graph 513 | } 514 | 515 | func (p *printer) name(x ast.Node) string { 516 | if x == nil { 517 | return "nil" 518 | } 519 | if p.id[x] == 0 { 520 | p.id[x] = len(p.id) + 1 521 | } 522 | return fmt.Sprintf("n%d", p.id[x]) 523 | } 524 | 525 | var escaper = strings.NewReplacer( 526 | `\`, `\\`, 527 | `"`, `\"`, 528 | ) 529 | 530 | func nodeLabel(fset *token.FileSet, x ast.Node) string { 531 | pos := fset.Position(x.Pos()) 532 | label := fmt.Sprintf("%T %s:%d", x, pos.Filename, pos.Line) 533 | switch x := x.(type) { 534 | case *ast.Ident: 535 | label = x.Name + " " + label 536 | case *ast.SelectorExpr: 537 | label = "." + x.Sel.Name + " " + label 538 | } 539 | return label 540 | } 541 | 542 | func (p *printer) print(x ast.Node) { 543 | if x == nil || p.visited[x] { 544 | return 545 | } 546 | p.visited[x] = true 547 | name := p.name(x) // allocate id now 548 | for _, y := range p.g.Follow[x] { 549 | p.print(y) 550 | } 551 | 552 | label := nodeLabel(p.g.FileSet, x) 553 | 554 | fmt.Fprintf(&p.buf, "%s [label=\"%s\"];\n", name, escaper.Replace(label)) 555 | for _, y := range p.g.Follow[x] { 556 | e := escaper.Replace(p.edge(x, y)) 557 | if strings.HasPrefix(e, "!") { 558 | e = e[1:] + `", color="red` 559 | } 560 | fmt.Fprintf(&p.buf, "%s -> %s [label=\"%s\"];\n", name, p.name(y), e) 561 | } 562 | } 563 | 564 | func (g *Graph) Dot(edge func(src, dst ast.Node) string) []byte { 565 | if edge == nil { 566 | edge = func(src, dst ast.Node) string { return "" } 567 | } 568 | p := &printer{ 569 | visited: make(map[ast.Node]bool), 570 | id: make(map[ast.Node]int), 571 | edge: edge, 572 | g: g, 573 | } 574 | 575 | fmt.Fprintf(&p.buf, "digraph cfg {\n") 576 | p.print(g.Start) 577 | fmt.Fprintf(&p.buf, "}\n") 578 | return p.buf.Bytes() 579 | } 580 | -------------------------------------------------------------------------------- /vardecl/vardecl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package vardecl 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "go/ast" 11 | "go/token" 12 | "strings" 13 | 14 | "golang.org/x/tools/go/types" 15 | 16 | "rsc.io/grind/block" 17 | "rsc.io/grind/flow" 18 | "rsc.io/grind/grinder" 19 | ) 20 | 21 | func Grind(ctxt *grinder.Context, pkg *grinder.Package) { 22 | grinder.GrindFuncDecls(ctxt, pkg, grindFunc) 23 | } 24 | 25 | func grindFunc(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { 26 | vars := analyzeFunc(pkg, edit, fn.Body) 27 | // fmt.Printf("%s", vardecl.PrintVars(conf.Fset, vars)) 28 | for _, v := range vars { 29 | spec := v.Decl.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec) 30 | if len(spec.Names) > 1 { 31 | // TODO: Handle decls with multiple variables 32 | continue 33 | } 34 | if pkg.FileSet.Position(v.Decl.Pos()).Line != pkg.FileSet.Position(v.Decl.End()).Line { 35 | // Declaration spans line. Maybe not great to move or duplicate? 36 | continue 37 | } 38 | keepDecl := false 39 | for _, d := range v.Defs { 40 | if d.Init == v.Decl { 41 | keepDecl = true 42 | continue 43 | } 44 | switch x := d.Init.(type) { 45 | default: 46 | panic("unexpected init") 47 | case *ast.EmptyStmt: 48 | edit.CopyLine(v.Decl.Pos(), v.Decl.End(), x.Semicolon) 49 | case *ast.AssignStmt: 50 | edit.Insert(x.TokPos, ":") 51 | if !hasType(pkg, fn, x.Rhs[0], x.Lhs[0]) { 52 | typ := edit.TextAt(spec.Type.Pos(), spec.Type.End()) 53 | if strings.Contains(typ, " ") || typ == "interface{}" || typ == "struct{}" || strings.HasPrefix(typ, "*") { 54 | typ = "(" + typ + ")" 55 | } 56 | edit.Insert(x.Rhs[0].Pos(), typ+"(") 57 | edit.Insert(x.Rhs[0].End(), ")") 58 | } 59 | } 60 | } 61 | if !keepDecl { 62 | edit.DeleteLine(v.Decl.Pos(), v.Decl.End()) 63 | } 64 | } 65 | 66 | if edit.NumEdits() == 0 { 67 | initToDecl(ctxt, pkg, edit, fn) 68 | } 69 | } 70 | 71 | func hasType(pkg *grinder.Package, fn *ast.FuncDecl, x, v ast.Expr) bool { 72 | // Does x (by itself) default to v's type? 73 | // Find the scope in which x appears. 74 | xScope := pkg.Info.Scopes[fn.Type] 75 | ast.Inspect(fn.Body, func(z ast.Node) bool { 76 | if z == nil { 77 | return false 78 | } 79 | if x.Pos() < z.Pos() || z.End() <= x.Pos() { 80 | return false 81 | } 82 | scope := pkg.Info.Scopes[z] 83 | if scope != nil { 84 | xScope = scope 85 | } 86 | return true 87 | }) 88 | xt, err := types.EvalNode(pkg.FileSet, x, pkg.Types, xScope) 89 | if err != nil { 90 | return false 91 | } 92 | vt := pkg.Info.Types[v] 93 | if types.Identical(xt.Type, vt.Type) { 94 | return true 95 | } 96 | 97 | // Might be untyped. 98 | vb, ok1 := vt.Type.(*types.Basic) 99 | xb, ok2 := xt.Type.(*types.Basic) 100 | if ok1 && ok2 { 101 | switch xb.Kind() { 102 | case types.UntypedInt: 103 | return vb.Kind() == types.Int 104 | case types.UntypedBool: 105 | return vb.Kind() == types.Bool 106 | case types.UntypedRune: 107 | return vb.Kind() == types.Rune 108 | case types.UntypedFloat: 109 | return vb.Kind() == types.Float64 110 | case types.UntypedComplex: 111 | return vb.Kind() == types.Complex128 112 | case types.UntypedString: 113 | return vb.Kind() == types.String 114 | } 115 | } 116 | return false 117 | } 118 | 119 | func analyzeFunc(pkg *grinder.Package, edit *grinder.EditBuffer, body *ast.BlockStmt) []*Var { 120 | const debug = false 121 | 122 | // Build list of candidate var declarations. 123 | inClosure := make(map[*ast.Object]bool) 124 | var objs []*ast.Object 125 | vardecl := make(map[*ast.Object]*ast.DeclStmt) 126 | ast.Inspect(body, func(x ast.Node) bool { 127 | switch x := x.(type) { 128 | case *ast.DeclStmt: 129 | decl := x.Decl.(*ast.GenDecl) 130 | if len(decl.Specs) != 1 || decl.Tok != token.VAR { 131 | break 132 | } 133 | spec := decl.Specs[0].(*ast.ValueSpec) 134 | if len(spec.Values) > 0 { 135 | break 136 | } 137 | for _, id := range spec.Names { 138 | if id.Obj != nil { 139 | objs = append(objs, id.Obj) 140 | vardecl[id.Obj] = x 141 | } 142 | } 143 | case *ast.FuncLit: 144 | ast.Inspect(x, func(x ast.Node) bool { 145 | switch x := x.(type) { 146 | case *ast.Ident: 147 | if x.Obj != nil { 148 | inClosure[x.Obj] = true 149 | } 150 | } 151 | return true 152 | }) 153 | return false 154 | } 155 | return true 156 | }) 157 | 158 | // Compute block information for entire AST. 159 | blocks := block.Build(pkg.FileSet, body) 160 | 161 | var vars []*Var 162 | // Handle each variable separately. 163 | for _, obj := range objs { 164 | // For now, refuse to touch variables shared with closures. 165 | // Could instead treat those variables as having their addresses 166 | // taken at the point where the closure appears in the source code. 167 | if inClosure[obj] { 168 | continue 169 | } 170 | // Build flow graph of nodes relevant to v. 171 | g := flow.Build(pkg.FileSet, body, func(x ast.Node) bool { 172 | return needForObj(pkg, obj, x) 173 | }) 174 | 175 | // Find reaching definitions. 176 | m := newIdentMatcher(pkg, g, obj) 177 | g.Dataflow(m) 178 | 179 | // If an instance of v can refer to multiple definitions, merge them. 180 | t := newUnionFind() 181 | for _, x := range m.list { 182 | for _, def := range m.out[x].list { 183 | t.Add(def) 184 | } 185 | } 186 | for _, x := range m.list { 187 | defs := m.out[x].list 188 | if len(defs) > 1 { 189 | for _, def := range defs[1:] { 190 | t.Merge(defs[0], def) 191 | } 192 | } 193 | } 194 | 195 | // Build list of candidate definitions. 196 | var defs []*Def 197 | nodedef := make(map[ast.Node]*Def) 198 | for _, list := range t.Sets() { 199 | x := list[0].(ast.Node) 200 | d := &Def{} 201 | defs = append(defs, d) 202 | nodedef[x] = d 203 | } 204 | 205 | // Build map from uses to candidate definitions. 206 | idToDef := make(map[ast.Node]*Def) 207 | for _, x := range m.list { 208 | if _, ok := x.(*ast.Ident); ok { 209 | if debug { 210 | fmt.Printf("ID:IN %s\n", m.nodeIn(x)) 211 | fmt.Printf("ID:OUT %s\n", m.nodeOut(x)) 212 | } 213 | defs := m.out[x].list 214 | if len(defs) > 0 { 215 | idToDef[x] = nodedef[t.Find(defs[0]).(ast.Node)] 216 | } 217 | } 218 | } 219 | 220 | // Compute start/end of where defn is needed, 221 | // along with block where defn must be placed. 222 | for _, x := range m.list { 223 | // Skip declaration without initializer. 224 | // We can move the zero initialization forward. 225 | switch x := x.(type) { 226 | case *ast.DeclStmt: 227 | if x.Decl.(*ast.GenDecl).Specs[0].(*ast.ValueSpec).Values == nil { 228 | continue 229 | } 230 | } 231 | // Must use all entries in list. 232 | // Although most defs have been merged in previous passes, 233 | // the implicit zero definition of a var decl has not been. 234 | for _, def := range m.out[x].list { 235 | d := nodedef[t.Find(def).(ast.Node)] 236 | bx := blocks.Map[x] 237 | if debug { 238 | ddepth := -1 239 | if d.Block != nil { 240 | ddepth = d.Block.Depth 241 | } 242 | fmt.Printf("ID:X %s | d=%p %p b=%p bxdepth=%d ddepth=%d\n", m.nodeIn(x), d, d.Block, bx, bx.Depth, ddepth) 243 | } 244 | if d.Block == nil { 245 | d.Block = blocks.Map[x] 246 | } else { 247 | // Hoist into block containing both preliminary d.Block and x. 248 | for bx.Depth > d.Block.Depth { 249 | bx = bx.Parent 250 | } 251 | for d.Block.Depth > bx.Depth { 252 | d.Start = d.Block.Root.Pos() 253 | d.Block = d.Block.Parent 254 | } 255 | for d.Block != bx { 256 | d.Start = d.Block.Root.Pos() 257 | d.Block = d.Block.Parent 258 | bx = bx.Parent 259 | } 260 | } 261 | if pos := x.Pos(); d.Start == 0 || pos < d.Start { 262 | d.Start = pos 263 | } 264 | if end := x.End(); end > d.End { 265 | d.End = end 266 | } 267 | if debug { 268 | fmt.Printf("ID:X -> %s:%d,%d (%d,%d) ddepth=%d\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, pkg.FileSet.Position(d.End).Line, d.Start, d.End, d.Block.Depth) 269 | } 270 | } 271 | } 272 | 273 | // Move tentative declaration sites up as required. 274 | for { 275 | changed := false 276 | 277 | for di, d := range defs { 278 | if d == nil { 279 | continue 280 | } 281 | orig := blocks.Map[vardecl[obj]].Depth 282 | if d.Block == nil { 283 | continue 284 | } 285 | // Cannot move declarations into loops. 286 | // Without liveness analysis we cannot be sure the variable is dead on entry. 287 | for b := d.Block; b.Depth > orig; b = b.Parent { 288 | switch b.Root.(type) { 289 | case *ast.ForStmt, *ast.RangeStmt: 290 | for d.Block != b { 291 | d.Start = d.Block.Root.Pos() 292 | d.End = d.Block.Root.End() 293 | d.Block = d.Block.Parent 294 | changed = true 295 | } 296 | } 297 | } 298 | 299 | // Gotos. 300 | for labelname, list := range blocks.Goto { 301 | label := blocks.Label[labelname] 302 | for _, g := range list { 303 | // Cannot declare between backward goto (possibly in nested block) 304 | // and target label in same or outer block; without liveness information, 305 | // we can't be sure the variable is dead at the label. 306 | if vardecl[obj].Pos() < label.Pos() && label.Pos() < d.Start && d.Start < g.Pos() { 307 | for label.Pos() < d.Block.Root.Pos() { 308 | d.Block = d.Block.Parent 309 | } 310 | d.Start = label.Pos() 311 | changed = true 312 | } 313 | 314 | // Cannot declare between forward goto (possibly in nested block) 315 | // and target label in same block; Go disallows jumping over declaration. 316 | if g.Pos() < d.Start && d.Start <= label.Pos() && blocks.Map[label] == d.Block { 317 | if false { 318 | fmt.Printf("%s:%d: goto %s blocks declaration of %s here\n", pkg.FileSet.Position(d.Start).Filename, pkg.FileSet.Position(d.Start).Line, labelname, obj.Name) 319 | } 320 | d.Start = g.Pos() 321 | changed = true 322 | } 323 | } 324 | } 325 | 326 | // If we've decided on an implicit if/for/switch block, 327 | // make sure we can actually put the declaration there. 328 | // If we can't initialize the variable with := in the initializer, 329 | // must move it up out of the loop. 330 | // Need to do this now, so that the move is visible to the 331 | // goto and "one definition per block" checks below. 332 | // TODO(rsc): This should be done for all variables simultaneously, 333 | // to allow 334 | // for x, y := range z 335 | // instead of 336 | // var x, y int 337 | // var x, y = range z 338 | if !canDeclare(d.Block.Root, obj) { 339 | d.Start = d.Block.Root.Pos() 340 | d.Block = d.Block.Parent 341 | changed = true 342 | } 343 | 344 | // From a purely flow control point of view, in something like: 345 | // var x int 346 | // { 347 | // x = 2 348 | // y = x+x 349 | // x = y 350 | // } 351 | // use(x) 352 | // The declaration 'x = 2' could be turned into a :=, since that value 353 | // is only used on the next line, except that the := will make the assignment 354 | // on the next line no longer refer to the outer x. 355 | // For each instance of x, if the proposed declaration shadows the 356 | // actual target of that x, merge the proposal into the outer declaration. 357 | for x, xDef := range idToDef { 358 | if xDef != d && xDef.Block.Depth < d.Block.Depth && d.Start <= x.Pos() && x.Pos() < d.Block.Root.End() { 359 | // xDef is an outer definition, so its start is already earlier than d's. 360 | // No need to update xDef.Start. 361 | // Not clear we care about xDef.End. 362 | // Update idToDef mappings to redirect d to xDef. 363 | for y, yDef := range idToDef { 364 | if yDef == d { 365 | idToDef[y] = xDef 366 | } 367 | } 368 | defs[di] = nil 369 | changed = true // because idToDef changed 370 | } 371 | } 372 | } 373 | 374 | // There can only be one definition (with a given name) per block. 375 | // Merge as needed. 376 | blockdef := make(map[*block.Block]*Def) 377 | for di, d := range defs { 378 | if d == nil { 379 | continue 380 | } 381 | dd := blockdef[d.Block] 382 | if dd == nil { 383 | blockdef[d.Block] = d 384 | continue 385 | } 386 | //fmt.Printf("merge defs %p %p\n", d, dd) 387 | if d.Start < dd.Start { 388 | dd.Start = d.Start 389 | } 390 | if d.End > dd.End { 391 | dd.End = d.End 392 | } 393 | for y, yDef := range idToDef { 394 | if yDef == d { 395 | idToDef[y] = dd 396 | } 397 | } 398 | defs[di] = nil 399 | changed = true 400 | } 401 | 402 | if changed { 403 | continue 404 | } 405 | 406 | // Find place to put declaration. 407 | // We established canDeclare(d.Block, obj) above. 408 | for _, d := range defs { 409 | if d == nil || d.Block == nil { 410 | continue 411 | } 412 | switch x := d.Block.Root.(type) { 413 | default: 414 | panic(fmt.Sprintf("unexpected declaration block root %T", d.Block.Root)) 415 | 416 | case *ast.BlockStmt: 417 | d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.List) 418 | 419 | case *ast.CaseClause: 420 | d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body) 421 | 422 | case *ast.CommClause: 423 | d.Init = placeInit(edit, d.Start, obj, vardecl[obj], x.Body) 424 | 425 | case *ast.IfStmt: 426 | if x.Init == nil { 427 | panic("if without init") 428 | } 429 | d.Init = x.Init 430 | 431 | case *ast.ForStmt: 432 | if x.Init == nil { 433 | panic("for without init") 434 | } 435 | d.Init = x.Init 436 | 437 | case *ast.RangeStmt: 438 | d.Init = x 439 | 440 | case *ast.SwitchStmt: 441 | if x.Init == nil { 442 | panic("switch without init") 443 | } 444 | d.Init = x 445 | 446 | case *ast.TypeSwitchStmt: 447 | if x.Init == nil { 448 | panic("type switch without init") 449 | } 450 | d.Init = x 451 | } 452 | if d.Init != nil && d.Init.Pos() < d.Start { 453 | d.Start = d.Init.Pos() 454 | changed = true 455 | } 456 | } 457 | 458 | if !changed { 459 | break 460 | } 461 | } 462 | 463 | // Build report. 464 | v := &Var{Obj: obj, Decl: vardecl[obj]} 465 | for _, d := range defs { 466 | if d == nil || d.Block == nil { 467 | continue 468 | } 469 | if debug { 470 | fset := pkg.FileSet 471 | fmt.Printf("\tdepth %d: %s:%d,%d (%d,%d)\n", d.Block.Depth, fset.Position(d.Start).Filename, fset.Position(d.Start).Line, fset.Position(d.End).Line, d.Start, d.End) 472 | for _, x := range m.list { 473 | if len(m.out[x].list) > 0 { 474 | if d.Block == nodedef[t.Find(m.out[x].list[0]).(ast.Node)].Block { 475 | fmt.Printf("\t%s:%d %T (%d)\n", fset.Position(x.Pos()).Filename, fset.Position(x.Pos()).Line, x, x.Pos()) 476 | } 477 | } 478 | } 479 | } 480 | v.Defs = append(v.Defs, d) 481 | } 482 | 483 | if len(v.Defs) == 1 && v.Defs[0].Init == vardecl[obj] { 484 | // No changes suggested. 485 | continue 486 | } 487 | 488 | vars = append(vars, v) 489 | } 490 | 491 | return vars 492 | } 493 | 494 | func unlabel(x ast.Stmt) ast.Stmt { 495 | for { 496 | y, ok := x.(*ast.LabeledStmt) 497 | if !ok { 498 | return x 499 | } 500 | x = y.Stmt 501 | } 502 | } 503 | 504 | func placeInit(edit *grinder.EditBuffer, start token.Pos, obj *ast.Object, decl *ast.DeclStmt, list []ast.Stmt) ast.Node { 505 | declPos := -1 506 | i := 0 507 | for i < len(list) && edit.End(list[i]) < start { 508 | if unlabel(list[i]) == decl { 509 | declPos = i 510 | } 511 | i++ 512 | } 513 | if i >= len(list) { 514 | panic(fmt.Sprintf("unexpected start position")) 515 | } 516 | switch x := unlabel(list[i]).(type) { 517 | case *ast.AssignStmt: 518 | if canDeclare(x, obj) { 519 | return x 520 | } 521 | } 522 | 523 | if declPos >= 0 && allSimple(list[declPos:i]) { 524 | return decl 525 | } 526 | 527 | for j := i + 1; j < len(list); j++ { 528 | if unlabel(list[j]) == decl { 529 | if allSimple(list[i:j]) { 530 | return decl 531 | } 532 | break 533 | } 534 | } 535 | 536 | x := list[i] 537 | for { 538 | xx, ok := x.(*ast.LabeledStmt) 539 | if !ok || xx.Stmt.Pos() > start { 540 | break 541 | } 542 | x = xx.Stmt 543 | } 544 | 545 | return &ast.EmptyStmt{ 546 | Semicolon: x.Pos(), 547 | } 548 | } 549 | 550 | func allSimple(list []ast.Stmt) bool { 551 | for _, x := range list { 552 | switch unlabel(x).(type) { 553 | case *ast.DeclStmt, *ast.AssignStmt, *ast.ExprStmt, *ast.EmptyStmt, *ast.IncDecStmt: 554 | // ok 555 | default: 556 | return false 557 | } 558 | } 559 | return true 560 | } 561 | 562 | func canDeclare(x ast.Node, obj *ast.Object) bool { 563 | switch x := x.(type) { 564 | case *ast.BlockStmt, *ast.CaseClause, *ast.CommClause: 565 | return true 566 | 567 | case *ast.IfStmt: 568 | if canDeclare(x.Init, obj) { 569 | return true 570 | } 571 | 572 | case *ast.SwitchStmt: 573 | if canDeclare(x.Init, obj) { 574 | return true 575 | } 576 | 577 | case *ast.TypeSwitchStmt: 578 | if canDeclare(x.Init, obj) { 579 | return true 580 | } 581 | 582 | case *ast.ForStmt: 583 | if canDeclare(x.Init, obj) { 584 | return true 585 | } 586 | 587 | case *ast.RangeStmt: 588 | return false 589 | // TODO: Can enable this but only with type information. 590 | // Need to make sure the obj type matches the range variable type. 591 | // There's nowhere to insert a conversion if not. 592 | if isIdentObj(x.Key, obj) && (x.Value == nil || isBlank(x.Value)) || isBlank(x.Key) && isIdentObj(x.Value, obj) { 593 | return true 594 | } 595 | 596 | case *ast.AssignStmt: // for recursive calls 597 | if len(x.Lhs) == 1 && x.Tok == token.ASSIGN && isIdentObj(x.Lhs[0], obj) { 598 | onRhs := false 599 | for _, y := range x.Rhs { 600 | ast.Inspect(y, func(z ast.Node) bool { 601 | if isIdentObj(z, obj) { 602 | onRhs = true 603 | } 604 | return !onRhs 605 | }) 606 | } 607 | if onRhs { 608 | return false 609 | } 610 | return true 611 | } 612 | } 613 | return false 614 | } 615 | 616 | func isBlank(x ast.Node) bool { 617 | id, ok := x.(*ast.Ident) 618 | return ok && id.Name == "_" 619 | } 620 | 621 | func isIdentObj(x ast.Node, obj *ast.Object) bool { 622 | id, ok := x.(*ast.Ident) 623 | return ok && id.Obj == obj 624 | } 625 | 626 | func PrintVars(fset *token.FileSet, vars []*Var) []byte { 627 | var buf bytes.Buffer 628 | 629 | for _, v := range vars { 630 | pos := v.Decl.Pos() 631 | fmt.Fprintf(&buf, "var %s %s:%d\n", v.Obj.Name, fset.Position(pos).Filename, fset.Position(pos).Line) 632 | for _, d := range v.Defs { 633 | fmt.Fprintf(&buf, "\tdepth %d (%T): %s:%d,%d\n", d.Block.Depth, d.Block.Root, fset.Position(d.Start).Filename, fset.Position(d.Start).Line, fset.Position(d.End).Line) 634 | if d.Init != nil { 635 | x := d.Init 636 | fmt.Fprintf(&buf, "\t\tinit: %s:%d %T\n", fset.Position(x.Pos()).Filename, fset.Position(x.Pos()).Line, x) 637 | } 638 | } 639 | } 640 | 641 | return buf.Bytes() 642 | } 643 | 644 | type Var struct { 645 | Obj *ast.Object 646 | Decl *ast.DeclStmt 647 | Defs []*Def 648 | } 649 | 650 | type Def struct { 651 | Block *block.Block 652 | Start token.Pos 653 | End token.Pos 654 | Init ast.Node 655 | } 656 | 657 | func commonBlock(x, y *block.Block) *block.Block { 658 | if x == nil { 659 | return y 660 | } 661 | for x.Depth > y.Depth { 662 | x = x.Parent 663 | } 664 | for y.Depth > x.Depth { 665 | y = y.Parent 666 | } 667 | for x != y { 668 | x = x.Parent 669 | y = y.Parent 670 | } 671 | return x 672 | } 673 | 674 | // Because we don't have a proper liveness analysis, we don't know 675 | // which variables can be moved inside loop bodies and which cannot. 676 | // (We might also want escape analysis to avoid causing allocations.) 677 | // Move up to the outermost ForStmt if present. 678 | // (The ForStmt itself is not part of the looping control flow.) 679 | func outsideLoop(x *block.Block) *block.Block { 680 | for y := x.Parent; y != nil; y = y.Parent { 681 | switch y.Root.(type) { 682 | case *ast.ForStmt, *ast.RangeStmt: 683 | x = y 684 | } 685 | } 686 | return x 687 | } 688 | 689 | func needForObj(pkg *grinder.Package, obj *ast.Object, x ast.Node) (need bool) { 690 | switch x := x.(type) { 691 | case *ast.Ident: 692 | if x.Obj == obj { 693 | return true 694 | } 695 | 696 | case *ast.UnaryExpr: 697 | if x.Op == token.AND { 698 | y := x.X 699 | for { 700 | switch yy := y.(type) { 701 | case *ast.ParenExpr: 702 | y = yy.X 703 | continue 704 | case *ast.SelectorExpr: 705 | // If yy.X is a pointer, stop. 706 | t := pkg.Info.Types[yy.X].Type 707 | if t != nil { 708 | t = t.Underlying() 709 | if t == nil { 710 | panic("underlying nil") 711 | } 712 | _, ok := t.(*types.Pointer) 713 | if ok { 714 | break 715 | } 716 | } 717 | y = yy.X 718 | continue 719 | case *ast.IndexExpr: 720 | // If yy.X is a pointer or slice, stop. 721 | t := pkg.Info.Types[yy.X].Type 722 | if t != nil { 723 | t = t.Underlying() 724 | if t == nil { 725 | panic("underlying nil") 726 | } 727 | _, ok := t.(*types.Pointer) 728 | if ok { 729 | break 730 | } 731 | _, ok = t.(*types.Slice) 732 | if ok { 733 | break 734 | } 735 | } 736 | y = yy.X 737 | continue 738 | } 739 | break 740 | } 741 | 742 | switch y := y.(type) { 743 | case *ast.Ident: 744 | if y.Obj == obj { 745 | return true 746 | } 747 | } 748 | } 749 | 750 | case *ast.DeclStmt: 751 | g := x.Decl.(*ast.GenDecl) 752 | if g.Tok != token.VAR { 753 | break 754 | } 755 | for _, spec := range g.Specs { 756 | vs := spec.(*ast.ValueSpec) 757 | for _, id := range vs.Names { 758 | if id.Obj == obj { 759 | return true 760 | } 761 | } 762 | } 763 | 764 | case *ast.AssignStmt: 765 | if x.Tok != token.ASSIGN { 766 | break 767 | } 768 | for _, y := range x.Lhs { 769 | y = unparen(y) 770 | switch y := y.(type) { 771 | case *ast.Ident: 772 | if y.Obj == obj { 773 | return true 774 | } 775 | } 776 | } 777 | 778 | case *ast.IncDecStmt: 779 | y := unparen(x.X) 780 | switch y := y.(type) { 781 | case *ast.Ident: 782 | if y.Obj == obj { 783 | return true 784 | } 785 | } 786 | } 787 | return false 788 | } 789 | 790 | func unparen(x ast.Expr) ast.Expr { 791 | for { 792 | p, ok := x.(*ast.ParenExpr) 793 | if !ok { 794 | return x 795 | } 796 | x = p.X 797 | } 798 | } 799 | 800 | type defSet struct { 801 | list []ast.Node 802 | addrTaken bool 803 | } 804 | 805 | type identMatcher struct { 806 | in map[ast.Node]defSet 807 | out map[ast.Node]defSet 808 | list []ast.Node 809 | fset *token.FileSet 810 | g *flow.Graph 811 | obj *ast.Object 812 | pkg *grinder.Package 813 | } 814 | 815 | func newIdentMatcher(pkg *grinder.Package, g *flow.Graph, obj *ast.Object) *identMatcher { 816 | return &identMatcher{ 817 | in: make(map[ast.Node]defSet), 818 | out: make(map[ast.Node]defSet), 819 | pkg: pkg, 820 | fset: pkg.FileSet, 821 | g: g, 822 | obj: obj, 823 | } 824 | } 825 | 826 | func (m *identMatcher) nodeIn(x ast.Node) string { 827 | var buf bytes.Buffer 828 | 829 | fmt.Fprintf(&buf, "%s:", nodeLabel(m.fset, x)) 830 | for _, y := range m.in[x].list { 831 | fmt.Fprintf(&buf, " %s", nodeLabel(m.fset, y)) 832 | } 833 | return buf.String() 834 | } 835 | 836 | func (m *identMatcher) nodeOut(x ast.Node) string { 837 | var buf bytes.Buffer 838 | 839 | fmt.Fprintf(&buf, "%s:", nodeLabel(m.fset, x)) 840 | for _, y := range m.out[x].list { 841 | fmt.Fprintf(&buf, " %s", nodeLabel(m.fset, y)) 842 | } 843 | return buf.String() 844 | } 845 | 846 | func (m *identMatcher) Init(x ast.Node) { 847 | m.in[x] = defSet{} 848 | } 849 | 850 | func (m *identMatcher) Transfer(x ast.Node) { 851 | if !needForObj(m.pkg, m.obj, x) { 852 | m.out[x] = m.in[x] 853 | return 854 | } 855 | 856 | _, ok := m.out[x] 857 | if !ok && x != m.g.Start && x != m.g.End { 858 | m.list = append(m.list, x) 859 | } 860 | 861 | switch x := x.(type) { 862 | case *ast.Ident: 863 | if len(m.in[x].list) == 1 && isDecl(m.in[x].list[0]) { // first use 864 | m.out[x] = defSet{[]ast.Node{x}, false} 865 | return 866 | } 867 | 868 | case *ast.DeclStmt: 869 | m.out[x] = defSet{[]ast.Node{x}, false} 870 | return 871 | 872 | case *ast.UnaryExpr: 873 | m.out[x] = defSet{m.in[x].list, true} 874 | return 875 | 876 | case *ast.AssignStmt: 877 | if m.in[x].addrTaken { 878 | m.out[x] = defSet{mergef(m.in[x].list, []ast.Node{x}), true} 879 | } else { 880 | m.out[x] = defSet{[]ast.Node{x}, false} 881 | } 882 | return 883 | 884 | case *ast.IncDecStmt: 885 | m.out[x] = defSet{mergef(m.in[x].list, []ast.Node{x}), m.in[x].addrTaken} 886 | return 887 | } 888 | m.out[x] = m.in[x] 889 | } 890 | 891 | func isDecl(x ast.Node) bool { 892 | _, ok := x.(*ast.DeclStmt) 893 | return ok 894 | } 895 | 896 | func (m *identMatcher) Join(x, y ast.Node) bool { 897 | dx := m.in[x] 898 | dy := m.out[y] 899 | new := mergef(dx.list, dy.list) 900 | if len(new) > len(dx.list) || !dx.addrTaken && dy.addrTaken { 901 | m.in[x] = defSet{new, dx.addrTaken || dy.addrTaken} 902 | return true 903 | } 904 | return false 905 | } 906 | 907 | func nodeLabel(fset *token.FileSet, x ast.Node) string { 908 | pos := fset.Position(x.Pos()) 909 | label := fmt.Sprintf("%T %s:%d", x, pos.Filename, pos.Line) 910 | switch x := x.(type) { 911 | case *ast.Ident: 912 | label = x.Name + " " + label 913 | case *ast.SelectorExpr: 914 | label = "." + x.Sel.Name + " " + label 915 | } 916 | return label 917 | } 918 | 919 | func mergef(l1, l2 []ast.Node) []ast.Node { 920 | if l1 == nil { 921 | return l2 922 | } 923 | if l2 == nil { 924 | return l1 925 | } 926 | var out []ast.Node 927 | seen := map[ast.Node]bool{} 928 | for _, x := range l1 { 929 | out = append(out, x) 930 | seen[x] = true 931 | } 932 | for _, x := range l2 { 933 | if !seen[x] { 934 | out = append(out, x) 935 | } 936 | } 937 | return out 938 | } 939 | 940 | type unionFind struct { 941 | parent map[interface{}]interface{} 942 | rank map[interface{}]int 943 | all []interface{} 944 | } 945 | 946 | func newUnionFind() *unionFind { 947 | return &unionFind{ 948 | parent: make(map[interface{}]interface{}), 949 | rank: make(map[interface{}]int), 950 | } 951 | } 952 | 953 | func (u *unionFind) Add(x interface{}) { 954 | _, ok := u.parent[x] 955 | if ok { 956 | return 957 | } 958 | u.parent[x] = x 959 | u.rank[x] = 0 960 | u.all = append(u.all, x) 961 | } 962 | 963 | func (u *unionFind) Merge(x, y interface{}) { 964 | xRoot := u.Find(x) 965 | yRoot := u.Find(y) 966 | if xRoot == yRoot { 967 | return 968 | } 969 | 970 | if u.rank[xRoot] < u.rank[yRoot] { 971 | u.parent[xRoot] = yRoot 972 | } else if u.rank[xRoot] > u.rank[yRoot] { 973 | u.parent[yRoot] = xRoot 974 | } else { 975 | u.parent[yRoot] = xRoot 976 | u.rank[xRoot]++ 977 | } 978 | } 979 | 980 | func (u *unionFind) Find(x interface{}) interface{} { 981 | if u.parent[x] != x { 982 | u.parent[x] = u.Find(u.parent[x]) 983 | } 984 | return u.parent[x] 985 | } 986 | 987 | func (u *unionFind) Sets() [][]interface{} { 988 | var out [][]interface{} 989 | m := make(map[interface{}][]interface{}) 990 | for _, x := range u.all { 991 | u.Find(x) 992 | } 993 | for _, x := range u.all { 994 | root := u.Find(x) 995 | list := m[root] 996 | if list == nil { 997 | list = append(list, root) 998 | } 999 | if root != x { 1000 | list = append(list, x) 1001 | } 1002 | m[root] = list 1003 | } 1004 | for _, x := range u.all { 1005 | if u.Find(x) == x { 1006 | out = append(out, m[x]) 1007 | } 1008 | } 1009 | return out 1010 | } 1011 | 1012 | func initToDecl(ctxt *grinder.Context, pkg *grinder.Package, edit *grinder.EditBuffer, fn *ast.FuncDecl) { 1013 | // Rewrite x := T{} (for struct or array type T) and x := (*T)(nil) to var x T. 1014 | ast.Inspect(fn.Body, func(x ast.Node) bool { 1015 | list := grinder.BlockList(x) 1016 | for _, stmt := range list { 1017 | as, ok := stmt.(*ast.AssignStmt) 1018 | if !ok || len(as.Lhs) > 1 || as.Tok != token.DEFINE { 1019 | continue 1020 | } 1021 | var typ string 1022 | if t, ok := isNilPtr(pkg, edit, as.Rhs[0]); ok { 1023 | typ = t 1024 | } else if t, ok := isStructOrArrayLiteral(pkg, edit, as.Rhs[0]); ok { 1025 | typ = t 1026 | } 1027 | if typ != "" { 1028 | edit.Replace(stmt.Pos(), stmt.End(), "var "+as.Lhs[0].(*ast.Ident).Name+" "+typ) 1029 | } 1030 | } 1031 | return true 1032 | }) 1033 | } 1034 | 1035 | func isNilPtr(pkg *grinder.Package, edit *grinder.EditBuffer, x ast.Expr) (typ string, ok bool) { 1036 | conv, ok := x.(*ast.CallExpr) 1037 | if !ok || len(conv.Args) != 1 { 1038 | return "", false 1039 | } 1040 | id, ok := unparen(conv.Args[0]).(*ast.Ident) 1041 | if !ok || id.Name != "nil" { 1042 | return "", false 1043 | } 1044 | if obj := pkg.Info.Uses[id]; obj == nil || obj.Pkg() != nil { 1045 | return "", false 1046 | } 1047 | fn := unparen(conv.Fun) 1048 | tv, ok := pkg.Info.Types[fn] 1049 | if !ok || !tv.IsType() { 1050 | return "", false 1051 | } 1052 | return edit.TextAt(fn.Pos(), fn.End()), true 1053 | } 1054 | 1055 | func isStructOrArrayLiteral(pkg *grinder.Package, edit *grinder.EditBuffer, x ast.Expr) (typ string, ok bool) { 1056 | lit, ok := x.(*ast.CompositeLit) 1057 | if !ok || len(lit.Elts) > 0 { 1058 | return "", false 1059 | } 1060 | tv, ok := pkg.Info.Types[x] 1061 | if !ok { 1062 | return "", false 1063 | } 1064 | t := tv.Type 1065 | if name, ok := t.(*types.Named); ok { 1066 | t = name.Underlying() 1067 | } 1068 | switch t.(type) { 1069 | default: 1070 | return "", false 1071 | case *types.Struct, *types.Array: 1072 | // ok 1073 | } 1074 | return edit.TextAt(lit.Type.Pos(), lit.Type.End()), true 1075 | } 1076 | --------------------------------------------------------------------------------