1 // Package code answers structural and type questions about Go code.
2 package code
3 4 import (
5 "fmt"
6 "go/ast"
7 "go/build/constraint"
8 "go/constant"
9 "go/token"
10 "go/types"
11 "go/version"
12 "path/filepath"
13 "strings"
14 15 "honnef.co/go/tools/analysis/facts/generated"
16 "honnef.co/go/tools/analysis/facts/purity"
17 "honnef.co/go/tools/analysis/facts/tokenfile"
18 "honnef.co/go/tools/go/ast/astutil"
19 "honnef.co/go/tools/go/types/typeutil"
20 "honnef.co/go/tools/knowledge"
21 "honnef.co/go/tools/pattern"
22 23 "golang.org/x/tools/go/analysis"
24 )
25 26 type Positioner interface {
27 Pos() token.Pos
28 }
29 30 func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool {
31 typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice)
32 if !ok {
33 return false
34 }
35 elem := types.Unalias(typ.Elem())
36 if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 {
37 // Before Go 1.18, one could not directly convert from []T (where 'type T byte')
38 // to string. See also https://github.com/golang/go/issues/23536.
39 elem = elem.Underlying()
40 }
41 return types.Identical(elem, types.Typ[types.Byte])
42 }
43 44 func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
45 ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer)
46 if !ok {
47 return false
48 }
49 return typeutil.IsTypeWithName(ptr.Elem(), name)
50 }
51 52 func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool {
53 return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name)
54 }
55 56 func IsInTest(pass *analysis.Pass, node Positioner) bool {
57 // FIXME(dh): this doesn't work for global variables with
58 // initializers
59 f := pass.Fset.File(node.Pos())
60 return f != nil && strings.HasSuffix(f.Name(), "_test.go")
61 }
62 63 // IsMain reports whether the package being processed is a package
64 // main.
65 func IsMain(pass *analysis.Pass) bool {
66 return pass.Pkg.Name() == "main"
67 }
68 69 // IsMainLike reports whether the package being processed is a
70 // main-like package. A main-like package is a package that is
71 // package main, or that is intended to be used by a tool framework
72 // such as cobra to implement a command.
73 //
74 // Note that this function errs on the side of false positives; it may
75 // return true for packages that aren't main-like. IsMainLike is
76 // intended for analyses that wish to suppress diagnostics for
77 // main-like packages to avoid false positives.
78 func IsMainLike(pass *analysis.Pass) bool {
79 if pass.Pkg.Name() == "main" {
80 return true
81 }
82 for _, imp := range pass.Pkg.Imports() {
83 if imp.Path() == "github.com/spf13/cobra" {
84 return true
85 }
86 }
87 return false
88 }
89 90 func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string {
91 info := pass.TypesInfo
92 sel := info.Selections[expr]
93 if sel == nil {
94 if x, ok := expr.X.(*ast.Ident); ok {
95 pkg, ok := info.ObjectOf(x).(*types.PkgName)
96 if !ok {
97 // This shouldn't happen
98 return fmt.Sprintf("%s.%s", x.Name, expr.Sel.Name)
99 }
100 return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name)
101 }
102 panic(fmt.Sprintf("unsupported selector: %v", expr))
103 }
104 if v, ok := sel.Obj().(*types.Var); ok && v.IsField() {
105 return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name())
106 } else {
107 return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name())
108 }
109 }
110 111 func IsNil(pass *analysis.Pass, expr ast.Expr) bool {
112 return pass.TypesInfo.Types[expr].IsNil()
113 }
114 115 func BoolConst(pass *analysis.Pass, expr ast.Expr) bool {
116 val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val()
117 return constant.BoolVal(val)
118 }
119 120 func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool {
121 // We explicitly don't support typed bools because more often than
122 // not, custom bool types are used as binary enums and the explicit
123 // comparison is desired. We err on the side of false negatives and
124 // treat aliases like other custom types.
125 126 ident, ok := expr.(*ast.Ident)
127 if !ok {
128 return false
129 }
130 obj := pass.TypesInfo.ObjectOf(ident)
131 c, ok := obj.(*types.Const)
132 if !ok {
133 return false
134 }
135 basic, ok := c.Type().(*types.Basic)
136 if !ok {
137 return false
138 }
139 if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool {
140 return false
141 }
142 return true
143 }
144 145 func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) {
146 tv := pass.TypesInfo.Types[expr]
147 if tv.Value == nil {
148 return 0, false
149 }
150 if tv.Value.Kind() != constant.Int {
151 return 0, false
152 }
153 return constant.Int64Val(tv.Value)
154 }
155 156 func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) {
157 val := pass.TypesInfo.Types[expr].Value
158 if val == nil {
159 return "", false
160 }
161 if val.Kind() != constant.String {
162 return "", false
163 }
164 return constant.StringVal(val), true
165 }
166 167 func CallName(pass *analysis.Pass, call *ast.CallExpr) string {
168 // See the comment in typeutil.FuncName for why this doesn't require special handling
169 // of aliases.
170 171 fun := astutil.Unparen(call.Fun)
172 173 // Instantiating a function cannot return another generic function, so doing this once is enough
174 switch idx := fun.(type) {
175 case *ast.IndexExpr:
176 fun = idx.X
177 case *ast.IndexListExpr:
178 fun = idx.X
179 }
180 181 // (foo)[T] is not a valid instantiation, so no need to unparen again.
182 183 switch fun := fun.(type) {
184 case *ast.SelectorExpr:
185 fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func)
186 if !ok {
187 return ""
188 }
189 return typeutil.FuncName(fn)
190 case *ast.Ident:
191 obj := pass.TypesInfo.ObjectOf(fun)
192 switch obj := obj.(type) {
193 case *types.Func:
194 return typeutil.FuncName(obj)
195 case *types.Builtin:
196 return obj.Name()
197 default:
198 return ""
199 }
200 default:
201 return ""
202 }
203 }
204 205 func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool {
206 // See the comment in typeutil.FuncName for why this doesn't require special handling
207 // of aliases.
208 209 call, ok := node.(*ast.CallExpr)
210 if !ok {
211 return false
212 }
213 return CallName(pass, call) == name
214 }
215 216 func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool {
217 // See the comment in typeutil.FuncName for why this doesn't require special handling
218 // of aliases.
219 220 call, ok := node.(*ast.CallExpr)
221 if !ok {
222 return false
223 }
224 q := CallName(pass, call)
225 for _, name := range names {
226 if q == name {
227 return true
228 }
229 }
230 return false
231 }
232 233 func File(pass *analysis.Pass, node Positioner) *ast.File {
234 m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File)
235 return m[pass.Fset.File(node.Pos())]
236 }
237 238 // BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as
239 // GOOS and GOARCH in file names.
240 func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) {
241 var expr constraint.Expr
242 for _, cmt := range f.Comments {
243 if len(cmt.List) == 0 {
244 continue
245 }
246 for _, el := range cmt.List {
247 if el.Pos() > f.Package {
248 break
249 }
250 if line := el.Text; strings.HasPrefix(line, "//go:build") {
251 var err error
252 expr, err = constraint.Parse(line)
253 if err != nil {
254 expr = nil
255 }
256 break
257 }
258 }
259 }
260 261 name := pass.Fset.PositionFor(f.Pos(), false).Filename
262 oexpr := constraintsFromName(name)
263 if oexpr != nil {
264 if expr == nil {
265 expr = oexpr
266 } else {
267 expr = &constraint.AndExpr{X: expr, Y: oexpr}
268 }
269 }
270 271 return expr, expr != nil
272 }
273 274 func constraintsFromName(name string) constraint.Expr {
275 name = filepath.Base(name)
276 name = strings.TrimSuffix(name, ".go")
277 name = strings.TrimSuffix(name, "_test")
278 var goos, goarch string
279 switch strings.Count(name, "_") {
280 case 0:
281 // No GOOS or GOARCH in the file name.
282 case 1:
283 _, c, _ := strings.Cut(name, "_")
284 if _, ok := knowledge.KnownGOOS[c]; ok {
285 goos = c
286 } else if _, ok := knowledge.KnownGOARCH[c]; ok {
287 goarch = c
288 }
289 default:
290 n := strings.LastIndex(name, "_")
291 if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok {
292 // The file name is *_stuff_GOOS.go
293 goos = name[n+1:]
294 } else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok {
295 // The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go
296 goarch = name[n+1:]
297 _, c, _ := strings.Cut(name[:n], "_")
298 if _, ok := knowledge.KnownGOOS[c]; ok {
299 // The file name is *_GOOS_GOARCH.go
300 goos = c
301 }
302 } else {
303 // The file name could also be something like foo_windows_nonsense.go — and because nonsense
304 // isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either.
305 }
306 }
307 308 var expr constraint.Expr
309 if goos != "" {
310 expr = &constraint.TagExpr{Tag: goos}
311 }
312 if goarch != "" {
313 if expr == nil {
314 expr = &constraint.TagExpr{Tag: goarch}
315 } else {
316 expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}}
317 }
318 }
319 return expr
320 }
321 322 // IsGenerated reports whether pos is in a generated file. It ignores
323 // //line directives.
324 func IsGenerated(pass *analysis.Pass, pos token.Pos) bool {
325 _, ok := Generator(pass, pos)
326 return ok
327 }
328 329 // Generator returns the generator that generated the file containing
330 // pos. It ignores //line directives.
331 func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) {
332 file := pass.Fset.PositionFor(pos, false).Filename
333 m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator)
334 g, ok := m[file]
335 return g, ok
336 }
337 338 // MayHaveSideEffects reports whether expr may have side effects. If
339 // the purity argument is nil, this function implements a purely
340 // syntactic check, meaning that any function call may have side
341 // effects, regardless of the called function's body. Otherwise,
342 // purity will be consulted to determine the purity of function calls.
343 func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool {
344 switch expr := expr.(type) {
345 case *ast.BadExpr:
346 return true
347 case *ast.Ellipsis:
348 return MayHaveSideEffects(pass, expr.Elt, purity)
349 case *ast.FuncLit:
350 // the literal itself cannot have side effects, only calling it
351 // might, which is handled by CallExpr.
352 return false
353 case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
354 // types cannot have side effects
355 return false
356 case *ast.BasicLit:
357 return false
358 case *ast.BinaryExpr:
359 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity)
360 case *ast.CallExpr:
361 if purity == nil {
362 return true
363 }
364 switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) {
365 case *types.Func:
366 if _, ok := purity[obj]; !ok {
367 return true
368 }
369 case *types.Builtin:
370 switch obj.Name() {
371 case "len", "cap":
372 default:
373 return true
374 }
375 default:
376 return true
377 }
378 for _, arg := range expr.Args {
379 if MayHaveSideEffects(pass, arg, purity) {
380 return true
381 }
382 }
383 return false
384 case *ast.CompositeLit:
385 if MayHaveSideEffects(pass, expr.Type, purity) {
386 return true
387 }
388 for _, elt := range expr.Elts {
389 if MayHaveSideEffects(pass, elt, purity) {
390 return true
391 }
392 }
393 return false
394 case *ast.Ident:
395 return false
396 case *ast.IndexExpr:
397 return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity)
398 case *ast.IndexListExpr:
399 // In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in
400 // being safe.
401 if MayHaveSideEffects(pass, expr.X, purity) {
402 return true
403 }
404 for _, idx := range expr.Indices {
405 if MayHaveSideEffects(pass, idx, purity) {
406 return true
407 }
408 }
409 return false
410 case *ast.KeyValueExpr:
411 return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity)
412 case *ast.SelectorExpr:
413 return MayHaveSideEffects(pass, expr.X, purity)
414 case *ast.SliceExpr:
415 return MayHaveSideEffects(pass, expr.X, purity) ||
416 MayHaveSideEffects(pass, expr.Low, purity) ||
417 MayHaveSideEffects(pass, expr.High, purity) ||
418 MayHaveSideEffects(pass, expr.Max, purity)
419 case *ast.StarExpr:
420 return MayHaveSideEffects(pass, expr.X, purity)
421 case *ast.TypeAssertExpr:
422 return MayHaveSideEffects(pass, expr.X, purity)
423 case *ast.UnaryExpr:
424 if MayHaveSideEffects(pass, expr.X, purity) {
425 return true
426 }
427 return expr.Op == token.ARROW || expr.Op == token.AND
428 case *ast.ParenExpr:
429 return MayHaveSideEffects(pass, expr.X, purity)
430 case nil:
431 return false
432 default:
433 panic(fmt.Sprintf("internal error: unhandled type %T", expr))
434 }
435 }
436 437 // LanguageVersion returns the version of the Go language that node has access to. This
438 // might differ from the version of the Go standard library.
439 func LanguageVersion(pass *analysis.Pass, node Positioner) string {
440 // As of Go 1.21, two places can specify the minimum Go version:
441 // - 'go' directives in go.mod and go.work files
442 // - individual files by using '//go:build'
443 //
444 // Individual files can upgrade to a higher version than the module version. Individual files
445 // can also downgrade to a lower version, but only if the module version is at least Go 1.21.
446 //
447 // The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will
448 // not type-check on versions that are too old, and thus never reach our analyzes. In practice,
449 // such ineffective downgrading will always be useless, as the compiler will not restrict the
450 // language features used, and doesn't ever rely on minimum versions to restrict the use of the
451 // standard library. However, for us, both choices (respecting or ignoring ineffective
452 // downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing
453 // noisy positives.
454 //
455 // The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion.
456 // ast.File's version is populated by the parser, whereas types.Package's version is populated
457 // from the Go version specified in the types.Config, which is set by our package loader, based
458 // on the module information provided by go/packages, via 'go list -json'.
459 //
460 // As of Go 1.21, standard library packages do not present themselves as modules, and thus do
461 // not have a version set on their types.Package. In this case, we fall back to the version
462 // provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to
463 // the Go version that Staticcheck was built with when no module information exists. In the
464 // future, the standard library will hopefully be a proper module (see
465 // https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version
466 // of standard library packages will match that of the used Go version. At that point,
467 // Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting
468 // code due to language changes.
469 //
470 // We also lack module information when building in GOPATH mode. In this case, the implied
471 // language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We
472 // don't handle this yet, and it will not matter until Go 1.22.
473 //
474 // It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module
475 // version at all is provided, which should preclude per-file downgrading. On the other hand,
476 // https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21
477 // in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all
478 // relevant language changes before Go 1.22 will lead to type-checking failures and never reach
479 // us.
480 //
481 // Per-file upgrading is permitted in GOPATH mode.
482 483 // If the file has its own Go version, we will return that. Otherwise, we default to
484 // the type checker's GoVersion, which is populated from either the Go module, or from
485 // our '-go' flag.
486 return pass.TypesInfo.FileVersions[File(pass, node)]
487 }
488 489 // StdlibVersion returns the version of the Go standard library that node can expect to
490 // have access to. This might differ from the language version for versions of Go older
491 // than 1.21.
492 func StdlibVersion(pass *analysis.Pass, node Positioner) string {
493 // The Go version as specified in go.mod or via the '-go' flag
494 n := pass.Pkg.GoVersion()
495 496 f := File(pass, node)
497 if f == nil {
498 panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false)))
499 }
500 501 if nf := f.GoVersion; nf != "" {
502 if version.Compare(n, "go1.21") == -1 {
503 // Before Go 1.21, the Go version set in go.mod specified the maximum language
504 // version available to the module. It wasn't uncommon to set the version to
505 // Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib)
506 // to files tagged for 1.20, and supporting a lower version overall. As such,
507 // a file tagged lower than the module version couldn't expect to have access
508 // to the standard library of the version set in go.mod.
509 //
510 // At the same time, a file tagged higher than the module version, while not
511 // able to use newer language features, would still have been able to use a
512 // newer standard library.
513 //
514 // While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users'
515 // expectations have not.
516 return nf
517 } else {
518 // Go 1.21 and newer refuse to build modules that depend on versions newer
519 // than the used version of the Go toolchain. This means that in a 1.22 module
520 // with a file tagged as 1.17, the file can expect to have access to 1.22's
521 // standard library (but not to 1.22 language features). A file tagged with a
522 // version higher than the minimum version has access to the newer standard
523 // library (and language features.)
524 //
525 // Do note that strictly speaking we're conflating the Go version and the
526 // module version in our check. Nothing is stopping a user from using Go 1.17
527 // (which didn't implement the new rules for versions in go.mod) to build a Go
528 // 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22
529 // standard library. However, we believe that if a module requires 1.21 or
530 // newer, then the author clearly expects the new behavior, and doesn't care
531 // for the old one. Otherwise they would've specified an older version.
532 //
533 // In other words, the module version also specifies what it itself actually means, with
534 // >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for
535 // the language.
536 537 if version.Compare(nf, n) == 1 {
538 return nf
539 }
540 }
541 }
542 543 return n
544 }
545 546 var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`)
547 548 func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) {
549 m, ok := Match(pass, integerLiteralQ, node)
550 if !ok {
551 return types.TypeAndValue{}, false
552 }
553 return m.State["tv"].(types.TypeAndValue), true
554 }
555 556 func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool {
557 tv, ok := IntegerLiteral(pass, node)
558 if !ok {
559 return false
560 }
561 return constant.Compare(tv.Value, token.EQL, value)
562 }
563 564 // IsMethod reports whether expr is a method call of a named method with signature meth.
565 // If name is empty, it is not checked.
566 // For now, method expressions (Type.Method(recv, ..)) are not considered method calls.
567 func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool {
568 if name != "" && expr.Sel.Name != name {
569 return false
570 }
571 sel, ok := pass.TypesInfo.Selections[expr]
572 if !ok || sel.Kind() != types.MethodVal {
573 return false
574 }
575 return types.Identical(sel.Type(), meth)
576 }
577 578 func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool {
579 found := false
580 fn := func(node ast.Node) bool {
581 ident2, ok := node.(*ast.Ident)
582 if !ok {
583 return true
584 }
585 if ident == pass.TypesInfo.ObjectOf(ident2) {
586 found = true
587 return false
588 }
589 return true
590 }
591 ast.Inspect(expr, fn)
592 return found
593 }
594