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