├── LICENSE ├── NOTICE ├── README.md ├── cmd └── used │ └── main.go ├── docs ├── factory.svg └── talk.slide ├── testdata ├── blank.go ├── cgo.go ├── consts.go ├── conversion.go ├── cyclic.go ├── elem.go ├── embedded_call.go ├── embedding.go ├── exported_fields.go ├── exported_fields_main.go ├── exported_method_test.go ├── fields.go ├── functions.go ├── generated1.go ├── generated2.go ├── interfaces.go ├── main.go ├── mapslice.go ├── methods.go ├── nested.go ├── pointer-type-embedding.go ├── selectors.go ├── switch_interface.go ├── unused-argument.go └── unused_type.go ├── used.go └── used_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Quinn Slack 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Uses code adapted from honnef.co/go/tools/unused (license follows). 2 | 3 | Copyright (c) 2016 Dominik Honnef 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # used - compute the most-used identifiers in a Go program 2 | 3 | The `used` tool analyzes a Go program to compute the most-used identifiers. 4 | 5 | It can also filter the output of [gometalinter](https://github.com/alecthomas/gometalinter) to show problems only on the most-used identifiers. This lets you use data to prioritize what to fix/improve in a Go program. 6 | 7 | ## Install 8 | 9 | ``` 10 | go get github.com/sqs/used/cmd/used 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Show the most-used identifiers in a Go program 16 | 17 | ```bash 18 | $ cat > /tmp/file.go < us[j].N }) // largest N first 150 | if lintOutput { 151 | ps := runner.lint(lprog, us) 152 | for _, p := range ps { 153 | runner.unclean = true 154 | pos := lprog.Fset.Position(p.Pos.Pos()) 155 | fmt.Printf("%v: %s\n", relativePositionString(pos), p.Text) 156 | } 157 | } else { 158 | var more func() bool 159 | if top > 0 { 160 | more = func() bool { 161 | top-- 162 | return top > 0 163 | } 164 | } else { 165 | more = func() bool { return true } 166 | } 167 | filterLintInput(lprog.Fset, us, more) 168 | } 169 | if runner.unclean { 170 | os.Exit(1) 171 | } 172 | } 173 | 174 | type gometalinterIssue struct { 175 | Linter string `json:"linter"` 176 | Severity string `json:"severity"` 177 | Path string `json:"path"` 178 | Line int `json:"line"` 179 | Col int `json:"col"` 180 | Message string `json:"message"` 181 | } 182 | 183 | func (i gometalinterIssue) position() token.Position { 184 | return token.Position{Filename: i.Path, Line: i.Line, Column: i.Col} 185 | } 186 | 187 | func filterLintInput(fset *token.FileSet, us []used.Used, more func() bool) { 188 | objScope := func(obj types.Object) *types.Scope { 189 | o, ok := obj.(interface { 190 | Scope() *types.Scope 191 | }) 192 | if ok { 193 | return o.Scope() 194 | } 195 | return nil 196 | } 197 | 198 | containsPosition := func(rng interface { 199 | Pos() token.Pos 200 | End() token.Pos 201 | }, p token.Position) bool { 202 | start := fset.Position(rng.Pos()) 203 | end := fset.Position(rng.End()) 204 | if start.Filename != end.Filename { 205 | panic(fmt.Sprintf("start.Filename (%s) != end.Filename (%s)", start.Filename, end.Filename)) 206 | } 207 | return filepath.Base(p.Filename) == filepath.Base(start.Filename) && // TODO(sqs): filepath.Base is not correct 208 | p.Line >= start.Line && p.Line <= end.Line 209 | // ((p.Line == start.Line && p.Column >= start.Column) || p.Line > start.Line) && 210 | // ((p.Line == end.Line && p.Column <= end.Column) || p.Line < end.Line) 211 | } 212 | 213 | isInScope := func(us []used.Used, issue gometalinterIssue) bool { 214 | p := issue.position() 215 | for _, u := range us { 216 | scope := objScope(u.Obj) 217 | if scope == nil { 218 | continue // TODO(sqs): omits types 219 | } 220 | if scope.Pos() == token.NoPos || scope.End() == token.NoPos { 221 | continue // TODO(sqs) 222 | } 223 | //log.Printf("%v has range %v - %v", u.Obj, fset.Position(scope.Pos()), fset.Position(scope.End())) 224 | if containsPosition(scope, p) { 225 | //log.Printf("%v in scope because for %v", p, u.Obj) 226 | return true 227 | } 228 | //log.Printf("%v NOT IN RANGE %v - %v", issue, fset.Position(scope.Pos()), fset.Position(scope.End())) 229 | } 230 | return false 231 | } 232 | 233 | // TODO(sqs): detect if linter output is in text or JSON format and handle both 234 | s := bufio.NewScanner(os.Stdin) 235 | for s.Scan() { 236 | line := s.Bytes() 237 | if len(line) == 0 || bytes.Equal(line, []byte("[")) || bytes.Equal(line, []byte("]")) { 238 | continue 239 | } 240 | var issue gometalinterIssue 241 | line = bytes.TrimSuffix(line, []byte(",")) 242 | if err := json.Unmarshal(line, &issue); err != nil { 243 | log.Fatalf("%s (line was: %q)", err, line) 244 | } 245 | if isInScope(us, issue) { 246 | _, _ = os.Stdout.Write(line) 247 | _, _ = os.Stdout.Write([]byte("\n")) 248 | if !more() { 249 | break 250 | } 251 | } 252 | } 253 | if s.Err() != nil { 254 | log.Fatal(s.Err()) 255 | } 256 | } 257 | 258 | func parseIgnore(s string) ([]lint.Ignore, error) { 259 | var out []lint.Ignore 260 | if len(s) == 0 { 261 | return nil, nil 262 | } 263 | for _, part := range strings.Fields(s) { 264 | p := strings.Split(part, ":") 265 | if len(p) != 2 { 266 | return nil, errors.New("malformed ignore string") 267 | } 268 | path := p[0] 269 | checks := strings.Split(p[1], ",") 270 | out = append(out, lint.Ignore{Pattern: path, Checks: checks}) 271 | } 272 | return out, nil 273 | } 274 | 275 | func shortPath(path string) string { 276 | cwd, err := os.Getwd() 277 | if err != nil { 278 | return path 279 | } 280 | if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) { 281 | return rel 282 | } 283 | return path 284 | } 285 | 286 | func relativePositionString(pos token.Position) string { 287 | s := shortPath(pos.Filename) 288 | if pos.IsValid() { 289 | if s != "" { 290 | s += ":" 291 | } 292 | s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) 293 | } 294 | if s == "" { 295 | s = "-" 296 | } 297 | return s 298 | } 299 | 300 | type runner struct { 301 | checker *used.Checker 302 | tags []string 303 | ignores []lint.Ignore 304 | 305 | unclean bool 306 | } 307 | 308 | func (runner runner) resolveRelative(importPaths []string) (goFiles bool, err error) { 309 | if len(importPaths) == 0 { 310 | return false, nil 311 | } 312 | if strings.HasSuffix(importPaths[0], ".go") { 313 | // User is specifying a package in terms of .go files, don't resolve 314 | return true, nil 315 | } 316 | wd, err := os.Getwd() 317 | if err != nil { 318 | return false, err 319 | } 320 | ctx := build.Default 321 | ctx.BuildTags = runner.tags 322 | for i, path := range importPaths { 323 | bpkg, err := ctx.Import(path, wd, build.FindOnly) 324 | if err != nil { 325 | return false, fmt.Errorf("can't load package %q: %v", path, err) 326 | } 327 | importPaths[i] = bpkg.ImportPath 328 | } 329 | return false, nil 330 | } 331 | 332 | func (runner *runner) check(lprog *loader.Program) []used.Used { 333 | return runner.checker.Check(lprog) 334 | } 335 | 336 | func (runner *runner) lint(lprog *loader.Program, us []used.Used) []used.Problem { 337 | l := used.NewLintData(us) 338 | return l.LintProblems(lprog) 339 | } 340 | -------------------------------------------------------------------------------- /docs/factory.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/talk.slide: -------------------------------------------------------------------------------- 1 | Using data to write better Go code 2 | 19:00 23 Mar 2017 3 | 4 | Quinn Slack 5 | Founder/CEO, Sourcegraph 6 | sqs@sourcegraph.com 7 | https://sourcegraph.com 8 | @sqs 9 | 10 | * Why does better code matter? 11 | 12 | Your Go program already: 13 | 14 | - compiles 15 | - has users 16 | - solves problems 17 | - makes money (or in San Francisco, has raised VC money) 18 | 19 | So...why does better code matter? 20 | 21 | * Why does the environment matter? 22 | 23 | Your widget factory already makes money. 24 | 25 | So...why does the environment matter? 26 | 27 | .image factory.svg _ 200 28 | 29 | * The unseen future costs you impose on others 30 | 31 | What about the people in the future who will... 32 | 33 | - use your code? 34 | - maintain your code? 35 | - learn from your code? 36 | 37 | * Solving the problem 38 | 39 | Fixing the environment is not as easy as "just don't pollute." 40 | 41 | Writing better code is not as easy as "just write better code." 42 | 43 | Given finite time, what improvement would have the biggest impact? 44 | 45 | * To write better code, treat code as data 46 | 47 | Introducing [[https://github.com/sqs/used][github.com/sqs/used]], a new tool that analyzes a Go program to approximately answer: 48 | 49 | "What's the highest impact piece of code I can fix right now?" 50 | 51 | It can: 52 | 53 | - list the most-used identifiers 54 | - sort linter output to show higher-impact problems first 55 | 56 | * Demos: Using used to improve real Go projects 57 | 58 | - [[https://github.com/gorilla/mux][github.com/gorilla/mux]] 59 | - [[https://github.com/driusan/dgit][github.com/driusan/dgit]] 60 | - [[https://github.com/alistanis/st][github.com/alistanis/st]] 61 | - [[https://github.com/mholt/caddy][github.com/mholt/caddy]] 62 | - [[https://github.com/radovskyb/watcher][github.com/radovskyb/watcher]] 63 | - [[https://github.com/chewxy/gorgonia][github.com/chewxy/gorgonia]] 64 | - Others? We'll do it live! 65 | -------------------------------------------------------------------------------- /testdata/blank.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import _ "fmt" // MATCH /_ is used 1 time/ 4 | 5 | type t1 struct{} // MATCH /t1 is used 0 times/ 6 | type t2 struct{} // MATCH /t2 is used 2 times/ 7 | type t3 struct{} // MATCH /t3 is used 2 times/ 8 | 9 | var _ = t2{} 10 | 11 | func fn1() { // MATCH /fn1 is used 0 times/ 12 | _ = t1{} 13 | var _ = t1{} 14 | } 15 | 16 | func fn2() { // MATCH /fn2 is used 2 times/ 17 | _ = t3{} 18 | } 19 | 20 | func init() { // MATCH /init is used 2 times/ 21 | fn2() 22 | } 23 | 24 | func _() {} 25 | 26 | type _ struct{} 27 | -------------------------------------------------------------------------------- /testdata/cgo.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | //go:cgo_export_dynamic 4 | func foo() {} 5 | 6 | func bar() {} // MATCH /bar is used 0 times/ 7 | -------------------------------------------------------------------------------- /testdata/consts.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | const c1 = 1 4 | 5 | const c2 = 1 6 | const c3 = 1 7 | const c4 = 1 8 | 9 | var _ = []int{c3: 1} 10 | 11 | type T1 struct { 12 | F1 [c1]int 13 | } 14 | 15 | func init() { 16 | _ = []int{c2: 1} 17 | var _ [c4]int 18 | } 19 | -------------------------------------------------------------------------------- /testdata/conversion.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "compress/flate" 5 | "unsafe" 6 | ) 7 | 8 | type t1 struct { 9 | a int 10 | b int 11 | } 12 | 13 | type t2 struct { 14 | a int 15 | b int 16 | } 17 | 18 | type t3 struct { 19 | a int 20 | b int // MATCH /b is used 0 times/ 21 | } 22 | 23 | type t4 struct { 24 | a int 25 | b int // MATCH /b is used 0 times/ 26 | } 27 | 28 | type t5 struct { 29 | a int 30 | b int 31 | } 32 | 33 | type t6 struct { 34 | a int 35 | b int 36 | } 37 | 38 | type t7 struct { 39 | a int 40 | b int 41 | } 42 | 43 | type t8 struct { 44 | a int 45 | b int 46 | } 47 | 48 | type t9 struct { 49 | Offset int64 50 | Err error 51 | } 52 | 53 | type t10 struct { 54 | a int 55 | b int 56 | } 57 | 58 | func fn() { 59 | // All fields in t2 used because they're initialised in t1 60 | v1 := t1{0, 1} 61 | v2 := t2(v1) 62 | _ = v2 63 | 64 | // Field b isn't used by anyone 65 | v3 := t3{} 66 | v4 := t4(v3) 67 | println(v3.a) 68 | _ = v4 69 | 70 | // Both fields are used 71 | v5 := t5{} 72 | v6 := t6(v5) 73 | println(v5.a) 74 | println(v6.b) 75 | 76 | v7 := &t7{} 77 | println(v7.a) 78 | println(v7.b) 79 | v8 := (*t8)(v7) 80 | _ = v8 81 | 82 | vb := flate.ReadError{} 83 | v9 := t9(vb) 84 | _ = v9 85 | 86 | // All fields are used because this is an unsafe conversion 87 | var b []byte 88 | v10 := (*t10)(unsafe.Pointer(&b[0])) 89 | _ = v10 90 | } 91 | 92 | func init() { fn() } 93 | -------------------------------------------------------------------------------- /testdata/cyclic.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func a() { // MATCH /a is used 0 times/ 4 | b() 5 | } 6 | 7 | func b() { // MATCH /b is used 0 times/ 8 | a() 9 | } 10 | -------------------------------------------------------------------------------- /testdata/elem.go: -------------------------------------------------------------------------------- 1 | // Test of field usage detection 2 | 3 | package pkg 4 | 5 | type t15 struct{ f151 int } 6 | type a2 [1]t15 7 | 8 | type t16 struct{} 9 | type a3 [1][1]t16 10 | 11 | func foo() { 12 | _ = a2{0: {1}} 13 | _ = a3{{{}}} 14 | } 15 | 16 | func init() { foo() } 17 | -------------------------------------------------------------------------------- /testdata/embedded_call.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | var t1 struct { 4 | t2 5 | t3 6 | t4 7 | } 8 | 9 | type t2 struct{} 10 | type t3 struct{} 11 | type t4 struct{ t5 } 12 | type t5 struct{} 13 | 14 | func (t2) foo() {} 15 | func (t3) bar() {} 16 | func (t5) baz() {} 17 | func init() { 18 | t1.foo() 19 | _ = t1.bar 20 | t1.baz() 21 | } 22 | -------------------------------------------------------------------------------- /testdata/embedding.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type I interface { 4 | f1() 5 | f2() 6 | } 7 | 8 | func init() { 9 | var _ I 10 | } 11 | 12 | type t1 struct{} 13 | type T2 struct{ t1 } 14 | 15 | func (t1) f1() {} 16 | func (T2) f2() {} 17 | 18 | func Fn() { 19 | var v T2 20 | _ = v.t1 21 | } 22 | 23 | type I2 interface { 24 | f3() 25 | f4() 26 | } 27 | 28 | type t3 struct{} 29 | type t4 struct { 30 | x int // MATCH /x is used 0 times/ 31 | y int // MATCH /y is used 0 times/ 32 | t3 33 | } 34 | 35 | func (*t3) f3() {} 36 | func (*t4) f4() {} 37 | 38 | func init() { 39 | var i I2 = &t4{} 40 | i.f3() 41 | i.f4() 42 | } 43 | -------------------------------------------------------------------------------- /testdata/exported_fields.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t1 struct { 4 | F1 int 5 | } 6 | 7 | type T2 struct { 8 | F2 int 9 | } 10 | 11 | var v struct { 12 | T3 13 | } 14 | 15 | type T3 struct{} 16 | 17 | func (T3) Foo() {} 18 | 19 | func init() { 20 | v.Foo() 21 | } 22 | 23 | func init() { 24 | _ = t1{} 25 | } 26 | 27 | type codeResponse struct { 28 | Tree *codeNode `json:"tree"` 29 | } 30 | 31 | type codeNode struct { 32 | } 33 | 34 | func init() { 35 | var _ codeResponse 36 | } 37 | -------------------------------------------------------------------------------- /testdata/exported_fields_main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type t1 struct { 4 | F1 int 5 | } 6 | 7 | type T2 struct { 8 | F2 int 9 | } 10 | 11 | func init() { 12 | _ = t1{} 13 | _ = T2{} 14 | } 15 | -------------------------------------------------------------------------------- /testdata/exported_method_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "io/ioutil" 7 | "testing" 8 | ) 9 | 10 | type countReadSeeker struct { 11 | io.ReadSeeker 12 | N int64 13 | } 14 | 15 | func (rs *countReadSeeker) Read(buf []byte) (int, error) { 16 | n, err := rs.ReadSeeker.Read(buf) 17 | rs.N += int64(n) 18 | return n, err 19 | } 20 | 21 | func TestFoo(t *testing.T) { 22 | r := bytes.NewReader([]byte("Hello, world!")) 23 | cr := &countReadSeeker{ReadSeeker: r} 24 | ioutil.ReadAll(cr) 25 | if cr.N != 13 { 26 | t.Errorf("got %d, want 13", cr.N) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /testdata/fields.go: -------------------------------------------------------------------------------- 1 | // Test of field usage detection 2 | 3 | package pkg 4 | 5 | type t1 struct{ f11, f12 int } 6 | type t2 struct{ f21, f22 int } 7 | type t3 struct{ f31 t4 } 8 | type t4 struct{ f41 int } 9 | type t5 struct{ f51 int } 10 | type t6 struct{ f61 int } 11 | type t7 struct{ f71 int } 12 | type m1 map[string]t7 13 | type t8 struct{ f81 int } 14 | type t9 struct{ f91 int } 15 | type t10 struct{ f101 int } 16 | type t11 struct{ f111 int } 17 | type s1 []t11 18 | type t12 struct{ f121 int } 19 | type s2 []t12 20 | type t13 struct{ f131 int } 21 | type t14 struct{ f141 int } 22 | type a1 [1]t14 23 | type t15 struct{ f151 int } 24 | type a2 [1]t15 25 | type t16 struct{ f161 int } 26 | type t17 struct{ f171, f172 int } // MATCH /t17 is used 0 times/ 27 | // MATCH:28 /f183 is used 0 times/ 28 | type t18 struct{ f181, f182, f183 int } // MATCH /f182 is used 0 times/ 29 | 30 | type t19 struct{ f191 int } 31 | type m2 map[string]t19 32 | 33 | type t20 struct{ f201 int } 34 | type m3 map[string]t20 35 | 36 | type t21 struct{ f211, f212 int } // MATCH /f211 is used 0 times/ 37 | 38 | func foo() { 39 | _ = t10{1} 40 | _ = t21{f212: 1} 41 | _ = []t1{{1, 2}} 42 | _ = t2{1, 2} 43 | _ = []struct{ a int }{{1}} 44 | 45 | // XXX 46 | // _ = []struct{ foo struct{ bar int } }{{struct{ bar int }{1}}} 47 | 48 | _ = []t1{t1{1, 2}} 49 | _ = []t3{{t4{1}}} 50 | _ = map[string]t5{"a": {1}} 51 | _ = map[t6]string{{1}: "a"} 52 | _ = m1{"a": {1}} 53 | _ = map[t8]t8{{}: {1}} 54 | _ = map[t9]t9{{1}: {}} 55 | _ = s1{{1}} 56 | _ = s2{2: {1}} 57 | _ = [...]t13{{1}} 58 | _ = a1{{1}} 59 | _ = a2{0: {1}} 60 | _ = map[[1]t16]int{{{1}}: 1} 61 | y := struct{ x int }{} // MATCH /x is used 0 times/ 62 | _ = y 63 | _ = t18{f181: 1} 64 | _ = []m2{{"a": {1}}} 65 | _ = [][]m3{{{"a": {1}}}} 66 | } 67 | 68 | func init() { foo() } 69 | -------------------------------------------------------------------------------- /testdata/functions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type state func() state // MATCH /state is used 1 time/ 4 | 5 | func a() state { // MATCH /a is used 3 times/ 6 | return a 7 | } 8 | 9 | func main() { // MATCH /main is used 2 times/ 10 | st := a // MATCH /st is used 1 time/ 11 | _ = st() 12 | } 13 | 14 | type t1 struct{} // MATCH /t1 is used 0 times/ 15 | type t2 struct{} // MATCH /t2 is used 1 time/ 16 | type t3 struct{} // MATCH /t3 is used 1 time/ 17 | 18 | func fn1() t1 { return t1{} } // MATCH /fn1 is used 0 times/ 19 | func fn2() (x t2) { return } // MATCH /fn2 is used 2 times/ 20 | // MATCH:19 /x is used 1 time/ 21 | func fn3() *t3 { return nil } // MATCH /fn3 is used 2 times/ 22 | 23 | func fn4() { // MATCH /fn4 is used 2 times/ 24 | const x = 1 // MATCH /x is used 1 time/ 25 | const y = 2 // MATCH /y is used 0 times/ 26 | type foo int // MATCH /foo is used 0 times/ 27 | type bar int // MATCH /bar is used 2 times/ 28 | 29 | _ = x 30 | var _ bar 31 | } 32 | 33 | func init() { // MATCH /init is used 2 times/ 34 | fn2() 35 | fn3() 36 | fn4() 37 | } 38 | -------------------------------------------------------------------------------- /testdata/generated1.go: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT 2 | 3 | package pkg 4 | 5 | type t struct{} 6 | -------------------------------------------------------------------------------- /testdata/generated2.go: -------------------------------------------------------------------------------- 1 | // Code generated by a bunch of monkeys with typewriters and RSI. 2 | 3 | package pkg 4 | 5 | type t struct{} 6 | -------------------------------------------------------------------------------- /testdata/interfaces.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type I interface { 4 | fn1() 5 | } 6 | 7 | type t struct{} 8 | 9 | func (t) fn1() {} 10 | func (t) fn2() {} // MATCH /fn2 is used 0 times/ 11 | 12 | func init() { 13 | var _ I 14 | var _ t 15 | } 16 | -------------------------------------------------------------------------------- /testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func Fn1() {} 4 | func Fn2() {} // MATCH /Fn2 is used 0 times/ 5 | 6 | const X = 1 // MATCH /X is used 0 times/ 7 | 8 | var Y = 2 // MATCH /Y is used 0 times/ 9 | 10 | type Z struct{} // MATCH /Z is used 0 times/ 11 | 12 | func main() { 13 | Fn1() 14 | } 15 | -------------------------------------------------------------------------------- /testdata/mapslice.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type M map[int]int 4 | 5 | func Fn() { 6 | var n M 7 | _ = []M{n} 8 | } 9 | -------------------------------------------------------------------------------- /testdata/methods.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t1 struct{} 4 | type t2 struct{ t3 } 5 | type t3 struct{} 6 | 7 | func (t1) Foo() {} 8 | func (t3) Foo() {} 9 | func (t3) foo() {} // MATCH /foo is used 0 times/ 10 | 11 | func init() { 12 | _ = t1{} 13 | _ = t2{} 14 | } 15 | -------------------------------------------------------------------------------- /testdata/nested.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t struct{} // MATCH /t is used 0 times/ 4 | 5 | func (t) fragment() {} 6 | 7 | func fn() bool { // MATCH /fn is used 0 times/ 8 | var v interface{} = t{} 9 | switch obj := v.(type) { 10 | // XXX it shouldn't report fragment(), because fn is used 0 times 11 | case interface { 12 | fragment() // MATCH /fragment is used 0 times/ 13 | }: 14 | obj.fragment() 15 | } 16 | return false 17 | } 18 | -------------------------------------------------------------------------------- /testdata/pointer-type-embedding.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func init() { 4 | var p P 5 | _ = p.n 6 | } 7 | 8 | type T0 struct { 9 | m int // MATCH /m is used 0 times/ 10 | n int 11 | } 12 | 13 | type T1 struct { 14 | T0 15 | } 16 | 17 | type P *T1 18 | -------------------------------------------------------------------------------- /testdata/selectors.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t struct { 4 | f int 5 | } 6 | 7 | func fn(v *t) { 8 | println(v.f) 9 | } 10 | 11 | func init() { 12 | var v t 13 | fn(&v) 14 | } 15 | -------------------------------------------------------------------------------- /testdata/switch_interface.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t struct{} 4 | 5 | func (t) fragment() {} 6 | 7 | func fn() bool { 8 | var v interface{} = t{} 9 | switch obj := v.(type) { 10 | case interface { 11 | fragment() 12 | }: 13 | obj.fragment() 14 | } 15 | return false 16 | } 17 | 18 | var x = fn() 19 | var _ = x 20 | -------------------------------------------------------------------------------- /testdata/unused-argument.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type t1 struct{} 4 | type t2 struct{} 5 | 6 | func (t1) foo(arg *t2) {} 7 | 8 | func init() { 9 | t1{}.foo(nil) 10 | } 11 | -------------------------------------------------------------------------------- /testdata/unused_type.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type t1 struct{} // MATCH /t1 is used 0 times/ 4 | 5 | func (t1) Fn() {} 6 | 7 | type t2 struct{} 8 | 9 | func (*t2) Fn() {} 10 | 11 | func init() { 12 | (*t2).Fn(nil) 13 | } 14 | 15 | type t3 struct{} // MATCH /t3 is used 0 times/ 16 | 17 | func (t3) fn() 18 | -------------------------------------------------------------------------------- /used.go: -------------------------------------------------------------------------------- 1 | package used // import "github.com/sqs/used" 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/token" 7 | "go/types" 8 | "io" 9 | "path/filepath" 10 | "strings" 11 | 12 | "honnef.co/go/tools/lint" 13 | 14 | "golang.org/x/tools/go/loader" 15 | "golang.org/x/tools/go/types/typeutil" 16 | ) 17 | 18 | func NewLintChecker(c *Checker) *LintChecker { 19 | l := &LintChecker{ 20 | c: c, 21 | } 22 | return l 23 | } 24 | 25 | func NewLintData(used []Used) *LintChecker { 26 | l := &LintChecker{ 27 | used: used, 28 | } 29 | return l 30 | } 31 | 32 | type LintChecker struct { 33 | c *Checker 34 | used []Used 35 | } 36 | 37 | func (l *LintChecker) Init(*lint.Program) {} 38 | func (l *LintChecker) Funcs() map[string]lint.Func { 39 | return map[string]lint.Func{ 40 | "U1000": l.Lint, 41 | } 42 | } 43 | 44 | func typString(obj types.Object) string { 45 | switch obj := obj.(type) { 46 | case *types.Func: 47 | return "func" 48 | case *types.Var: 49 | if obj.IsField() { 50 | return "field" 51 | } 52 | return "var" 53 | case *types.Const: 54 | return "const" 55 | case *types.TypeName: 56 | return "type" 57 | default: 58 | // log.Printf("%T", obj) 59 | return "identifier" 60 | } 61 | } 62 | 63 | type Problem struct { 64 | Pos lint.Positioner 65 | Text string 66 | } 67 | 68 | func (l *LintChecker) LintProblems(prog *loader.Program) (problems []Problem) { 69 | if l.used == nil { 70 | l.used = l.c.Check(prog) 71 | } 72 | for _, u := range l.used { 73 | name := u.Obj.Name() 74 | if sig, ok := u.Obj.Type().(*types.Signature); ok && sig.Recv() != nil { 75 | switch sig.Recv().Type().(type) { 76 | case *types.Named, *types.Pointer: 77 | typ := types.TypeString(sig.Recv().Type(), func(*types.Package) string { return "" }) 78 | if len(typ) > 0 && typ[0] == '*' { 79 | name = fmt.Sprintf("(%s).%s", typ, u.Obj.Name()) 80 | } else if len(typ) > 0 { 81 | name = fmt.Sprintf("%s.%s", typ, u.Obj.Name()) 82 | } 83 | } 84 | } 85 | var plural string 86 | if u.N != 1 { 87 | plural = "s" 88 | } 89 | problems = append(problems, Problem{u.Obj, fmt.Sprintf("%s %s is used %d time%s", typString(u.Obj), name, u.N, plural)}) 90 | } 91 | return problems 92 | } 93 | 94 | func (l *LintChecker) Lint(j *lint.Job) { 95 | ps := l.LintProblems(j.Program.Prog) 96 | for _, p := range ps { 97 | j.Errorf(p.Pos, "%s", p.Text) 98 | } 99 | } 100 | 101 | type graph struct { 102 | roots []*graphNode 103 | nodes map[interface{}]*graphNode 104 | } 105 | 106 | func (g *graph) markUsedBy(obj, usedBy interface{}) { 107 | objNode := g.getNode(obj) 108 | usedByNode := g.getNode(usedBy) 109 | if objNode.obj == usedByNode.obj { 110 | return 111 | } 112 | usedByNode.uses[objNode] = struct{}{} 113 | } 114 | 115 | var labelCounter = 1 116 | 117 | func (g *graph) getNode(obj interface{}) *graphNode { 118 | for { 119 | if pt, ok := obj.(*types.Pointer); ok { 120 | obj = pt.Elem() 121 | } else { 122 | break 123 | } 124 | } 125 | _, ok := g.nodes[obj] 126 | if !ok { 127 | g.addObj(obj) 128 | } 129 | 130 | return g.nodes[obj] 131 | } 132 | 133 | func (g *graph) addObj(obj interface{}) { 134 | if pt, ok := obj.(*types.Pointer); ok { 135 | obj = pt.Elem() 136 | } 137 | node := &graphNode{obj: obj, uses: make(map[*graphNode]struct{}), n: labelCounter} 138 | g.nodes[obj] = node 139 | labelCounter++ 140 | 141 | if obj, ok := obj.(*types.Struct); ok { 142 | n := obj.NumFields() 143 | for i := 0; i < n; i++ { 144 | field := obj.Field(i) 145 | g.markUsedBy(obj, field) 146 | } 147 | } 148 | } 149 | 150 | type graphNode struct { 151 | obj interface{} 152 | uses map[*graphNode]struct{} 153 | used bool 154 | usages uint 155 | quiet bool 156 | n int 157 | } 158 | 159 | type CheckMode int 160 | 161 | const ( 162 | CheckConstants CheckMode = 1 << iota 163 | CheckFields 164 | CheckFunctions 165 | CheckTypes 166 | CheckVariables 167 | 168 | CheckAll = CheckConstants | CheckFields | CheckFunctions | CheckTypes | CheckVariables 169 | ) 170 | 171 | type Used struct { 172 | Obj types.Object 173 | Position token.Position 174 | EndPosition token.Position 175 | N uint // number of usages 176 | } 177 | 178 | type Checker struct { 179 | Mode CheckMode 180 | WholeProgram bool 181 | ConsiderReflection bool 182 | Debug io.Writer 183 | 184 | graph *graph 185 | 186 | msCache typeutil.MethodSetCache 187 | lprog *loader.Program 188 | topmostCache map[*types.Scope]*types.Scope 189 | interfaces []*types.Interface 190 | } 191 | 192 | func NewChecker(mode CheckMode) *Checker { 193 | return &Checker{ 194 | Mode: mode, 195 | graph: &graph{ 196 | nodes: make(map[interface{}]*graphNode), 197 | }, 198 | topmostCache: make(map[*types.Scope]*types.Scope), 199 | } 200 | } 201 | 202 | func (c *Checker) checkConstants() bool { return (c.Mode & CheckConstants) > 0 } 203 | func (c *Checker) checkFields() bool { return (c.Mode & CheckFields) > 0 } 204 | func (c *Checker) checkFunctions() bool { return (c.Mode & CheckFunctions) > 0 } 205 | func (c *Checker) checkTypes() bool { return (c.Mode & CheckTypes) > 0 } 206 | func (c *Checker) checkVariables() bool { return (c.Mode & CheckVariables) > 0 } 207 | 208 | func (c *Checker) markFields(typ types.Type) { 209 | structType, ok := typ.Underlying().(*types.Struct) 210 | if !ok { 211 | return 212 | } 213 | n := structType.NumFields() 214 | for i := 0; i < n; i++ { 215 | field := structType.Field(i) 216 | c.graph.markUsedBy(field, typ) 217 | } 218 | } 219 | 220 | type Error struct { 221 | Errors map[string][]error 222 | } 223 | 224 | func (e Error) Error() string { 225 | return fmt.Sprintf("errors in %d packages", len(e.Errors)) 226 | } 227 | 228 | func (c *Checker) Check(lprog *loader.Program) []Used { 229 | var used []Used 230 | c.lprog = lprog 231 | if c.WholeProgram { 232 | c.findExportedInterfaces() 233 | } 234 | for _, pkg := range c.lprog.InitialPackages() { 235 | c.processDefs(pkg) 236 | c.processUses(pkg) 237 | c.processTypes(pkg) 238 | c.processSelections(pkg) 239 | c.processAST(pkg) 240 | } 241 | 242 | for _, node := range c.graph.nodes { 243 | obj, ok := node.obj.(types.Object) 244 | if !ok { 245 | continue 246 | } 247 | typNode, ok := c.graph.nodes[obj.Type()] 248 | if !ok { 249 | continue 250 | } 251 | node.uses[typNode] = struct{}{} 252 | } 253 | 254 | roots := map[*graphNode]struct{}{} 255 | for _, root := range c.graph.roots { 256 | roots[root] = struct{}{} 257 | } 258 | markNodesUsed(roots) 259 | c.markNodesQuiet() 260 | 261 | if c.Debug != nil { 262 | c.printDebugGraph(c.Debug) 263 | } 264 | 265 | for _, node := range c.graph.nodes { 266 | obj, ok := node.obj.(types.Object) 267 | if !ok { 268 | continue 269 | } 270 | var pkg *loader.PackageInfo 271 | if !false { 272 | for _, pkg2 := range c.lprog.InitialPackages() { 273 | if pkg2.Pkg == obj.Pkg() { 274 | pkg = pkg2 275 | break 276 | } 277 | } 278 | } 279 | if pkg == nil { 280 | continue 281 | } 282 | 283 | pos := c.lprog.Fset.Position(obj.Pos()) 284 | if pos.Filename == "" || filepath.Base(pos.Filename) == "C" { 285 | continue 286 | } 287 | generated := false 288 | for _, file := range c.lprog.Package(obj.Pkg().Path()).Files { 289 | if c.lprog.Fset.Position(file.Pos()).Filename != pos.Filename { 290 | continue 291 | } 292 | if len(file.Comments) > 0 { 293 | generated = isGenerated(file.Comments[0].Text()) 294 | } 295 | break 296 | } 297 | if generated { 298 | continue 299 | } 300 | 301 | used = append(used, Used{Obj: obj, Position: pos, N: node.usages}) 302 | } 303 | return used 304 | } 305 | 306 | func (c *Checker) useExportedFields(typ types.Type) { 307 | if st, ok := typ.Underlying().(*types.Struct); ok { 308 | n := st.NumFields() 309 | for i := 0; i < n; i++ { 310 | field := st.Field(i) 311 | if field.Exported() { 312 | c.graph.markUsedBy(field, typ) 313 | } 314 | } 315 | } 316 | } 317 | 318 | func (c *Checker) useExportedMethods(typ types.Type) { 319 | named, ok := typ.(*types.Named) 320 | if !ok { 321 | return 322 | } 323 | ms := typeutil.IntuitiveMethodSet(named, &c.msCache) 324 | for i := 0; i < len(ms); i++ { 325 | meth := ms[i].Obj() 326 | if meth.Exported() { 327 | c.graph.markUsedBy(meth, typ) 328 | } 329 | } 330 | 331 | st, ok := named.Underlying().(*types.Struct) 332 | if !ok { 333 | return 334 | } 335 | n := st.NumFields() 336 | for i := 0; i < n; i++ { 337 | field := st.Field(i) 338 | if !field.Anonymous() { 339 | continue 340 | } 341 | ms := typeutil.IntuitiveMethodSet(field.Type(), &c.msCache) 342 | for j := 0; j < len(ms); j++ { 343 | if ms[j].Obj().Exported() { 344 | c.graph.markUsedBy(field, typ) 345 | break 346 | } 347 | } 348 | } 349 | } 350 | 351 | func (c *Checker) processDefs(pkg *loader.PackageInfo) { 352 | for _, obj := range pkg.Defs { 353 | if obj == nil { 354 | continue 355 | } 356 | c.graph.getNode(obj) 357 | 358 | if obj, ok := obj.(*types.TypeName); ok { 359 | c.graph.markUsedBy(obj.Type().Underlying(), obj.Type()) 360 | c.graph.markUsedBy(obj.Type(), obj) // TODO is this needed? 361 | c.graph.markUsedBy(obj, obj.Type()) 362 | 363 | // We mark all exported fields as used. For normal 364 | // operation, we have to. The user may use these fields 365 | // without us knowing. 366 | // 367 | // TODO(dh): In whole-program mode, however, we mark them 368 | // as used because of reflection (such as JSON 369 | // marshaling). Strictly speaking, we would only need to 370 | // mark them used if an instance of the type was 371 | // accessible via an interface value. 372 | if !c.WholeProgram || c.ConsiderReflection { 373 | c.useExportedFields(obj.Type()) 374 | } 375 | 376 | // TODO(dh): Traditionally we have not marked all exported 377 | // methods as exported, even though they're strictly 378 | // speaking accessible through reflection. We've done that 379 | // because using methods just via reflection is rare, and 380 | // not worth the false negatives. With the new -reflect 381 | // flag, however, we should reconsider that choice. 382 | if !c.WholeProgram { 383 | c.useExportedMethods(obj.Type()) 384 | } 385 | } 386 | 387 | switch obj := obj.(type) { 388 | case *types.Var, *types.Const, *types.Func, *types.TypeName: 389 | if obj.Exported() { 390 | // Exported variables and constants use their types, 391 | // even if there's no expression using them in the 392 | // checked program. 393 | // 394 | // Also operates on funcs and type names, but that's 395 | // irrelevant/redundant. 396 | c.graph.markUsedBy(obj.Type(), obj) 397 | } 398 | if obj.Name() == "_" { 399 | node := c.graph.getNode(obj) 400 | node.quiet = true 401 | scope := c.topmostScope(pkg.Pkg.Scope().Innermost(obj.Pos()), pkg.Pkg) 402 | if scope == pkg.Pkg.Scope() { 403 | c.graph.roots = append(c.graph.roots, node) 404 | } else { 405 | c.graph.markUsedBy(obj, scope) 406 | } 407 | } else { 408 | // Variables declared in functions are used. This is 409 | // done so that arguments and return parameters are 410 | // always marked as used. 411 | if _, ok := obj.(*types.Var); ok { 412 | if obj.Parent() != obj.Pkg().Scope() && obj.Parent() != nil { 413 | c.graph.markUsedBy(obj, c.topmostScope(obj.Parent(), obj.Pkg())) 414 | c.graph.markUsedBy(obj.Type(), obj) 415 | } 416 | } 417 | } 418 | } 419 | 420 | if fn, ok := obj.(*types.Func); ok { 421 | // A function uses its signature 422 | c.graph.markUsedBy(fn, fn.Type()) 423 | 424 | // A function uses its return types 425 | sig := fn.Type().(*types.Signature) 426 | res := sig.Results() 427 | n := res.Len() 428 | for i := 0; i < n; i++ { 429 | c.graph.markUsedBy(res.At(i).Type(), fn) 430 | } 431 | } 432 | 433 | if obj, ok := obj.(interface { 434 | Scope() *types.Scope 435 | Pkg() *types.Package 436 | }); ok { 437 | scope := obj.Scope() 438 | c.graph.markUsedBy(c.topmostScope(scope, obj.Pkg()), obj) 439 | } 440 | 441 | if c.isRoot(obj) { 442 | node := c.graph.getNode(obj) 443 | c.graph.roots = append(c.graph.roots, node) 444 | if obj, ok := obj.(*types.PkgName); ok { 445 | scope := obj.Pkg().Scope() 446 | c.graph.markUsedBy(scope, obj) 447 | } 448 | } 449 | } 450 | } 451 | 452 | func (c *Checker) processUses(pkg *loader.PackageInfo) { 453 | for ident, usedObj := range pkg.Uses { 454 | if _, ok := usedObj.(*types.PkgName); ok { 455 | continue 456 | } 457 | pos := ident.Pos() 458 | scope := pkg.Pkg.Scope().Innermost(pos) 459 | scope = c.topmostScope(scope, pkg.Pkg) 460 | if scope != pkg.Pkg.Scope() { 461 | c.graph.markUsedBy(usedObj, scope) 462 | } 463 | 464 | switch usedObj.(type) { 465 | case *types.Var, *types.Const: 466 | c.graph.markUsedBy(usedObj.Type(), usedObj) 467 | } 468 | } 469 | } 470 | 471 | func (c *Checker) findExportedInterfaces() { 472 | c.interfaces = []*types.Interface{types.Universe.Lookup("error").Type().(*types.Named).Underlying().(*types.Interface)} 473 | var pkgs []*loader.PackageInfo 474 | if c.WholeProgram { 475 | for _, pkg := range c.lprog.AllPackages { 476 | pkgs = append(pkgs, pkg) 477 | } 478 | } else { 479 | pkgs = c.lprog.InitialPackages() 480 | } 481 | 482 | for _, pkg := range pkgs { 483 | for _, tv := range pkg.Types { 484 | iface, ok := tv.Type.(*types.Interface) 485 | if !ok { 486 | continue 487 | } 488 | if iface.NumMethods() == 0 { 489 | continue 490 | } 491 | c.interfaces = append(c.interfaces, iface) 492 | } 493 | } 494 | } 495 | 496 | func (c *Checker) processTypes(pkg *loader.PackageInfo) { 497 | named := map[*types.Named]*types.Pointer{} 498 | var interfaces []*types.Interface 499 | for _, tv := range pkg.Types { 500 | if typ, ok := tv.Type.(interface { 501 | Elem() types.Type 502 | }); ok { 503 | c.graph.markUsedBy(typ.Elem(), typ) 504 | } 505 | 506 | switch obj := tv.Type.(type) { 507 | case *types.Named: 508 | named[obj] = types.NewPointer(obj) 509 | c.graph.markUsedBy(obj, obj.Underlying()) 510 | c.graph.markUsedBy(obj.Underlying(), obj) 511 | case *types.Interface: 512 | if obj.NumMethods() > 0 { 513 | interfaces = append(interfaces, obj) 514 | } 515 | case *types.Struct: 516 | if pkg.Pkg.Name() != "main" && !c.WholeProgram { 517 | c.useExportedFields(obj) 518 | } 519 | } 520 | } 521 | 522 | // Pretend that all types are meant to implement as many 523 | // interfaces as possible. 524 | // 525 | // TODO(dh): For normal operations, that's the best we can do, as 526 | // we have no idea what external users will do with our types. In 527 | // whole-program mode, we could be more conservative, in two ways: 528 | // 1) Only consider interfaces if a type has been assigned to one 529 | // 2) Use SSA and flow analysis and determine the exact set of 530 | // interfaces that is relevant. 531 | fn := func(iface *types.Interface) { 532 | for obj, objPtr := range named { 533 | if !types.Implements(obj, iface) && !types.Implements(objPtr, iface) { 534 | continue 535 | } 536 | ifaceMethods := make(map[string]struct{}, iface.NumMethods()) 537 | n := iface.NumMethods() 538 | for i := 0; i < n; i++ { 539 | meth := iface.Method(i) 540 | ifaceMethods[meth.Name()] = struct{}{} 541 | } 542 | for _, obj := range []types.Type{obj, objPtr} { 543 | ms := c.msCache.MethodSet(obj) 544 | n := ms.Len() 545 | for i := 0; i < n; i++ { 546 | sel := ms.At(i) 547 | meth := sel.Obj().(*types.Func) 548 | _, found := ifaceMethods[meth.Name()] 549 | if !found { 550 | continue 551 | } 552 | c.graph.markUsedBy(meth.Type().(*types.Signature).Recv().Type(), obj) // embedded receiver 553 | if len(sel.Index()) > 1 { 554 | f := getField(obj, sel.Index()[0]) 555 | c.graph.markUsedBy(f, obj) // embedded receiver 556 | } 557 | c.graph.markUsedBy(meth, obj) 558 | } 559 | } 560 | } 561 | } 562 | 563 | for _, iface := range interfaces { 564 | fn(iface) 565 | } 566 | for _, iface := range c.interfaces { 567 | fn(iface) 568 | } 569 | } 570 | 571 | func (c *Checker) processSelections(pkg *loader.PackageInfo) { 572 | fn := func(expr *ast.SelectorExpr, sel *types.Selection, offset int) { 573 | scope := pkg.Pkg.Scope().Innermost(expr.Pos()) 574 | c.graph.markUsedBy(expr.X, c.topmostScope(scope, pkg.Pkg)) 575 | c.graph.markUsedBy(sel.Obj(), expr.X) 576 | if len(sel.Index()) > 1 { 577 | typ := sel.Recv() 578 | indices := sel.Index() 579 | for _, idx := range indices[:len(indices)-offset] { 580 | obj := getField(typ, idx) 581 | typ = obj.Type() 582 | c.graph.markUsedBy(obj, expr.X) 583 | } 584 | } 585 | } 586 | 587 | for expr, sel := range pkg.Selections { 588 | switch sel.Kind() { 589 | case types.FieldVal: 590 | fn(expr, sel, 0) 591 | case types.MethodVal: 592 | fn(expr, sel, 1) 593 | } 594 | } 595 | } 596 | 597 | func dereferenceType(typ types.Type) types.Type { 598 | if typ, ok := typ.(*types.Pointer); ok { 599 | return typ.Elem() 600 | } 601 | return typ 602 | } 603 | 604 | // processConversion marks fields as used if they're part of a type conversion. 605 | func (c *Checker) processConversion(pkg *loader.PackageInfo, node ast.Node) { 606 | if node, ok := node.(*ast.CallExpr); ok { 607 | callTyp := pkg.TypeOf(node.Fun) 608 | var typDst *types.Struct 609 | var ok bool 610 | switch typ := callTyp.(type) { 611 | case *types.Named: 612 | typDst, ok = typ.Underlying().(*types.Struct) 613 | case *types.Pointer: 614 | typDst, ok = typ.Elem().Underlying().(*types.Struct) 615 | default: 616 | return 617 | } 618 | if !ok { 619 | return 620 | } 621 | 622 | if typ, ok := pkg.TypeOf(node.Args[0]).(*types.Basic); ok && typ.Kind() == types.UnsafePointer { 623 | // This is an unsafe conversion. Assume that all the 624 | // fields are relevant (they are, because of memory 625 | // layout) 626 | n := typDst.NumFields() 627 | for i := 0; i < n; i++ { 628 | c.graph.markUsedBy(typDst.Field(i), typDst) 629 | } 630 | return 631 | } 632 | 633 | typSrc, ok := dereferenceType(pkg.TypeOf(node.Args[0])).Underlying().(*types.Struct) 634 | if !ok { 635 | return 636 | } 637 | 638 | // When we convert from type t1 to t2, were t1 and t2 are 639 | // structs, all fields are relevant, as otherwise the 640 | // conversion would fail. 641 | // 642 | // We mark t2's fields as used by t1's fields, and vice 643 | // versa. That way, if no code actually refers to a field 644 | // in either type, it's still correctly marked as unused. 645 | // If a field is used in either struct, it's implicitly 646 | // relevant in the other one, too. 647 | // 648 | // It works in a similar way for conversions between types 649 | // of two packages, only that the extra information in the 650 | // graph is redundant unless we're in whole program mode. 651 | n := typDst.NumFields() 652 | for i := 0; i < n; i++ { 653 | fDst := typDst.Field(i) 654 | fSrc := typSrc.Field(i) 655 | c.graph.markUsedBy(fDst, fSrc) 656 | c.graph.markUsedBy(fSrc, fDst) 657 | } 658 | } 659 | } 660 | 661 | // processCompositeLiteral marks fields as used if the struct is used 662 | // in a composite literal. 663 | func (c *Checker) processCompositeLiteral(pkg *loader.PackageInfo, node ast.Node) { 664 | // XXX how does this actually work? wouldn't it match t{}? 665 | if node, ok := node.(*ast.CompositeLit); ok { 666 | typ := pkg.TypeOf(node) 667 | if _, ok := typ.(*types.Named); ok { 668 | typ = typ.Underlying() 669 | } 670 | if _, ok := typ.(*types.Struct); !ok { 671 | return 672 | } 673 | 674 | if isBasicStruct(node.Elts) { 675 | c.markFields(typ) 676 | } 677 | } 678 | } 679 | 680 | // processCgoExported marks functions as used if they're being 681 | // exported to cgo. 682 | func (c *Checker) processCgoExported(pkg *loader.PackageInfo, node ast.Node) { 683 | if node, ok := node.(*ast.FuncDecl); ok { 684 | if node.Doc == nil { 685 | return 686 | } 687 | for _, cmt := range node.Doc.List { 688 | if !strings.HasPrefix(cmt.Text, "//go:cgo_export_") { 689 | return 690 | } 691 | obj := pkg.ObjectOf(node.Name) 692 | c.graph.roots = append(c.graph.roots, c.graph.getNode(obj)) 693 | } 694 | } 695 | } 696 | 697 | func (c *Checker) processVariableDeclaration(pkg *loader.PackageInfo, node ast.Node) { 698 | if decl, ok := node.(*ast.GenDecl); ok { 699 | for _, spec := range decl.Specs { 700 | spec, ok := spec.(*ast.ValueSpec) 701 | if !ok { 702 | continue 703 | } 704 | for i, name := range spec.Names { 705 | if i >= len(spec.Values) { 706 | break 707 | } 708 | value := spec.Values[i] 709 | fn := func(node ast.Node) bool { 710 | if node3, ok := node.(*ast.Ident); ok { 711 | obj := pkg.ObjectOf(node3) 712 | if _, ok := obj.(*types.PkgName); ok { 713 | return true 714 | } 715 | c.graph.markUsedBy(obj, pkg.ObjectOf(name)) 716 | } 717 | return true 718 | } 719 | ast.Inspect(value, fn) 720 | } 721 | } 722 | } 723 | } 724 | 725 | func (c *Checker) processArrayConstants(pkg *loader.PackageInfo, node ast.Node) { 726 | if decl, ok := node.(*ast.ArrayType); ok { 727 | ident, ok := decl.Len.(*ast.Ident) 728 | if !ok { 729 | return 730 | } 731 | c.graph.markUsedBy(pkg.ObjectOf(ident), pkg.TypeOf(decl)) 732 | } 733 | } 734 | 735 | func (c *Checker) processKnownReflectMethodCallers(pkg *loader.PackageInfo, node ast.Node) { 736 | call, ok := node.(*ast.CallExpr) 737 | if !ok { 738 | return 739 | } 740 | sel, ok := call.Fun.(*ast.SelectorExpr) 741 | if !ok { 742 | return 743 | } 744 | if types.TypeString(pkg.TypeOf(sel.X), nil) != "*net/rpc.Server" { 745 | x, ok := sel.X.(*ast.Ident) 746 | if !ok { 747 | return 748 | } 749 | pkgname, ok := pkg.ObjectOf(x).(*types.PkgName) 750 | if !ok { 751 | return 752 | } 753 | if pkgname.Imported().Path() != "net/rpc" { 754 | return 755 | } 756 | } 757 | 758 | var arg ast.Expr 759 | switch sel.Sel.Name { 760 | case "Register": 761 | if len(call.Args) != 1 { 762 | return 763 | } 764 | arg = call.Args[0] 765 | case "RegisterName": 766 | if len(call.Args) != 2 { 767 | return 768 | } 769 | arg = call.Args[1] 770 | } 771 | typ := pkg.TypeOf(arg) 772 | ms := types.NewMethodSet(typ) 773 | for i := 0; i < ms.Len(); i++ { 774 | c.graph.markUsedBy(ms.At(i).Obj(), typ) 775 | } 776 | } 777 | 778 | func (c *Checker) processAST(pkg *loader.PackageInfo) { 779 | fn := func(node ast.Node) bool { 780 | c.processConversion(pkg, node) 781 | c.processKnownReflectMethodCallers(pkg, node) 782 | c.processCompositeLiteral(pkg, node) 783 | c.processCgoExported(pkg, node) 784 | c.processVariableDeclaration(pkg, node) 785 | c.processArrayConstants(pkg, node) 786 | return true 787 | } 788 | for _, file := range pkg.Files { 789 | ast.Inspect(file, fn) 790 | } 791 | } 792 | 793 | func isBasicStruct(elts []ast.Expr) bool { 794 | for _, elt := range elts { 795 | if _, ok := elt.(*ast.KeyValueExpr); !ok { 796 | return true 797 | } 798 | } 799 | return false 800 | } 801 | 802 | func isPkgScope(obj types.Object) bool { 803 | return obj.Parent() == obj.Pkg().Scope() 804 | } 805 | 806 | func isMain(obj types.Object) bool { 807 | if obj.Pkg().Name() != "main" { 808 | return false 809 | } 810 | if obj.Name() != "main" { 811 | return false 812 | } 813 | if !isPkgScope(obj) { 814 | return false 815 | } 816 | if !isFunction(obj) { 817 | return false 818 | } 819 | if isMethod(obj) { 820 | return false 821 | } 822 | return true 823 | } 824 | 825 | func isFunction(obj types.Object) bool { 826 | _, ok := obj.(*types.Func) 827 | return ok 828 | } 829 | 830 | func isMethod(obj types.Object) bool { 831 | if !isFunction(obj) { 832 | return false 833 | } 834 | return obj.(*types.Func).Type().(*types.Signature).Recv() != nil 835 | } 836 | 837 | func isVariable(obj types.Object) bool { 838 | _, ok := obj.(*types.Var) 839 | return ok 840 | } 841 | 842 | func isConstant(obj types.Object) bool { 843 | _, ok := obj.(*types.Const) 844 | return ok 845 | } 846 | 847 | func isType(obj types.Object) bool { 848 | _, ok := obj.(*types.TypeName) 849 | return ok 850 | } 851 | 852 | func isField(obj types.Object) bool { 853 | if obj, ok := obj.(*types.Var); ok && obj.IsField() { 854 | return true 855 | } 856 | return false 857 | } 858 | 859 | func (c *Checker) checkFlags(v interface{}) bool { 860 | obj, ok := v.(types.Object) 861 | if !ok { 862 | return false 863 | } 864 | if isFunction(obj) && !c.checkFunctions() { 865 | return false 866 | } 867 | if isVariable(obj) && !c.checkVariables() { 868 | return false 869 | } 870 | if isConstant(obj) && !c.checkConstants() { 871 | return false 872 | } 873 | if isType(obj) && !c.checkTypes() { 874 | return false 875 | } 876 | if isField(obj) && !c.checkFields() { 877 | return false 878 | } 879 | return true 880 | } 881 | 882 | func (c *Checker) isRoot(obj types.Object) bool { 883 | // - in local mode, main, init, tests, and non-test, non-main exported are roots 884 | // - in global mode (not yet implemented), main, init and tests are roots 885 | 886 | if _, ok := obj.(*types.PkgName); ok { 887 | return true 888 | } 889 | 890 | if isMain(obj) || (isFunction(obj) && !isMethod(obj) && obj.Name() == "init") { 891 | return true 892 | } 893 | if obj.Exported() { 894 | f := c.lprog.Fset.Position(obj.Pos()).Filename 895 | if strings.HasSuffix(f, "_test.go") { 896 | return strings.HasPrefix(obj.Name(), "Test") || 897 | strings.HasPrefix(obj.Name(), "Benchmark") || 898 | strings.HasPrefix(obj.Name(), "Example") 899 | } 900 | 901 | // Package-level are used, except in package main 902 | if isPkgScope(obj) && obj.Pkg().Name() != "main" && !c.WholeProgram { 903 | return true 904 | } 905 | } 906 | return false 907 | } 908 | 909 | func markNodesUsed(nodes map[*graphNode]struct{}) { 910 | for node := range nodes { 911 | wasUsed := node.used 912 | node.used = true 913 | node.usages++ 914 | if !wasUsed { 915 | markNodesUsed(node.uses) 916 | } 917 | } 918 | } 919 | 920 | func (c *Checker) markNodesQuiet() { 921 | for _, node := range c.graph.nodes { 922 | if node.used { 923 | continue 924 | } 925 | if obj, ok := node.obj.(types.Object); ok && !c.checkFlags(obj) { 926 | node.quiet = true 927 | continue 928 | } 929 | c.markObjQuiet(node.obj) 930 | } 931 | } 932 | 933 | func (c *Checker) markObjQuiet(obj interface{}) { 934 | switch obj := obj.(type) { 935 | case *types.Named: 936 | n := obj.NumMethods() 937 | for i := 0; i < n; i++ { 938 | meth := obj.Method(i) 939 | node := c.graph.getNode(meth) 940 | node.quiet = true 941 | c.markObjQuiet(meth.Scope()) 942 | } 943 | case *types.Struct: 944 | n := obj.NumFields() 945 | for i := 0; i < n; i++ { 946 | field := obj.Field(i) 947 | c.graph.nodes[field].quiet = true 948 | } 949 | case *types.Func: 950 | c.markObjQuiet(obj.Scope()) 951 | case *types.Scope: 952 | if obj == nil { 953 | return 954 | } 955 | if obj.Parent() == types.Universe { 956 | return 957 | } 958 | for _, name := range obj.Names() { 959 | v := obj.Lookup(name) 960 | if n, ok := c.graph.nodes[v]; ok { 961 | n.quiet = true 962 | } 963 | } 964 | n := obj.NumChildren() 965 | for i := 0; i < n; i++ { 966 | c.markObjQuiet(obj.Child(i)) 967 | } 968 | } 969 | } 970 | 971 | func getField(typ types.Type, idx int) *types.Var { 972 | switch obj := typ.(type) { 973 | case *types.Pointer: 974 | return getField(obj.Elem(), idx) 975 | case *types.Named: 976 | switch v := obj.Underlying().(type) { 977 | case *types.Struct: 978 | return v.Field(idx) 979 | case *types.Pointer: 980 | return getField(v.Elem(), idx) 981 | default: 982 | panic(fmt.Sprintf("unexpected type %s", typ)) 983 | } 984 | case *types.Struct: 985 | return obj.Field(idx) 986 | } 987 | return nil 988 | } 989 | 990 | func (c *Checker) topmostScope(scope *types.Scope, pkg *types.Package) (ret *types.Scope) { 991 | if top, ok := c.topmostCache[scope]; ok { 992 | return top 993 | } 994 | defer func() { 995 | c.topmostCache[scope] = ret 996 | }() 997 | if scope == pkg.Scope() { 998 | return scope 999 | } 1000 | if scope.Parent().Parent() == pkg.Scope() { 1001 | return scope 1002 | } 1003 | return c.topmostScope(scope.Parent(), pkg) 1004 | } 1005 | 1006 | func (c *Checker) printDebugGraph(w io.Writer) { 1007 | fmt.Fprintln(w, "digraph {") 1008 | fmt.Fprintln(w, "n0 [label = roots]") 1009 | for _, node := range c.graph.nodes { 1010 | s := fmt.Sprintf("%s (%T)", node.obj, node.obj) 1011 | s = strings.Replace(s, "\n", "", -1) 1012 | s = strings.Replace(s, `"`, "", -1) 1013 | fmt.Fprintf(w, `n%d [label = %q]`, node.n, s) 1014 | color := "black" 1015 | switch { 1016 | case node.used: 1017 | color = "green" 1018 | case node.quiet: 1019 | color = "orange" 1020 | case !c.checkFlags(node.obj): 1021 | color = "purple" 1022 | default: 1023 | color = "red" 1024 | } 1025 | fmt.Fprintf(w, "[color = %s]", color) 1026 | fmt.Fprintln(w) 1027 | } 1028 | 1029 | for _, node1 := range c.graph.nodes { 1030 | for node2 := range node1.uses { 1031 | fmt.Fprintf(w, "n%d -> n%d\n", node1.n, node2.n) 1032 | } 1033 | } 1034 | for _, root := range c.graph.roots { 1035 | fmt.Fprintf(w, "n0 -> n%d\n", root.n) 1036 | } 1037 | fmt.Fprintln(w, "}") 1038 | } 1039 | 1040 | func isGenerated(comment string) bool { 1041 | return strings.Contains(comment, "Code generated by") || 1042 | strings.Contains(comment, "DO NOT EDIT") 1043 | } 1044 | -------------------------------------------------------------------------------- /used_test.go: -------------------------------------------------------------------------------- 1 | package used 2 | 3 | // Copyright (c) 2013 The Go Authors. All rights reserved. 4 | // 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found at 7 | // https://developers.google.com/open-source/licenses/bsd. 8 | 9 | import ( 10 | "flag" 11 | "fmt" 12 | "go/parser" 13 | "go/token" 14 | "io/ioutil" 15 | "os" 16 | "path" 17 | "path/filepath" 18 | "regexp" 19 | "strconv" 20 | "strings" 21 | "testing" 22 | 23 | "golang.org/x/tools/go/loader" 24 | "honnef.co/go/tools/lint" 25 | ) 26 | 27 | var lintMatch = flag.String("match", "", "restrict testdata matches to this pattern") 28 | 29 | func TestAll(t *testing.T) { 30 | baseDir := "testdata" 31 | fis, err := ioutil.ReadDir(baseDir) 32 | if err != nil { 33 | t.Fatalf("ioutil.ReadDir: %v", err) 34 | } 35 | if len(fis) == 0 { 36 | t.Fatalf("no files in %v", baseDir) 37 | } 38 | rx, err := regexp.Compile(*lintMatch) 39 | if err != nil { 40 | t.Fatalf("Bad -match value %q: %v", *lintMatch, err) 41 | } 42 | fis = filterMatches(rx, fis) 43 | 44 | conf := &loader.Config{ 45 | ParserMode: parser.ParseComments, 46 | } 47 | sources := map[string][]byte{} 48 | for _, fi := range fis { 49 | filename := path.Join(baseDir, fi.Name()) 50 | src, err := ioutil.ReadFile(filename) 51 | if err != nil { 52 | t.Errorf("Failed reading %s: %v", fi.Name(), err) 53 | continue 54 | } 55 | f, err := conf.ParseFile(filename, src) 56 | if err != nil { 57 | t.Errorf("error parsing %s: %s", filename, err) 58 | continue 59 | } 60 | sources[fi.Name()] = src 61 | conf.CreateFromFiles(fi.Name(), f) 62 | } 63 | 64 | lprog, err := conf.Load() 65 | if err != nil { 66 | t.Fatalf("error loading program: %s", err) 67 | } 68 | 69 | checker := NewChecker(CheckAll) 70 | l := &lint.Linter{Checker: NewLintChecker(checker)} 71 | res := l.Lint(lprog) 72 | for _, fi := range fis { 73 | name := fi.Name() 74 | src := sources[name] 75 | 76 | ins := parseInstructions(t, name, src) 77 | 78 | for _, in := range ins { 79 | ok := false 80 | for i, p := range res { 81 | pos := lprog.Fset.Position(p.Position) 82 | if pos.Line != in.Line || filepath.Base(pos.Filename) != name { 83 | continue 84 | } 85 | if in.Match.MatchString(p.Text) { 86 | // remove this problem from ps 87 | copy(res[i:], res[i+1:]) 88 | res = res[:len(res)-1] 89 | 90 | //t.Logf("/%v/ matched at %s:%d", in.Match, fi.Name(), in.Line) 91 | ok = true 92 | break 93 | } 94 | } 95 | if !ok { 96 | t.Errorf("Lint failed at %s:%d; /%v/ did not match", name, in.Line, in.Match) 97 | } 98 | } 99 | } 100 | for _, p := range res { 101 | pos := lprog.Fset.Position(p.Position) 102 | name := filepath.Base(pos.Filename) 103 | for _, fi := range fis { 104 | if name == fi.Name() { 105 | t.Errorf("Unexpected problem at %s: %v", pos, p.Text) 106 | break 107 | } 108 | } 109 | } 110 | } 111 | 112 | func filterMatches(rx *regexp.Regexp, fis []os.FileInfo) []os.FileInfo { 113 | matches := fis[:0] 114 | for _, fi := range fis { 115 | if rx.MatchString(fi.Name()) { 116 | matches = append(matches, fi) 117 | } 118 | } 119 | return matches 120 | } 121 | 122 | type instruction struct { 123 | Line int // the line number this applies to 124 | Match *regexp.Regexp // what pattern to match 125 | Replacement string // what the suggested replacement line should be 126 | } 127 | 128 | // parseInstructions parses instructions from the comments in a Go source file. 129 | // It returns nil if none were parsed. 130 | func parseInstructions(t *testing.T, filename string, src []byte) []instruction { 131 | fset := token.NewFileSet() 132 | f, err := parser.ParseFile(fset, filename, src, parser.ParseComments) 133 | if err != nil { 134 | t.Fatalf("Test file %v does not parse: %v", filename, err) 135 | } 136 | var ins []instruction 137 | for _, cg := range f.Comments { 138 | ln := fset.Position(cg.Pos()).Line 139 | raw := cg.Text() 140 | for _, line := range strings.Split(raw, "\n") { 141 | if line == "" || strings.HasPrefix(line, "#") { 142 | continue 143 | } 144 | if line == "OK" && ins == nil { 145 | // so our return value will be non-nil 146 | ins = make([]instruction, 0) 147 | continue 148 | } 149 | if !strings.Contains(line, "MATCH") { 150 | continue 151 | } 152 | rx, err := extractPattern(line) 153 | if err != nil { 154 | t.Fatalf("At %v:%d: %v", filename, ln, err) 155 | } 156 | matchLine := ln 157 | if i := strings.Index(line, "MATCH:"); i >= 0 { 158 | // This is a match for a different line. 159 | lns := strings.TrimPrefix(line[i:], "MATCH:") 160 | lns = lns[:strings.Index(lns, " ")] 161 | matchLine, err = strconv.Atoi(lns) 162 | if err != nil { 163 | t.Fatalf("Bad match line number %q at %v:%d: %v", lns, filename, ln, err) 164 | } 165 | } 166 | var repl string 167 | if r, ok := extractReplacement(line); ok { 168 | repl = r 169 | } 170 | ins = append(ins, instruction{ 171 | Line: matchLine, 172 | Match: rx, 173 | Replacement: repl, 174 | }) 175 | } 176 | } 177 | return ins 178 | } 179 | 180 | func extractPattern(line string) (*regexp.Regexp, error) { 181 | n := strings.Index(line, " ") 182 | if n == 01 { 183 | return nil, fmt.Errorf("malformed match instruction %q", line) 184 | } 185 | line = line[n+1:] 186 | var pat string 187 | switch line[0] { 188 | case '/': 189 | a, b := strings.Index(line, "/"), strings.LastIndex(line, "/") 190 | if a == -1 || a == b { 191 | return nil, fmt.Errorf("malformed match instruction %q", line) 192 | } 193 | pat = line[a+1 : b] 194 | case '"': 195 | a, b := strings.Index(line, `"`), strings.LastIndex(line, `"`) 196 | if a == -1 || a == b { 197 | return nil, fmt.Errorf("malformed match instruction %q", line) 198 | } 199 | pat = regexp.QuoteMeta(line[a+1 : b]) 200 | default: 201 | return nil, fmt.Errorf("malformed match instruction %q", line) 202 | } 203 | 204 | rx, err := regexp.Compile(pat) 205 | if err != nil { 206 | return nil, fmt.Errorf("bad match pattern %q: %v", pat, err) 207 | } 208 | return rx, nil 209 | } 210 | 211 | func extractReplacement(line string) (string, bool) { 212 | // Look for this: / -> ` 213 | // (the end of a match and start of a backtick string), 214 | // and then the closing backtick. 215 | const start = "/ -> `" 216 | a, b := strings.Index(line, start), strings.LastIndex(line, "`") 217 | if a < 0 || a > b { 218 | return "", false 219 | } 220 | return line[a+len(start) : b], true 221 | } 222 | --------------------------------------------------------------------------------