cmd: update x/tools to 59ff18c

$ go get golang.org/x/tools@59ff18c
	$ GOWORK=off go mod tidy
	$ GOWORK=off go mod vendor

This implies golang.org/x/sys@v0.38.0, for which I have also
updated src/go.mod for consistency.

I also upgraded x/mod@3f03020 to bring in some fixes to
code that go vet would otherwise have flagged in this CL.

This brings in a number of fixes and improvements to the analysis
tools within cmd/fix, which I will apply to std and cmd presently.

For golang/go#71859

Change-Id: I56c49e7ab28eb6ba55408a3721e9a898ee3067a1
Reviewed-on: https://go-review.googlesource.com/c/go/+/719760
Auto-Submit: Alan Donovan <adonovan@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Alan Donovan 2025-11-11 14:56:59 -05:00 committed by Gopher Robot
parent 50128a2154
commit 410ef44f00
136 changed files with 3430 additions and 1509 deletions

View file

@ -6,12 +6,12 @@ require (
github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938
golang.org/x/build v0.0.0-20250806225920-b7c66c047964
golang.org/x/mod v0.29.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.37.0
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8
golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526
golang.org/x/sync v0.18.0
golang.org/x/sys v0.38.0
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54
golang.org/x/term v0.34.0
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5
golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883
)
require (

View file

@ -10,19 +10,19 @@ golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938 h1:VJ182b/ajNehMFRltVfCh
golang.org/x/arch v0.22.1-0.20251016010524-fea4a9ec4938/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964 h1:yRs1K51GKq7hsIO+YHJ8LsslrvwFceNPIv0tYjpcBd0=
golang.org/x/build v0.0.0-20250806225920-b7c66c047964/go.mod h1:i9Vx7+aOQUpYJRxSO+OpRStVBCVL/9ccI51xblWm5WY=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526 h1:LPpBM4CGUFMC47OqgAr2YIUxEUjH1Ur+D3KR/1LiuuQ=
golang.org/x/mod v0.30.1-0.20251114215501-3f03020ad526/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 h1:E2/AqCUMZGgd73TQkxUMcMla25GB9i/5HOdLr+uH7Vo=
golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5 h1:cz7f45KGWAtyIrz6bm45Gc+lw8beIxBSW3EQh4Bwbg4=
golang.org/x/tools v0.38.1-0.20251015192825-7d9453ccc0f5/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883 h1:aeO0AW8d+a+5+hNQx9f4J5egD89zftrY2x42KGQjLzI=
golang.org/x/tools v0.39.1-0.20251114194111-59ff18ce4883/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=

View file

@ -33,7 +33,7 @@ type printer struct {
}
// printf prints to the buffer.
func (p *printer) printf(format string, args ...interface{}) {
func (p *printer) printf(format string, args ...any) {
fmt.Fprintf(p, format, args...)
}

View file

@ -94,7 +94,7 @@ func (x *FileSyntax) Span() (start, end Position) {
// line, the new line is added at the end of the block containing hint,
// extracting hint into a new block if it is not yet in one.
//
// If the hint is non-nil buts its first token does not match,
// If the hint is non-nil but its first token does not match,
// the new line is added after the block containing hint
// (or hint itself, if not in a block).
//
@ -600,7 +600,7 @@ func (in *input) readToken() {
// Checked all punctuation. Must be identifier token.
if c := in.peekRune(); !isIdent(c) {
in.Error(fmt.Sprintf("unexpected input character %#q", c))
in.Error(fmt.Sprintf("unexpected input character %#q", rune(c)))
}
// Scan over identifier.

View file

@ -368,7 +368,7 @@ func (f *File) add(errs *ErrorList, block *LineBlock, line *Line, verb string, a
Err: err,
})
}
errorf := func(format string, args ...interface{}) {
errorf := func(format string, args ...any) {
wrapError(fmt.Errorf(format, args...))
}
@ -574,7 +574,7 @@ func parseReplace(filename string, line *Line, verb string, args []string, fix V
Err: err,
}
}
errorf := func(format string, args ...interface{}) *Error {
errorf := func(format string, args ...any) *Error {
return wrapError(fmt.Errorf(format, args...))
}
@ -685,7 +685,7 @@ func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string,
Err: err,
})
}
errorf := func(format string, args ...interface{}) {
errorf := func(format string, args ...any) {
wrapError(fmt.Errorf(format, args...))
}
@ -1594,7 +1594,7 @@ func (f *File) AddRetract(vi VersionInterval, rationale string) error {
r.Syntax = f.Syntax.addLine(nil, "retract", "[", AutoQuote(vi.Low), ",", AutoQuote(vi.High), "]")
}
if rationale != "" {
for _, line := range strings.Split(rationale, "\n") {
for line := range strings.SplitSeq(rationale, "\n") {
com := Comment{Token: "// " + line}
r.Syntax.Comment().Before = append(r.Syntax.Comment().Before, com)
}

View file

@ -261,7 +261,7 @@ func modPathOK(r rune) bool {
// importPathOK reports whether r can appear in a package import path element.
//
// Import paths are intermediate between module paths and file paths: we allow
// Import paths are intermediate between module paths and file paths: we
// disallow characters that would be confusing or ambiguous as arguments to
// 'go get' (such as '@' and ' ' ), but allow certain characters that are
// otherwise-unambiguous on the command line and historically used for some
@ -802,8 +802,8 @@ func MatchPrefixPatterns(globs, target string) bool {
for globs != "" {
// Extract next non-empty glob in comma-separated list.
var glob string
if i := strings.Index(globs, ","); i >= 0 {
glob, globs = globs[:i], globs[i+1:]
if before, after, ok := strings.Cut(globs, ","); ok {
glob, globs = before, after
} else {
glob, globs = globs, ""
}

View file

@ -45,8 +45,8 @@ func IsValid(v string) bool {
// Canonical returns the canonical formatting of the semantic version v.
// It fills in any missing .MINOR or .PATCH and discards build metadata.
// Two semantic versions compare equal only if their canonical formattings
// are identical strings.
// Two semantic versions compare equal only if their canonical formatting
// is an identical string.
// The canonical invalid semantic version is the empty string.
func Canonical(v string) string {
p, ok := parse(v)

View file

@ -20,13 +20,13 @@ type parCache struct {
type cacheEntry struct {
done uint32
mu sync.Mutex
result interface{}
result any
}
// Do calls the function f if and only if Do is being called for the first time with this key.
// No call to Do with a given key returns until the one call to f returns.
// Do returns the value returned by the one call to f.
func (c *parCache) Do(key interface{}, f func() interface{}) interface{} {
func (c *parCache) Do(key any, f func() any) any {
entryIface, ok := c.m.Load(key)
if !ok {
entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
@ -46,7 +46,7 @@ func (c *parCache) Do(key interface{}, f func() interface{}) interface{} {
// Get returns the cached result associated with key.
// It returns nil if there is no such result.
// If the result for key is being computed, Get does not wait for the computation to finish.
func (c *parCache) Get(key interface{}) interface{} {
func (c *parCache) Get(key any) any {
entryIface, ok := c.m.Load(key)
if !ok {
return nil

View file

@ -244,7 +244,7 @@ func (c *Client) Lookup(path, vers string) (lines []string, err error) {
data []byte
err error
}
result := c.record.Do(file, func() interface{} {
result := c.record.Do(file, func() any {
// Try the on-disk cache, or else get from web.
writeCache := false
data, err := c.ops.ReadCache(file)
@ -284,7 +284,7 @@ func (c *Client) Lookup(path, vers string) (lines []string, err error) {
// (with or without /go.mod).
prefix := path + " " + vers + " "
var hashes []string
for _, line := range strings.Split(string(result.data), "\n") {
for line := range strings.SplitSeq(string(result.data), "\n") {
if strings.HasPrefix(line, prefix) {
hashes = append(hashes, line)
}
@ -552,7 +552,7 @@ func (c *Client) readTile(tile tlog.Tile) ([]byte, error) {
err error
}
result := c.tileCache.Do(tile, func() interface{} {
result := c.tileCache.Do(tile, func() any {
// Try the requested tile in on-disk cache.
data, err := c.ops.ReadCache(c.tileCacheKey(tile))
if err == nil {

View file

@ -240,8 +240,8 @@ func isValidName(name string) bool {
// NewVerifier construct a new [Verifier] from an encoded verifier key.
func NewVerifier(vkey string) (Verifier, error) {
name, vkey := chop(vkey, "+")
hash16, key64 := chop(vkey, "+")
name, vkey, _ := strings.Cut(vkey, "+")
hash16, key64, _ := strings.Cut(vkey, "+")
hash, err1 := strconv.ParseUint(hash16, 16, 32)
key, err2 := base64.StdEncoding.DecodeString(key64)
if len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
@ -276,12 +276,8 @@ func NewVerifier(vkey string) (Verifier, error) {
// chop chops s at the first instance of sep, if any,
// and returns the text before and after sep.
// If sep is not present, chop returns before is s and after is empty.
func chop(s, sep string) (before, after string) {
i := strings.Index(s, sep)
if i < 0 {
return s, ""
}
return s[:i], s[i+len(sep):]
func chop(s, sep string) (before, after string, ok bool) {
return strings.Cut(s, sep)
}
// verifier is a trivial Verifier implementation.
@ -297,10 +293,10 @@ func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) }
// NewSigner constructs a new [Signer] from an encoded signer key.
func NewSigner(skey string) (Signer, error) {
priv1, skey := chop(skey, "+")
priv2, skey := chop(skey, "+")
name, skey := chop(skey, "+")
hash16, key64 := chop(skey, "+")
priv1, skey, _ := strings.Cut(skey, "+")
priv2, skey, _ := strings.Cut(skey, "+")
name, skey, _ := strings.Cut(skey, "+")
hash16, key64, _ := strings.Cut(skey, "+")
hash, err1 := strconv.ParseUint(hash16, 16, 32)
key, err2 := base64.StdEncoding.DecodeString(key64)
if priv1 != "PRIVATE" || priv2 != "KEY" || len(hash16) != 8 || err1 != nil || err2 != nil || !isValidName(name) || len(key) == 0 {
@ -557,7 +553,7 @@ func Open(msg []byte, known Verifiers) (*Note, error) {
return nil, errMalformedNote
}
line = line[len(sigPrefix):]
name, b64 := chop(string(line), " ")
name, b64, _ := chop(string(line), " ")
sig, err := base64.StdEncoding.DecodeString(b64)
if err != nil || !isValidName(name) || b64 == "" || len(sig) < 5 {
return nil, errMalformedNote

View file

@ -76,8 +76,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "invalid module@version syntax", http.StatusBadRequest)
return
}
i := strings.Index(mod, "@")
escPath, escVers := mod[:i], mod[i+1:]
escPath, escVers, _ := strings.Cut(mod, "@")
path, err := module.UnescapePath(escPath)
if err != nil {
reportError(w, err)

View file

@ -66,7 +66,7 @@ func (s *TestServer) ReadRecords(ctx context.Context, id, n int64) ([][]byte, er
defer s.mu.Unlock()
var list [][]byte
for i := int64(0); i < n; i++ {
for i := range n {
if id+i >= int64(len(s.records)) {
return nil, fmt.Errorf("missing records")
}

View file

@ -35,7 +35,7 @@ type Tree struct {
// A future backwards-incompatible encoding would use a different
// first line (for example, "go.sum database tree v2").
func FormatTree(tree Tree) []byte {
return []byte(fmt.Sprintf("go.sum database tree\n%d\n%s\n", tree.N, tree.Hash))
return fmt.Appendf(nil, "go.sum database tree\n%d\n%s\n", tree.N, tree.Hash)
}
var errMalformedTree = errors.New("malformed tree note")
@ -87,7 +87,7 @@ func FormatRecord(id int64, text []byte) (msg []byte, err error) {
if !isValidRecordText(text) {
return nil, errMalformedRecord
}
msg = []byte(fmt.Sprintf("%d\n", id))
msg = fmt.Appendf(nil, "%d\n", id)
msg = append(msg, text...)
msg = append(msg, '\n')
return msg, nil

View file

@ -194,7 +194,7 @@ func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) {
// and consumes a hash from an adjacent subtree.
m := int(bits.TrailingZeros64(uint64(n + 1)))
indexes := make([]int64, m)
for i := 0; i < m; i++ {
for i := range m {
// We arrange indexes in sorted order.
// Note that n>>i is always odd.
indexes[m-1-i] = StoredHashIndex(i, n>>uint(i)-1)
@ -210,7 +210,7 @@ func StoredHashesForRecordHash(n int64, h Hash, r HashReader) ([]Hash, error) {
}
// Build new hashes.
for i := 0; i < m; i++ {
for i := range m {
h = NodeHash(old[m-1-i], h)
hashes = append(hashes, h)
}

View file

@ -780,7 +780,7 @@ func (fi dataFileInfo) Size() int64 { return int64(len(fi.f.data)) }
func (fi dataFileInfo) Mode() os.FileMode { return 0644 }
func (fi dataFileInfo) ModTime() time.Time { return time.Time{} }
func (fi dataFileInfo) IsDir() bool { return false }
func (fi dataFileInfo) Sys() interface{} { return nil }
func (fi dataFileInfo) Sys() any { return nil }
// isVendoredPackage attempts to report whether the given filename is contained
// in a package whose import path contains (but does not end with) the component

View file

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file.
// Package errgroup provides synchronization, error propagation, and Context
// cancelation for groups of goroutines working on subtasks of a common task.
// cancellation for groups of goroutines working on subtasks of a common task.
//
// [errgroup.Group] is related to [sync.WaitGroup] but adds handling of tasks
// returning errors.

View file

@ -226,6 +226,7 @@ struct ltchars {
#include <linux/cryptouser.h>
#include <linux/devlink.h>
#include <linux/dm-ioctl.h>
#include <linux/elf.h>
#include <linux/errqueue.h>
#include <linux/ethtool_netlink.h>
#include <linux/falloc.h>
@ -529,6 +530,7 @@ ccflags="$@"
$2 ~ /^O[CNPFPL][A-Z]+[^_][A-Z]+$/ ||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)DLY$/ ||
$2 ~ /^(NL|CR|TAB|BS|VT|FF)[0-9]$/ ||
$2 ~ /^(DT|EI|ELF|EV|NN|NT|PF|SHF|SHN|SHT|STB|STT|VER)_/ ||
$2 ~ /^O?XTABS$/ ||
$2 ~ /^TC[IO](ON|OFF)$/ ||
$2 ~ /^IN_/ ||

View file

@ -2643,3 +2643,9 @@ func SchedGetAttr(pid int, flags uint) (*SchedAttr, error) {
//sys Cachestat(fd uint, crange *CachestatRange, cstat *Cachestat_t, flags uint) (err error)
//sys Mseal(b []byte, flags uint) (err error)
//sys setMemPolicy(mode int, mask *CPUSet, size int) (err error) = SYS_SET_MEMPOLICY
func SetMemPolicy(mode int, mask *CPUSet) error {
return setMemPolicy(mode, mask, _CPU_SETSIZE)
}

View file

@ -853,20 +853,86 @@ const (
DM_VERSION_MAJOR = 0x4
DM_VERSION_MINOR = 0x32
DM_VERSION_PATCHLEVEL = 0x0
DT_ADDRRNGHI = 0x6ffffeff
DT_ADDRRNGLO = 0x6ffffe00
DT_BLK = 0x6
DT_CHR = 0x2
DT_DEBUG = 0x15
DT_DIR = 0x4
DT_ENCODING = 0x20
DT_FIFO = 0x1
DT_FINI = 0xd
DT_FLAGS_1 = 0x6ffffffb
DT_GNU_HASH = 0x6ffffef5
DT_HASH = 0x4
DT_HIOS = 0x6ffff000
DT_HIPROC = 0x7fffffff
DT_INIT = 0xc
DT_JMPREL = 0x17
DT_LNK = 0xa
DT_LOOS = 0x6000000d
DT_LOPROC = 0x70000000
DT_NEEDED = 0x1
DT_NULL = 0x0
DT_PLTGOT = 0x3
DT_PLTREL = 0x14
DT_PLTRELSZ = 0x2
DT_REG = 0x8
DT_REL = 0x11
DT_RELA = 0x7
DT_RELACOUNT = 0x6ffffff9
DT_RELAENT = 0x9
DT_RELASZ = 0x8
DT_RELCOUNT = 0x6ffffffa
DT_RELENT = 0x13
DT_RELSZ = 0x12
DT_RPATH = 0xf
DT_SOCK = 0xc
DT_SONAME = 0xe
DT_STRSZ = 0xa
DT_STRTAB = 0x5
DT_SYMBOLIC = 0x10
DT_SYMENT = 0xb
DT_SYMTAB = 0x6
DT_TEXTREL = 0x16
DT_UNKNOWN = 0x0
DT_VALRNGHI = 0x6ffffdff
DT_VALRNGLO = 0x6ffffd00
DT_VERDEF = 0x6ffffffc
DT_VERDEFNUM = 0x6ffffffd
DT_VERNEED = 0x6ffffffe
DT_VERNEEDNUM = 0x6fffffff
DT_VERSYM = 0x6ffffff0
DT_WHT = 0xe
ECHO = 0x8
ECRYPTFS_SUPER_MAGIC = 0xf15f
EFD_SEMAPHORE = 0x1
EFIVARFS_MAGIC = 0xde5e81e4
EFS_SUPER_MAGIC = 0x414a53
EI_CLASS = 0x4
EI_DATA = 0x5
EI_MAG0 = 0x0
EI_MAG1 = 0x1
EI_MAG2 = 0x2
EI_MAG3 = 0x3
EI_NIDENT = 0x10
EI_OSABI = 0x7
EI_PAD = 0x8
EI_VERSION = 0x6
ELFCLASS32 = 0x1
ELFCLASS64 = 0x2
ELFCLASSNONE = 0x0
ELFCLASSNUM = 0x3
ELFDATA2LSB = 0x1
ELFDATA2MSB = 0x2
ELFDATANONE = 0x0
ELFMAG = "\177ELF"
ELFMAG0 = 0x7f
ELFMAG1 = 'E'
ELFMAG2 = 'L'
ELFMAG3 = 'F'
ELFOSABI_LINUX = 0x3
ELFOSABI_NONE = 0x0
EM_386 = 0x3
EM_486 = 0x6
EM_68K = 0x4
@ -1152,14 +1218,24 @@ const (
ETH_P_WCCP = 0x883e
ETH_P_X25 = 0x805
ETH_P_XDSA = 0xf8
ET_CORE = 0x4
ET_DYN = 0x3
ET_EXEC = 0x2
ET_HIPROC = 0xffff
ET_LOPROC = 0xff00
ET_NONE = 0x0
ET_REL = 0x1
EV_ABS = 0x3
EV_CNT = 0x20
EV_CURRENT = 0x1
EV_FF = 0x15
EV_FF_STATUS = 0x17
EV_KEY = 0x1
EV_LED = 0x11
EV_MAX = 0x1f
EV_MSC = 0x4
EV_NONE = 0x0
EV_NUM = 0x2
EV_PWR = 0x16
EV_REL = 0x2
EV_REP = 0x14
@ -2276,7 +2352,167 @@ const (
NLM_F_REPLACE = 0x100
NLM_F_REQUEST = 0x1
NLM_F_ROOT = 0x100
NN_386_IOPERM = "LINUX"
NN_386_TLS = "LINUX"
NN_ARC_V2 = "LINUX"
NN_ARM_FPMR = "LINUX"
NN_ARM_GCS = "LINUX"
NN_ARM_HW_BREAK = "LINUX"
NN_ARM_HW_WATCH = "LINUX"
NN_ARM_PACA_KEYS = "LINUX"
NN_ARM_PACG_KEYS = "LINUX"
NN_ARM_PAC_ENABLED_KEYS = "LINUX"
NN_ARM_PAC_MASK = "LINUX"
NN_ARM_POE = "LINUX"
NN_ARM_SSVE = "LINUX"
NN_ARM_SVE = "LINUX"
NN_ARM_SYSTEM_CALL = "LINUX"
NN_ARM_TAGGED_ADDR_CTRL = "LINUX"
NN_ARM_TLS = "LINUX"
NN_ARM_VFP = "LINUX"
NN_ARM_ZA = "LINUX"
NN_ARM_ZT = "LINUX"
NN_AUXV = "CORE"
NN_FILE = "CORE"
NN_GNU_PROPERTY_TYPE_0 = "GNU"
NN_LOONGARCH_CPUCFG = "LINUX"
NN_LOONGARCH_CSR = "LINUX"
NN_LOONGARCH_HW_BREAK = "LINUX"
NN_LOONGARCH_HW_WATCH = "LINUX"
NN_LOONGARCH_LASX = "LINUX"
NN_LOONGARCH_LBT = "LINUX"
NN_LOONGARCH_LSX = "LINUX"
NN_MIPS_DSP = "LINUX"
NN_MIPS_FP_MODE = "LINUX"
NN_MIPS_MSA = "LINUX"
NN_PPC_DEXCR = "LINUX"
NN_PPC_DSCR = "LINUX"
NN_PPC_EBB = "LINUX"
NN_PPC_HASHKEYR = "LINUX"
NN_PPC_PKEY = "LINUX"
NN_PPC_PMU = "LINUX"
NN_PPC_PPR = "LINUX"
NN_PPC_SPE = "LINUX"
NN_PPC_TAR = "LINUX"
NN_PPC_TM_CDSCR = "LINUX"
NN_PPC_TM_CFPR = "LINUX"
NN_PPC_TM_CGPR = "LINUX"
NN_PPC_TM_CPPR = "LINUX"
NN_PPC_TM_CTAR = "LINUX"
NN_PPC_TM_CVMX = "LINUX"
NN_PPC_TM_CVSX = "LINUX"
NN_PPC_TM_SPR = "LINUX"
NN_PPC_VMX = "LINUX"
NN_PPC_VSX = "LINUX"
NN_PRFPREG = "CORE"
NN_PRPSINFO = "CORE"
NN_PRSTATUS = "CORE"
NN_PRXFPREG = "LINUX"
NN_RISCV_CSR = "LINUX"
NN_RISCV_TAGGED_ADDR_CTRL = "LINUX"
NN_RISCV_VECTOR = "LINUX"
NN_S390_CTRS = "LINUX"
NN_S390_GS_BC = "LINUX"
NN_S390_GS_CB = "LINUX"
NN_S390_HIGH_GPRS = "LINUX"
NN_S390_LAST_BREAK = "LINUX"
NN_S390_PREFIX = "LINUX"
NN_S390_PV_CPU_DATA = "LINUX"
NN_S390_RI_CB = "LINUX"
NN_S390_SYSTEM_CALL = "LINUX"
NN_S390_TDB = "LINUX"
NN_S390_TIMER = "LINUX"
NN_S390_TODCMP = "LINUX"
NN_S390_TODPREG = "LINUX"
NN_S390_VXRS_HIGH = "LINUX"
NN_S390_VXRS_LOW = "LINUX"
NN_SIGINFO = "CORE"
NN_TASKSTRUCT = "CORE"
NN_VMCOREDD = "LINUX"
NN_X86_SHSTK = "LINUX"
NN_X86_XSAVE_LAYOUT = "LINUX"
NN_X86_XSTATE = "LINUX"
NSFS_MAGIC = 0x6e736673
NT_386_IOPERM = 0x201
NT_386_TLS = 0x200
NT_ARC_V2 = 0x600
NT_ARM_FPMR = 0x40e
NT_ARM_GCS = 0x410
NT_ARM_HW_BREAK = 0x402
NT_ARM_HW_WATCH = 0x403
NT_ARM_PACA_KEYS = 0x407
NT_ARM_PACG_KEYS = 0x408
NT_ARM_PAC_ENABLED_KEYS = 0x40a
NT_ARM_PAC_MASK = 0x406
NT_ARM_POE = 0x40f
NT_ARM_SSVE = 0x40b
NT_ARM_SVE = 0x405
NT_ARM_SYSTEM_CALL = 0x404
NT_ARM_TAGGED_ADDR_CTRL = 0x409
NT_ARM_TLS = 0x401
NT_ARM_VFP = 0x400
NT_ARM_ZA = 0x40c
NT_ARM_ZT = 0x40d
NT_AUXV = 0x6
NT_FILE = 0x46494c45
NT_GNU_PROPERTY_TYPE_0 = 0x5
NT_LOONGARCH_CPUCFG = 0xa00
NT_LOONGARCH_CSR = 0xa01
NT_LOONGARCH_HW_BREAK = 0xa05
NT_LOONGARCH_HW_WATCH = 0xa06
NT_LOONGARCH_LASX = 0xa03
NT_LOONGARCH_LBT = 0xa04
NT_LOONGARCH_LSX = 0xa02
NT_MIPS_DSP = 0x800
NT_MIPS_FP_MODE = 0x801
NT_MIPS_MSA = 0x802
NT_PPC_DEXCR = 0x111
NT_PPC_DSCR = 0x105
NT_PPC_EBB = 0x106
NT_PPC_HASHKEYR = 0x112
NT_PPC_PKEY = 0x110
NT_PPC_PMU = 0x107
NT_PPC_PPR = 0x104
NT_PPC_SPE = 0x101
NT_PPC_TAR = 0x103
NT_PPC_TM_CDSCR = 0x10f
NT_PPC_TM_CFPR = 0x109
NT_PPC_TM_CGPR = 0x108
NT_PPC_TM_CPPR = 0x10e
NT_PPC_TM_CTAR = 0x10d
NT_PPC_TM_CVMX = 0x10a
NT_PPC_TM_CVSX = 0x10b
NT_PPC_TM_SPR = 0x10c
NT_PPC_VMX = 0x100
NT_PPC_VSX = 0x102
NT_PRFPREG = 0x2
NT_PRPSINFO = 0x3
NT_PRSTATUS = 0x1
NT_PRXFPREG = 0x46e62b7f
NT_RISCV_CSR = 0x900
NT_RISCV_TAGGED_ADDR_CTRL = 0x902
NT_RISCV_VECTOR = 0x901
NT_S390_CTRS = 0x304
NT_S390_GS_BC = 0x30c
NT_S390_GS_CB = 0x30b
NT_S390_HIGH_GPRS = 0x300
NT_S390_LAST_BREAK = 0x306
NT_S390_PREFIX = 0x305
NT_S390_PV_CPU_DATA = 0x30e
NT_S390_RI_CB = 0x30d
NT_S390_SYSTEM_CALL = 0x307
NT_S390_TDB = 0x308
NT_S390_TIMER = 0x301
NT_S390_TODCMP = 0x302
NT_S390_TODPREG = 0x303
NT_S390_VXRS_HIGH = 0x30a
NT_S390_VXRS_LOW = 0x309
NT_SIGINFO = 0x53494749
NT_TASKSTRUCT = 0x4
NT_VMCOREDD = 0x700
NT_X86_SHSTK = 0x204
NT_X86_XSAVE_LAYOUT = 0x205
NT_X86_XSTATE = 0x202
OCFS2_SUPER_MAGIC = 0x7461636f
OCRNL = 0x8
OFDEL = 0x80
@ -2463,6 +2699,59 @@ const (
PERF_RECORD_MISC_USER = 0x2
PERF_SAMPLE_BRANCH_PLM_ALL = 0x7
PERF_SAMPLE_WEIGHT_TYPE = 0x1004000
PF_ALG = 0x26
PF_APPLETALK = 0x5
PF_ASH = 0x12
PF_ATMPVC = 0x8
PF_ATMSVC = 0x14
PF_AX25 = 0x3
PF_BLUETOOTH = 0x1f
PF_BRIDGE = 0x7
PF_CAIF = 0x25
PF_CAN = 0x1d
PF_DECnet = 0xc
PF_ECONET = 0x13
PF_FILE = 0x1
PF_IB = 0x1b
PF_IEEE802154 = 0x24
PF_INET = 0x2
PF_INET6 = 0xa
PF_IPX = 0x4
PF_IRDA = 0x17
PF_ISDN = 0x22
PF_IUCV = 0x20
PF_KCM = 0x29
PF_KEY = 0xf
PF_LLC = 0x1a
PF_LOCAL = 0x1
PF_MAX = 0x2e
PF_MCTP = 0x2d
PF_MPLS = 0x1c
PF_NETBEUI = 0xd
PF_NETLINK = 0x10
PF_NETROM = 0x6
PF_NFC = 0x27
PF_PACKET = 0x11
PF_PHONET = 0x23
PF_PPPOX = 0x18
PF_QIPCRTR = 0x2a
PF_R = 0x4
PF_RDS = 0x15
PF_ROSE = 0xb
PF_ROUTE = 0x10
PF_RXRPC = 0x21
PF_SECURITY = 0xe
PF_SMC = 0x2b
PF_SNA = 0x16
PF_TIPC = 0x1e
PF_UNIX = 0x1
PF_UNSPEC = 0x0
PF_VSOCK = 0x28
PF_W = 0x2
PF_WANPIPE = 0x19
PF_X = 0x1
PF_X25 = 0x9
PF_XDP = 0x2c
PID_FS_MAGIC = 0x50494446
PIPEFS_MAGIC = 0x50495045
PPPIOCGNPMODE = 0xc008744c
@ -2758,6 +3047,23 @@ const (
PTRACE_SYSCALL_INFO_NONE = 0x0
PTRACE_SYSCALL_INFO_SECCOMP = 0x3
PTRACE_TRACEME = 0x0
PT_AARCH64_MEMTAG_MTE = 0x70000002
PT_DYNAMIC = 0x2
PT_GNU_EH_FRAME = 0x6474e550
PT_GNU_PROPERTY = 0x6474e553
PT_GNU_RELRO = 0x6474e552
PT_GNU_STACK = 0x6474e551
PT_HIOS = 0x6fffffff
PT_HIPROC = 0x7fffffff
PT_INTERP = 0x3
PT_LOAD = 0x1
PT_LOOS = 0x60000000
PT_LOPROC = 0x70000000
PT_NOTE = 0x4
PT_NULL = 0x0
PT_PHDR = 0x6
PT_SHLIB = 0x5
PT_TLS = 0x7
P_ALL = 0x0
P_PGID = 0x2
P_PID = 0x1
@ -3091,6 +3397,47 @@ const (
SEEK_MAX = 0x4
SEEK_SET = 0x0
SELINUX_MAGIC = 0xf97cff8c
SHF_ALLOC = 0x2
SHF_EXCLUDE = 0x8000000
SHF_EXECINSTR = 0x4
SHF_GROUP = 0x200
SHF_INFO_LINK = 0x40
SHF_LINK_ORDER = 0x80
SHF_MASKOS = 0xff00000
SHF_MASKPROC = 0xf0000000
SHF_MERGE = 0x10
SHF_ORDERED = 0x4000000
SHF_OS_NONCONFORMING = 0x100
SHF_RELA_LIVEPATCH = 0x100000
SHF_RO_AFTER_INIT = 0x200000
SHF_STRINGS = 0x20
SHF_TLS = 0x400
SHF_WRITE = 0x1
SHN_ABS = 0xfff1
SHN_COMMON = 0xfff2
SHN_HIPROC = 0xff1f
SHN_HIRESERVE = 0xffff
SHN_LIVEPATCH = 0xff20
SHN_LOPROC = 0xff00
SHN_LORESERVE = 0xff00
SHN_UNDEF = 0x0
SHT_DYNAMIC = 0x6
SHT_DYNSYM = 0xb
SHT_HASH = 0x5
SHT_HIPROC = 0x7fffffff
SHT_HIUSER = 0xffffffff
SHT_LOPROC = 0x70000000
SHT_LOUSER = 0x80000000
SHT_NOBITS = 0x8
SHT_NOTE = 0x7
SHT_NULL = 0x0
SHT_NUM = 0xc
SHT_PROGBITS = 0x1
SHT_REL = 0x9
SHT_RELA = 0x4
SHT_SHLIB = 0xa
SHT_STRTAB = 0x3
SHT_SYMTAB = 0x2
SHUT_RD = 0x0
SHUT_RDWR = 0x2
SHUT_WR = 0x1
@ -3317,6 +3664,16 @@ const (
STATX_UID = 0x8
STATX_WRITE_ATOMIC = 0x10000
STATX__RESERVED = 0x80000000
STB_GLOBAL = 0x1
STB_LOCAL = 0x0
STB_WEAK = 0x2
STT_COMMON = 0x5
STT_FILE = 0x4
STT_FUNC = 0x2
STT_NOTYPE = 0x0
STT_OBJECT = 0x1
STT_SECTION = 0x3
STT_TLS = 0x6
SYNC_FILE_RANGE_WAIT_AFTER = 0x4
SYNC_FILE_RANGE_WAIT_BEFORE = 0x1
SYNC_FILE_RANGE_WRITE = 0x2
@ -3553,6 +3910,8 @@ const (
UTIME_OMIT = 0x3ffffffe
V9FS_MAGIC = 0x1021997
VERASE = 0x2
VER_FLG_BASE = 0x1
VER_FLG_WEAK = 0x2
VINTR = 0x0
VKILL = 0x3
VLNEXT = 0xf

View file

@ -2238,3 +2238,13 @@ func Mseal(b []byte, flags uint) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func setMemPolicy(mode int, mask *CPUSet, size int) (err error) {
_, _, e1 := Syscall(SYS_SET_MEMPOLICY, uintptr(mode), uintptr(unsafe.Pointer(mask)), uintptr(size))
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View file

@ -3590,6 +3590,8 @@ type Nhmsg struct {
Flags uint32
}
const SizeofNhmsg = 0x8
type NexthopGrp struct {
Id uint32
Weight uint8
@ -3597,6 +3599,8 @@ type NexthopGrp struct {
Resvd2 uint16
}
const SizeofNexthopGrp = 0x8
const (
NHA_UNSPEC = 0x0
NHA_ID = 0x1
@ -6332,3 +6336,30 @@ type SockDiagReq struct {
}
const RTM_NEWNVLAN = 0x70
const (
MPOL_BIND = 0x2
MPOL_DEFAULT = 0x0
MPOL_F_ADDR = 0x2
MPOL_F_MEMS_ALLOWED = 0x4
MPOL_F_MOF = 0x8
MPOL_F_MORON = 0x10
MPOL_F_NODE = 0x1
MPOL_F_NUMA_BALANCING = 0x2000
MPOL_F_RELATIVE_NODES = 0x4000
MPOL_F_SHARED = 0x1
MPOL_F_STATIC_NODES = 0x8000
MPOL_INTERLEAVE = 0x3
MPOL_LOCAL = 0x4
MPOL_MAX = 0x7
MPOL_MF_INTERNAL = 0x10
MPOL_MF_LAZY = 0x8
MPOL_MF_MOVE_ALL = 0x4
MPOL_MF_MOVE = 0x2
MPOL_MF_STRICT = 0x1
MPOL_MF_VALID = 0x7
MPOL_MODE_FLAGS = 0xe000
MPOL_PREFERRED = 0x1
MPOL_PREFERRED_MANY = 0x5
MPOL_WEIGHTED_INTERLEAVE = 0x6
)

View file

@ -892,8 +892,12 @@ const socket_error = uintptr(^uint32(0))
//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
//sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx
//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex
//sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2
//sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2
//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry
//sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable
//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange
//sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2
//sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange
//sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2
@ -916,6 +920,17 @@ type RawSockaddrInet6 struct {
Scope_id uint32
}
// RawSockaddrInet is a union that contains an IPv4, an IPv6 address, or an address family. See
// https://learn.microsoft.com/en-us/windows/win32/api/ws2ipdef/ns-ws2ipdef-sockaddr_inet.
//
// A [*RawSockaddrInet] may be converted to a [*RawSockaddrInet4] or [*RawSockaddrInet6] using
// unsafe, depending on the address family.
type RawSockaddrInet struct {
Family uint16
Port uint16
Data [6]uint32
}
type RawSockaddr struct {
Family uint16
Data [14]int8

View file

@ -2320,6 +2320,82 @@ type MibIfRow2 struct {
OutQLen uint64
}
// IP_ADDRESS_PREFIX stores an IP address prefix. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix.
type IpAddressPrefix struct {
Prefix RawSockaddrInet
PrefixLength uint8
}
// NL_ROUTE_ORIGIN enumeration from nldef.h or
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_origin.
const (
NlroManual = 0
NlroWellKnown = 1
NlroDHCP = 2
NlroRouterAdvertisement = 3
Nlro6to4 = 4
)
// NL_ROUTE_ORIGIN enumeration from nldef.h or
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_route_protocol.
const (
MIB_IPPROTO_OTHER = 1
MIB_IPPROTO_LOCAL = 2
MIB_IPPROTO_NETMGMT = 3
MIB_IPPROTO_ICMP = 4
MIB_IPPROTO_EGP = 5
MIB_IPPROTO_GGP = 6
MIB_IPPROTO_HELLO = 7
MIB_IPPROTO_RIP = 8
MIB_IPPROTO_IS_IS = 9
MIB_IPPROTO_ES_IS = 10
MIB_IPPROTO_CISCO = 11
MIB_IPPROTO_BBN = 12
MIB_IPPROTO_OSPF = 13
MIB_IPPROTO_BGP = 14
MIB_IPPROTO_IDPR = 15
MIB_IPPROTO_EIGRP = 16
MIB_IPPROTO_DVMRP = 17
MIB_IPPROTO_RPL = 18
MIB_IPPROTO_DHCP = 19
MIB_IPPROTO_NT_AUTOSTATIC = 10002
MIB_IPPROTO_NT_STATIC = 10006
MIB_IPPROTO_NT_STATIC_NON_DOD = 10007
)
// MIB_IPFORWARD_ROW2 stores information about an IP route entry. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_row2.
type MibIpForwardRow2 struct {
InterfaceLuid uint64
InterfaceIndex uint32
DestinationPrefix IpAddressPrefix
NextHop RawSockaddrInet
SitePrefixLength uint8
ValidLifetime uint32
PreferredLifetime uint32
Metric uint32
Protocol uint32
Loopback uint8
AutoconfigureAddress uint8
Publish uint8
Immortal uint8
Age uint32
Origin uint32
}
// MIB_IPFORWARD_TABLE2 contains a table of IP route entries. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipforward_table2.
type MibIpForwardTable2 struct {
NumEntries uint32
Table [1]MibIpForwardRow2
}
// Rows returns the IP route entries in the table.
func (t *MibIpForwardTable2) Rows() []MibIpForwardRow2 {
return unsafe.Slice(&t.Table[0], t.NumEntries)
}
// MIB_UNICASTIPADDRESS_ROW stores information about a unicast IP address. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_row.
type MibUnicastIpAddressRow struct {

View file

@ -182,13 +182,17 @@ var (
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2")
procFreeMibTable = modiphlpapi.NewProc("FreeMibTable")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo")
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2")
procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2")
procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2")
procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
procAddDllDirectory = modkernel32.NewProc("AddDllDirectory")
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
@ -1624,6 +1628,11 @@ func CancelMibChangeNotify2(notificationHandle Handle) (errcode error) {
return
}
func FreeMibTable(memory unsafe.Pointer) {
syscall.SyscallN(procFreeMibTable.Addr(), uintptr(memory))
return
}
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetAdaptersAddresses.Addr(), uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)))
if r0 != 0 {
@ -1664,6 +1673,22 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) {
return
}
func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetIpForwardTable2.Addr(), uintptr(family), uintptr(unsafe.Pointer(table)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row)))
if r0 != 0 {
@ -1684,6 +1709,18 @@ func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsa
return
}
func NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {
_p0 = 1
}
r0, _, _ := syscall.SyscallN(procNotifyRouteChange2.Addr(), uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)))
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {

1
src/cmd/vendor/golang.org/x/telemetry/codereview.cfg generated vendored Normal file
View file

@ -0,0 +1 @@
issuerepo: golang/go

View file

@ -33,8 +33,9 @@ type Diagnostic struct {
URL string
// SuggestedFixes is an optional list of fixes to address the
// problem described by the diagnostic. Each one represents
// an alternative strategy; at most one may be applied.
// problem described by the diagnostic. Each one represents an
// alternative strategy, and should have a distinct and
// descriptive message; at most one may be applied.
//
// Fixes for different diagnostics should be treated as
// independent changes to the same baseline file state,

View file

@ -13,22 +13,20 @@ import (
"encoding/json"
"flag"
"fmt"
"go/token"
"io"
"log"
"os"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
)
// flags common to all {single,multi,unit}checkers.
var (
JSON = false // -json
Context = -1 // -c=N: if N>0, display offending line plus N lines of context
Fix bool // -fix
diffFlag bool // -diff (changes [ApplyFixes] behavior)
JSON = false // -json
Context = -1 // -c=N: if N>0, display offending line plus N lines of context
Fix bool // -fix
Diff bool // -diff
)
// Parse creates a flag for each of the analyzer's flags,
@ -78,7 +76,7 @@ func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
flag.BoolVar(&diffFlag, "diff", false, "with -fix, don't update the files, but print a unified diff")
flag.BoolVar(&Diff, "diff", false, "with -fix, don't update the files, but print a unified diff")
// Add shims for legacy vet flags to enable existing
// scripts that run vet to continue to work.
@ -310,150 +308,3 @@ var vetLegacyFlags = map[string]string{
"unusedfuncs": "unusedresult.funcs",
"unusedstringmethods": "unusedresult.stringmethods",
}
// ---- output helpers common to all drivers ----
//
// These functions should not depend on global state (flags)!
// Really they belong in a different package.
// TODO(adonovan): don't accept an io.Writer if we don't report errors.
// Either accept a bytes.Buffer (infallible), or return a []byte.
// PrintPlain prints a diagnostic in plain text form.
// If contextLines is nonnegative, it also prints the
// offending line plus this many lines of context.
func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
print := func(pos, end token.Pos, message string) {
posn := fset.Position(pos)
fmt.Fprintf(out, "%s: %s\n", posn, message)
// show offending line plus N lines of context.
if contextLines >= 0 {
end := fset.Position(end)
if !end.IsValid() {
end = posn
}
// TODO(adonovan): highlight the portion of the line indicated
// by pos...end using ASCII art, terminal colors, etc?
data, _ := os.ReadFile(posn.Filename)
lines := strings.Split(string(data), "\n")
for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
if 1 <= i && i <= len(lines) {
fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
}
}
}
}
print(diag.Pos, diag.End, diag.Message)
for _, rel := range diag.Related {
print(rel.Pos, rel.End, "\t"+rel.Message)
}
}
// A JSONTree is a mapping from package ID to analysis name to result.
// Each result is either a jsonError or a list of JSONDiagnostic.
type JSONTree map[string]map[string]any
// A TextEdit describes the replacement of a portion of a file.
// Start and End are zero-based half-open indices into the original byte
// sequence of the file, and New is the new text.
type JSONTextEdit struct {
Filename string `json:"filename"`
Start int `json:"start"`
End int `json:"end"`
New string `json:"new"`
}
// A JSONSuggestedFix describes an edit that should be applied as a whole or not
// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
// consists of multiple non-contiguous edits.
type JSONSuggestedFix struct {
Message string `json:"message"`
Edits []JSONTextEdit `json:"edits"`
}
// A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
//
// TODO(matloob): include End position if present.
type JSONDiagnostic struct {
Category string `json:"category,omitempty"`
Posn string `json:"posn"` // e.g. "file.go:line:column"
Message string `json:"message"`
SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
Related []JSONRelatedInformation `json:"related,omitempty"`
}
// A JSONRelated describes a secondary position and message related to
// a primary diagnostic.
//
// TODO(adonovan): include End position if present.
type JSONRelatedInformation struct {
Posn string `json:"posn"` // e.g. "file.go:line:column"
Message string `json:"message"`
}
// Add adds the result of analysis 'name' on package 'id'.
// The result is either a list of diagnostics or an error.
func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
var v any
if err != nil {
type jsonError struct {
Err string `json:"error"`
}
v = jsonError{err.Error()}
} else if len(diags) > 0 {
diagnostics := make([]JSONDiagnostic, 0, len(diags))
for _, f := range diags {
var fixes []JSONSuggestedFix
for _, fix := range f.SuggestedFixes {
var edits []JSONTextEdit
for _, edit := range fix.TextEdits {
edits = append(edits, JSONTextEdit{
Filename: fset.Position(edit.Pos).Filename,
Start: fset.Position(edit.Pos).Offset,
End: fset.Position(edit.End).Offset,
New: string(edit.NewText),
})
}
fixes = append(fixes, JSONSuggestedFix{
Message: fix.Message,
Edits: edits,
})
}
var related []JSONRelatedInformation
for _, r := range f.Related {
related = append(related, JSONRelatedInformation{
Posn: fset.Position(r.Pos).String(),
Message: r.Message,
})
}
jdiag := JSONDiagnostic{
Category: f.Category,
Posn: fset.Position(f.Pos).String(),
Message: f.Message,
SuggestedFixes: fixes,
Related: related,
}
diagnostics = append(diagnostics, jdiag)
}
v = diagnostics
}
if v != nil {
m, ok := tree[id]
if !ok {
m = make(map[string]any)
tree[id] = m
}
m[name] = v
}
}
func (tree JSONTree) Print(out io.Writer) error {
data, err := json.MarshalIndent(tree, "", "\t")
if err != nil {
log.Panicf("internal error: JSON marshaling failed: %v", err)
}
_, err = fmt.Fprintf(out, "%s\n", data)
return err
}

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "appends",
Doc: analysisinternal.MustExtractDoc(doc, "appends"),
Doc: analyzerutil.MustExtractDoc(doc, "appends"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/appends",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -19,7 +19,7 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report mismatches between assembly files and Go declarations"
@ -175,7 +175,7 @@ func run(pass *analysis.Pass) (any, error) {
Files:
for _, fname := range sfiles {
content, tf, err := analysisinternal.ReadFile(pass, fname)
content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}

View file

@ -18,7 +18,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@ -29,7 +29,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "assign",
Doc: analysisinternal.MustExtractDoc(doc, "assign"),
Doc: analyzerutil.MustExtractDoc(doc, "assign"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/assign",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "atomic",
Doc: analysisinternal.MustExtractDoc(doc, "atomic"),
Doc: analyzerutil.MustExtractDoc(doc, "atomic"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/atomic",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,

View file

@ -14,7 +14,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "check //go:build and // +build directives"
@ -86,7 +86,7 @@ func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since this may not be a Go source file.
// Read the raw bytes instead.
content, tf, err := analysisinternal.ReadFile(pass, filename)
content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}

View file

@ -350,8 +350,8 @@ func typeOKForCgoCall(t types.Type, m map[types.Type]bool) bool {
case *types.Array:
return typeOKForCgoCall(t.Elem(), m)
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if !typeOKForCgoCall(t.Field(i).Type(), m) {
for field := range t.Fields() {
if !typeOKForCgoCall(field.Type(), m) {
return false
}
}

View file

@ -328,8 +328,8 @@ func lockPath(tpkg *types.Package, typ types.Type, seen map[types.Type]bool) typ
ttyp, ok := typ.Underlying().(*types.Tuple)
if ok {
for i := 0; i < ttyp.Len(); i++ {
subpath := lockPath(tpkg, ttyp.At(i).Type(), seen)
for v := range ttyp.Variables() {
subpath := lockPath(tpkg, v.Type(), seen)
if subpath != nil {
return append(subpath, typ.String())
}

View file

@ -16,9 +16,12 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/ctrlflowinternal"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/cfginternal"
"golang.org/x/tools/internal/typesinternal"
)
var Analyzer = &analysis.Analyzer{
@ -26,7 +29,7 @@ var Analyzer = &analysis.Analyzer{
Doc: "build a control-flow graph",
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow",
Run: run,
ResultType: reflect.TypeOf(new(CFGs)),
ResultType: reflect.TypeFor[*CFGs](),
FactTypes: []analysis.Fact{new(noReturn)},
Requires: []*analysis.Analyzer{inspect.Analyzer},
}
@ -44,7 +47,20 @@ type CFGs struct {
defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs
funcDecls map[*types.Func]*declInfo
funcLits map[*ast.FuncLit]*litInfo
pass *analysis.Pass // transient; nil after construction
noReturn map[*types.Func]bool // functions lacking a reachable return statement
pass *analysis.Pass // transient; nil after construction
}
// TODO(adonovan): add (*CFGs).NoReturn to public API.
func (c *CFGs) isNoReturn(fn *types.Func) bool {
return c.noReturn[fn]
}
func init() {
// Expose the hidden method to callers in x/tools.
ctrlflowinternal.NoReturn = func(c any, fn *types.Func) bool {
return c.(*CFGs).isNoReturn(fn)
}
}
// CFGs has two maps: funcDecls for named functions and funcLits for
@ -54,15 +70,14 @@ type CFGs struct {
// *types.Func but not the other way.
type declInfo struct {
decl *ast.FuncDecl
cfg *cfg.CFG // iff decl.Body != nil
started bool // to break cycles
noReturn bool
decl *ast.FuncDecl
cfg *cfg.CFG // iff decl.Body != nil
started bool // to break cycles
}
type litInfo struct {
cfg *cfg.CFG
noReturn bool
noReturn bool // (currently unused)
}
// FuncDecl returns the control-flow graph for a named function.
@ -118,6 +133,7 @@ func run(pass *analysis.Pass) (any, error) {
defs: pass.TypesInfo.Defs,
funcDecls: funcDecls,
funcLits: funcLits,
noReturn: make(map[*types.Func]bool),
pass: pass,
}
@ -138,7 +154,7 @@ func run(pass *analysis.Pass) (any, error) {
li := funcLits[lit]
if li.cfg == nil {
li.cfg = cfg.New(lit.Body, c.callMayReturn)
if !hasReachableReturn(li.cfg) {
if cfginternal.IsNoReturn(li.cfg) {
li.noReturn = true
}
}
@ -158,27 +174,28 @@ func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) {
// The buildDecl call tree thus resembles the static call graph.
// We mark each node when we start working on it to break cycles.
if !di.started { // break cycle
di.started = true
if di.started {
return // break cycle
}
di.started = true
if isIntrinsicNoReturn(fn) {
di.noReturn = true
}
if di.decl.Body != nil {
di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
if !hasReachableReturn(di.cfg) {
di.noReturn = true
}
}
if di.noReturn {
c.pass.ExportObjectFact(fn, new(noReturn))
}
noreturn := isIntrinsicNoReturn(fn)
// debugging
if false {
log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), di.noReturn)
if di.decl.Body != nil {
di.cfg = cfg.New(di.decl.Body, c.callMayReturn)
if cfginternal.IsNoReturn(di.cfg) {
noreturn = true
}
}
if noreturn {
c.pass.ExportObjectFact(fn, new(noReturn))
c.noReturn[fn] = true
}
// debugging
if false {
log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn)
}
}
// callMayReturn reports whether the called function may return.
@ -201,31 +218,26 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
// Function or method declared in this package?
if di, ok := c.funcDecls[fn]; ok {
c.buildDecl(fn, di)
return !di.noReturn
return !c.noReturn[fn]
}
// Not declared in this package.
// Is there a fact from another package?
return !c.pass.ImportObjectFact(fn, new(noReturn))
if c.pass.ImportObjectFact(fn, new(noReturn)) {
c.noReturn[fn] = true
return false
}
return true
}
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
func hasReachableReturn(g *cfg.CFG) bool {
for _, b := range g.Blocks {
if b.Live && b.Return() != nil {
return true
}
}
return false
}
// isIntrinsicNoReturn reports whether a function intrinsically never
// returns because it stops execution of the calling thread.
// It is the base case in the recursion.
func isIntrinsicNoReturn(fn *types.Func) bool {
// Add functions here as the need arises, but don't allocate memory.
path, name := fn.Pkg().Path(), fn.Name()
return path == "syscall" && (name == "Exit" || name == "ExitProcess" || name == "ExitThread") ||
path == "runtime" && name == "Goexit"
return typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") ||
typesinternal.IsFunctionNamed(fn, "runtime", "Goexit")
}

View file

@ -12,7 +12,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "defers",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Doc: analysisinternal.MustExtractDoc(doc, "defers"),
Doc: analyzerutil.MustExtractDoc(doc, "defers"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/defers",
Run: run,
}

View file

@ -14,7 +14,7 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = `check Go toolchain directives such as //go:debug
@ -86,7 +86,7 @@ func checkGoFile(pass *analysis.Pass, f *ast.File) {
func checkOtherFile(pass *analysis.Pass, filename string) error {
// We cannot use the Go parser, since is not a Go source file.
// Read the raw bytes instead.
content, tf, err := analysisinternal.ReadFile(pass, filename)
content, tf, err := analyzerutil.ReadFile(pass, filename)
if err != nil {
return err
}

View file

@ -12,7 +12,7 @@ import (
"go/types"
"golang.org/x/tools/go/analysis"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)

View file

@ -13,7 +13,7 @@ import (
"unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
const Doc = "report assembly that clobbers the frame pointer before saving it"
@ -98,7 +98,7 @@ func run(pass *analysis.Pass) (any, error) {
}
for _, fname := range sfiles {
content, tf, err := analysisinternal.ReadFile(pass, fname)
content, tf, err := analyzerutil.ReadFile(pass, fname)
if err != nil {
return nil, err
}

View file

@ -17,7 +17,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
)

View file

@ -12,7 +12,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typeparams"
)
@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "ifaceassert",
Doc: analysisinternal.MustExtractDoc(doc, "ifaceassert"),
Doc: analyzerutil.MustExtractDoc(doc, "ifaceassert"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ifaceassert",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -20,9 +20,10 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/diff"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/refactor/inline"
@ -34,7 +35,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "inline",
Doc: analysisinternal.MustExtractDoc(doc, "inline"),
Doc: analyzerutil.MustExtractDoc(doc, "inline"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inline",
Run: run,
FactTypes: []analysis.Fact{
@ -132,8 +133,6 @@ func (a *analyzer) HandleConst(nameIdent, rhsIdent *ast.Ident) {
// inline inlines each static call to an inlinable function
// and each reference to an inlinable constant or type alias.
//
// TODO(adonovan): handle multiple diffs that each add the same import.
func (a *analyzer) inline() {
for cur := range a.root.Preorder((*ast.CallExpr)(nil), (*ast.Ident)(nil)) {
switch n := cur.Node().(type) {
@ -167,6 +166,10 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) {
return // nope
}
if a.withinTestOf(cur, fn) {
return // don't inline a function from within its own test
}
// Inline the call.
content, err := a.readFile(call)
if err != nil {
@ -229,6 +232,44 @@ func (a *analyzer) inlineCall(call *ast.CallExpr, cur inspector.Cursor) {
}
}
// withinTestOf reports whether cur is within a dedicated test
// function for the inlinable target function.
// A call within its dedicated test should not be inlined.
func (a *analyzer) withinTestOf(cur inspector.Cursor, target *types.Func) bool {
curFuncDecl, ok := moreiters.First(cur.Enclosing((*ast.FuncDecl)(nil)))
if !ok {
return false // not in a function
}
funcDecl := curFuncDecl.Node().(*ast.FuncDecl)
if funcDecl.Recv != nil {
return false // not a test func
}
if strings.TrimSuffix(a.pass.Pkg.Path(), "_test") != target.Pkg().Path() {
return false // different package
}
if !strings.HasSuffix(a.pass.Fset.File(funcDecl.Pos()).Name(), "_test.go") {
return false // not a test file
}
// Computed expected SYMBOL portion of "TestSYMBOL_comment"
// for the target symbol.
symbol := target.Name()
if recv := target.Signature().Recv(); recv != nil {
_, named := typesinternal.ReceiverNamed(recv)
symbol = named.Obj().Name() + "_" + symbol
}
// TODO(adonovan): use a proper Test function parser.
fname := funcDecl.Name.Name
for _, pre := range []string{"Test", "Example", "Bench"} {
if fname == pre+symbol || strings.HasPrefix(fname, pre+symbol+"_") {
return true
}
}
return false
}
// If tn is the TypeName of an inlinable alias, suggest inlining its use at cur.
func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
inalias, ok := a.inlinableAliases[tn]
@ -375,8 +416,8 @@ func typenames(t types.Type) []*types.TypeName {
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for i := range t.NumFields() {
visit(t.Field(i).Type())
for field := range t.Fields() {
visit(field.Type())
}
case *types.Signature:
// Ignore the receiver: although it may be present, it has no meaning
@ -389,11 +430,11 @@ func typenames(t types.Type) []*types.TypeName {
visit(t.Params())
visit(t.Results())
case *types.Interface:
for i := range t.NumEmbeddeds() {
visit(t.EmbeddedType(i))
for etyp := range t.EmbeddedTypes() {
visit(etyp)
}
for i := range t.NumExplicitMethods() {
visit(t.ExplicitMethod(i).Type())
for method := range t.ExplicitMethods() {
visit(method.Type())
}
case *types.Tuple:
for v := range t.Variables() {

View file

@ -41,7 +41,7 @@ var Analyzer = &analysis.Analyzer{
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect",
Run: run,
RunDespiteErrors: true,
ResultType: reflect.TypeOf(new(inspector.Inspector)),
ResultType: reflect.TypeFor[*inspector.Inspector](),
}
func run(pass *analysis.Pass) (any, error) {

View file

@ -0,0 +1,17 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package ctrlflowinternal exposes internals of ctrlflow.
// It cannot actually depend on symbols from ctrlflow.
package ctrlflowinternal
import "go/types"
// NoReturn exposes the (*ctrlflow.CFGs).NoReturn method to the buildssa analyzer.
//
// You must link [golang.org/x/tools/go/analysis/passes/ctrlflow] into your
// application for it to be non-nil.
var NoReturn = func(cfgs any, fn *types.Func) bool {
panic("x/tools/go/analysis/passes/ctrlflow is not linked into this application")
}

View file

@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
@ -23,7 +23,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "loopclosure",
Doc: analysisinternal.MustExtractDoc(doc, "loopclosure"),
Doc: analyzerutil.MustExtractDoc(doc, "loopclosure"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/loopclosure",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -55,8 +55,8 @@ func run(pass *analysis.Pass) (any, error) {
switch n := n.(type) {
case *ast.File:
// Only traverse the file if its goversion is strictly before go1.22.
goversion := versions.FileVersion(pass.TypesInfo, n)
return versions.Before(goversion, versions.Go1_22)
return !analyzerutil.FileUsesGoVersion(pass, n, versions.Go1_22)
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
@ -308,12 +308,11 @@ func parallelSubtest(info *types.Info, call *ast.CallExpr) []ast.Stmt {
if !ok {
continue
}
expr := exprStmt.X
if isMethodCall(info, expr, "testing", "T", "Parallel") {
call, _ := expr.(*ast.CallExpr)
if call == nil {
continue
}
call, ok := exprStmt.X.(*ast.CallExpr)
if !ok {
continue
}
if isMethodCall(info, call, "testing", "T", "Parallel") {
x, _ := call.Fun.(*ast.SelectorExpr)
if x == nil {
continue
@ -347,26 +346,6 @@ func unlabel(stmt ast.Stmt) (ast.Stmt, bool) {
}
}
// isMethodCall reports whether expr is a method call of
// <pkgPath>.<typeName>.<method>.
func isMethodCall(info *types.Info, expr ast.Expr, pkgPath, typeName, method string) bool {
call, ok := expr.(*ast.CallExpr)
if !ok {
return false
}
// Check that we are calling a method <method>
f := typeutil.StaticCallee(info, call)
if f == nil || f.Name() != method {
return false
}
recv := f.Type().(*types.Signature).Recv()
if recv == nil {
return false
}
// Check that the receiver is a <pkgPath>.<typeName> or
// *<pkgPath>.<typeName>.
_, named := typesinternal.ReceiverNamed(recv)
return typesinternal.IsTypeNamed(named, pkgPath, typeName)
func isMethodCall(info *types.Info, call *ast.CallExpr, pkgPath, typeName, method string) bool {
return typesinternal.IsMethodNamed(typeutil.Callee(info, call), pkgPath, typeName, method)
}

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "lostcancel",
Doc: analysisinternal.MustExtractDoc(doc, "lostcancel"),
Doc: analyzerutil.MustExtractDoc(doc, "lostcancel"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/lostcancel",
Run: run,
Requires: []*analysis.Analyzer{
@ -316,8 +316,8 @@ outer:
}
func tupleContains(tuple *types.Tuple, v *types.Var) bool {
for i := 0; i < tuple.Len(); i++ {
if tuple.At(i) == v {
for v0 := range tuple.Variables() {
if v0 == v {
return true
}
}

View file

@ -9,29 +9,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/versions"
)
var AnyAnalyzer = &analysis.Analyzer{
Name: "any",
Doc: analysisinternal.MustExtractDoc(doc, "any"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: runAny,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
Name: "any",
Doc: analyzerutil.MustExtractDoc(doc, "any"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: runAny,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#any",
}
// The any pass replaces interface{} with go1.18's 'any'.
func runAny(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.18") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_18) {
for curIface := range curFile.Preorder((*ast.InterfaceType)(nil)) {
iface := curIface.Node().(*ast.InterfaceType)

View file

@ -15,20 +15,19 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var BLoopAnalyzer = &analysis.Analyzer{
Name: "bloop",
Doc: analysisinternal.MustExtractDoc(doc, "bloop"),
Doc: analyzerutil.MustExtractDoc(doc, "bloop"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -45,16 +44,13 @@ var BLoopAnalyzer = &analysis.Analyzer{
// for i := 0; i < b.N; i++ {} => for b.Loop() {}
// for range b.N {}
func bloop(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
if !typesinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
// edits computes the text edits for a matched for/range loop
@ -102,7 +98,7 @@ func bloop(pass *analysis.Pass) (any, error) {
(*ast.ForStmt)(nil),
(*ast.RangeStmt)(nil),
}
for curFile := range filesUsing(inspect, info, "go1.24") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curLoop := range curFile.Preorder(loops...) {
switch n := curLoop.Node().(type) {
case *ast.ForStmt:
@ -189,6 +185,7 @@ func enclosingFunc(c inspector.Cursor) (inspector.Cursor, bool) {
// 2. The only b.N loop in that benchmark function
// - b.Loop() can only be called once per benchmark execution
// - Multiple calls result in "B.Loop called with timer stopped" error
// - Multiple loops may have complex interdependencies that are hard to analyze
func usesBenchmarkNOnce(c inspector.Cursor, info *types.Info) bool {
// Find the enclosing benchmark function
curFunc, ok := enclosingFunc(c)
@ -205,17 +202,14 @@ func usesBenchmarkNOnce(c inspector.Cursor, info *types.Info) bool {
return false
}
// Count b.N references in this benchmark function
// Count all b.N references in this benchmark function (including nested functions)
bnRefCount := 0
filter := []ast.Node{(*ast.SelectorExpr)(nil), (*ast.FuncLit)(nil)}
filter := []ast.Node{(*ast.SelectorExpr)(nil)}
curFunc.Inspect(filter, func(cur inspector.Cursor) bool {
switch n := cur.Node().(type) {
case *ast.FuncLit:
return false // don't descend into nested function literals
case *ast.SelectorExpr:
if n.Sel.Name == "N" && typesinternal.IsPointerToNamed(info.TypeOf(n.X), "testing", "B") {
bnRefCount++
}
sel := cur.Node().(*ast.SelectorExpr)
if sel.Sel.Name == "N" &&
typesinternal.IsPointerToNamed(info.TypeOf(sel.X), "testing", "B") {
bnRefCount++
}
return true
})
@ -240,7 +234,7 @@ func isIncrementLoop(info *types.Info, loop *ast.ForStmt) *types.Var {
if assign, ok := loop.Init.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Rhs) == 1 &&
isZeroIntLiteral(info, assign.Rhs[0]) &&
isZeroIntConst(info, assign.Rhs[0]) &&
is[*ast.IncDecStmt](loop.Post) &&
loop.Post.(*ast.IncDecStmt).Tok == token.INC &&
astutil.EqualSyntax(loop.Post.(*ast.IncDecStmt).X, assign.Lhs[0]) {

View file

@ -176,8 +176,12 @@ This analyzer finds declarations of functions of this form:
and suggests a fix to turn them into inlinable wrappers around
go1.26's built-in new(expr) function:
//go:fix inline
func varOf(x int) *int { return new(x) }
(The directive comment causes the 'inline' analyzer to suggest
that calls to such functions are inlined.)
In addition, this analyzer suggests a fix for each call
to one of the functions before it is transformed, so that
@ -187,9 +191,9 @@ is replaced by:
use(new(123))
(Wrapper functions such as varOf are common when working with Go
Wrapper functions such as varOf are common when working with Go
serialization packages such as for JSON or protobuf, where pointers
are often used to express optionality.)
are often used to express optionality.
# Analyzer omitzero
@ -327,6 +331,44 @@ iterator offered by the same data type:
where x is one of various well-known types in the standard library.
# Analyzer stringscut
stringscut: replace strings.Index etc. with strings.Cut
This analyzer replaces certain patterns of use of [strings.Index] and string slicing by [strings.Cut], added in go1.18.
For example:
idx := strings.Index(s, substr)
if idx >= 0 {
return s[:idx]
}
is replaced by:
before, _, ok := strings.Cut(s, substr)
if ok {
return before
}
And:
idx := strings.Index(s, substr)
if idx >= 0 {
return
}
is replaced by:
found := strings.Contains(s, substr)
if found {
return
}
It also handles variants using [strings.IndexByte] instead of Index, or the bytes package instead of strings.
Fixes are offered only in cases in which there are no potential modifications of the idx, s, or substr expressions between their definition and use.
# Analyzer stringscutprefix
stringscutprefix: replace HasPrefix/TrimPrefix with CutPrefix

View file

@ -14,21 +14,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var errorsastypeAnalyzer = &analysis.Analyzer{
Name: "errorsastype",
Doc: analysisinternal.MustExtractDoc(doc, "errorsastype"),
Doc: analyzerutil.MustExtractDoc(doc, "errorsastype"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#errorsastype",
Requires: []*analysis.Analyzer{generated.Analyzer, typeindexanalyzer.Analyzer},
Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer},
Run: errorsastype,
}
@ -78,8 +78,6 @@ func init() {
//
// - if errors.As(err, myerr) && othercond { ... }
func errorsastype(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@ -97,7 +95,7 @@ func errorsastype(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curDeclStmt)
if !fileUses(info, file, "go1.26") {
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // errors.AsType is too new
}
@ -127,6 +125,8 @@ func errorsastype(pass *analysis.Pass) (any, error) {
errtype := types.TypeString(v.Type(), qual)
// Choose a name for the "ok" variable.
// TODO(adonovan): this pattern also appears in stditerators,
// and is wanted elsewhere; factor.
okName := "ok"
if okVar := lookup(info, curCall, "ok"); okVar != nil {
// The name 'ok' is already declared, but

View file

@ -13,18 +13,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var FmtAppendfAnalyzer = &analysis.Analyzer{
Name: "fmtappendf",
Doc: analysisinternal.MustExtractDoc(doc, "fmtappendf"),
Doc: analyzerutil.MustExtractDoc(doc, "fmtappendf"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -35,8 +34,6 @@ var FmtAppendfAnalyzer = &analysis.Analyzer{
// The fmtappend function replaces []byte(fmt.Sprintf(...)) by
// fmt.Appendf(nil, ...), and similarly for Sprint, Sprintln.
func fmtappendf(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for _, fn := range []types.Object{
index.Object("fmt", "Sprintf"),
@ -50,7 +47,7 @@ func fmtappendf(pass *analysis.Pass) (any, error) {
conv := curCall.Parent().Node().(*ast.CallExpr)
tv := pass.TypesInfo.Types[conv.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) &&
fileUses(pass.TypesInfo, astutil.EnclosingFile(curCall), "go1.19") {
analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
// Have: []byte(fmt.SprintX(...))
// Find "Sprint" identifier.

View file

@ -10,22 +10,18 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/versions"
)
var ForVarAnalyzer = &analysis.Analyzer{
Name: "forvar",
Doc: analysisinternal.MustExtractDoc(doc, "forvar"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: forvar,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
Name: "forvar",
Doc: analyzerutil.MustExtractDoc(doc, "forvar"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: forvar,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#forvar",
}
// forvar offers to fix unnecessary copying of a for variable
@ -39,54 +35,77 @@ var ForVarAnalyzer = &analysis.Analyzer{
// where the two idents are the same,
// and the ident is defined (:=) as a variable in the for statement.
// (Note that this 'fix' does not work for three clause loops
// because the Go specification says "The variable used by each subsequent iteration
// because the Go specfilesUsingGoVersionsays "The variable used by each subsequent iteration
// is declared implicitly before executing the post statement and initialized to the
// value of the previous iteration's variable at that moment.")
//
// Variant: same thing in an IfStmt.Init, when the IfStmt is the sole
// loop body statement:
//
// for _, x := range foo {
// if x := x; cond { ... }
// }
//
// (The restriction is necessary to avoid potential problems arising
// from merging two distinct variables.)
//
// This analyzer is synergistic with stditerators,
// which may create redundant "x := x" statements.
func forvar(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.22") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
loop := curLoop.Node().(*ast.RangeStmt)
if loop.Tok != token.DEFINE {
continue
}
isLoopVarRedecl := func(assign *ast.AssignStmt) bool {
for i, lhs := range assign.Lhs {
if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
(astutil.EqualSyntax(lhs, loop.Key) || astutil.EqualSyntax(lhs, loop.Value))) {
return false
isLoopVarRedecl := func(stmt ast.Stmt) bool {
if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == len(assign.Rhs) {
for i, lhs := range assign.Lhs {
if !(astutil.EqualSyntax(lhs, assign.Rhs[i]) &&
(astutil.EqualSyntax(lhs, loop.Key) ||
astutil.EqualSyntax(lhs, loop.Value))) {
return false
}
}
return true
}
return true
return false
}
// Have: for k, v := range x { stmts }
//
// Delete the prefix of stmts that are
// of the form k := k; v := v; k, v := k, v; v, k := v, k.
for _, stmt := range loop.Body.List {
if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == len(assign.Rhs) &&
isLoopVarRedecl(assign) {
curStmt, _ := curLoop.FindNode(stmt)
edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
End: stmt.End(),
Message: "copying variable is unneeded",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove unneeded redeclaration",
TextEdits: edits,
}},
})
}
if isLoopVarRedecl(stmt) {
// { x := x; ... }
// ------
} else if ifstmt, ok := stmt.(*ast.IfStmt); ok &&
ifstmt.Init != nil &&
len(loop.Body.List) == 1 && // must be sole statement in loop body
isLoopVarRedecl(ifstmt.Init) {
// if x := x; cond {
// ------
stmt = ifstmt.Init
} else {
break // stop at first other statement
}
curStmt, _ := curLoop.FindNode(stmt)
edits := refactor.DeleteStmt(pass.Fset.File(stmt.Pos()), curStmt)
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(),
End: stmt.End(),
Message: "copying variable is unneeded",
SuggestedFixes: []analysis.SuggestedFix{{
Message: "Remove unneeded redeclaration",
TextEdits: edits,
}},
})
}
}
}
}

View file

@ -15,23 +15,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
var MapsLoopAnalyzer = &analysis.Analyzer{
Name: "mapsloop",
Doc: analysisinternal.MustExtractDoc(doc, "mapsloop"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: mapsloop,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
Name: "mapsloop",
Doc: analyzerutil.MustExtractDoc(doc, "mapsloop"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: mapsloop,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#mapsloop",
}
// The mapsloop pass offers to simplify a loop of map insertions:
@ -55,8 +52,6 @@ var MapsLoopAnalyzer = &analysis.Analyzer{
// m = make(M)
// m = M{}
func mapsloop(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "maps", "bytes", "runtime") {
@ -223,8 +218,7 @@ func mapsloop(pass *analysis.Pass) (any, error) {
}
// Find all range loops around m[k] = v.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.23") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {

View file

@ -15,19 +15,18 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var MinMaxAnalyzer = &analysis.Analyzer{
Name: "minmax",
Doc: analysisinternal.MustExtractDoc(doc, "minmax"),
Doc: analyzerutil.MustExtractDoc(doc, "minmax"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -56,8 +55,6 @@ var MinMaxAnalyzer = &analysis.Analyzer{
// - "x := a" or "x = a" or "var x = a" in pattern 2
// - "x < b" or "a < b" in pattern 2
func minmax(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Check for user-defined min/max functions that can be removed
checkUserDefinedMinMax(pass)
@ -201,8 +198,7 @@ func minmax(pass *analysis.Pass) (any, error) {
// Find all "if a < b { lhs = rhs }" statements.
info := pass.TypesInfo
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for curFile := range filesUsing(inspect, info, "go1.21") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
astFile := curFile.Node().(*ast.File)
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)

View file

@ -16,15 +16,15 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
//go:embed doc.go
@ -48,6 +48,7 @@ var Suite = []*analysis.Analyzer{
// SlicesDeleteAnalyzer, // not nil-preserving!
SlicesSortAnalyzer,
stditeratorsAnalyzer,
stringscutAnalyzer,
StringsCutPrefixAnalyzer,
StringsSeqAnalyzer,
StringsBuilderAnalyzer,
@ -57,18 +58,6 @@ var Suite = []*analysis.Analyzer{
// -- helpers --
// skipGenerated decorates pass.Report to suppress diagnostics in generated files.
func skipGenerated(pass *analysis.Pass) {
report := pass.Report
pass.Report = func(diag analysis.Diagnostic) {
generated := pass.ResultOf[generated.Analyzer].(*generated.Result)
if generated.IsGenerated(diag.Pos) {
return // skip
}
report(diag)
}
}
// formatExprs formats a comma-separated list of expressions.
func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
var buf strings.Builder
@ -81,8 +70,8 @@ func formatExprs(fset *token.FileSet, exprs []ast.Expr) string {
return buf.String()
}
// isZeroIntLiteral reports whether e is an integer whose value is 0.
func isZeroIntLiteral(info *types.Info, e ast.Expr) bool {
// isZeroIntConst reports whether e is an integer whose value is 0.
func isZeroIntConst(info *types.Info, e ast.Expr) bool {
return isIntLiteral(info, e, 0)
}
@ -91,36 +80,28 @@ func isIntLiteral(info *types.Info, e ast.Expr, n int64) bool {
return info.Types[e].Value == constant.MakeInt64(n)
}
// filesUsing returns a cursor for each *ast.File in the inspector
// filesUsingGoVersion returns a cursor for each *ast.File in the inspector
// that uses at least the specified version of Go (e.g. "go1.24").
//
// The pass's analyzer must require [inspect.Analyzer].
//
// TODO(adonovan): opt: eliminate this function, instead following the
// approach of [fmtappendf], which uses typeindex and [fileUses].
// See "Tip" at [fileUses] for motivation.
func filesUsing(inspect *inspector.Inspector, info *types.Info, version string) iter.Seq[inspector.Cursor] {
// approach of [fmtappendf], which uses typeindex and
// [analyzerutil.FileUsesGoVersion]; see "Tip" documented at the
// latter function for motivation.
func filesUsingGoVersion(pass *analysis.Pass, version string) iter.Seq[inspector.Cursor] {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
return func(yield func(inspector.Cursor) bool) {
for curFile := range inspect.Root().Children() {
file := curFile.Node().(*ast.File)
if !versions.Before(info.FileVersions[file], version) && !yield(curFile) {
if analyzerutil.FileUsesGoVersion(pass, file, version) && !yield(curFile) {
break
}
}
}
}
// fileUses reports whether the specified file uses at least the
// specified version of Go (e.g. "go1.24").
//
// Tip: we recommend using this check "late", just before calling
// pass.Report, rather than "early" (when entering each ast.File, or
// each candidate node of interest, during the traversal), because the
// operation is not free, yet is not a highly selective filter: the
// fraction of files that pass most version checks is high and
// increases over time.
func fileUses(info *types.Info, file *ast.File, version string) bool {
return !versions.Before(info.FileVersions[file], version)
}
// within reports whether the current pass is analyzing one of the
// specified standard packages or their dependencies.
func within(pass *analysis.Pass, pkgs ...string) bool {

View file

@ -17,13 +17,14 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/versions"
)
var NewExprAnalyzer = &analysis.Analyzer{
Name: "newexpr",
Doc: analysisinternal.MustExtractDoc(doc, "newexpr"),
Doc: analyzerutil.MustExtractDoc(doc, "newexpr"),
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#newexpr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -60,7 +61,7 @@ func run(pass *analysis.Pass) (any, error) {
// Check file version.
file := astutil.EnclosingFile(curFuncDecl)
if !fileUses(info, file, "go1.26") {
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
@ -87,25 +88,18 @@ func run(pass *analysis.Pass) (any, error) {
}
}
// Disabled until we resolve https://go.dev/issue/75726
// (Go version skew between caller and callee in inliner.)
// TODO(adonovan): fix and reenable.
// Add a //go:fix inline annotation, if not already present.
//
// Also, restore these lines to our section of doc.go:
// //go:fix inline
// ...
// (The directive comment causes the inline analyzer to suggest
// that calls to such functions are inlined.)
if false {
// Add a //go:fix inline annotation, if not already present.
// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
edits = append(edits, analysis.TextEdit{
Pos: decl.Pos(),
End: decl.Pos(),
NewText: []byte("//go:fix inline\n"),
})
}
// The inliner will not inline a newer callee body into an
// older Go file; see https://go.dev/issue/75726.
//
// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
edits = append(edits, analysis.TextEdit{
Pos: decl.Pos(),
End: decl.Pos(),
NewText: []byte("//go:fix inline\n"),
})
}
if len(edits) > 0 {
@ -140,7 +134,7 @@ func run(pass *analysis.Pass) (any, error) {
// Check file version.
file := astutil.EnclosingFile(curCall)
if !fileUses(info, file, "go1.26") {
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}

View file

@ -12,21 +12,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/versions"
)
var OmitZeroAnalyzer = &analysis.Analyzer{
Name: "omitzero",
Doc: analysisinternal.MustExtractDoc(doc, "omitzero"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: omitzero,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
Name: "omitzero",
Doc: analyzerutil.MustExtractDoc(doc, "omitzero"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: omitzero,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#omitzero",
}
func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Field) {
@ -48,25 +44,20 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
// No omitempty in json tag
return
}
omitEmptyPos, omitEmptyEnd, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3])
omitEmpty, err := astutil.RangeInStringLiteral(curField.Tag, match[2], match[3])
if err != nil {
return
}
removePos, removeEnd := omitEmptyPos, omitEmptyEnd
var remove analysis.Range = omitEmpty
jsonTag := reflect.StructTag(tagconv).Get("json")
if jsonTag == ",omitempty" {
// Remove the entire struct tag if json is the only package used
if match[1]-match[0] == len(tagconv) {
removePos = curField.Tag.Pos()
removeEnd = curField.Tag.End()
remove = curField.Tag
} else {
// Remove the json tag if omitempty is the only field
removePos, err = astutil.PosInStringLiteral(curField.Tag, match[0])
if err != nil {
return
}
removeEnd, err = astutil.PosInStringLiteral(curField.Tag, match[1])
remove, err = astutil.RangeInStringLiteral(curField.Tag, match[0], match[1])
if err != nil {
return
}
@ -81,8 +72,8 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
Message: "Remove redundant omitempty tag",
TextEdits: []analysis.TextEdit{
{
Pos: removePos,
End: removeEnd,
Pos: remove.Pos(),
End: remove.End(),
},
},
},
@ -90,8 +81,8 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
Message: "Replace omitempty with omitzero (behavior change)",
TextEdits: []analysis.TextEdit{
{
Pos: omitEmptyPos,
End: omitEmptyEnd,
Pos: omitEmpty.Pos(),
End: omitEmpty.End(),
NewText: []byte(",omitzero"),
},
},
@ -100,18 +91,14 @@ func checkOmitEmptyField(pass *analysis.Pass, info *types.Info, curField *ast.Fi
}
// The omitzero pass searches for instances of "omitempty" in a json field tag on a
// struct. Since "omitempty" does not have any effect when applied to a struct field,
// struct. Since "omitfilesUsingGoVersions not have any effect when applied to a struct field,
// it suggests either deleting "omitempty" or replacing it with "omitzero", which
// correctly excludes structs from a json encoding.
func omitzero(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info := pass.TypesInfo
for curFile := range filesUsing(inspect, info, "go1.24") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
checkOmitEmptyField(pass, info, curField)
checkOmitEmptyField(pass, pass.TypesInfo, curField)
}
}
}

View file

@ -10,13 +10,14 @@ import (
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/versions"
)
var plusBuildAnalyzer = &analysis.Analyzer{
Name: "plusbuild",
Doc: analysisinternal.MustExtractDoc(doc, "plusbuild"),
Doc: analyzerutil.MustExtractDoc(doc, "plusbuild"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#plusbuild",
Run: plusbuild,
}
@ -28,7 +29,7 @@ func init() {
func plusbuild(pass *analysis.Pass) (any, error) {
check := func(f *ast.File) {
if !fileUses(pass.TypesInfo, f, "go1.18") {
if !analyzerutil.FileUsesGoVersion(pass, f, versions.Go1_18) {
return
}

View file

@ -15,19 +15,18 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var RangeIntAnalyzer = &analysis.Analyzer{
Name: "rangeint",
Doc: analysisinternal.MustExtractDoc(doc, "rangeint"),
Doc: analyzerutil.MustExtractDoc(doc, "rangeint"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -66,21 +65,19 @@ var RangeIntAnalyzer = &analysis.Analyzer{
// - a constant; or
// - len(s), where s has the above properties.
func rangeint(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
info = pass.TypesInfo
typeindex = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
)
info := pass.TypesInfo
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
typeindex := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
for curFile := range filesUsing(inspect, info, "go1.22") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_22) {
nextLoop:
for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
loop := curLoop.Node().(*ast.ForStmt)
if init, ok := loop.Init.(*ast.AssignStmt); ok &&
isSimpleAssign(init) &&
is[*ast.Ident](init.Lhs[0]) &&
isZeroIntLiteral(info, init.Rhs[0]) {
isZeroIntConst(info, init.Rhs[0]) {
// Have: for i = 0; ... (or i := 0)
index := init.Lhs[0].(*ast.Ident)

View file

@ -14,9 +14,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@ -26,9 +25,8 @@ import (
var ReflectTypeForAnalyzer = &analysis.Analyzer{
Name: "reflecttypefor",
Doc: analysisinternal.MustExtractDoc(doc, "reflecttypefor"),
Doc: analyzerutil.MustExtractDoc(doc, "reflecttypefor"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -37,8 +35,6 @@ var ReflectTypeForAnalyzer = &analysis.Analyzer{
}
func reflecttypefor(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@ -89,7 +85,7 @@ func reflecttypefor(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curCall)
if versions.Before(info.FileVersions[file], "go1.22") {
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_22) {
continue // TypeFor requires go1.22
}
tokFile := pass.Fset.File(file.Pos())

View file

@ -13,25 +13,21 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default.
var AppendClippedAnalyzer = &analysis.Analyzer{
Name: "appendclipped",
Doc: analysisinternal.MustExtractDoc(doc, "appendclipped"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: appendclipped,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
Name: "appendclipped",
Doc: analyzerutil.MustExtractDoc(doc, "appendclipped"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: appendclipped,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#appendclipped",
}
// The appendclipped pass offers to simplify a tower of append calls:
@ -59,8 +55,6 @@ var AppendClippedAnalyzer = &analysis.Analyzer{
// The fix does not always preserve nilness the of base slice when the
// addends (a, b, c) are all empty (see #73557).
func appendclipped(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "bytes", "runtime") {
@ -205,8 +199,7 @@ func appendclipped(pass *analysis.Pass) (any, error) {
skip := make(map[*ast.CallExpr]bool)
// Visit calls of form append(x, y...).
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
for curFile := range filesUsing(inspect, info, "go1.21") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
@ -266,7 +259,7 @@ func clippedSlice(info *types.Info, e ast.Expr) (res ast.Expr, empty bool) {
// x[:0:0], x[:len(x):len(x)], x[:k:k]
if e.Slice3 && e.High != nil && e.Max != nil && astutil.EqualSyntax(e.High, e.Max) { // x[:k:k]
res = e
empty = isZeroIntLiteral(info, e.High) // x[:0:0]
empty = isZeroIntConst(info, e.High) // x[:0:0]
if call, ok := e.High.(*ast.CallExpr); ok &&
typeutil.Callee(info, call) == builtinLen &&
astutil.EqualSyntax(call.Args[0], e.X) {

View file

@ -14,20 +14,19 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var SlicesContainsAnalyzer = &analysis.Analyzer{
Name: "slicescontains",
Doc: analysisinternal.MustExtractDoc(doc, "slicescontains"),
Doc: analyzerutil.MustExtractDoc(doc, "slicescontains"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -66,8 +65,6 @@ var SlicesContainsAnalyzer = &analysis.Analyzer{
// TODO(adonovan): Add a check that needle/predicate expression from
// if-statement has no effects. Now the program behavior may change.
func slicescontains(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
@ -75,9 +72,8 @@ func slicescontains(pass *analysis.Pass) (any, error) {
}
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
)
// check is called for each RangeStmt of this form:
@ -312,7 +308,7 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// Special case:
// prev="lhs = false" body={ lhs = true; break }
// => lhs = slices.Contains(...) (or negation)
// => lhs = slices.Contains(...) (or its negation)
if assign, ok := body.List[0].(*ast.AssignStmt); ok &&
len(body.List) == 2 &&
assign.Tok == token.ASSIGN &&
@ -320,13 +316,12 @@ func slicescontains(pass *analysis.Pass) (any, error) {
len(assign.Rhs) == 1 {
// Have: body={ lhs = rhs; break }
if prevAssign, ok := prevStmt.(*ast.AssignStmt); ok &&
len(prevAssign.Lhs) == 1 &&
len(prevAssign.Rhs) == 1 &&
astutil.EqualSyntax(prevAssign.Lhs[0], assign.Lhs[0]) &&
is[*ast.Ident](assign.Rhs[0]) &&
info.Uses[assign.Rhs[0].(*ast.Ident)] == builtinTrue {
isTrueOrFalse(info, assign.Rhs[0]) ==
-isTrueOrFalse(info, prevAssign.Rhs[0]) {
// Have:
// lhs = false
@ -336,15 +331,14 @@ func slicescontains(pass *analysis.Pass) (any, error) {
//
// TODO(adonovan):
// - support "var lhs bool = false" and variants.
// - support negation.
// Both these variants seem quite significant.
// - allow the break to be omitted.
neg := cond(isTrueOrFalse(info, assign.Rhs[0]) < 0, "!", "")
report([]analysis.TextEdit{
// Replace "rhs" of previous assignment by slices.Contains(...)
// Replace "rhs" of previous assignment by [!]slices.Contains(...)
{
Pos: prevAssign.Rhs[0].Pos(),
End: prevAssign.Rhs[0].End(),
NewText: []byte(contains),
NewText: []byte(neg + contains),
},
// Delete the loop and preceding space.
{
@ -388,7 +382,7 @@ func slicescontains(pass *analysis.Pass) (any, error) {
}
}
for curFile := range filesUsing(inspect, info, "go1.21") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
@ -420,13 +414,19 @@ func slicescontains(pass *analysis.Pass) (any, error) {
// isReturnTrueOrFalse returns nonzero if stmt returns true (+1) or false (-1).
func isReturnTrueOrFalse(info *types.Info, stmt ast.Stmt) int {
if ret, ok := stmt.(*ast.ReturnStmt); ok && len(ret.Results) == 1 {
if id, ok := ret.Results[0].(*ast.Ident); ok {
switch info.Uses[id] {
case builtinTrue:
return +1
case builtinFalse:
return -1
}
return isTrueOrFalse(info, ret.Results[0])
}
return 0
}
// isTrueOrFalse returns nonzero if expr is literally true (+1) or false (-1).
func isTrueOrFalse(info *types.Info, expr ast.Expr) int {
if id, ok := expr.(*ast.Ident); ok {
switch info.Uses[id] {
case builtinTrue:
return +1
case builtinFalse:
return -1
}
}
return 0

View file

@ -12,24 +12,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default (not nil-preserving).
var SlicesDeleteAnalyzer = &analysis.Analyzer{
Name: "slicesdelete",
Doc: analysisinternal.MustExtractDoc(doc, "slicesdelete"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
},
Run: slicesdelete,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
Name: "slicesdelete",
Doc: analyzerutil.MustExtractDoc(doc, "slicesdelete"),
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: slicesdelete,
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesdelete",
}
// The slicesdelete pass attempts to replace instances of append(s[:i], s[i+k:]...)
@ -37,15 +33,12 @@ var SlicesDeleteAnalyzer = &analysis.Analyzer{
// Other variations that will also have suggested replacements include:
// append(s[:i-1], s[i:]...) and append(s[:i+k1], s[i+k2:]) where k2 > k1.
func slicesdelete(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "runtime") {
return nil, nil
}
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info := pass.TypesInfo
report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
insert := func(pos token.Pos, text string) analysis.TextEdit {
@ -55,7 +48,7 @@ func slicesdelete(pass *analysis.Pass) (any, error) {
return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
}
isIntShadowed := func() bool {
scope := pass.TypesInfo.Scopes[file].Innermost(call.Lparen)
scope := info.Scopes[file].Innermost(call.Lparen)
if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
return true // int type is shadowed
}
@ -130,7 +123,7 @@ func slicesdelete(pass *analysis.Pass) (any, error) {
}},
})
}
for curFile := range filesUsing(inspect, info, "go1.21") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)

View file

@ -11,20 +11,19 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
// (Not to be confused with go/analysis/passes/sortslice.)
var SlicesSortAnalyzer = &analysis.Analyzer{
Name: "slicessort",
Doc: analysisinternal.MustExtractDoc(doc, "slicessort"),
Doc: analyzerutil.MustExtractDoc(doc, "slicessort"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -52,8 +51,6 @@ var SlicesSortAnalyzer = &analysis.Analyzer{
// - sort.Sort(x) where x has a named slice type whose Less method is the natural order.
// -> sort.Slice(x)
func slicessort(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "slices", "sort", "runtime") {
@ -87,7 +84,7 @@ func slicessort(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curCall)
if isIndex(compare.X, i) && isIndex(compare.Y, j) &&
fileUses(info, file, "go1.21") {
analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
// Have: sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
prefix, importEdits := refactor.AddImport(

View file

@ -14,9 +14,8 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
@ -26,9 +25,8 @@ import (
var stditeratorsAnalyzer = &analysis.Analyzer{
Name: "stditerators",
Doc: analysisinternal.MustExtractDoc(doc, "stditerators"),
Doc: analyzerutil.MustExtractDoc(doc, "stditerators"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stditerators,
@ -89,8 +87,6 @@ var stditeratorsTable = [...]struct {
// iterator for that reason? We don't want to go fix to
// undo optimizations. Do we need a suppression mechanism?
func stditerators(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@ -116,6 +112,10 @@ func stditerators(pass *analysis.Pass) (any, error) {
//
// for ... { e := x.At(i); use(e) }
//
// or
//
// for ... { if e := x.At(i); cond { use(e) } }
//
// then chooseName prefers the name e and additionally
// returns the var's symbol. We'll transform this to:
//
@ -124,10 +124,11 @@ func stditerators(pass *analysis.Pass) (any, error) {
// which leaves a redundant assignment that a
// subsequent 'forvar' pass will eliminate.
chooseName := func(curBody inspector.Cursor, x ast.Expr, i *types.Var) (string, *types.Var) {
// Is body { elem := x.At(i); ... } ?
body := curBody.Node().(*ast.BlockStmt)
if len(body.List) > 0 {
if assign, ok := body.List[0].(*ast.AssignStmt); ok &&
// isVarAssign reports whether stmt has the form v := x.At(i)
// and returns the variable if so.
isVarAssign := func(stmt ast.Stmt) *types.Var {
if assign, ok := stmt.(*ast.AssignStmt); ok &&
assign.Tok == token.DEFINE &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
@ -138,15 +139,47 @@ func stditerators(pass *analysis.Pass) (any, error) {
astutil.EqualSyntax(ast.Unparen(call.Fun).(*ast.SelectorExpr).X, x) &&
is[*ast.Ident](call.Args[0]) &&
info.Uses[call.Args[0].(*ast.Ident)] == i {
// Have: { elem := x.At(i); ... }
// Have: elem := x.At(i)
id := assign.Lhs[0].(*ast.Ident)
return id.Name, info.Defs[id].(*types.Var)
return info.Defs[id].(*types.Var)
}
}
return nil
}
body := curBody.Node().(*ast.BlockStmt)
if len(body.List) > 0 {
// Is body { elem := x.At(i); ... } ?
if v := isVarAssign(body.List[0]); v != nil {
return v.Name(), v
}
// Or { if elem := x.At(i); cond { ... } } ?
if ifstmt, ok := body.List[0].(*ast.IfStmt); ok && ifstmt.Init != nil {
if v := isVarAssign(ifstmt.Init); v != nil {
return v.Name(), v
}
}
}
loop := curBody.Parent().Node()
return refactor.FreshName(info.Scopes[loop], loop.Pos(), row.elemname), nil
// Choose a fresh name only if
// (a) the preferred name is already declared here, and
// (b) there are references to it from the loop body.
// TODO(adonovan): this pattern also appears in errorsastype,
// and is wanted elsewhere; factor.
name := row.elemname
if v := lookup(info, curBody, name); v != nil {
// is it free in body?
for curUse := range index.Uses(v) {
if curBody.Contains(curUse) {
name = refactor.FreshName(info.Scopes[loop], loop.Pos(), name)
break
}
}
}
return name, nil
}
// Process each call of x.Len().
@ -191,7 +224,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
}
// Have: for i := 0; i < x.Len(); i++ { ... }.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rng = analysisinternal.Range(loop.For, loop.Post.End())
rng = astutil.RangeOf(loop.For, loop.Post.End())
indexVar = v
curBody = curFor.ChildAt(edge.ForStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
@ -234,7 +267,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
// Have: for i := range x.Len() { ... }
// ~~~~~~~~~~~~~
rng = analysisinternal.Range(loop.Range, loop.X.End())
rng = astutil.RangeOf(loop.Range, loop.X.End())
indexVar = info.Defs[id].(*types.Var)
curBody = curRange.ChildAt(edge.RangeStmt_Body, -1)
elem, elemVar = chooseName(curBody, lenSel.X, indexVar)
@ -313,7 +346,7 @@ func stditerators(pass *analysis.Pass) (any, error) {
// may be somewhat expensive.)
if v, ok := methodGoVersion(row.pkgpath, row.typename, row.itermethod); !ok {
panic("no version found")
} else if file := astutil.EnclosingFile(curLenCall); !fileUses(info, file, v.String()) {
} else if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curLenCall), v.String()) {
continue nextCall
}

View file

@ -15,9 +15,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
@ -26,9 +25,8 @@ import (
var StringsBuilderAnalyzer = &analysis.Analyzer{
Name: "stringsbuilder",
Doc: analysisinternal.MustExtractDoc(doc, "stringsbuilder"),
Doc: analyzerutil.MustExtractDoc(doc, "stringsbuilder"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -38,8 +36,6 @@ var StringsBuilderAnalyzer = &analysis.Analyzer{
// stringsbuilder replaces string += string in a loop by strings.Builder.
func stringsbuilder(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
// Skip the analyzer in packages where its
// fixes would create an import cycle.
if within(pass, "strings", "runtime") {

View file

@ -0,0 +1,580 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modernize
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"iter"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/goplsexport"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var stringscutAnalyzer = &analysis.Analyzer{
Name: "stringscut",
Doc: analyzerutil.MustExtractDoc(doc, "stringscut"),
Requires: []*analysis.Analyzer{
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
Run: stringscut,
URL: "https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize#stringscut",
}
func init() {
// Export to gopls until this is a published modernizer.
goplsexport.StringsCutModernizer = stringscutAnalyzer
}
// stringscut offers a fix to replace an occurrence of strings.Index{,Byte} with
// strings.{Cut,Contains}, and similar fixes for functions in the bytes package.
// Consider some candidate for replacement i := strings.Index(s, substr).
// The following must hold for a replacement to occur:
//
// 1. All instances of i and s must be in one of these forms.
// Binary expressions:
// (a): establishing that i < 0: e.g.: i < 0, 0 > i, i == -1, -1 == i
// (b): establishing that i > -1: e.g.: i >= 0, 0 <= i, i == 0, 0 == i
//
// Slice expressions:
// a: s[:i], s[0:i]
// b: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
//
// 2. There can be no uses of s, substr, or i where they are
// potentially modified (i.e. in assignments, or function calls with unknown side
// effects).
//
// Then, the replacement involves the following substitutions:
//
// 1. Replace "i := strings.Index(s, substr)" with "before, after, ok := strings.Cut(s, substr)"
//
// 2. Replace instances of binary expressions (a) with !ok and binary expressions (b) with ok.
//
// 3. Replace slice expressions (a) with "before" and slice expressions (b) with after.
//
// 4. The assignments to before, after, and ok may use the blank identifier "_" if they are unused.
//
// For example:
//
// i := strings.Index(s, substr)
// if i >= 0 {
// use(s[:i], s[i+len(substr):])
// }
//
// Would become:
//
// before, after, ok := strings.Cut(s, substr)
// if ok {
// use(before, after)
// }
//
// If the condition involving `i` establishes that i > -1, then we replace it with
// `if ok“. Variants listed above include i >= 0, i > 0, and i == 0.
// If the condition is negated (e.g. establishes `i < 0`), we use `if !ok` instead.
// If the slices of `s` match `s[:i]` or `s[i+len(substr):]` or their variants listed above,
// then we replace them with before and after.
//
// When the index `i` is used only to check for the presence of the substring or byte slice,
// the suggested fix uses Contains() instead of Cut.
//
// For example:
//
// i := strings.Index(s, substr)
// if i >= 0 {
// return
// }
//
// Would become:
//
// found := strings.Contains(s, substr)
// if found {
// return
// }
func stringscut(pass *analysis.Pass) (any, error) {
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsIndex = index.Object("strings", "Index")
stringsIndexByte = index.Object("strings", "IndexByte")
bytesIndex = index.Object("bytes", "Index")
bytesIndexByte = index.Object("bytes", "IndexByte")
)
for _, obj := range []types.Object{
stringsIndex,
stringsIndexByte,
bytesIndex,
bytesIndexByte,
} {
// (obj may be nil)
nextcall:
for curCall := range index.Calls(obj) {
// Check file version.
if !analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(curCall), versions.Go1_18) {
continue // strings.Index not available in this file
}
indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
obj := typeutil.Callee(info, indexCall)
if obj == nil {
continue
}
var iIdent *ast.Ident // defining identifier of i var
switch ek, idx := curCall.ParentEdge(); ek {
case edge.ValueSpec_Values:
// Have: var i = strings.Index(...)
curName := curCall.Parent().ChildAt(edge.ValueSpec_Names, idx)
iIdent = curName.Node().(*ast.Ident)
case edge.AssignStmt_Rhs:
// Have: i := strings.Index(...)
// (Must be i's definition.)
curLhs := curCall.Parent().ChildAt(edge.AssignStmt_Lhs, idx)
iIdent, _ = curLhs.Node().(*ast.Ident) // may be nil
}
if iIdent == nil {
continue
}
// Inv: iIdent is i's definition. The following would be skipped: 'var i int; i = strings.Index(...)'
// Get uses of i.
iObj := info.ObjectOf(iIdent)
if iObj == nil {
continue
}
var (
s = indexCall.Args[0]
substr = indexCall.Args[1]
)
// Check that there are no statements that alter the value of s
// or substr after the call to Index().
if !indexArgValid(info, index, s, indexCall.Pos()) ||
!indexArgValid(info, index, substr, indexCall.Pos()) {
continue nextcall
}
// Next, examine all uses of i. If the only uses are of the
// forms mentioned above (e.g. i < 0, i >= 0, s[:i] and s[i +
// len(substr)]), then we can replace the call to Index()
// with a call to Cut() and use the returned ok, before,
// and after variables accordingly.
lessZero, greaterNegOne, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr)
// Either there are no uses of before, after, or ok, or some use
// of i does not match our criteria - don't suggest a fix.
if lessZero == nil && greaterNegOne == nil && beforeSlice == nil && afterSlice == nil {
continue
}
// If the only uses are ok and !ok, don't suggest a Cut() fix - these should be using Contains()
isContains := (len(lessZero) > 0 || len(greaterNegOne) > 0) && len(beforeSlice) == 0 && len(afterSlice) == 0
scope := iObj.Parent()
var (
// TODO(adonovan): avoid FreshName when not needed; see errorsastype.
okVarName = refactor.FreshName(scope, iIdent.Pos(), "ok")
beforeVarName = refactor.FreshName(scope, iIdent.Pos(), "before")
afterVarName = refactor.FreshName(scope, iIdent.Pos(), "after")
foundVarName = refactor.FreshName(scope, iIdent.Pos(), "found") // for Contains()
)
// If there will be no uses of ok, before, or after, use the
// blank identifier instead.
if len(lessZero) == 0 && len(greaterNegOne) == 0 {
okVarName = "_"
}
if len(beforeSlice) == 0 {
beforeVarName = "_"
}
if len(afterSlice) == 0 {
afterVarName = "_"
}
var edits []analysis.TextEdit
replace := func(exprs []ast.Expr, new string) {
for _, expr := range exprs {
edits = append(edits, analysis.TextEdit{
Pos: expr.Pos(),
End: expr.End(),
NewText: []byte(new),
})
}
}
// Get the ident for the call to strings.Index, which could just be
// "Index" if the strings package is dot imported.
indexCallId := typesinternal.UsedIdent(info, indexCall.Fun)
replacedFunc := "Cut"
if isContains {
replacedFunc = "Contains"
replace(lessZero, "!"+foundVarName) // idx < 0 -> !found
replace(greaterNegOne, foundVarName) // idx > -1 -> found
// Replace the assignment with found, and replace the call to
// Index or IndexByte with a call to Contains.
// i := strings.Index (...)
// ----- --------
// found := strings.Contains(...)
edits = append(edits, analysis.TextEdit{
Pos: iIdent.Pos(),
End: iIdent.End(),
NewText: []byte(foundVarName),
}, analysis.TextEdit{
Pos: indexCallId.Pos(),
End: indexCallId.End(),
NewText: []byte("Contains"),
})
} else {
replace(lessZero, "!"+okVarName) // idx < 0 -> !ok
replace(greaterNegOne, okVarName) // idx > -1 -> ok
replace(beforeSlice, beforeVarName) // s[:idx] -> before
replace(afterSlice, afterVarName) // s[idx+k:] -> after
// Replace the assignment with before, after, ok, and replace
// the call to Index or IndexByte with a call to Cut.
// i := strings.Index(...)
// ----------------- -----
// before, after, ok := strings.Cut (...)
edits = append(edits, analysis.TextEdit{
Pos: iIdent.Pos(),
End: iIdent.End(),
NewText: fmt.Appendf(nil, "%s, %s, %s", beforeVarName, afterVarName, okVarName),
}, analysis.TextEdit{
Pos: indexCallId.Pos(),
End: indexCallId.End(),
NewText: []byte("Cut"),
})
}
// Calls to IndexByte have a byte as their second arg, which
// must be converted to a string or []byte to be a valid arg for Cut/Contains.
if obj.Name() == "IndexByte" {
switch obj.Pkg().Name() {
case "strings":
searchByteVal := info.Types[substr].Value
if searchByteVal == nil {
// substr is a variable, e.g. substr := byte('b')
// use string(substr)
edits = append(edits, []analysis.TextEdit{
{
Pos: substr.Pos(),
NewText: []byte("string("),
},
{
Pos: substr.End(),
NewText: []byte(")"),
},
}...)
} else {
// substr is a byte constant
val, _ := constant.Int64Val(searchByteVal) // inv: must be a valid byte
// strings.Cut/Contains requires a string, so convert byte literal to string literal; e.g. 'a' -> "a", 55 -> "7"
edits = append(edits, analysis.TextEdit{
Pos: substr.Pos(),
End: substr.End(),
NewText: strconv.AppendQuote(nil, string(byte(val))),
})
}
case "bytes":
// bytes.Cut/Contains requires a []byte, so wrap substr in a []byte{}
edits = append(edits, []analysis.TextEdit{
{
Pos: substr.Pos(),
NewText: []byte("[]byte{"),
},
{
Pos: substr.End(),
NewText: []byte("}"),
},
}...)
}
}
pass.Report(analysis.Diagnostic{
Pos: indexCall.Fun.Pos(),
End: indexCall.Fun.End(),
Message: fmt.Sprintf("%s.%s can be simplified using %s.%s",
obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
Category: "stringscut",
SuggestedFixes: []analysis.SuggestedFix{{
Message: fmt.Sprintf("Simplify %s.%s call using %s.%s", obj.Pkg().Name(), obj.Name(), obj.Pkg().Name(), replacedFunc),
TextEdits: edits,
}},
})
}
}
return nil, nil
}
// indexArgValid reports whether expr is a valid strings.Index(_, _) arg
// for the transformation. An arg is valid iff it is:
// - constant;
// - a local variable with no modifying uses after the Index() call; or
// - []byte(x) where x is also valid by this definition.
// All other expressions are assumed not referentially transparent,
// so we cannot be sure that all uses are safe to replace.
func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afterPos token.Pos) bool {
tv := info.Types[expr]
if tv.Value != nil {
return true // constant
}
switch expr := expr.(type) {
case *ast.CallExpr:
return types.Identical(tv.Type, byteSliceType) &&
indexArgValid(info, index, expr.Args[0], afterPos) // check s in []byte(s)
case *ast.Ident:
sObj := info.Uses[expr]
sUses := index.Uses(sObj)
return !hasModifyingUses(info, sUses, afterPos)
default:
// For now, skip instances where s or substr are not
// identifers, basic lits, or call expressions of the form
// []byte(s).
// TODO(mkalil): Handle s and substr being expressions like ptr.field[i].
// From adonovan: We'd need to analyze s and substr to see
// whether they are referentially transparent, and if not,
// analyze all code between declaration and use and see if
// there are statements or expressions with potential side
// effects.
return false
}
}
// checkIdxUses inspects the uses of i to make sure they match certain criteria that
// allows us to suggest a modernization. If all uses of i, s and substr match
// one of the following four valid formats, it returns a list of occurrences for
// each format. If any of the uses do not match one of the formats, return nil
// for all values, since we should not offer a replacement.
// 1. lessZero - a condition involving i establishing that i is negative (e.g. i < 0, 0 > i, i == -1, -1 == i)
// 2. greaterNegOne - a condition involving i establishing that i is non-negative (e.g. i >= 0, 0 <= i, i == 0, 0 == i)
// 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i]
// 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr))
func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (lessZero, greaterNegOne, beforeSlice, afterSlice []ast.Expr) {
use := func(cur inspector.Cursor) bool {
ek, _ := cur.ParentEdge()
n := cur.Parent().Node()
switch ek {
case edge.BinaryExpr_X, edge.BinaryExpr_Y:
check := n.(*ast.BinaryExpr)
switch checkIdxComparison(info, check) {
case -1:
lessZero = append(lessZero, check)
return true
case 1:
greaterNegOne = append(greaterNegOne, check)
return true
}
// Check does not establish that i < 0 or i > -1.
// Might be part of an outer slice expression like s[i + k]
// which requires a different check.
// Check that the thing being sliced is s and that the slice
// doesn't have a max index.
if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok &&
sameObject(info, s, slice.X) &&
slice.Max == nil {
if isBeforeSlice(info, ek, slice) {
beforeSlice = append(beforeSlice, slice)
return true
} else if isAfterSlice(info, ek, slice, substr) {
afterSlice = append(afterSlice, slice)
return true
}
}
case edge.SliceExpr_Low, edge.SliceExpr_High:
slice := n.(*ast.SliceExpr)
// Check that the thing being sliced is s and that the slice doesn't
// have a max index.
if sameObject(info, s, slice.X) && slice.Max == nil {
if isBeforeSlice(info, ek, slice) {
beforeSlice = append(beforeSlice, slice)
return true
} else if isAfterSlice(info, ek, slice, substr) {
afterSlice = append(afterSlice, slice)
return true
}
}
}
return false
}
for curIdent := range uses {
if !use(curIdent) {
return nil, nil, nil, nil
}
}
return lessZero, greaterNegOne, beforeSlice, afterSlice
}
// hasModifyingUses reports whether any of the uses involve potential
// modifications. Uses involving assignments before the "afterPos" won't be
// considered.
func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPos token.Pos) bool {
for curUse := range uses {
ek, _ := curUse.ParentEdge()
if ek == edge.AssignStmt_Lhs {
if curUse.Node().Pos() <= afterPos {
continue
}
assign := curUse.Parent().Node().(*ast.AssignStmt)
if sameObject(info, assign.Lhs[0], curUse.Node().(*ast.Ident)) {
// Modifying use because we are reassigning the value of the object.
return true
}
} else if ek == edge.UnaryExpr_X &&
curUse.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
// Modifying use because we might be passing the object by reference (an explicit &).
// We can ignore the case where we have a method call on the expression (which
// has an implicit &) because we know the type of s and substr are strings
// which cannot have methods on them.
return true
}
}
return false
}
// checkIdxComparison reports whether the check establishes that i is negative
// or non-negative. It returns -1 in the first case, 1 in the second, and 0 if
// we can confirm neither condition. We assume that a check passed to
// checkIdxComparison has i as one of its operands.
func checkIdxComparison(info *types.Info, check *ast.BinaryExpr) int {
// Check establishes that i is negative.
// e.g.: i < 0, 0 > i, i == -1, -1 == i
if check.Op == token.LSS && (isNegativeConst(info, check.Y) || isZeroIntConst(info, check.Y)) || //i < (0 or neg)
check.Op == token.GTR && (isNegativeConst(info, check.X) || isZeroIntConst(info, check.X)) || // (0 or neg) > i
check.Op == token.LEQ && (isNegativeConst(info, check.Y)) || //i <= (neg)
check.Op == token.GEQ && (isNegativeConst(info, check.X)) || // (neg) >= i
check.Op == token.EQL &&
(isNegativeConst(info, check.X) || isNegativeConst(info, check.Y)) { // i == neg; neg == i
return -1
}
// Check establishes that i is non-negative.
// e.g.: i >= 0, 0 <= i, i == 0, 0 == i
if check.Op == token.GTR && (isNonNegativeConst(info, check.Y) || isIntLiteral(info, check.Y, -1)) || // i > (non-neg or -1)
check.Op == token.LSS && (isNonNegativeConst(info, check.X) || isIntLiteral(info, check.X, -1)) || // (non-neg or -1) < i
check.Op == token.GEQ && isNonNegativeConst(info, check.Y) || // i >= (non-neg)
check.Op == token.LEQ && isNonNegativeConst(info, check.X) || // (non-neg) <= i
check.Op == token.EQL &&
(isNonNegativeConst(info, check.X) || isNonNegativeConst(info, check.Y)) { // i == non-neg; non-neg == i
return 1
}
return 0
}
// isNegativeConst returns true if the expr is a const int with value < zero.
func isNegativeConst(info *types.Info, expr ast.Expr) bool {
if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(tv.Value); ok {
return v < 0
}
}
return false
}
// isNoneNegativeConst returns true if the expr is a const int with value >= zero.
func isNonNegativeConst(info *types.Info, expr ast.Expr) bool {
if tv, ok := info.Types[expr]; ok && tv.Value != nil && tv.Value.Kind() == constant.Int {
if v, ok := constant.Int64Val(tv.Value); ok {
return v >= 0
}
}
return false
}
// isBeforeSlice reports whether the SliceExpr is of the form s[:i] or s[0:i].
func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool {
return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low))
}
// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):],
// or s[i + k:] where k is a const is equal to len(substr).
func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool {
lowExpr, ok := slice.Low.(*ast.BinaryExpr)
if !ok || slice.High != nil {
return false
}
// Returns true if the expression is a call to len(substr).
isLenCall := func(expr ast.Expr) bool {
call, ok := expr.(*ast.CallExpr)
if !ok || len(call.Args) != 1 {
return false
}
return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen
}
// Handle len([]byte(substr))
if is[*ast.CallExpr](substr) {
call := substr.(*ast.CallExpr)
tv := info.Types[call.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) {
// Only one arg in []byte conversion.
substr = call.Args[0]
}
}
substrLen := -1
substrVal := info.Types[substr].Value
if substrVal != nil {
switch substrVal.Kind() {
case constant.String:
substrLen = len(constant.StringVal(substrVal))
case constant.Int:
// constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a')
// or a numeric byte literal, e.g. bytes.IndexByte(_, 65)
substrLen = 1
}
}
switch ek {
case edge.BinaryExpr_X:
kVal := info.Types[lowExpr.Y].Value
if kVal == nil {
// i + len(substr)
return lowExpr.Op == token.ADD && isLenCall(lowExpr.Y)
} else {
// i + k
kInt, ok := constant.Int64Val(kVal)
return ok && substrLen == int(kInt)
}
case edge.BinaryExpr_Y:
kVal := info.Types[lowExpr.X].Value
if kVal == nil {
// len(substr) + i
return lowExpr.Op == token.ADD && isLenCall(lowExpr.X)
} else {
// k + i
kInt, ok := constant.Int64Val(kVal)
return ok && substrLen == int(kInt)
}
}
return false
}
// sameObject reports whether we know that the expressions resolve to the same object.
func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool {
if ident1, ok := expr1.(*ast.Ident); ok {
if ident2, ok := expr2.(*ast.Ident); ok {
uses1, ok1 := info.Uses[ident1]
uses2, ok2 := info.Uses[ident2]
return ok1 && ok2 && uses1 == uses2
}
}
return false
}

View file

@ -12,22 +12,20 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var StringsCutPrefixAnalyzer = &analysis.Analyzer{
Name: "stringscutprefix",
Doc: analysisinternal.MustExtractDoc(doc, "stringscutprefix"),
Doc: analyzerutil.MustExtractDoc(doc, "stringscutprefix"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -56,12 +54,9 @@ var StringsCutPrefixAnalyzer = &analysis.Analyzer{
// Variants:
// - bytes.HasPrefix/HasSuffix usage as pattern 1.
func stringscutprefix(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsTrimPrefix = index.Object("strings", "TrimPrefix")
bytesTrimPrefix = index.Object("bytes", "TrimPrefix")
@ -72,7 +67,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
return nil, nil
}
for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.20") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_20) {
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
@ -206,6 +201,7 @@ func stringscutprefix(pass *analysis.Pass) (any, error) {
if astutil.EqualSyntax(lhs, bin.X) && astutil.EqualSyntax(call.Args[0], bin.Y) ||
(astutil.EqualSyntax(lhs, bin.Y) && astutil.EqualSyntax(call.Args[0], bin.X)) {
// TODO(adonovan): avoid FreshName when not needed; see errorsastype.
okVarName := refactor.FreshName(info.Scopes[ifStmt], ifStmt.Pos(), "ok")
// Have one of:
// if rest := TrimPrefix(s, prefix); rest != s { (ditto Suffix)

View file

@ -13,19 +13,17 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var StringsSeqAnalyzer = &analysis.Analyzer{
Name: "stringsseq",
Doc: analysisinternal.MustExtractDoc(doc, "stringsseq"),
Doc: analyzerutil.MustExtractDoc(doc, "stringsseq"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -48,12 +46,9 @@ var StringsSeqAnalyzer = &analysis.Analyzer{
// - bytes.SplitSeq
// - bytes.FieldsSeq
func stringsseq(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
stringsSplit = index.Object("strings", "Split")
stringsFields = index.Object("strings", "Fields")
@ -64,7 +59,7 @@ func stringsseq(pass *analysis.Pass) (any, error) {
return nil, nil
}
for curFile := range filesUsing(inspect, info, "go1.24") {
for curFile := range filesUsingGoVersion(pass, versions.Go1_24) {
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)

View file

@ -17,19 +17,18 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var TestingContextAnalyzer = &analysis.Analyzer{
Name: "testingcontext",
Doc: analysisinternal.MustExtractDoc(doc, "testingcontext"),
Doc: analyzerutil.MustExtractDoc(doc, "testingcontext"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -56,8 +55,6 @@ var TestingContextAnalyzer = &analysis.Analyzer{
// - the call is within a test or subtest function
// - the relevant testing.{T,B,F} is named and not shadowed at the call
func testingContext(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@ -137,7 +134,7 @@ calls:
testObj = isTestFn(info, n)
}
}
if testObj != nil && fileUses(info, astutil.EnclosingFile(cur), "go1.24") {
if testObj != nil && analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(cur), versions.Go1_24) {
// Have a test function. Check that we can resolve the relevant
// testing.{T,B,F} at the current position.
if _, obj := lhs[0].Parent().LookupParent(testObj.Name(), lhs[0].Pos()); obj == testObj {

View file

@ -14,19 +14,18 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
"golang.org/x/tools/internal/versions"
)
var WaitGroupAnalyzer = &analysis.Analyzer{
Name: "waitgroup",
Doc: analysisinternal.MustExtractDoc(doc, "waitgroup"),
Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
Requires: []*analysis.Analyzer{
generated.Analyzer,
inspect.Analyzer,
typeindexanalyzer.Analyzer,
},
@ -61,8 +60,6 @@ var WaitGroupAnalyzer = &analysis.Analyzer{
// other effects, or blocked, or if WaitGroup.Go propagated panics
// from child to parent goroutine, the argument would be different.)
func waitgroup(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
var (
index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
info = pass.TypesInfo
@ -128,7 +125,7 @@ func waitgroup(pass *analysis.Pass) (any, error) {
}
file := astutil.EnclosingFile(curAddCall)
if !fileUses(info, file, "go1.25") {
if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_25) {
continue
}
tokFile := pass.Fset.File(file.Pos())

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "nilfunc",
Doc: analysisinternal.MustExtractDoc(doc, "nilfunc"),
Doc: analyzerutil.MustExtractDoc(doc, "nilfunc"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/nilfunc",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -21,7 +21,7 @@ import (
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/fmtstr"
"golang.org/x/tools/internal/typeparams"
@ -38,7 +38,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "printf",
Doc: analysisinternal.MustExtractDoc(doc, "printf"),
Doc: analyzerutil.MustExtractDoc(doc, "printf"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/printf",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -612,7 +612,7 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
// breaking existing tests and CI scripts.
if idx == len(call.Args)-1 &&
fileVersion != "" && // fail open
versions.AtLeast(fileVersion, "go1.24") {
versions.AtLeast(fileVersion, versions.Go1_24) {
pass.Report(analysis.Diagnostic{
Pos: formatArg.Pos(),
@ -662,7 +662,7 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
anyIndex = true
}
rng := opRange(formatArg, op)
if !okPrintfArg(pass, call, rng, &maxArgIndex, firstArg, name, op) {
if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
// One error per format is enough.
return
}
@ -694,9 +694,9 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
// such as the position of the %v substring of "...%v...".
func opRange(formatArg ast.Expr, op *fmtstr.Operation) analysis.Range {
if lit, ok := formatArg.(*ast.BasicLit); ok {
start, end, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
rng, err := astutil.RangeInStringLiteral(lit, op.Range.Start, op.Range.End)
if err == nil {
return analysisinternal.Range(start, end) // position of "%v"
return rng // position of "%v"
}
}
return formatArg // entire format string
@ -707,6 +707,7 @@ type printfArgType int
const (
argBool printfArgType = 1 << iota
argByte
argInt
argRune
argString
@ -751,7 +752,7 @@ var printVerbs = []printVerb{
{'o', sharpNumFlag, argInt | argPointer},
{'O', sharpNumFlag, argInt | argPointer},
{'p', "-#", argPointer},
{'q', " -+.0#", argRune | argInt | argString},
{'q', " -+.0#", argRune | argInt | argString}, // note: when analyzing go1.26 code, argInt => argByte
{'s', " -+.0", argString},
{'t', "-", argBool},
{'T', "-", anyType},
@ -765,7 +766,7 @@ var printVerbs = []printVerb{
// okPrintfArg compares the operation to the arguments actually present,
// reporting any discrepancies it can discern, maxArgIndex was the index of the highest used index.
// If the final argument is ellipsissed, there's little it can do for that.
func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rng analysis.Range, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
verb := operation.Verb.Verb
var v printVerb
found := false
@ -777,6 +778,13 @@ func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, rng analysis.Range, ma
}
}
// When analyzing go1.26 code, rune and byte are the only %q integers (#72850).
if verb == 'q' &&
fileVersion != "" && // fail open
versions.AtLeast(fileVersion, versions.Go1_26) {
v.typ = argRune | argByte | argString
}
// Could verb's arg implement fmt.Formatter?
// Skip check for the %w verb, which requires an error.
formatter := false

View file

@ -204,8 +204,7 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
case *types.Struct:
// report whether all the elements of the struct match the expected type. For
// instance, with "%d" all the elements must be printable with the "%d" format.
for i := 0; i < typ.NumFields(); i++ {
typf := typ.Field(i)
for typf := range typ.Fields() {
if !m.match(typf.Type(), false) {
return false
}
@ -228,14 +227,20 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
types.Bool:
return m.t&argBool != 0
case types.Byte:
return m.t&(argInt|argByte) != 0
case types.Rune, types.UntypedRune:
return m.t&(argInt|argRune) != 0
case types.UntypedInt,
types.Int,
types.Int8,
types.Int16,
types.Int32,
// see case Rune for int32
types.Int64,
types.Uint,
types.Uint8,
// see case Byte for uint8
types.Uint16,
types.Uint32,
types.Uint64,
@ -259,9 +264,6 @@ func (m *argMatcher) match(typ types.Type, topLevel bool) bool {
case types.UnsafePointer:
return m.t&(argPointer|argInt) != 0
case types.UntypedRune:
return m.t&(argInt|argRune) != 0
case types.UntypedNil:
return false

View file

@ -19,7 +19,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -29,7 +29,7 @@ var doc string
// Analyzer describes sigchanyzer analysis function detector.
var Analyzer = &analysis.Analyzer{
Name: "sigchanyzer",
Doc: analysisinternal.MustExtractDoc(doc, "sigchanyzer"),
Doc: analyzerutil.MustExtractDoc(doc, "sigchanyzer"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/sigchanyzer",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -19,7 +19,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -29,7 +29,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "slog",
Doc: analysisinternal.MustExtractDoc(doc, "slog"),
Doc: analyzerutil.MustExtractDoc(doc, "slog"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/slog",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -168,7 +168,7 @@ func isAttr(t types.Type) bool {
// "slog.Logger.With" (instead of "(*log/slog.Logger).With")
func shortName(fn *types.Func) string {
var r string
if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
if recv := fn.Signature().Recv(); recv != nil {
if _, named := typesinternal.ReceiverNamed(recv); named != nil {
r = named.Obj().Name()
} else {
@ -188,7 +188,7 @@ func kvFuncSkipArgs(fn *types.Func) (int, bool) {
return 0, false
}
var recvName string // by default a slog package function
if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
if recv := fn.Signature().Recv(); recv != nil {
_, named := typesinternal.ReceiverNamed(recv)
if named == nil {
return 0, false // anon struct/interface

View file

@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
)
//go:embed doc.go
@ -21,7 +21,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stdmethods",
Doc: analysisinternal.MustExtractDoc(doc, "stdmethods"),
Doc: analyzerutil.MustExtractDoc(doc, "stdmethods"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stdmethods",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -131,12 +131,12 @@ func canonicalMethod(pass *analysis.Pass, id *ast.Ident) {
}
// Do the =s (if any) all match?
if !matchParams(pass, expect.args, args, "=") || !matchParams(pass, expect.results, results, "=") {
if !matchParams(expect.args, args, "=") || !matchParams(expect.results, results, "=") {
return
}
// Everything must match.
if !matchParams(pass, expect.args, args, "") || !matchParams(pass, expect.results, results, "") {
if !matchParams(expect.args, args, "") || !matchParams(expect.results, results, "") {
expectFmt := id.Name + "(" + argjoin(expect.args) + ")"
if len(expect.results) == 1 {
expectFmt += " " + argjoin(expect.results)
@ -168,7 +168,7 @@ func argjoin(x []string) string {
}
// Does each type in expect with the given prefix match the corresponding type in actual?
func matchParams(pass *analysis.Pass, expect []string, actual *types.Tuple, prefix string) bool {
func matchParams(expect []string, actual *types.Tuple, prefix string) bool {
for i, x := range expect {
if !strings.HasPrefix(x, prefix) {
continue

View file

@ -14,7 +14,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
@ -25,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "stringintconv",
Doc: analysisinternal.MustExtractDoc(doc, "stringintconv"),
Doc: analyzerutil.MustExtractDoc(doc, "stringintconv"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/stringintconv",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -30,7 +30,7 @@ func init() {
var Analyzer = &analysis.Analyzer{
Name: "testinggoroutine",
Doc: analysisinternal.MustExtractDoc(doc, "testinggoroutine"),
Doc: analyzerutil.MustExtractDoc(doc, "testinggoroutine"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/testinggoroutine",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -36,7 +36,7 @@ func localFunctionDecls(info *types.Info, files []*ast.File) func(*types.Func) *
// isMethodNamed returns true if f is a method defined
// in package with the path pkgPath with a name in names.
//
// (Unlike [analysisinternal.IsMethodNamed], it ignores the receiver type name.)
// (Unlike [analysis.IsMethodNamed], it ignores the receiver type name.)
func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
@ -44,7 +44,7 @@ func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
return false
}
if f.Type().(*types.Signature).Recv() == nil {
if f.Signature().Recv() == nil {
return false
}
return slices.Contains(names, f.Name())

View file

@ -15,7 +15,8 @@ import (
"unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -24,7 +25,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "tests",
Doc: analysisinternal.MustExtractDoc(doc, "tests"),
Doc: analyzerutil.MustExtractDoc(doc, "tests"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/tests",
Run: run,
}
@ -464,7 +465,7 @@ func checkTest(pass *analysis.Pass, fn *ast.FuncDecl, prefix string) {
if tparams := fn.Type.TypeParams; tparams != nil && len(tparams.List) > 0 {
// Note: cmd/go/internal/load also errors about TestXXX and BenchmarkXXX functions with type parameters.
// We have currently decided to also warn before compilation/package loading. This can help users in IDEs.
pass.ReportRangef(analysisinternal.Range(tparams.Opening, tparams.Closing),
pass.ReportRangef(astutil.RangeOf(tparams.Opening, tparams.Closing),
"%s has type parameters: it will not be run by go test as a %sXXX function",
fn.Name.Name, prefix)
}

View file

@ -18,7 +18,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -30,7 +30,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "timeformat",
Doc: analysisinternal.MustExtractDoc(doc, "timeformat"),
Doc: analyzerutil.MustExtractDoc(doc, "timeformat"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/timeformat",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -39,7 +39,7 @@ var Analyzer = &analysis.Analyzer{
func run(pass *analysis.Pass) (any, error) {
// Note: (time.Time).Format is a method and can be a typeutil.Callee
// without directly importing "time". So we cannot just skip this package
// when !analysisinternal.Imports(pass.Pkg, "time").
// when !analysis.Imports(pass.Pkg, "time").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

View file

@ -13,7 +13,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -22,7 +22,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unmarshal",
Doc: analysisinternal.MustExtractDoc(doc, "unmarshal"),
Doc: analyzerutil.MustExtractDoc(doc, "unmarshal"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unmarshal",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -39,7 +39,7 @@ func run(pass *analysis.Pass) (any, error) {
// Note: (*"encoding/json".Decoder).Decode, (* "encoding/gob".Decoder).Decode
// and (* "encoding/xml".Decoder).Decode are methods and can be a typeutil.Callee
// without directly importing their packages. So we cannot just skip this package
// when !analysisinternal.Imports(pass.Pkg, "encoding/...").
// when !analysis.Imports(pass.Pkg, "encoding/...").
// TODO(taking): Consider using a prepass to collect typeutil.Callees.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
@ -57,7 +57,7 @@ func run(pass *analysis.Pass) (any, error) {
// Classify the callee (without allocating memory).
argidx := -1
recv := fn.Type().(*types.Signature).Recv()
recv := fn.Signature().Recv()
if fn.Name() == "Unmarshal" && recv == nil {
// "encoding/json".Unmarshal
// "encoding/xml".Unmarshal

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/refactor"
)
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unreachable",
Doc: analysisinternal.MustExtractDoc(doc, "unreachable"),
Doc: analyzerutil.MustExtractDoc(doc, "unreachable"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unreachable",
Requires: []*analysis.Analyzer{inspect.Analyzer},
RunDespiteErrors: true,

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unsafeptr",
Doc: analysisinternal.MustExtractDoc(doc, "unsafeptr"),
Doc: analyzerutil.MustExtractDoc(doc, "unsafeptr"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unsafeptr",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -25,7 +25,8 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/astutil"
)
//go:embed doc.go
@ -33,7 +34,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "unusedresult",
Doc: analysisinternal.MustExtractDoc(doc, "unusedresult"),
Doc: analyzerutil.MustExtractDoc(doc, "unusedresult"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/unusedresult",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,
@ -149,11 +150,11 @@ func run(pass *analysis.Pass) (any, error) {
if !ok {
return // e.g. var or builtin
}
if sig := fn.Type().(*types.Signature); sig.Recv() != nil {
if sig := fn.Signature(); sig.Recv() != nil {
// method (e.g. foo.String())
if types.Identical(sig, sigNoArgsStringResult) {
if stringMethods[fn.Name()] {
pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of (%s).%s call not used",
sig.Recv().Type(), fn.Name())
}
@ -161,7 +162,7 @@ func run(pass *analysis.Pass) (any, error) {
} else {
// package-level function (e.g. fmt.Errorf)
if pkgFuncs[[2]string{fn.Pkg().Path(), fn.Name()}] {
pass.ReportRangef(analysisinternal.Range(call.Pos(), call.Lparen),
pass.ReportRangef(astutil.RangeOf(call.Pos(), call.Lparen),
"result of %s.%s call not used",
fn.Pkg().Path(), fn.Name())
}

View file

@ -15,7 +15,7 @@ import (
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/analyzerutil"
"golang.org/x/tools/internal/typesinternal"
)
@ -24,7 +24,7 @@ var doc string
var Analyzer = &analysis.Analyzer{
Name: "waitgroup",
Doc: analysisinternal.MustExtractDoc(doc, "waitgroup"),
Doc: analyzerutil.MustExtractDoc(doc, "waitgroup"),
URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/waitgroup",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Run: run,

View file

@ -49,7 +49,7 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/internal/analysisflags"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysis/driverutil"
"golang.org/x/tools/internal/facts"
)
@ -183,16 +183,18 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
// but apply all fixes from the root actions.
// Convert results to form needed by ApplyFixes.
fixActions := make([]analysisflags.FixAction, len(results))
fixActions := make([]driverutil.FixAction, len(results))
for i, res := range results {
fixActions[i] = analysisflags.FixAction{
fixActions[i] = driverutil.FixAction{
Name: res.a.Name,
Pkg: res.pkg,
Files: res.files,
FileSet: fset,
ReadFileFunc: os.ReadFile,
ReadFileFunc: os.ReadFile, // TODO(adonovan): respect overlays
Diagnostics: res.diagnostics,
}
}
if err := analysisflags.ApplyFixes(fixActions, false); err != nil {
if err := driverutil.ApplyFixes(fixActions, analysisflags.Diff, false); err != nil {
// Fail when applying fixes failed.
log.Print(err)
exit = 1
@ -209,7 +211,7 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
if analysisflags.JSON {
// JSON output
tree := make(analysisflags.JSONTree)
tree := make(driverutil.JSONTree)
for _, res := range results {
tree.Add(fset, id, res.a.Name, res.diagnostics, res.err)
}
@ -225,7 +227,7 @@ func processResults(fset *token.FileSet, id string, results []result) (exit int)
}
for _, res := range results {
for _, diag := range res.diagnostics {
analysisflags.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
driverutil.PrintPlain(os.Stderr, fset, analysisflags.Context, diag)
exit = 1
}
}
@ -428,7 +430,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
ResultOf: inputs,
Report: func(d analysis.Diagnostic) {
// Unitchecker doesn't apply fixes, but it does report them in the JSON output.
if err := analysisinternal.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
if err := driverutil.ValidateFixes(fset, a, d.SuggestedFixes); err != nil {
// Since we have diagnostics, the exit code will be nonzero,
// so logging these errors is sufficient.
log.Println(err)
@ -444,14 +446,14 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
Module: module,
}
pass.ReadFile = analysisinternal.CheckedReadFile(pass, os.ReadFile)
pass.ReadFile = driverutil.CheckedReadFile(pass, os.ReadFile)
t0 := time.Now()
act.result, act.err = a.Run(pass)
if act.err == nil { // resolve URLs on diagnostics.
for i := range act.diagnostics {
if url, uerr := analysisflags.ResolveURL(a, act.diagnostics[i]); uerr == nil {
if url, uerr := driverutil.ResolveURL(a, act.diagnostics[i]); uerr == nil {
act.diagnostics[i].URL = url
} else {
act.err = uerr // keep the last error
@ -482,9 +484,7 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
results := make([]result, len(analyzers))
for i, a := range analyzers {
act := actions[a]
results[i].a = a
results[i].err = act.err
results[i].diagnostics = act.diagnostics
results[i] = result{pkg, files, a, act.diagnostics, act.err}
}
data := facts.Encode()
@ -499,6 +499,8 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
}
type result struct {
pkg *types.Package
files []*ast.File
a *analysis.Analyzer
diagnostics []analysis.Diagnostic
err error

View file

@ -467,7 +467,9 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
// This algorithm could be implemented using c.Inspect,
// but it is about 2.5x slower.
best := int32(-1) // push index of latest (=innermost) node containing range
// best is the push-index of the latest (=innermost) node containing range.
// (Beware: latest is not always innermost because FuncDecl.{Name,Type} overlap.)
best := int32(-1)
for i, limit := c.indices(); i < limit; i++ {
ev := events[i]
if ev.index > i { // push?
@ -481,6 +483,19 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) {
continue
}
} else {
// Edge case: FuncDecl.Name and .Type overlap:
// Don't update best from Name to FuncDecl.Type.
//
// The condition can be read as:
// - n is FuncType
// - n.parent is FuncDecl
// - best is strictly beneath the FuncDecl
if ev.typ == 1<<nFuncType &&
events[ev.parent].typ == 1<<nFuncDecl &&
best > ev.parent {
continue
}
nodeEnd = n.End()
if n.Pos() > start {
break // disjoint, after; stop

View file

@ -13,7 +13,7 @@ import (
)
type builder struct {
cfg *CFG
blocks []*Block
mayReturn func(*ast.CallExpr) bool
current *Block
lblocks map[string]*lblock // labeled blocks
@ -32,12 +32,18 @@ start:
*ast.SendStmt,
*ast.IncDecStmt,
*ast.GoStmt,
*ast.DeferStmt,
*ast.EmptyStmt,
*ast.AssignStmt:
// No effect on control flow.
b.add(s)
case *ast.DeferStmt:
b.add(s)
// Assume conservatively that this behaves like:
// defer func() { recover() }
// so any subsequent panic may act like a return.
b.current.returns = true
case *ast.ExprStmt:
b.add(s)
if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) {
@ -64,6 +70,7 @@ start:
goto start // effectively: tailcall stmt(g, s.Stmt, label)
case *ast.ReturnStmt:
b.current.returns = true
b.add(s)
b.current = b.newBlock(KindUnreachable, s)
@ -483,14 +490,13 @@ func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock
// It does not automatically become the current block.
// comment is an optional string for more readable debugging output.
func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block {
g := b.cfg
block := &Block{
Index: int32(len(g.Blocks)),
Index: int32(len(b.blocks)),
Kind: kind,
Stmt: stmt,
}
block.Succs = block.succs2[:0]
g.Blocks = append(g.Blocks, block)
b.blocks = append(b.blocks, block)
return block
}

View file

@ -47,13 +47,16 @@ import (
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/internal/cfginternal"
)
// A CFG represents the control-flow graph of a single function.
//
// The entry point is Blocks[0]; there may be multiple return blocks.
type CFG struct {
Blocks []*Block // block[0] is entry; order otherwise undefined
Blocks []*Block // block[0] is entry; order otherwise undefined
noreturn bool // function body lacks a reachable return statement
}
// A Block represents a basic block: a list of statements and
@ -67,12 +70,13 @@ type CFG struct {
// an [ast.Expr], Succs[0] is the successor if the condition is true, and
// Succs[1] is the successor if the condition is false.
type Block struct {
Nodes []ast.Node // statements, expressions, and ValueSpecs
Succs []*Block // successor nodes in the graph
Index int32 // index within CFG.Blocks
Live bool // block is reachable from entry
Kind BlockKind // block kind
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
Nodes []ast.Node // statements, expressions, and ValueSpecs
Succs []*Block // successor nodes in the graph
Index int32 // index within CFG.Blocks
Live bool // block is reachable from entry
returns bool // block contains return or defer (which may recover and return)
Kind BlockKind // block kind
Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details)
succs2 [2]*Block // underlying array for Succs
}
@ -141,14 +145,14 @@ func (kind BlockKind) String() string {
func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
b := builder{
mayReturn: mayReturn,
cfg: new(CFG),
}
b.current = b.newBlock(KindBody, body)
b.stmt(body)
// Compute liveness (reachability from entry point), breadth-first.
q := make([]*Block, 0, len(b.cfg.Blocks))
q = append(q, b.cfg.Blocks[0]) // entry point
// Compute liveness (reachability from entry point),
// breadth-first, marking Block.Live flags.
q := make([]*Block, 0, len(b.blocks))
q = append(q, b.blocks[0]) // entry point
for len(q) > 0 {
b := q[len(q)-1]
q = q[:len(q)-1]
@ -162,12 +166,30 @@ func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG {
// Does control fall off the end of the function's body?
// Make implicit return explicit.
if b.current != nil && b.current.Live {
b.current.returns = true
b.add(&ast.ReturnStmt{
Return: body.End() - 1,
})
}
return b.cfg
// Is any return (or defer+recover) block reachable?
noreturn := true
for _, bl := range b.blocks {
if bl.Live && bl.returns {
noreturn = false
break
}
}
return &CFG{Blocks: b.blocks, noreturn: noreturn}
}
// isNoReturn reports whether the function has no reachable return.
// TODO(adonovan): add (*CFG).NoReturn to public API.
func isNoReturn(_cfg any) bool { return _cfg.(*CFG).noreturn }
func init() {
cfginternal.IsNoReturn = isNoReturn // expose to ctrlflow analyzer
}
func (b *Block) String() string {
@ -187,6 +209,14 @@ func (b *Block) comment(fset *token.FileSet) string {
//
// When control falls off the end of the function, the ReturnStmt is synthetic
// and its [ast.Node.End] position may be beyond the end of the file.
//
// A function that contains no return statement (explicit or implied)
// may yet return normally, and may even return a nonzero value. For example:
//
// func() (res any) {
// defer func() { res = recover() }()
// panic(123)
// }
func (b *Block) Return() (ret *ast.ReturnStmt) {
if len(b.Nodes) > 0 {
ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt)

View file

@ -249,7 +249,7 @@ func (enc *Encoder) For(obj types.Object) (Path, error) {
case *types.Func:
// A func, if not package-level, must be a method.
if recv := obj.Type().(*types.Signature).Recv(); recv == nil {
if recv := obj.Signature().Recv(); recv == nil {
return "", fmt.Errorf("func is not a method: %v", obj)
}
@ -405,7 +405,7 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
return "", false
}
_, named := typesinternal.ReceiverNamed(meth.Type().(*types.Signature).Recv())
_, named := typesinternal.ReceiverNamed(meth.Signature().Recv())
if named == nil {
return "", false
}

View file

@ -304,8 +304,7 @@ func (h hasher) hash(t types.Type) uint32 {
case *types.Named:
hash := h.hashTypeName(t.Obj())
targs := t.TypeArgs()
for i := 0; i < targs.Len(); i++ {
targ := targs.At(i)
for targ := range targs.Types() {
hash += 2 * h.hash(targ)
}
return hash

View file

@ -0,0 +1,6 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package analyzerutil provides implementation helpers for analyzers.
package analyzerutil

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysisinternal
package analyzerutil
import (
"fmt"
@ -35,7 +35,7 @@ import (
//
// var Analyzer = &analysis.Analyzer{
// Name: "halting",
// Doc: analysisinternal.MustExtractDoc(doc, "halting"),
// Doc: analyzerutil.MustExtractDoc(doc, "halting"),
// ...
// }
func MustExtractDoc(content, name string) string {

View file

@ -0,0 +1,30 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analyzerutil
// This file defines helpers for calling [analysis.Pass.ReadFile].
import (
"go/token"
"os"
"golang.org/x/tools/go/analysis"
)
// ReadFile reads a file and adds it to the FileSet in pass
// so that we can report errors against it using lineStart.
func ReadFile(pass *analysis.Pass, filename string) ([]byte, *token.File, error) {
readFile := pass.ReadFile
if readFile == nil {
readFile = os.ReadFile
}
content, err := readFile(filename)
if err != nil {
return nil, nil, err
}
tf := pass.Fset.AddFile(filename, -1, len(content))
tf.SetLinesForContent(content)
return content, tf, nil
}

View file

@ -0,0 +1,42 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analyzerutil
import (
"go/ast"
"strings"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/packagepath"
"golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/versions"
)
// FileUsesGoVersion reports whether the specified file may use features of the
// specified version of Go (e.g. "go1.24").
//
// Tip: we recommend using this check "late", just before calling
// pass.Report, rather than "early" (when entering each ast.File, or
// each candidate node of interest, during the traversal), because the
// operation is not free, yet is not a highly selective filter: the
// fraction of files that pass most version checks is high and
// increases over time.
func FileUsesGoVersion(pass *analysis.Pass, file *ast.File, version string) (_res bool) {
fileVersion := pass.TypesInfo.FileVersions[file]
// Standard packages that are part of toolchain bootstrapping
// are not considered to use a version of Go later than the
// current bootstrap toolchain version.
// The bootstrap rule does not cover tests,
// and some tests (e.g. debug/elf/file_test.go) rely on this.
pkgpath := pass.Pkg.Path()
if packagepath.IsStdPackage(pkgpath) &&
stdlib.IsBootstrapPackage(pkgpath) && // (excludes "*_test" external test packages)
!strings.HasSuffix(pass.Fset.File(file.Pos()).Name(), "_test.go") { // (excludes all tests)
fileVersion = stdlib.BootstrapVersion.String() // package must bootstrap
}
return !versions.Before(fileVersion, version)
}

View file

@ -2,37 +2,48 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package analysisflags
// Package driverutil defines implementation helper functions for
// analysis drivers such as unitchecker, {single,multi}checker, and
// analysistest.
package driverutil
// This file defines the -fix logic common to unitchecker and
// {single,multi}checker.
import (
"bytes"
"fmt"
"go/format"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"go/types"
"log"
"maps"
"os"
"sort"
"strconv"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/astutil/free"
"golang.org/x/tools/internal/diff"
)
// FixAction abstracts a checker action (running one analyzer on one
// package) for the purposes of applying its diagnostics' fixes.
type FixAction struct {
Name string // e.g. "analyzer@package"
Name string // e.g. "analyzer@package"
Pkg *types.Package // (for import removal)
Files []*ast.File
FileSet *token.FileSet
ReadFileFunc analysisinternal.ReadFileFunc
ReadFileFunc ReadFileFunc
Diagnostics []analysis.Diagnostic
}
// ApplyFixes attempts to apply the first suggested fix associated
// with each diagnostic reported by the specified actions.
// All fixes must have been validated by [analysisinternal.ValidateFixes].
// All fixes must have been validated by [ValidateFixes].
//
// Each fix is treated as an independent change; fixes are merged in
// an arbitrary deterministic order as if by a three-way diff tool
@ -58,15 +69,15 @@ type FixAction struct {
// composition of the two fixes is semantically correct. Coalescing
// identical edits is appropriate for imports, but not for, say,
// increments to a counter variable; the correct resolution in that
// case might be to increment it twice. Or consider two fixes that
// each delete the penultimate reference to an import or local
// variable: each fix is sound individually, and they may be textually
// distant from each other, but when both are applied, the program is
// no longer valid because it has an unreferenced import or local
// variable.
// TODO(adonovan): investigate replacing the final "gofmt" step with a
// formatter that applies the unused-import deletion logic of
// "goimports".
// case might be to increment it twice.
//
// Or consider two fixes that each delete the penultimate reference to
// a local variable: each fix is sound individually, and they may be
// textually distant from each other, but when both are applied, the
// program is no longer valid because it has an unreferenced local
// variable. (ApplyFixes solves the analogous problem for imports by
// eliminating imports whose name is unreferenced in the remainder of
// the fixed file.)
//
// Merging depends on both the order of fixes and they order of edits
// within them. For example, if three fixes add import "a" twice and
@ -80,12 +91,15 @@ type FixAction struct {
// applyFixes returns success if all fixes are valid, could be cleanly
// merged, and the corresponding files were successfully updated.
//
// If the -diff flag was set, instead of updating the files it display the final
// patch composed of all the cleanly merged fixes.
// If printDiff (from the -diff flag) is set, instead of updating the
// files it display the final patch composed of all the cleanly merged
// fixes.
//
// TODO(adonovan): handle file-system level aliases such as symbolic
// links using robustio.FileID.
func ApplyFixes(actions []FixAction, verbose bool) error {
func ApplyFixes(actions []FixAction, printDiff, verbose bool) error {
generated := make(map[*token.File]bool)
// Select fixes to apply.
//
// If there are several for a given Diagnostic, choose the first.
@ -96,6 +110,15 @@ func ApplyFixes(actions []FixAction, verbose bool) error {
}
var fixes []*fixact
for _, act := range actions {
for _, file := range act.Files {
tokFile := act.FileSet.File(file.FileStart)
// Memoize, since there may be many actions
// for the same package (list of files).
if _, seen := generated[tokFile]; !seen {
generated[tokFile] = ast.IsGenerated(file)
}
}
for _, diag := range act.Diagnostics {
for i := range diag.SuggestedFixes {
fix := &diag.SuggestedFixes[i]
@ -119,7 +142,7 @@ func ApplyFixes(actions []FixAction, verbose bool) error {
// packages are not disjoint, due to test variants, so this
// would not really address the issue.)
baselineContent := make(map[string][]byte)
getBaseline := func(readFile analysisinternal.ReadFileFunc, filename string) ([]byte, error) {
getBaseline := func(readFile ReadFileFunc, filename string) ([]byte, error) {
content, ok := baselineContent[filename]
if !ok {
var err error
@ -134,16 +157,32 @@ func ApplyFixes(actions []FixAction, verbose bool) error {
// Apply each fix, updating the current state
// only if the entire fix can be cleanly merged.
accumulatedEdits := make(map[string][]diff.Edit)
goodFixes := 0
var (
accumulatedEdits = make(map[string][]diff.Edit)
filePkgs = make(map[string]*types.Package) // maps each file to an arbitrary package that includes it
goodFixes = 0 // number of fixes cleanly applied
skippedFixes = 0 // number of fixes skipped (because e.g. edits a generated file)
)
fixloop:
for _, fixact := range fixes {
// Skip a fix if any of its edits touch a generated file.
for _, edit := range fixact.fix.TextEdits {
file := fixact.act.FileSet.File(edit.Pos)
if generated[file] {
skippedFixes++
continue fixloop
}
}
// Convert analysis.TextEdits to diff.Edits, grouped by file.
// Precondition: a prior call to validateFix succeeded.
fileEdits := make(map[string][]diff.Edit)
for _, edit := range fixact.fix.TextEdits {
file := fixact.act.FileSet.File(edit.Pos)
filePkgs[file.Name()] = fixact.act.Pkg
baseline, err := getBaseline(fixact.act.ReadFileFunc, file.Name())
if err != nil {
log.Printf("skipping fix to file %s: %v", file.Name(), err)
@ -191,7 +230,7 @@ fixloop:
log.Printf("%s: fix %s applied", fixact.act.Name, fixact.fix.Message)
}
}
badFixes := len(fixes) - goodFixes
badFixes := len(fixes) - goodFixes - skippedFixes // number of fixes that could not be applied
// Show diff or update files to final state.
var files []string
@ -214,11 +253,11 @@ fixloop:
}
// Attempt to format each file.
if formatted, err := format.Source(final); err == nil {
if formatted, err := FormatSourceRemoveImports(filePkgs[file], final); err == nil {
final = formatted
}
if diffFlag {
if printDiff {
// Since we formatted the file, we need to recompute the diff.
unified := diff.Unified(file+" (old)", file+" (new)", string(baseline), string(final))
// TODO(adonovan): abstract the I/O.
@ -262,23 +301,149 @@ fixloop:
// These numbers are potentially misleading:
// The denominator includes duplicate conflicting fixes due to
// common files in packages "p" and "p [p.test]", which may
// have been fixed fixed and won't appear in the re-run.
// have been fixed and won't appear in the re-run.
// TODO(adonovan): eliminate identical fixes as an initial
// filtering step.
//
// TODO(adonovan): should we log that n files were updated in case of total victory?
if badFixes > 0 || filesUpdated < totalFiles {
if diffFlag {
return fmt.Errorf("%d of %d fixes skipped (e.g. due to conflicts)", badFixes, len(fixes))
if printDiff {
return fmt.Errorf("%d of %s skipped (e.g. due to conflicts)",
badFixes,
plural(len(fixes), "fix", "fixes"))
} else {
return fmt.Errorf("applied %d of %d fixes; %d files updated. (Re-run the command to apply more.)",
goodFixes, len(fixes), filesUpdated)
return fmt.Errorf("applied %d of %s; %s updated. (Re-run the command to apply more.)",
goodFixes,
plural(len(fixes), "fix", "fixes"),
plural(filesUpdated, "file", "files"))
}
}
if verbose {
log.Printf("applied %d fixes, updated %d files", len(fixes), filesUpdated)
if skippedFixes > 0 {
log.Printf("skipped %s that would edit generated files",
plural(skippedFixes, "fix", "fixes"))
}
log.Printf("applied %s, updated %s",
plural(len(fixes), "fix", "fixes"),
plural(filesUpdated, "file", "files"))
}
return nil
}
// FormatSourceRemoveImports is a variant of [format.Source] that
// removes imports that became redundant when fixes were applied.
//
// Import removal is necessarily heuristic since we do not have type
// information for the fixed file and thus cannot accurately tell
// whether k is among the free names of T{k: 0}, which requires
// knowledge of whether T is a struct type.
func FormatSourceRemoveImports(pkg *types.Package, src []byte) ([]byte, error) {
// This function was reduced from the "strict entire file"
// path through [format.Source].
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "fixed.go", src, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
return nil, err
}
ast.SortImports(fset, file)
removeUnneededImports(fset, pkg, file)
// printerNormalizeNumbers means to canonicalize number literal prefixes
// and exponents while printing. See https://golang.org/doc/go1.13#gofmt.
//
// This value is defined in go/printer specifically for go/format and cmd/gofmt.
const printerNormalizeNumbers = 1 << 30
cfg := &printer.Config{
Mode: printer.UseSpaces | printer.TabIndent | printerNormalizeNumbers,
Tabwidth: 8,
}
var buf bytes.Buffer
if err := cfg.Fprint(&buf, fset, file); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// removeUnneededImports removes import specs that are not referenced
// within the fixed file. It uses [free.Names] to heuristically
// approximate the set of imported names needed by the body of the
// file based only on syntax.
//
// pkg provides type information about the unmodified package, in
// particular the name that would implicitly be declared by a
// non-renaming import of a given existing dependency.
func removeUnneededImports(fset *token.FileSet, pkg *types.Package, file *ast.File) {
// Map each existing dependency to its default import name.
// (We'll need this to interpret non-renaming imports.)
packageNames := make(map[string]string)
for _, imp := range pkg.Imports() {
packageNames[imp.Path()] = imp.Name()
}
// Compute the set of free names of the file,
// ignoring its import decls.
freenames := make(map[string]bool)
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
continue // skip import
}
// TODO(adonovan): we could do better than includeComplitIdents=false
// since we have type information about the unmodified package,
// which is a good source of heuristics.
const includeComplitIdents = false
maps.Copy(freenames, free.Names(decl, includeComplitIdents))
}
// Check whether each import's declared name is free (referenced) by the file.
var deletions []func()
for _, spec := range file.Imports {
path, err := strconv.Unquote(spec.Path.Value)
if err != nil {
continue // malformed import; ignore
}
explicit := "" // explicit PkgName, if any
if spec.Name != nil {
explicit = spec.Name.Name
}
name := explicit // effective PkgName
if name == "" {
// Non-renaming import: use package's default name.
name = packageNames[path]
}
switch name {
case "":
continue // assume it's a new import
case ".":
continue // dot imports are tricky
case "_":
continue // keep blank imports
}
if !freenames[name] {
// Import's effective name is not free in (not used by) the file.
// Enqueue it for deletion after the loop.
deletions = append(deletions, func() {
astutil.DeleteNamedImport(fset, file, explicit, path)
})
}
}
// Apply the deletions.
for _, del := range deletions {
del()
}
}
// plural returns "n nouns", selecting the plural form as approriate.
func plural(n int, singular, plural string) string {
if n == 1 {
return "1 " + singular
} else {
return fmt.Sprintf("%d %s", n, plural)
}
}

View file

@ -0,0 +1,161 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package driverutil
// This file defined output helpers common to all drivers.
import (
"encoding/json"
"fmt"
"go/token"
"io"
"log"
"os"
"strings"
"golang.org/x/tools/go/analysis"
)
// TODO(adonovan): don't accept an io.Writer if we don't report errors.
// Either accept a bytes.Buffer (infallible), or return a []byte.
// PrintPlain prints a diagnostic in plain text form.
// If contextLines is nonnegative, it also prints the
// offending line plus this many lines of context.
func PrintPlain(out io.Writer, fset *token.FileSet, contextLines int, diag analysis.Diagnostic) {
print := func(pos, end token.Pos, message string) {
posn := fset.Position(pos)
fmt.Fprintf(out, "%s: %s\n", posn, message)
// show offending line plus N lines of context.
if contextLines >= 0 {
end := fset.Position(end)
if !end.IsValid() {
end = posn
}
// TODO(adonovan): highlight the portion of the line indicated
// by pos...end using ASCII art, terminal colors, etc?
data, _ := os.ReadFile(posn.Filename)
lines := strings.Split(string(data), "\n")
for i := posn.Line - contextLines; i <= end.Line+contextLines; i++ {
if 1 <= i && i <= len(lines) {
fmt.Fprintf(out, "%d\t%s\n", i, lines[i-1])
}
}
}
}
print(diag.Pos, diag.End, diag.Message)
for _, rel := range diag.Related {
print(rel.Pos, rel.End, "\t"+rel.Message)
}
}
// A JSONTree is a mapping from package ID to analysis name to result.
// Each result is either a jsonError or a list of JSONDiagnostic.
type JSONTree map[string]map[string]any
// A TextEdit describes the replacement of a portion of a file.
// Start and End are zero-based half-open indices into the original byte
// sequence of the file, and New is the new text.
type JSONTextEdit struct {
Filename string `json:"filename"`
Start int `json:"start"`
End int `json:"end"`
New string `json:"new"`
}
// A JSONSuggestedFix describes an edit that should be applied as a whole or not
// at all. It might contain multiple TextEdits/text_edits if the SuggestedFix
// consists of multiple non-contiguous edits.
type JSONSuggestedFix struct {
Message string `json:"message"`
Edits []JSONTextEdit `json:"edits"`
}
// A JSONDiagnostic describes the JSON schema of an analysis.Diagnostic.
//
// TODO(matloob): include End position if present.
type JSONDiagnostic struct {
Category string `json:"category,omitempty"`
Posn string `json:"posn"` // e.g. "file.go:line:column"
Message string `json:"message"`
SuggestedFixes []JSONSuggestedFix `json:"suggested_fixes,omitempty"`
Related []JSONRelatedInformation `json:"related,omitempty"`
}
// A JSONRelated describes a secondary position and message related to
// a primary diagnostic.
//
// TODO(adonovan): include End position if present.
type JSONRelatedInformation struct {
Posn string `json:"posn"` // e.g. "file.go:line:column"
Message string `json:"message"`
}
// Add adds the result of analysis 'name' on package 'id'.
// The result is either a list of diagnostics or an error.
func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
var v any
if err != nil {
type jsonError struct {
Err string `json:"error"`
}
v = jsonError{err.Error()}
} else if len(diags) > 0 {
diagnostics := make([]JSONDiagnostic, 0, len(diags))
for _, f := range diags {
var fixes []JSONSuggestedFix
for _, fix := range f.SuggestedFixes {
var edits []JSONTextEdit
for _, edit := range fix.TextEdits {
edits = append(edits, JSONTextEdit{
Filename: fset.Position(edit.Pos).Filename,
Start: fset.Position(edit.Pos).Offset,
End: fset.Position(edit.End).Offset,
New: string(edit.NewText),
})
}
fixes = append(fixes, JSONSuggestedFix{
Message: fix.Message,
Edits: edits,
})
}
var related []JSONRelatedInformation
for _, r := range f.Related {
related = append(related, JSONRelatedInformation{
Posn: fset.Position(r.Pos).String(),
Message: r.Message,
})
}
jdiag := JSONDiagnostic{
Category: f.Category,
Posn: fset.Position(f.Pos).String(),
Message: f.Message,
SuggestedFixes: fixes,
Related: related,
}
diagnostics = append(diagnostics, jdiag)
}
v = diagnostics
}
if v != nil {
m, ok := tree[id]
if !ok {
m = make(map[string]any)
tree[id] = m
}
m[name] = v
}
}
func (tree JSONTree) Print(out io.Writer) error {
data, err := json.MarshalIndent(tree, "", "\t")
if err != nil {
log.Panicf("internal error: JSON marshaling failed: %v", err)
}
_, err = fmt.Fprintf(out, "%s\n", data)
return err
}

View file

@ -0,0 +1,43 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package driverutil
// This file defines helpers for implementing [analysis.Pass.ReadFile].
import (
"fmt"
"slices"
"golang.org/x/tools/go/analysis"
)
// A ReadFileFunc is a function that returns the
// contents of a file, such as [os.ReadFile].
type ReadFileFunc = func(filename string) ([]byte, error)
// CheckedReadFile returns a wrapper around a Pass.ReadFile
// function that performs the appropriate checks.
func CheckedReadFile(pass *analysis.Pass, readFile ReadFileFunc) ReadFileFunc {
return func(filename string) ([]byte, error) {
if err := CheckReadable(pass, filename); err != nil {
return nil, err
}
return readFile(filename)
}
}
// CheckReadable enforces the access policy defined by the ReadFile field of [analysis.Pass].
func CheckReadable(pass *analysis.Pass, filename string) error {
if slices.Contains(pass.OtherFiles, filename) ||
slices.Contains(pass.IgnoredFiles, filename) {
return nil
}
for _, f := range pass.Files {
if pass.Fset.File(f.FileStart).Name() == filename {
return nil
}
}
return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
}

Some files were not shown because too many files have changed in this diff Show more