module cmd
-go 1.24
+go 1.25
require (
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142
- golang.org/x/arch v0.12.0
- golang.org/x/build v0.0.0-20241205234318-b850320af2a4
- golang.org/x/mod v0.22.0
- golang.org/x/sync v0.10.0
- golang.org/x/sys v0.28.0
- golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3
- golang.org/x/term v0.27.0
- golang.org/x/tools v0.28.1-0.20250131145412-98746475647e
+ golang.org/x/arch v0.14.0
+ golang.org/x/build v0.0.0-20250211223606-a5e3f75caa63
+ golang.org/x/mod v0.23.0
+ golang.org/x/sync v0.11.0
+ golang.org/x/sys v0.30.0
+ golang.org/x/telemetry v0.0.0-20250212145848-75305293b65a
+ golang.org/x/term v0.29.0
+ golang.org/x/tools v0.30.1-0.20250212161021-f9aad7054b5f
)
require (
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef // indirect
)
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
-golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
-golang.org/x/build v0.0.0-20241205234318-b850320af2a4 h1:ri5CIHQTJCd3jd0Jez97HiPE+VMT0hFNKqLHn2EjrXk=
-golang.org/x/build v0.0.0-20241205234318-b850320af2a4/go.mod h1:9O1P9bdbWH7KXtcbo+6amI/59H5mNq7+CTE1eKqNsjg=
-golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
-golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3 h1:rCLsPBq7l0E9Z451UgkWFkaWYhgt7dGmAlpD6hLjK5I=
-golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3/go.mod h1:8h4Hgq+jcTvCDv2+i7NrfWwpYHcESleo2nGHxLbFLJ4=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
-golang.org/x/tools v0.28.1-0.20250131145412-98746475647e h1:6Kzwg7JxW2HRWToKpIKqlpF8l8XMasoALX3OcAMdgL8=
-golang.org/x/tools v0.28.1-0.20250131145412-98746475647e/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
+golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
+golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/build v0.0.0-20250211223606-a5e3f75caa63 h1:QZ8/V1B4oK7N5t6w0zX5dAxFIHt0WaTX+r1z29cWXjY=
+golang.org/x/build v0.0.0-20250211223606-a5e3f75caa63/go.mod h1:JhINjMoWj8G2oLkaBLNDBIr/GLqJNOkCr4XzFWWYCf4=
+golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
+golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/telemetry v0.0.0-20250212145848-75305293b65a h1:3fgycqG+90xOafOruMBVZXa8DUeOt5qbGLjQoNvZ8Ew=
+golang.org/x/telemetry v0.0.0-20250212145848-75305293b65a/go.mod h1:Ng+6E7PnWNge4EifZkPKeQUnm5iyAoH8qQgw3pLCiF4=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
+golang.org/x/tools v0.30.1-0.20250212161021-f9aad7054b5f h1:wN7/h1uT0B8rVpI6iWEPBC6qO1tdoMaNR6cOwdqqy/s=
+golang.org/x/tools v0.30.1-0.20250212161021-f9aad7054b5f/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=
}
}
+ if inst.Op == ANDI && inst.Args[2].(Simm).Imm == 255 {
+ op = "zext.b"
+ args = args[:len(args)-1]
+ }
+
if inst.Op == ADDIW && inst.Args[2].(Simm).Imm == 0 {
op = "sext.w"
args = args[:len(args)-1]
-// Code generated by x86map -fmt=decoder x86.csv DO NOT EDIT.
+// Code generated by x86map -fmt=decoder ../x86.csv DO NOT EDIT.
package x86asm
if plainText(i) != "[" {
return ""
}
- // The open bracket must be preceeded by a link-adjacent rune (or by nothing).
+ // The open bracket must be preceded by a link-adjacent rune (or by nothing).
if t := plainText(i - 1); t != "" {
r, _ := utf8.DecodeLastRuneInString(t)
if !isLinkAdjacentRune(r) {
}
defer f.Close()
data, err := io.ReadAll(f)
+ if err != nil {
+ return err
+ }
return CheckFragment(string(data))
}
in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
case ')':
rparen := in.lex()
+ // Don't preserve blank lines (denoted by a single empty comment, added above)
+ // at the end of the block.
+ if len(comments) == 1 && comments[0] == (Comment{}) {
+ comments = nil
+ }
x.RParen.Before = comments
x.RParen.Pos = rparen.pos
if !in.peek().isEOL() {
// SetLimit limits the number of active goroutines in this group to at most n.
// A negative value indicates no limit.
+// A limit of zero will prevent any new goroutines from being added.
//
// Any subsequent call to the Go method will block until it can add an active
// goroutine without exceeding the configured limit.
--- /dev/null
+// 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.
+
+//go:build go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
+
+package unix
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+//go:linkname runtime_getAuxv runtime.getAuxv
+func runtime_getAuxv() []uintptr
+
+// Auxv returns the ELF auxiliary vector as a sequence of key/value pairs.
+// The returned slice is always a fresh copy, owned by the caller.
+// It returns an error on non-ELF platforms, or if the auxiliary vector cannot be accessed,
+// which happens in some locked-down environments and build modes.
+func Auxv() ([][2]uintptr, error) {
+ vec := runtime_getAuxv()
+ vecLen := len(vec)
+
+ if vecLen == 0 {
+ return nil, syscall.ENOENT
+ }
+
+ if vecLen%2 != 0 {
+ return nil, syscall.EINVAL
+ }
+
+ result := make([]uintptr, vecLen)
+ copy(result, vec)
+ return unsafe.Slice((*[2]uintptr)(unsafe.Pointer(&result[0])), vecLen/2), nil
+}
--- /dev/null
+// 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.
+
+//go:build !go1.21 && (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos)
+
+package unix
+
+import "syscall"
+
+func Auxv() ([][2]uintptr, error) {
+ return nil, syscall.ENOTSUP
+}
return sendfile(outfd, infd, offset, count)
}
+func Dup3(oldfd, newfd, flags int) error {
+ if oldfd == newfd || flags&^O_CLOEXEC != 0 {
+ return EINVAL
+ }
+ how := F_DUP2FD
+ if flags&O_CLOEXEC != 0 {
+ how = F_DUP2FD_CLOEXEC
+ }
+ _, err := fcntl(oldfd, how, newfd)
+ return err
+}
+
/*
* Exposed directly
*/
func IoctlSetStrioctlRetInt(fd int, req int, s *Strioctl) (int, error) {
return ioctlPtrRet(fd, req, unsafe.Pointer(s))
}
+
+// Ucred Helpers
+// See ucred(3c) and getpeerucred(3c)
+
+//sys getpeerucred(fd uintptr, ucred *uintptr) (err error)
+//sys ucredFree(ucred uintptr) = ucred_free
+//sys ucredGet(pid int) (ucred uintptr, err error) = ucred_get
+//sys ucredGeteuid(ucred uintptr) (uid int) = ucred_geteuid
+//sys ucredGetegid(ucred uintptr) (gid int) = ucred_getegid
+//sys ucredGetruid(ucred uintptr) (uid int) = ucred_getruid
+//sys ucredGetrgid(ucred uintptr) (gid int) = ucred_getrgid
+//sys ucredGetsuid(ucred uintptr) (uid int) = ucred_getsuid
+//sys ucredGetsgid(ucred uintptr) (gid int) = ucred_getsgid
+//sys ucredGetpid(ucred uintptr) (pid int) = ucred_getpid
+
+// Ucred is an opaque struct that holds user credentials.
+type Ucred struct {
+ ucred uintptr
+}
+
+// We need to ensure that ucredFree is called on the underlying ucred
+// when the Ucred is garbage collected.
+func ucredFinalizer(u *Ucred) {
+ ucredFree(u.ucred)
+}
+
+func GetPeerUcred(fd uintptr) (*Ucred, error) {
+ var ucred uintptr
+ err := getpeerucred(fd, &ucred)
+ if err != nil {
+ return nil, err
+ }
+ result := &Ucred{
+ ucred: ucred,
+ }
+ // set the finalizer on the result so that the ucred will be freed
+ runtime.SetFinalizer(result, ucredFinalizer)
+ return result, nil
+}
+
+func UcredGet(pid int) (*Ucred, error) {
+ ucred, err := ucredGet(pid)
+ if err != nil {
+ return nil, err
+ }
+ result := &Ucred{
+ ucred: ucred,
+ }
+ // set the finalizer on the result so that the ucred will be freed
+ runtime.SetFinalizer(result, ucredFinalizer)
+ return result, nil
+}
+
+func (u *Ucred) Geteuid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGeteuid(u.ucred)
+}
+
+func (u *Ucred) Getruid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetruid(u.ucred)
+}
+
+func (u *Ucred) Getsuid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetsuid(u.ucred)
+}
+
+func (u *Ucred) Getegid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetegid(u.ucred)
+}
+
+func (u *Ucred) Getrgid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetrgid(u.ucred)
+}
+
+func (u *Ucred) Getsgid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetsgid(u.ucred)
+}
+
+func (u *Ucred) Getpid() int {
+ defer runtime.KeepAlive(u)
+ return ucredGetpid(u.ucred)
+}
FAN_REPORT_DFID_NAME = 0xc00
FAN_REPORT_DFID_NAME_TARGET = 0x1e00
FAN_REPORT_DIR_FID = 0x400
+ FAN_REPORT_FD_ERROR = 0x2000
FAN_REPORT_FID = 0x200
FAN_REPORT_NAME = 0x800
FAN_REPORT_PIDFD = 0x80
FUSE_SUPER_MAGIC = 0x65735546
FUTEXFS_SUPER_MAGIC = 0xbad1dea
F_ADD_SEALS = 0x409
+ F_CREATED_QUERY = 0x404
F_DUPFD = 0x0
F_DUPFD_CLOEXEC = 0x406
+ F_DUPFD_QUERY = 0x403
F_EXLCK = 0x4
F_GETFD = 0x1
F_GETFL = 0x3
IPPROTO_ROUTING = 0x2b
IPPROTO_RSVP = 0x2e
IPPROTO_SCTP = 0x84
+ IPPROTO_SMC = 0x100
IPPROTO_TCP = 0x6
IPPROTO_TP = 0x1d
IPPROTO_UDP = 0x11
IPV6_UNICAST_IF = 0x4c
IPV6_USER_FLOW = 0xe
IPV6_V6ONLY = 0x1a
+ IPV6_VERSION = 0x60
+ IPV6_VERSION_MASK = 0xf0
IPV6_XFRM_POLICY = 0x23
IP_ADD_MEMBERSHIP = 0x23
IP_ADD_SOURCE_MEMBERSHIP = 0x27
MADV_UNMERGEABLE = 0xd
MADV_WILLNEED = 0x3
MADV_WIPEONFORK = 0x12
+ MAP_DROPPABLE = 0x8
MAP_FILE = 0x0
MAP_FIXED = 0x10
MAP_FIXED_NOREPLACE = 0x100000
MSG_PEEK = 0x2
MSG_PROXY = 0x10
MSG_RST = 0x1000
+ MSG_SOCK_DEVMEM = 0x2000000
MSG_SYN = 0x400
MSG_TRUNC = 0x20
MSG_TRYHARD = 0x4
NFC_ATR_REQ_MAXSIZE = 0x40
NFC_ATR_RES_GB_MAXSIZE = 0x2f
NFC_ATR_RES_MAXSIZE = 0x40
+ NFC_ATS_MAXSIZE = 0x14
NFC_COMM_ACTIVE = 0x0
NFC_COMM_PASSIVE = 0x1
NFC_DEVICE_NAME_MAXSIZE = 0x8
NFNL_SUBSYS_QUEUE = 0x3
NFNL_SUBSYS_ULOG = 0x4
NFS_SUPER_MAGIC = 0x6969
+ NFT_BITWISE_BOOL = 0x0
NFT_CHAIN_FLAGS = 0x7
NFT_CHAIN_MAXNAMELEN = 0x100
NFT_CT_MAX = 0x17
PR_GET_PDEATHSIG = 0x2
PR_GET_SECCOMP = 0x15
PR_GET_SECUREBITS = 0x1b
+ PR_GET_SHADOW_STACK_STATUS = 0x4a
PR_GET_SPECULATION_CTRL = 0x34
PR_GET_TAGGED_ADDR_CTRL = 0x38
PR_GET_THP_DISABLE = 0x2a
PR_GET_TIMING = 0xd
PR_GET_TSC = 0x19
PR_GET_UNALIGN = 0x5
+ PR_LOCK_SHADOW_STACK_STATUS = 0x4c
PR_MCE_KILL = 0x21
PR_MCE_KILL_CLEAR = 0x0
PR_MCE_KILL_DEFAULT = 0x2
PR_PAC_GET_ENABLED_KEYS = 0x3d
PR_PAC_RESET_KEYS = 0x36
PR_PAC_SET_ENABLED_KEYS = 0x3c
+ PR_PMLEN_MASK = 0x7f000000
+ PR_PMLEN_SHIFT = 0x18
PR_PPC_DEXCR_CTRL_CLEAR = 0x4
PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC = 0x10
PR_PPC_DEXCR_CTRL_EDITABLE = 0x1
PR_SET_PTRACER = 0x59616d61
PR_SET_SECCOMP = 0x16
PR_SET_SECUREBITS = 0x1c
+ PR_SET_SHADOW_STACK_STATUS = 0x4b
PR_SET_SPECULATION_CTRL = 0x35
PR_SET_SYSCALL_USER_DISPATCH = 0x3b
PR_SET_TAGGED_ADDR_CTRL = 0x37
PR_SET_UNALIGN = 0x6
PR_SET_VMA = 0x53564d41
PR_SET_VMA_ANON_NAME = 0x0
+ PR_SHADOW_STACK_ENABLE = 0x1
+ PR_SHADOW_STACK_PUSH = 0x4
+ PR_SHADOW_STACK_WRITE = 0x2
PR_SME_GET_VL = 0x40
PR_SME_SET_VL = 0x3f
PR_SME_SET_VL_ONEXEC = 0x40000
RTM_NEWNEXTHOP = 0x68
RTM_NEWNEXTHOPBUCKET = 0x74
RTM_NEWNSID = 0x58
- RTM_NEWNVLAN = 0x70
RTM_NEWPREFIX = 0x34
RTM_NEWQDISC = 0x24
RTM_NEWROUTE = 0x18
RTM_NEWTCLASS = 0x28
RTM_NEWTFILTER = 0x2c
RTM_NEWTUNNEL = 0x78
+ RTM_NEWVLAN = 0x70
RTM_NR_FAMILIES = 0x1b
RTM_NR_MSGTYPES = 0x6c
RTM_SETDCB = 0x4f
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
F_SETOWN = 0x8
F_UNLCK = 0x2
F_WRLCK = 0x1
+ GCS_MAGIC = 0x47435300
HIDIOCGRAWINFO = 0x80084803
HIDIOCGRDESC = 0x90044802
HIDIOCGRDESCSIZE = 0x80044801
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x80
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x80
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x80
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x80
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x80
IUCLC = 0x1000
IXOFF = 0x400
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x80
IUCLC = 0x1000
IXOFF = 0x400
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x80
IUCLC = 0x1000
IXOFF = 0x400
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xffffff0f
+ IPV6_FLOWLABEL_MASK = 0xffff0f00
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x80000
IN_NONBLOCK = 0x800
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x7b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x36
SCM_TIMESTAMPING_PKTINFO = 0x3a
SCM_TIMESTAMPNS = 0x23
+ SCM_TS_OPT_ID = 0x51
SCM_TXTIME = 0x3d
SCM_WIFI_STATUS = 0x29
SECCOMP_IOCTL_NOTIF_ADDFD = 0x40182103
IN_CLOEXEC = 0x400000
IN_NONBLOCK = 0x4000
IOCTL_VM_SOCKETS_GET_LOCAL_CID = 0x200007b9
+ IPV6_FLOWINFO_MASK = 0xfffffff
+ IPV6_FLOWLABEL_MASK = 0xfffff
ISIG = 0x1
IUCLC = 0x200
IXOFF = 0x1000
SCM_TIMESTAMPING_OPT_STATS = 0x38
SCM_TIMESTAMPING_PKTINFO = 0x3c
SCM_TIMESTAMPNS = 0x21
+ SCM_TS_OPT_ID = 0x5a
SCM_TXTIME = 0x3f
SCM_WIFI_STATUS = 0x25
SECCOMP_IOCTL_NOTIF_ADDFD = 0x80182103
//go:cgo_import_dynamic libc_getpeername getpeername "libsocket.so"
//go:cgo_import_dynamic libc_setsockopt setsockopt "libsocket.so"
//go:cgo_import_dynamic libc_recvfrom recvfrom "libsocket.so"
+//go:cgo_import_dynamic libc_getpeerucred getpeerucred "libc.so"
+//go:cgo_import_dynamic libc_ucred_get ucred_get "libc.so"
+//go:cgo_import_dynamic libc_ucred_geteuid ucred_geteuid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getegid ucred_getegid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getruid ucred_getruid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getrgid ucred_getrgid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getsuid ucred_getsuid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getsgid ucred_getsgid "libc.so"
+//go:cgo_import_dynamic libc_ucred_getpid ucred_getpid "libc.so"
+//go:cgo_import_dynamic libc_ucred_free ucred_free "libc.so"
//go:cgo_import_dynamic libc_port_create port_create "libc.so"
//go:cgo_import_dynamic libc_port_associate port_associate "libc.so"
//go:cgo_import_dynamic libc_port_dissociate port_dissociate "libc.so"
//go:linkname procgetpeername libc_getpeername
//go:linkname procsetsockopt libc_setsockopt
//go:linkname procrecvfrom libc_recvfrom
+//go:linkname procgetpeerucred libc_getpeerucred
+//go:linkname procucred_get libc_ucred_get
+//go:linkname procucred_geteuid libc_ucred_geteuid
+//go:linkname procucred_getegid libc_ucred_getegid
+//go:linkname procucred_getruid libc_ucred_getruid
+//go:linkname procucred_getrgid libc_ucred_getrgid
+//go:linkname procucred_getsuid libc_ucred_getsuid
+//go:linkname procucred_getsgid libc_ucred_getsgid
+//go:linkname procucred_getpid libc_ucred_getpid
+//go:linkname procucred_free libc_ucred_free
//go:linkname procport_create libc_port_create
//go:linkname procport_associate libc_port_associate
//go:linkname procport_dissociate libc_port_dissociate
procgetpeername,
procsetsockopt,
procrecvfrom,
+ procgetpeerucred,
+ procucred_get,
+ procucred_geteuid,
+ procucred_getegid,
+ procucred_getruid,
+ procucred_getrgid,
+ procucred_getsuid,
+ procucred_getsgid,
+ procucred_getpid,
+ procucred_free,
procport_create,
procport_associate,
procport_dissociate,
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+func getpeerucred(fd uintptr, ucred *uintptr) (err error) {
+ _, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procgetpeerucred)), 2, uintptr(fd), uintptr(unsafe.Pointer(ucred)), 0, 0, 0, 0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGet(pid int) (ucred uintptr, err error) {
+ r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procucred_get)), 1, uintptr(pid), 0, 0, 0, 0, 0)
+ ucred = uintptr(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGeteuid(ucred uintptr) (uid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_geteuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ uid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetegid(ucred uintptr) (gid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getegid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ gid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetruid(ucred uintptr) (uid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getruid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ uid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetrgid(ucred uintptr) (gid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getrgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ gid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetsuid(ucred uintptr) (uid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsuid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ uid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetsgid(ucred uintptr) (gid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getsgid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ gid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredGetpid(ucred uintptr) (pid int) {
+ r0, _, _ := sysvicall6(uintptr(unsafe.Pointer(&procucred_getpid)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ pid = int(r0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func ucredFree(ucred uintptr) {
+ sysvicall6(uintptr(unsafe.Pointer(&procucred_free)), 1, uintptr(ucred), 0, 0, 0, 0, 0)
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
func port_create() (n int, err error) {
r0, _, e1 := sysvicall6(uintptr(unsafe.Pointer(&procport_create)), 0, 0, 0, 0, 0, 0, 0)
n = int(r0)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 4460
SYS_LSM_LIST_MODULES = 4461
SYS_MSEAL = 4462
+ SYS_SETXATTRAT = 4463
+ SYS_GETXATTRAT = 4464
+ SYS_LISTXATTRAT = 4465
+ SYS_REMOVEXATTRAT = 4466
)
SYS_LSM_SET_SELF_ATTR = 5460
SYS_LSM_LIST_MODULES = 5461
SYS_MSEAL = 5462
+ SYS_SETXATTRAT = 5463
+ SYS_GETXATTRAT = 5464
+ SYS_LISTXATTRAT = 5465
+ SYS_REMOVEXATTRAT = 5466
)
SYS_LSM_SET_SELF_ATTR = 5460
SYS_LSM_LIST_MODULES = 5461
SYS_MSEAL = 5462
+ SYS_SETXATTRAT = 5463
+ SYS_GETXATTRAT = 5464
+ SYS_LISTXATTRAT = 5465
+ SYS_REMOVEXATTRAT = 5466
)
SYS_LSM_SET_SELF_ATTR = 4460
SYS_LSM_LIST_MODULES = 4461
SYS_MSEAL = 4462
+ SYS_SETXATTRAT = 4463
+ SYS_GETXATTRAT = 4464
+ SYS_LISTXATTRAT = 4465
+ SYS_REMOVEXATTRAT = 4466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
SYS_LSM_SET_SELF_ATTR = 460
SYS_LSM_LIST_MODULES = 461
SYS_MSEAL = 462
+ SYS_SETXATTRAT = 463
+ SYS_GETXATTRAT = 464
+ SYS_LISTXATTRAT = 465
+ SYS_REMOVEXATTRAT = 466
)
NL80211_ATTR_MAC_HINT = 0xc8
NL80211_ATTR_MAC_MASK = 0xd7
NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca
- NL80211_ATTR_MAX = 0x14c
+ NL80211_ATTR_MAX = 0x14d
NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4
NL80211_ATTR_MAX_CSA_COUNTERS = 0xce
NL80211_ATTR_MAX_MATCH_SETS = 0x85
NL80211_MNTR_FLAG_CONTROL = 0x3
NL80211_MNTR_FLAG_COOK_FRAMES = 0x5
NL80211_MNTR_FLAG_FCSFAIL = 0x1
- NL80211_MNTR_FLAG_MAX = 0x6
+ NL80211_MNTR_FLAG_MAX = 0x7
NL80211_MNTR_FLAG_OTHER_BSS = 0x4
NL80211_MNTR_FLAG_PLCPFAIL = 0x2
NL80211_MPATH_FLAG_ACTIVE = 0x1
Family uint8
Protocol uint8
}
+
+const RTM_NEWNVLAN = 0x70
// LoadDLL loads DLL file into memory.
//
// Warning: using LoadDLL without an absolute path name is subject to
-// DLL preloading attacks. To safely load a system DLL, use LazyDLL
-// with System set to true, or use LoadLibraryEx directly.
+// DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL],
+// or use [LoadLibraryEx] directly.
func LoadDLL(name string) (dll *DLL, err error) {
namep, err := UTF16PtrFromString(name)
if err != nil {
}
// NewLazyDLL creates new LazyDLL associated with DLL file.
+//
+// Warning: using NewLazyDLL without an absolute path name is subject to
+// DLL preloading attacks. To safely load a system DLL, use [NewLazySystemDLL].
func NewLazyDLL(name string) *LazyDLL {
return &LazyDLL{Name: name}
}
}
return &DLL{Name: name, Handle: h}, nil
}
-
-type errString string
-
-func (s errString) Error() string { return string(s) }
// there is no possibility of strings from the crash report (which may
// contain PII) leaking into the telemetry system.
func parseStackPCs(crash string) ([]uintptr, error) {
+ // getSymbol parses the symbol name out of a line of the form:
+ // SYMBOL(ARGS)
+ //
+ // Note: SYMBOL may contain parens "pkg.(*T).method". However, type
+ // parameters are always replaced with ..., so they cannot introduce
+ // more parens. e.g., "pkg.(*T[...]).method".
+ //
+ // ARGS can contain parens. We want the first paren that is not
+ // immediately preceded by a ".".
+ //
+ // TODO(prattmic): This is mildly complicated and is only used to find
+ // runtime.sigpanic, so perhaps simplify this by checking explicitly
+ // for sigpanic.
+ getSymbol := func(line string) (string, error) {
+ var prev rune
+ for i, c := range line {
+ if line[i] != '(' {
+ prev = c
+ continue
+ }
+ if prev == '.' {
+ prev = c
+ continue
+ }
+ return line[:i], nil
+ }
+ return "", fmt.Errorf("no symbol for stack frame: %s", line)
+ }
+
// getPC parses the PC out of a line of the form:
// \tFILE:LINE +0xRELPC sp=... fp=... pc=...
getPC := func(line string) (uint64, error) {
childSentinel = sentinel()
on = false // are we in the first running goroutine?
lines = strings.Split(crash, "\n")
+ symLine = true // within a goroutine, every other line is a symbol or file/line/pc location, starting with symbol.
+ currSymbol string
+ prevSymbol string // symbol of the most recent previous frame with a PC.
)
for i := 0; i < len(lines); i++ {
line := lines[i]
// Note: SYMBOL may contain parens "pkg.(*T).method"
// The RELPC is sometimes missing.
- // Skip the symbol(args) line.
- i++
- if i == len(lines) {
- break
- }
- line = lines[i]
+ if symLine {
+ var err error
+ currSymbol, err = getSymbol(line)
+ if err != nil {
+ return nil, fmt.Errorf("error extracting symbol: %v", err)
+ }
- // Parse the PC, and correct for the parent and child's
- // different mappings of the text section.
- pc, err := getPC(line)
- if err != nil {
- // Inlined frame, perhaps; skip it.
- continue
+ symLine = false // Next line is FILE:LINE.
+ } else {
+ // Parse the PC, and correct for the parent and child's
+ // different mappings of the text section.
+ pc, err := getPC(line)
+ if err != nil {
+ // Inlined frame, perhaps; skip it.
+
+ // Done with this frame. Next line is a new frame.
+ //
+ // Don't update prevSymbol; we only want to
+ // track frames with a PC.
+ currSymbol = ""
+ symLine = true
+ continue
+ }
+
+ pc = pc-parentSentinel+childSentinel
+
+ // If the previous frame was sigpanic, then this frame
+ // was a trap (e.g., SIGSEGV).
+ //
+ // Typically all middle frames are calls, and report
+ // the "return PC". That is, the instruction following
+ // the CALL where the callee will eventually return to.
+ //
+ // runtime.CallersFrames is aware of this property and
+ // will decrement each PC by 1 to "back up" to the
+ // location of the CALL, which is the actual line
+ // number the user expects.
+ //
+ // This does not work for traps, as a trap is not a
+ // call, so the reported PC is not the return PC, but
+ // the actual PC of the trap.
+ //
+ // runtime.Callers is aware of this and will
+ // intentionally increment trap PCs in order to correct
+ // for the decrement performed by
+ // runtime.CallersFrames. See runtime.tracebackPCs and
+ // runtume.(*unwinder).symPC.
+ //
+ // We must emulate the same behavior, otherwise we will
+ // report the location of the instruction immediately
+ // prior to the trap, which may be on a different line,
+ // or even a different inlined functions.
+ //
+ // TODO(prattmic): The runtime applies the same trap
+ // behavior for other "injected calls", see injectCall
+ // in runtime.(*unwinder).next. Do we want to handle
+ // those as well? I don't believe we'd ever see
+ // runtime.asyncPreempt or runtime.debugCallV2 in a
+ // typical crash.
+ if prevSymbol == "runtime.sigpanic" {
+ pc++
+ }
+
+ pcs = append(pcs, uintptr(pc))
+
+ // Done with this frame. Next line is a new frame.
+ prevSymbol = currSymbol
+ currSymbol = ""
+ symLine = true
}
- pcs = append(pcs, uintptr(pc-parentSentinel+childSentinel))
}
return pcs, nil
}
// each pattern starts with a !.
Disable bool
- // SkipDigits is the number of hex digits to use in skip messages.
+ // SkipHexDigits is the number of hex digits to use in skip messages.
// If the set of available changes is the same in each run, as it should be,
// then this doesn't matter: we'll only exclude suffixes that uniquely identify
// a given change. But for some programs, especially bisecting runtime
// AllPackageFacts returns a new slice containing all package
// facts of the analysis's FactTypes in unspecified order.
+ // See comments for AllObjectFacts.
AllPackageFacts func() []PackageFact
// AllObjectFacts returns a new slice containing all object
// facts of the analysis's FactTypes in unspecified order.
+ //
+ // The result includes all facts exported by packages
+ // whose symbols are referenced by the current package
+ // (by qualified identifiers or field/method selections).
+ // And it includes all facts exported from the current
+ // package by the current analysis pass.
AllObjectFacts func() []ObjectFact
/* Further fields may be added in future. */
// user can choose to apply to their code. Usually the SuggestedFix is
// meant to fix the issue flagged by the diagnostic.
//
-// The TextEdits must not overlap, nor contain edits for other packages.
+// The TextEdits must not overlap, nor contain edits for other
+// packages. Edits need not be totally ordered, but the order
+// determines how insertions at the same point will be applied.
type SuggestedFix struct {
// A verb phrase describing the fix, to be shown to
// a user trying to decide whether to accept it.
setFalse
)
-func triStateFlag(name string, value triState, usage string) *triState {
- flag.Var(&value, name, usage)
- return &value
-}
-
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
func (ts *triState) Get() interface{} {
return *ts == setTrue
}
-func (ts triState) isTrue() bool {
- return ts == setTrue
-}
-
func (ts *triState) Set(value string) error {
b, err := strconv.ParseBool(value)
if err != nil {
elem := tu.Elem()
// Calculate offset of each element array.
fields := []*types.Var{
- types.NewVar(token.NoPos, nil, "fake0", elem),
- types.NewVar(token.NoPos, nil, "fake1", elem),
+ types.NewField(token.NoPos, nil, "fake0", elem, false),
+ types.NewField(token.NoPos, nil, "fake1", elem, false),
}
offsets := arch.sizes.Offsetsof(fields)
elemoff := int(offsets[1])
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) {
continue // short-circuit the heavy-weight gofmt check
}
- le := analysisutil.Format(pass.Fset, lhs)
- re := analysisutil.Format(pass.Fset, rhs)
+ le := analysisinternal.Format(pass.Fset, lhs)
+ re := analysisinternal.Format(pass.Fset, rhs)
if le == re {
pass.Report(analysis.Diagnostic{
Pos: stmt.Pos(), Message: fmt.Sprintf("self-assignment of %s to %s", re, le),
- SuggestedFixes: []analysis.SuggestedFix{
- {Message: "Remove", TextEdits: []analysis.TextEdit{
- {Pos: stmt.Pos(), End: stmt.End(), NewText: []byte{}},
- }},
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: "Remove self-assignment",
+ TextEdits: []analysis.TextEdit{{
+ Pos: stmt.Pos(),
+ End: stmt.End(),
+ }}},
},
})
}
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
- if !analysisutil.Imports(pass.Pkg, "sync/atomic") {
+func run(pass *analysis.Pass) (any, error) {
+ if !analysisinternal.Imports(pass.Pkg, "sync/atomic") {
return nil, nil // doesn't directly import sync/atomic
}
if !ok {
continue
}
- fn := typeutil.StaticCallee(pass.TypesInfo, call)
- if analysisutil.IsFunctionNamed(fn, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") {
+ obj := typeutil.Callee(pass.TypesInfo, call)
+ if analysisinternal.IsFunctionNamed(obj, "sync/atomic", "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr") {
checkAtomicAddAssignment(pass, n.Lhs[i], call)
}
}
arg := call.Args[0]
broken := false
- gofmt := func(e ast.Expr) string { return analysisutil.Format(pass.Fset, e) }
+ gofmt := func(e ast.Expr) string { return analysisinternal.Format(pass.Fset, e) }
if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND {
broken = gofmt(left) == gofmt(uarg.X)
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
)
const Doc = "check for common mistakes involving boolean operators"
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
nodeFilter := []ast.Node{
func (op boolOp) checkRedundant(pass *analysis.Pass, exprs []ast.Expr) {
seen := make(map[string]bool)
for _, e := range exprs {
- efmt := analysisutil.Format(pass.Fset, e)
+ efmt := analysisinternal.Format(pass.Fset, e)
if seen[efmt] {
pass.ReportRangef(e, "redundant %s: %s %s %s", op.name, efmt, op.tok, efmt)
} else {
}
// e is of the form 'x != c' or 'x == c'.
- xfmt := analysisutil.Format(pass.Fset, x)
- efmt := analysisutil.Format(pass.Fset, e)
+ xfmt := analysisinternal.Format(pass.Fset, x)
+ efmt := analysisinternal.Format(pass.Fset, e)
if prev, found := seen[xfmt]; found {
// checkRedundant handles the case in which efmt == prev.
if efmt != prev {
"strconv"
"golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
const debug = false
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
- if !analysisutil.Imports(pass.Pkg, "runtime/cgo") {
+func run(pass *analysis.Pass) (any, error) {
+ if !analysisinternal.Imports(pass.Pkg, "runtime/cgo") {
return nil, nil // doesn't use cgo
}
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/versions"
)
lhs := assign.Lhs
for i, x := range assign.Rhs {
if path := lockPathRhs(pass, x); path != nil {
- pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisutil.Format(pass.Fset, assign.Lhs[i]), path)
+ pass.ReportRangef(x, "assignment copies lock value to %v: %v", analysisinternal.Format(pass.Fset, assign.Lhs[i]), path)
lhs = nil // An lhs has been reported. We prefer the assignment warning and do not report twice.
}
}
if id, ok := l.(*ast.Ident); ok && id.Name != "_" {
if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil {
if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil {
- pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisutil.Format(pass.Fset, l), path)
+ pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", analysisinternal.Format(pass.Fset, l), path)
}
}
}
x = node.Value
}
if path := lockPathRhs(pass, x); path != nil {
- pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisutil.Format(pass.Fset, x), path)
+ pass.ReportRangef(x, "literal copies lock value from %v: %v", analysisinternal.Format(pass.Fset, x), path)
}
}
}
}
for _, x := range ce.Args {
if path := lockPathRhs(pass, x); path != nil {
- pass.ReportRangef(x, "call of %s copies lock value: %v", analysisutil.Format(pass.Fset, ce.Fun), path)
+ pass.ReportRangef(x, "call of %s copies lock value: %v", analysisinternal.Format(pass.Fset, ce.Fun), path)
}
}
}
return
}
if path := lockPath(pass.Pkg, typ, nil); path != nil {
- pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisutil.Format(pass.Fset, e), path)
+ pass.Reportf(e.Pos(), "range var %s copies lock: %v", analysisinternal.Format(pass.Fset, e), path)
}
}
// In go1.10, sync.noCopy did not implement Locker.
// (The Unlock method was added only in CL 121876.)
// TODO(adonovan): remove workaround when we drop go1.10.
- if analysisutil.IsNamedType(typ, "sync", "noCopy") {
+ if analysisinternal.IsTypeNamed(typ, "sync", "noCopy") {
return []string{typ.String()}
}
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
- if !analysisutil.Imports(pass.Pkg, "time") {
+func run(pass *analysis.Pass) (any, error) {
+ if !analysisinternal.Imports(pass.Pkg, "time") {
return nil, nil
}
checkDeferCall := func(node ast.Node) bool {
switch v := node.(type) {
case *ast.CallExpr:
- if analysisutil.IsFunctionNamed(typeutil.StaticCallee(pass.TypesInfo, v), "time", "Since") {
+ if analysisinternal.IsFunctionNamed(typeutil.Callee(pass.TypesInfo, v), "time", "Since") {
pass.Reportf(v.Pos(), "call to time.Since is not deferred")
}
case *ast.FuncLit:
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
const Doc = `report passing non-pointer or non-error values to errors.As
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+func run(pass *analysis.Pass) (any, error) {
switch pass.Pkg.Path() {
case "errors", "errors_test":
// These packages know how to use their own APIs.
return nil, nil
}
- if !analysisutil.Imports(pass.Pkg, "errors") {
+ if !analysisinternal.Imports(pass.Pkg, "errors") {
return nil, nil // doesn't directly import errors
}
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
- fn := typeutil.StaticCallee(pass.TypesInfo, call)
- if !analysisutil.IsFunctionNamed(fn, "errors", "As") {
+ obj := typeutil.Callee(pass.TypesInfo, call)
+ if !analysisinternal.IsFunctionNamed(obj, "errors", "As") {
return
}
if len(call.Args) < 2 {
"go/build"
"regexp"
"strings"
+ "unicode"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
Run: run,
}
-var (
- re = regexp.MustCompile
- asmWriteBP = re(`,\s*BP$`) // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
- asmMentionBP = re(`\bBP\b`)
- asmControlFlow = re(`^(J|RET)`)
-)
+// Per-architecture checks for instructions.
+// Assume comments, leading and trailing spaces are removed.
+type arch struct {
+ isFPWrite func(string) bool
+ isFPRead func(string) bool
+ isBranch func(string) bool
+}
+
+var re = regexp.MustCompile
+
+func hasAnyPrefix(s string, prefixes ...string) bool {
+ for _, p := range prefixes {
+ if strings.HasPrefix(s, p) {
+ return true
+ }
+ }
+ return false
+}
+
+var arches = map[string]arch{
+ "amd64": {
+ isFPWrite: re(`,\s*BP$`).MatchString, // TODO: can have false positive, e.g. for TESTQ BP,BP. Seems unlikely.
+ isFPRead: re(`\bBP\b`).MatchString,
+ isBranch: func(s string) bool {
+ return hasAnyPrefix(s, "J", "RET")
+ },
+ },
+ "arm64": {
+ isFPWrite: func(s string) bool {
+ if i := strings.LastIndex(s, ","); i > 0 && strings.HasSuffix(s[i:], "R29") {
+ return true
+ }
+ if hasAnyPrefix(s, "LDP", "LDAXP", "LDXP", "CASP") {
+ // Instructions which write to a pair of registers, e.g.
+ // LDP 8(R0), (R26, R29)
+ // CASPD (R2, R3), (R2), (R26, R29)
+ lp := strings.LastIndex(s, "(")
+ rp := strings.LastIndex(s, ")")
+ if lp > -1 && lp < rp {
+ return strings.Contains(s[lp:rp], ",") && strings.Contains(s[lp:rp], "R29")
+ }
+ }
+ return false
+ },
+ isFPRead: re(`\bR29\b`).MatchString,
+ isBranch: func(s string) bool {
+ // Get just the instruction
+ if i := strings.IndexFunc(s, unicode.IsSpace); i > 0 {
+ s = s[:i]
+ }
+ return arm64Branch[s]
+ },
+ },
+}
+
+// arm64 has many control flow instructions.
+// ^(B|RET) isn't sufficient or correct (e.g. BIC, BFI aren't control flow.)
+// It's easier to explicitly enumerate them in a map than to write a regex.
+// Borrowed from Go tree, cmd/asm/internal/arch/arm64.go
+var arm64Branch = map[string]bool{
+ "B": true,
+ "BL": true,
+ "BEQ": true,
+ "BNE": true,
+ "BCS": true,
+ "BHS": true,
+ "BCC": true,
+ "BLO": true,
+ "BMI": true,
+ "BPL": true,
+ "BVS": true,
+ "BVC": true,
+ "BHI": true,
+ "BLS": true,
+ "BGE": true,
+ "BLT": true,
+ "BGT": true,
+ "BLE": true,
+ "CBZ": true,
+ "CBZW": true,
+ "CBNZ": true,
+ "CBNZW": true,
+ "JMP": true,
+ "TBNZ": true,
+ "TBZ": true,
+ "RET": true,
+}
func run(pass *analysis.Pass) (interface{}, error) {
- if build.Default.GOARCH != "amd64" { // TODO: arm64 also?
+ arch, ok := arches[build.Default.GOARCH]
+ if !ok {
return nil, nil
}
if build.Default.GOOS != "linux" && build.Default.GOOS != "darwin" {
line = line[:i]
}
line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
// We start checking code at a TEXT line for a frameless function.
if strings.HasPrefix(line, "TEXT") && strings.Contains(line, "(SB)") && strings.Contains(line, "$0") {
continue
}
- if asmWriteBP.MatchString(line) { // clobber of BP, function is not OK
+ if arch.isFPWrite(line) {
pass.Reportf(analysisutil.LineStart(tf, lineno), "frame pointer is clobbered before saving")
active = false
continue
}
- if asmMentionBP.MatchString(line) { // any other use of BP might be a read, so function is OK
- active = false
- continue
- }
- if asmControlFlow.MatchString(line) { // give up after any branch instruction
+ if arch.isFPRead(line) || arch.isBranch(line) {
active = false
continue
}
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typesinternal"
)
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+func run(pass *analysis.Pass) (any, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
// Fast path: if the package doesn't import net/http,
// skip the traversal.
- if !analysisutil.Imports(pass.Pkg, "net/http") {
+ if !analysisinternal.Imports(pass.Pkg, "net/http") {
return nil, nil
}
return false // the function called does not return two values.
}
isPtr, named := typesinternal.ReceiverNamed(res.At(0))
- if !isPtr || named == nil || !analysisutil.IsNamedType(named, "net/http", "Response") {
+ if !isPtr || named == nil || !analysisinternal.IsTypeNamed(named, "net/http", "Response") {
return false // the first return type is not *http.Response.
}
return ok && id.Name == "http" // function in net/http package.
}
- if analysisutil.IsNamedType(typ, "net/http", "Client") {
+ if analysisinternal.IsTypeNamed(typ, "net/http", "Client") {
return true // method on http.Client.
}
ptr, ok := types.Unalias(typ).(*types.Pointer)
- return ok && analysisutil.IsNamedType(ptr.Elem(), "net/http", "Client") // method on *http.Client.
+ return ok && analysisinternal.IsTypeNamed(ptr.Elem(), "net/http", "Client") // method on *http.Client.
}
// restOfBlock, given a traversal stack, finds the innermost containing
package analysisutil
import (
- "bytes"
"go/ast"
- "go/printer"
"go/token"
"go/types"
"os"
"golang.org/x/tools/internal/analysisinternal"
)
-// Format returns a string representation of the expression.
-func Format(fset *token.FileSet, x ast.Expr) string {
- var b bytes.Buffer
- printer.Fprint(&b, fset, x)
- return b.String()
-}
-
// HasSideEffects reports whether evaluation of e has side effects.
func HasSideEffects(info *types.Info, e ast.Expr) bool {
safe := true
}
}
-// Imports returns true if path is imported by pkg.
-func Imports(pkg *types.Package, path string) bool {
- for _, imp := range pkg.Imports() {
- if imp.Path() == path {
- return true
- }
- }
- return false
-}
-
-// IsNamedType reports whether t is the named type with the given package path
-// and one of the given names.
-// This function avoids allocating the concatenation of "pkg.Name",
-// which is important for the performance of syntax matching.
-func IsNamedType(t types.Type, pkgPath string, names ...string) bool {
- n, ok := types.Unalias(t).(*types.Named)
- if !ok {
- return false
- }
- obj := n.Obj()
- if obj == nil || obj.Pkg() == nil || obj.Pkg().Path() != pkgPath {
- return false
- }
- name := obj.Name()
- for _, n := range names {
- if name == n {
- return true
- }
- }
- return false
-}
-
-// IsFunctionNamed reports whether f is a top-level function defined in the
-// given package and has one of the given names.
-// It returns false if f is nil or a method.
-func IsFunctionNamed(f *types.Func, pkgPath string, names ...string) bool {
- if f == nil {
- return false
- }
- if f.Pkg() == nil || f.Pkg().Path() != pkgPath {
- return false
- }
- if f.Type().(*types.Signature).Recv() != nil {
- return false
- }
- for _, n := range names {
- if f.Name() == n {
- return true
- }
- }
- return false
-}
-
var MustExtractDoc = analysisinternal.MustExtractDoc
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"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/typesinternal"
"golang.org/x/tools/internal/versions"
)
// Check that the receiver is a <pkgPath>.<typeName> or
// *<pkgPath>.<typeName>.
_, named := typesinternal.ReceiverNamed(recv)
- return analysisutil.IsNamedType(named, pkgPath, typeName)
+ return analysisinternal.IsTypeNamed(named, pkgPath, typeName)
}
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
// checkLostCancel analyzes a single named or literal function.
func run(pass *analysis.Pass) (interface{}, error) {
// Fast path: bypass check if file doesn't use context.WithCancel.
- if !analysisutil.Imports(pass.Pkg, contextPackage) {
+ if !analysisinternal.Imports(pass.Pkg, contextPackage) {
return nil, nil
}
package printf
import (
- "bytes"
_ "embed"
"fmt"
"go/ast"
"reflect"
"regexp"
"sort"
- "strconv"
"strings"
- "unicode/utf8"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"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/fmtstr"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/versions"
)
fn, kind := printfNameAndKind(pass, n)
switch kind {
case KindPrintf, KindErrorf:
- checkPrintf(pass, fileVersion, kind, n, fn)
+ checkPrintf(pass, fileVersion, kind, n, fn.FullName())
case KindPrint:
- checkPrint(pass, n, fn)
+ checkPrint(pass, n, fn.FullName())
}
}
})
sig := fn.Type().(*types.Signature)
return sig.Params().Len() == 2 &&
sig.Results().Len() == 0 &&
- analysisutil.IsNamedType(sig.Params().At(0).Type(), "fmt", "State") &&
+ analysisinternal.IsTypeNamed(sig.Params().At(0).Type(), "fmt", "State") &&
types.Identical(sig.Params().At(1).Type(), types.Typ[types.Rune])
}
-// formatState holds the parsed representation of a printf directive such as "%3.*[4]d".
-// It is constructed by parsePrintfVerb.
-type formatState struct {
- verb rune // the format verb: 'd' for "%d"
- format string // the full format directive from % through verb, "%.3d".
- name string // Printf, Sprintf etc.
- flags []byte // the list of # + etc.
- argNums []int // the successive argument numbers that are consumed, adjusted to refer to actual arg in call
- firstArg int // Index of first argument after the format in the Printf call.
- // Used only during parse.
- pass *analysis.Pass
- call *ast.CallExpr
- argNum int // Which argument we're expecting to format now.
- hasIndex bool // Whether the argument is indexed.
- indexPending bool // Whether we have an indexed argument that has not resolved.
- nbytes int // number of bytes of the format string consumed.
-}
-
// checkPrintf checks a call to a formatted print routine such as Printf.
-func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, fn *types.Func) {
+func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.CallExpr, name string) {
idx := formatStringIndex(pass, call)
if idx < 0 || idx >= len(call.Args) {
return
Pos: formatArg.Pos(),
End: formatArg.End(),
Message: fmt.Sprintf("non-constant format string in call to %s",
- fn.FullName()),
+ name),
SuggestedFixes: []analysis.SuggestedFix{{
Message: `Insert "%s" format string`,
TextEdits: []analysis.TextEdit{{
firstArg := idx + 1 // Arguments are immediately after format string.
if !strings.Contains(format, "%") {
if len(call.Args) > firstArg {
- pass.Reportf(call.Lparen, "%s call has arguments but no formatting directives", fn.FullName())
+ pass.Reportf(call.Lparen, "%s call has arguments but no formatting directives", name)
}
return
}
- // Hard part: check formats against args.
- argNum := firstArg
- maxArgNum := firstArg
+
+ // Pass the string constant value so
+ // fmt.Sprintf("%"+("s"), "hi", 3) can be reported as
+ // "fmt.Sprintf call needs 1 arg but has 2 args".
+ operations, err := fmtstr.Parse(format, idx)
+ if err != nil {
+ // All error messages are in predicate form ("call has a problem")
+ // so that they may be affixed into a subject ("log.Printf ").
+ pass.ReportRangef(call.Args[idx], "%s %s", name, err)
+ return
+ }
+
+ // index of the highest used index.
+ maxArgIndex := firstArg - 1
anyIndex := false
- for i, w := 0, 0; i < len(format); i += w {
- w = 1
- if format[i] != '%' {
- continue
- }
- state := parsePrintfVerb(pass, call, fn.FullName(), format[i:], firstArg, argNum)
- if state == nil {
- return
+ // Check formats against args.
+ for _, operation := range operations {
+ if operation.Prec.Index != -1 ||
+ operation.Width.Index != -1 ||
+ operation.Verb.Index != -1 {
+ anyIndex = true
}
- w = len(state.format)
- if !okPrintfArg(pass, call, state) { // One error per format is enough.
+ if !okPrintfArg(pass, call, &maxArgIndex, firstArg, name, operation) {
+ // One error per format is enough.
return
}
- if state.hasIndex {
- anyIndex = true
- }
- if state.verb == 'w' {
+ if operation.Verb.Verb == 'w' {
switch kind {
case KindNone, KindPrint, KindPrintf:
- pass.Reportf(call.Pos(), "%s does not support error-wrapping directive %%w", state.name)
+ pass.Reportf(call.Pos(), "%s does not support error-wrapping directive %%w", name)
return
}
}
- if len(state.argNums) > 0 {
- // Continue with the next sequential argument.
- argNum = state.argNums[len(state.argNums)-1] + 1
- }
- for _, n := range state.argNums {
- if n >= maxArgNum {
- maxArgNum = n + 1
- }
- }
}
// Dotdotdot is hard.
- if call.Ellipsis.IsValid() && maxArgNum >= len(call.Args)-1 {
+ if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
return
}
// If any formats are indexed, extra arguments are ignored.
return
}
// There should be no leftover arguments.
- if maxArgNum != len(call.Args) {
- expect := maxArgNum - firstArg
+ if maxArgIndex+1 < len(call.Args) {
+ expect := maxArgIndex + 1 - firstArg
numArgs := len(call.Args) - firstArg
- pass.ReportRangef(call, "%s call needs %v but has %v", fn.FullName(), count(expect, "arg"), count(numArgs, "arg"))
- }
-}
-
-// parseFlags accepts any printf flags.
-func (s *formatState) parseFlags() {
- for s.nbytes < len(s.format) {
- switch c := s.format[s.nbytes]; c {
- case '#', '0', '+', '-', ' ':
- s.flags = append(s.flags, c)
- s.nbytes++
- default:
- return
- }
+ pass.ReportRangef(call, "%s call needs %v but has %v", name, count(expect, "arg"), count(numArgs, "arg"))
}
}
-// scanNum advances through a decimal number if present.
-func (s *formatState) scanNum() {
- for ; s.nbytes < len(s.format); s.nbytes++ {
- c := s.format[s.nbytes]
- if c < '0' || '9' < c {
- return
- }
- }
-}
-
-// parseIndex scans an index expression. It returns false if there is a syntax error.
-func (s *formatState) parseIndex() bool {
- if s.nbytes == len(s.format) || s.format[s.nbytes] != '[' {
- return true
- }
- // Argument index present.
- s.nbytes++ // skip '['
- start := s.nbytes
- s.scanNum()
- ok := true
- if s.nbytes == len(s.format) || s.nbytes == start || s.format[s.nbytes] != ']' {
- ok = false // syntax error is either missing "]" or invalid index.
- s.nbytes = strings.Index(s.format[start:], "]")
- if s.nbytes < 0 {
- s.pass.ReportRangef(s.call, "%s format %s is missing closing ]", s.name, s.format)
- return false
- }
- s.nbytes = s.nbytes + start
- }
- arg32, err := strconv.ParseInt(s.format[start:s.nbytes], 10, 32)
- if err != nil || !ok || arg32 <= 0 || arg32 > int64(len(s.call.Args)-s.firstArg) {
- s.pass.ReportRangef(s.call, "%s format has invalid argument index [%s]", s.name, s.format[start:s.nbytes])
- return false
- }
- s.nbytes++ // skip ']'
- arg := int(arg32)
- arg += s.firstArg - 1 // We want to zero-index the actual arguments.
- s.argNum = arg
- s.hasIndex = true
- s.indexPending = true
- return true
-}
-
-// parseNum scans a width or precision (or *). It returns false if there's a bad index expression.
-func (s *formatState) parseNum() bool {
- if s.nbytes < len(s.format) && s.format[s.nbytes] == '*' {
- if s.indexPending { // Absorb it.
- s.indexPending = false
- }
- s.nbytes++
- s.argNums = append(s.argNums, s.argNum)
- s.argNum++
- } else {
- s.scanNum()
- }
- return true
-}
-
-// parsePrecision scans for a precision. It returns false if there's a bad index expression.
-func (s *formatState) parsePrecision() bool {
- // If there's a period, there may be a precision.
- if s.nbytes < len(s.format) && s.format[s.nbytes] == '.' {
- s.flags = append(s.flags, '.') // Treat precision as a flag.
- s.nbytes++
- if !s.parseIndex() {
- return false
- }
- if !s.parseNum() {
- return false
- }
- }
- return true
-}
-
-// parsePrintfVerb looks the formatting directive that begins the format string
-// and returns a formatState that encodes what the directive wants, without looking
-// at the actual arguments present in the call. The result is nil if there is an error.
-func parsePrintfVerb(pass *analysis.Pass, call *ast.CallExpr, name, format string, firstArg, argNum int) *formatState {
- state := &formatState{
- format: format,
- name: name,
- flags: make([]byte, 0, 5),
- argNum: argNum,
- argNums: make([]int, 0, 1),
- nbytes: 1, // There's guaranteed to be a percent sign.
- firstArg: firstArg,
- pass: pass,
- call: call,
- }
- // There may be flags.
- state.parseFlags()
- // There may be an index.
- if !state.parseIndex() {
- return nil
- }
- // There may be a width.
- if !state.parseNum() {
- return nil
- }
- // There may be a precision.
- if !state.parsePrecision() {
- return nil
- }
- // Now a verb, possibly prefixed by an index (which we may already have).
- if !state.indexPending && !state.parseIndex() {
- return nil
- }
- if state.nbytes == len(state.format) {
- pass.ReportRangef(call.Fun, "%s format %s is missing verb at end of string", name, state.format)
- return nil
- }
- verb, w := utf8.DecodeRuneInString(state.format[state.nbytes:])
- state.verb = verb
- state.nbytes += w
- if verb != '%' {
- state.argNums = append(state.argNums, state.argNum)
- }
- state.format = state.format[:state.nbytes]
- return state
-}
-
// printfArgType encodes the types of expressions a printf verb accepts. It is a bitmask.
type printfArgType int
{'X', sharpNumFlag, argRune | argInt | argString | argPointer | argFloat | argComplex},
}
-// okPrintfArg compares the formatState to the arguments actually present,
-// reporting any discrepancies it can discern. If the final argument is ellipsissed,
-// there's little it can do for that.
-func okPrintfArg(pass *analysis.Pass, call *ast.CallExpr, state *formatState) (ok bool) {
+// 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, maxArgIndex *int, firstArg int, name string, operation *fmtstr.Operation) (ok bool) {
+ verb := operation.Verb.Verb
var v printVerb
found := false
// Linear scan is fast enough for a small list.
for _, v = range printVerbs {
- if v.verb == state.verb {
+ if v.verb == verb {
found = true
break
}
}
- // Could current arg implement fmt.Formatter?
+ // Could verb's arg implement fmt.Formatter?
// Skip check for the %w verb, which requires an error.
formatter := false
- if v.typ != argError && state.argNum < len(call.Args) {
- if tv, ok := pass.TypesInfo.Types[call.Args[state.argNum]]; ok {
+ if v.typ != argError && operation.Verb.ArgIndex < len(call.Args) {
+ if tv, ok := pass.TypesInfo.Types[call.Args[operation.Verb.ArgIndex]]; ok {
formatter = isFormatter(tv.Type)
}
}
if !formatter {
if !found {
- pass.ReportRangef(call, "%s format %s has unknown verb %c", state.name, state.format, state.verb)
+ pass.ReportRangef(call, "%s format %s has unknown verb %c", name, operation.Text, verb)
return false
}
- for _, flag := range state.flags {
+ for _, flag := range operation.Flags {
// TODO: Disable complaint about '0' for Go 1.10. To be fixed properly in 1.11.
// See issues 23598 and 23605.
if flag == '0' {
continue
}
if !strings.ContainsRune(v.flags, rune(flag)) {
- pass.ReportRangef(call, "%s format %s has unrecognized flag %c", state.name, state.format, flag)
+ pass.ReportRangef(call, "%s format %s has unrecognized flag %c", name, operation.Text, flag)
return false
}
}
}
- // Verb is good. If len(state.argNums)>trueArgs, we have something like %.*s and all
- // but the final arg must be an integer.
- trueArgs := 1
- if state.verb == '%' {
- trueArgs = 0
+
+ var argIndexes []int
+ // First check for *.
+ if operation.Width.Dynamic != -1 {
+ argIndexes = append(argIndexes, operation.Width.Dynamic)
+ }
+ if operation.Prec.Dynamic != -1 {
+ argIndexes = append(argIndexes, operation.Prec.Dynamic)
}
- nargs := len(state.argNums)
- for i := 0; i < nargs-trueArgs; i++ {
- argNum := state.argNums[i]
- if !argCanBeChecked(pass, call, i, state) {
+ // If len(argIndexes)>0, we have something like %.*s and all
+ // indexes in argIndexes must be an integer.
+ for _, argIndex := range argIndexes {
+ if !argCanBeChecked(pass, call, argIndex, firstArg, operation, name) {
return
}
- arg := call.Args[argNum]
+ arg := call.Args[argIndex]
if reason, ok := matchArgType(pass, argInt, arg); !ok {
details := ""
if reason != "" {
details = " (" + reason + ")"
}
- pass.ReportRangef(call, "%s format %s uses non-int %s%s as argument of *", state.name, state.format, analysisutil.Format(pass.Fset, arg), details)
+ pass.ReportRangef(call, "%s format %s uses non-int %s%s as argument of *", name, operation.Text, analysisinternal.Format(pass.Fset, arg), details)
return false
}
}
- if state.verb == '%' || formatter {
+ // Collect to update maxArgNum in one loop.
+ if operation.Verb.ArgIndex != -1 && verb != '%' {
+ argIndexes = append(argIndexes, operation.Verb.ArgIndex)
+ }
+ for _, index := range argIndexes {
+ *maxArgIndex = max(*maxArgIndex, index)
+ }
+
+ // Special case for '%', go will print "fmt.Printf("%10.2%%dhello", 4)"
+ // as "%4hello", discard any runes between the two '%'s, and treat the verb '%'
+ // as an ordinary rune, so early return to skip the type check.
+ if verb == '%' || formatter {
return true
}
- argNum := state.argNums[len(state.argNums)-1]
- if !argCanBeChecked(pass, call, len(state.argNums)-1, state) {
+
+ // Now check verb's type.
+ verbArgIndex := operation.Verb.ArgIndex
+ if !argCanBeChecked(pass, call, verbArgIndex, firstArg, operation, name) {
return false
}
- arg := call.Args[argNum]
- if isFunctionValue(pass, arg) && state.verb != 'p' && state.verb != 'T' {
- pass.ReportRangef(call, "%s format %s arg %s is a func value, not called", state.name, state.format, analysisutil.Format(pass.Fset, arg))
+ arg := call.Args[verbArgIndex]
+ if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
+ pass.ReportRangef(call, "%s format %s arg %s is a func value, not called", name, operation.Text, analysisinternal.Format(pass.Fset, arg))
return false
}
if reason, ok := matchArgType(pass, v.typ, arg); !ok {
if reason != "" {
details = " (" + reason + ")"
}
- pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s%s", state.name, state.format, analysisutil.Format(pass.Fset, arg), typeString, details)
+ pass.ReportRangef(call, "%s format %s has arg %s of wrong type %s%s", name, operation.Text, analysisinternal.Format(pass.Fset, arg), typeString, details)
return false
}
- if v.typ&argString != 0 && v.verb != 'T' && !bytes.Contains(state.flags, []byte{'#'}) {
+ if v.typ&argString != 0 && v.verb != 'T' && !strings.Contains(operation.Flags, "#") {
if methodName, ok := recursiveStringer(pass, arg); ok {
- pass.ReportRangef(call, "%s format %s with arg %s causes recursive %s method call", state.name, state.format, analysisutil.Format(pass.Fset, arg), methodName)
+ pass.ReportRangef(call, "%s format %s with arg %s causes recursive %s method call", name, operation.Text, analysisinternal.Format(pass.Fset, arg), methodName)
return false
}
}
// argCanBeChecked reports whether the specified argument is statically present;
// it may be beyond the list of arguments or in a terminal slice... argument, which
// means we can't see it.
-func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, formatArg int, state *formatState) bool {
- argNum := state.argNums[formatArg]
- if argNum <= 0 {
+func argCanBeChecked(pass *analysis.Pass, call *ast.CallExpr, argIndex, firstArg int, operation *fmtstr.Operation, name string) bool {
+ if argIndex <= 0 {
// Shouldn't happen, so catch it with prejudice.
- panic("negative arg num")
+ panic("negative argIndex")
}
- if argNum < len(call.Args)-1 {
+ if argIndex < len(call.Args)-1 {
return true // Always OK.
}
if call.Ellipsis.IsValid() {
return false // We just can't tell; there could be many more arguments.
}
- if argNum < len(call.Args) {
+ if argIndex < len(call.Args) {
return true
}
// There are bad indexes in the format or there are fewer arguments than the format needs.
// This is the argument number relative to the format: Printf("%s", "hi") will give 1 for the "hi".
- arg := argNum - state.firstArg + 1 // People think of arguments as 1-indexed.
- pass.ReportRangef(call, "%s format %s reads arg #%d, but call has %v", state.name, state.format, arg, count(len(call.Args)-state.firstArg, "arg"))
+ arg := argIndex - firstArg + 1 // People think of arguments as 1-indexed.
+ pass.ReportRangef(call, "%s format %s reads arg #%d, but call has %v", name, operation.Text, arg, count(len(call.Args)-firstArg, "arg"))
return false
}
)
// checkPrint checks a call to an unformatted print routine such as Println.
-func checkPrint(pass *analysis.Pass, call *ast.CallExpr, fn *types.Func) {
+func checkPrint(pass *analysis.Pass, call *ast.CallExpr, name string) {
firstArg := 0
typ := pass.TypesInfo.Types[call.Fun].Type
if typ == nil {
if sel, ok := call.Args[0].(*ast.SelectorExpr); ok {
if x, ok := sel.X.(*ast.Ident); ok {
if x.Name == "os" && strings.HasPrefix(sel.Sel.Name, "Std") {
- pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", fn.FullName(), analysisutil.Format(pass.Fset, call.Args[0]))
+ pass.ReportRangef(call, "%s does not take io.Writer but has first arg %s", name, analysisinternal.Format(pass.Fset, call.Args[0]))
}
}
}
if strings.Contains(s, "%") {
m := printFormatRE.FindStringSubmatch(s)
if m != nil {
- pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", fn.FullName(), m[0])
+ pass.ReportRangef(call, "%s call has possible Printf formatting directive %s", name, m[0])
}
}
}
- if strings.HasSuffix(fn.Name(), "ln") {
+ if strings.HasSuffix(name, "ln") {
// The last item, if a string, should not have a newline.
arg = args[len(args)-1]
if s, ok := stringConstantExpr(pass, arg); ok {
if strings.HasSuffix(s, "\n") {
- pass.ReportRangef(call, "%s arg list ends with redundant newline", fn.FullName())
+ pass.ReportRangef(call, "%s arg list ends with redundant newline", name)
}
}
}
for _, arg := range args {
if isFunctionValue(pass, arg) {
- pass.ReportRangef(call, "%s arg %s is a func value, not called", fn.FullName(), analysisutil.Format(pass.Fset, arg))
+ pass.ReportRangef(call, "%s arg %s is a func value, not called", name, analysisinternal.Format(pass.Fset, arg))
}
if methodName, ok := recursiveStringer(pass, arg); ok {
- pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", fn.FullName(), analysisutil.Format(pass.Fset, arg), methodName)
+ pass.ReportRangef(call, "%s arg %s causes recursive call to %s method", name, analysisinternal.Format(pass.Fset, arg), methodName)
}
}
}
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/typeparams"
)
}
}
if amt >= minSize {
- ident := analysisutil.Format(pass.Fset, x)
+ ident := analysisinternal.Format(pass.Fset, x)
qualifier := ""
if len(sizes) > 1 {
qualifier = "may be "
import (
"bytes"
+ "slices"
+
_ "embed"
"go/ast"
"go/format"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
- if !analysisutil.Imports(pass.Pkg, "os/signal") {
+func run(pass *analysis.Pass) (any, error) {
+ if !analysisinternal.Imports(pass.Pkg, "os/signal") {
return nil, nil // doesn't directly import signal
}
// mutating the AST. See https://golang.org/issue/46129.
chanDeclCopy := &ast.CallExpr{}
*chanDeclCopy = *chanDecl
- chanDeclCopy.Args = append([]ast.Expr(nil), chanDecl.Args...)
+ chanDeclCopy.Args = slices.Clone(chanDecl.Args)
chanDeclCopy.Args = append(chanDeclCopy.Args, &ast.BasicLit{
Kind: token.INT,
Value: "1",
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"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/typesinternal"
)
default:
if unknownArg == nil {
pass.ReportRangef(arg, "%s arg %q should be a string or a slog.Attr (possible missing key or value)",
- shortName(fn), analysisutil.Format(pass.Fset, arg))
+ shortName(fn), analysisinternal.Format(pass.Fset, arg))
} else {
pass.ReportRangef(arg, "%s arg %q should probably be a string or a slog.Attr (previous arg %q cannot be a key)",
- shortName(fn), analysisutil.Format(pass.Fset, arg), analysisutil.Format(pass.Fset, unknownArg))
+ shortName(fn), analysisinternal.Format(pass.Fset, arg), analysisinternal.Format(pass.Fset, unknownArg))
}
// Stop here so we report at most one missing key per call.
return
}
func isAttr(t types.Type) bool {
- return analysisutil.IsNamedType(t, "log/slog", "Attr")
+ return analysisinternal.IsTypeNamed(t, "log/slog", "Attr")
}
// shortName returns a name for the function that is shorter than FullName.
"go/build"
"go/types"
"regexp"
+ "slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
// Prior to go1.22, versions.FileVersion returns only the
// toolchain version, which is of no use to us, so
// disable this analyzer on earlier versions.
- if !slicesContains(build.Default.ReleaseTags, "go1.22") {
+ if !slices.Contains(build.Default.ReleaseTags, "go1.22") {
return nil, nil
}
// Don't report diagnostics for modules marked before go1.21,
// since at that time the go directive wasn't clearly
// specified as a toolchain requirement.
- //
- // TODO(adonovan): after go1.21, call GoVersion directly.
- pkgVersion := any(pass.Pkg).(interface{ GoVersion() string }).GoVersion()
+ pkgVersion := pass.Pkg.GoVersion()
if !versions.AtLeast(pkgVersion, "go1.21") {
return nil, nil
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.File:
- if isGenerated(n) {
+ if ast.IsGenerated(n) {
// Suppress diagnostics in generated files (such as cgo).
fileVersion = ""
} else {
return nil, nil
}
-// Reduced from x/tools/gopls/internal/golang/util.go. Good enough for now.
-// TODO(adonovan): use ast.IsGenerated in go1.21.
-func isGenerated(f *ast.File) bool {
- for _, group := range f.Comments {
- for _, comment := range group.List {
- if matched := generatedRx.MatchString(comment.Text); matched {
- return true
- }
- }
- }
- return false
-}
-
// Matches cgo generated comment as well as the proposed standard:
//
// https://golang.org/s/generatedcode
}
return obj
}
-
-// TODO(adonovan): use go1.21 slices.Contains.
-func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
- for _, elem := range slice {
- if elem == x {
- return true
- }
- }
- return false
-}
// the type has methods, as some {String,GoString,Format}
// may change the behavior of fmt.Sprint.
if len(ttypes) == 1 && len(vtypes) == 1 && types.NewMethodSet(V0).Len() == 0 {
- fmtName, importEdits := analysisinternal.AddImport(pass.TypesInfo, file, arg.Pos(), "fmt", "fmt")
+ _, prefix, importEdits := analysisinternal.AddImport(pass.TypesInfo, file, "fmt", "fmt", "Sprint", arg.Pos())
if types.Identical(T0, types.Typ[types.String]) {
// string(x) -> fmt.Sprint(x)
addFix("Format the number as a decimal", append(importEdits,
analysis.TextEdit{
Pos: call.Fun.Pos(),
End: call.Fun.End(),
- NewText: []byte(fmtName + ".Sprint"),
+ NewText: []byte(prefix + "Sprint"),
}),
)
} else {
analysis.TextEdit{
Pos: call.Lparen + 1,
End: call.Lparen + 1,
- NewText: []byte(fmtName + ".Sprint("),
+ NewText: []byte(prefix + "Sprint("),
},
analysis.TextEdit{
Pos: call.Rparen,
// checkCanonicalFieldTag checks a single struct field tag.
func checkCanonicalFieldTag(pass *analysis.Pass, field *types.Var, tag string, seen *namesSeen) {
switch pass.Pkg.Path() {
- case "encoding/json", "encoding/xml":
+ case "encoding/json", "encoding/json/v2", "encoding/xml":
// These packages know how to use their own APIs.
// Sometimes they are testing what happens to incorrect programs.
return
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
func run(pass *analysis.Pass) (interface{}, error) {
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- if !analysisutil.Imports(pass.Pkg, "testing") {
+ if !analysisinternal.Imports(pass.Pkg, "testing") {
return nil, nil
}
// 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.)
func isMethodNamed(f *types.Func, pkgPath string, names ...string) bool {
if f == nil {
return false
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
if !ok {
return false
}
- return analysisutil.IsNamedType(ptr.Elem(), "testing", testingType)
+ return analysisinternal.IsTypeNamed(ptr.Elem(), "testing", testingType)
}
// Validate that fuzz target function's arguments are of accepted types.
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
+ "golang.org/x/tools/internal/analysisinternal"
)
const badFormat = "2006-02-01"
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+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 !analysisutil.Imports(pass.Pkg, "time").
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
call := n.(*ast.CallExpr)
- fn, ok := typeutil.Callee(pass.TypesInfo, call).(*types.Func)
- if !ok {
- return
- }
- if !isTimeDotFormat(fn) && !isTimeDotParse(fn) {
+ obj := typeutil.Callee(pass.TypesInfo, call)
+ if !analysisinternal.IsMethodNamed(obj, "time", "Time", "Format") &&
+ !analysisinternal.IsFunctionNamed(obj, "time", "Parse") {
return
}
if len(call.Args) > 0 {
return nil, nil
}
-func isTimeDotFormat(f *types.Func) bool {
- if f.Name() != "Format" || f.Pkg() == nil || f.Pkg().Path() != "time" {
- return false
- }
- // Verify that the receiver is time.Time.
- recv := f.Type().(*types.Signature).Recv()
- return recv != nil && analysisutil.IsNamedType(recv.Type(), "time", "Time")
-}
-
-func isTimeDotParse(f *types.Func) bool {
- return analysisutil.IsFunctionNamed(f, "time", "Parse")
-}
-
// badFormatAt return the start of a bad format in e or -1 if no bad format is found.
func badFormatAt(info *types.Info, e ast.Expr) int {
tv, ok := info.Types[e]
Run: run,
}
-func run(pass *analysis.Pass) (interface{}, error) {
+func run(pass *analysis.Pass) (any, error) {
switch pass.Pkg.Path() {
case "encoding/gob", "encoding/json", "encoding/xml", "encoding/asn1":
// These packages know how to use their own APIs.
// unreachable: check for unreachable code
//
// The unreachable analyzer finds statements that execution can never reach
-// because they are preceded by an return statement, a call to panic, an
+// because they are preceded by a return statement, a call to panic, an
// infinite loop, or similar constructs.
package unreachable
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/analysis/passes/internal/analysisutil"
"golang.org/x/tools/go/ast/inspector"
+ "golang.org/x/tools/internal/analysisinternal"
)
//go:embed doc.go
}
switch sel.Sel.Name {
case "Pointer", "UnsafeAddr":
- if analysisutil.IsNamedType(info.Types[sel.X].Type, "reflect", "Value") {
+ if analysisinternal.IsTypeNamed(info.Types[sel.X].Type, "reflect", "Value") {
return true
}
}
// isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader.
func isReflectHeader(t types.Type) bool {
- return analysisutil.IsNamedType(t, "reflect", "SliceHeader", "StringHeader")
+ return analysisinternal.IsTypeNamed(t, "reflect", "SliceHeader", "StringHeader")
}
// func() string
var sigNoArgsStringResult = types.NewSignature(nil, nil,
- types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.String])),
+ types.NewTuple(types.NewParam(token.NoPos, nil, "", types.Typ[types.String])),
false)
type stringSetFlag map[string]bool
}
pass := &analysis.Pass{
- Analyzer: a,
- Fset: fset,
- Files: files,
- OtherFiles: cfg.NonGoFiles,
- IgnoredFiles: cfg.IgnoredFiles,
- Pkg: pkg,
- TypesInfo: info,
- TypesSizes: tc.Sizes,
- TypeErrors: nil, // unitchecker doesn't RunDespiteErrors
- ResultOf: inputs,
- Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
+ Analyzer: a,
+ Fset: fset,
+ Files: files,
+ OtherFiles: cfg.NonGoFiles,
+ IgnoredFiles: cfg.IgnoredFiles,
+ Pkg: pkg,
+ TypesInfo: info,
+ TypesSizes: tc.Sizes,
+ TypeErrors: nil, // unitchecker doesn't RunDespiteErrors
+ 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 {
+ // Since we have diagnostics, the exit code will be nonzero,
+ // so logging these errors is sufficient.
+ log.Println(err)
+ d.SuggestedFixes = nil
+ }
+ act.diagnostics = append(act.diagnostics, d)
+ },
ImportObjectFact: facts.ImportObjectFact,
ExportObjectFact: facts.ExportObjectFact,
AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
Module: module,
}
- pass.ReadFile = analysisinternal.MakeReadFile(pass)
+ pass.ReadFile = analysisinternal.CheckedReadFile(pass, os.ReadFile)
t0 := time.Now()
act.result, act.err = a.Run(pass)
import (
"go/ast"
+ _ "unsafe"
+
+ "golang.org/x/tools/internal/astutil/edge"
)
// An Inspector provides methods for inspecting
events []event
}
+//go:linkname events
+func events(in *Inspector) []event { return in.events }
+
+func packEdgeKindAndIndex(ek edge.Kind, index int) int32 {
+ return int32(uint32(index+1)<<7 | uint32(ek))
+}
+
+// unpackEdgeKindAndIndex unpacks the edge kind and edge index (within
+// an []ast.Node slice) from the parent field of a pop event.
+//
+//go:linkname unpackEdgeKindAndIndex
+func unpackEdgeKindAndIndex(x int32) (edge.Kind, int) {
+ // The "parent" field of a pop node holds the
+ // edge Kind in the lower 7 bits and the index+1
+ // in the upper 25.
+ return edge.Kind(x & 0x7f), int(x>>7) - 1
+}
+
// New returns an Inspector for the specified syntax trees.
func New(files []*ast.File) *Inspector {
return &Inspector{traverse(files)}
// An event represents a push or a pop
// of an ast.Node during a traversal.
type event struct {
- node ast.Node
- typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
- index int // index of corresponding push or pop event
+ node ast.Node
+ typ uint64 // typeOf(node) on push event, or union of typ strictly between push and pop events on pop events
+ index int32 // index of corresponding push or pop event
+ parent int32 // index of parent's push node (push nodes only), or packed edge kind/index (pop nodes only)
}
// TODO: Experiment with storing only the second word of event.node (unsafe.Pointer).
// })
mask := maskOf(types)
- for i := 0; i < len(in.events); {
+ for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
// matches an element of the types slice.
func (in *Inspector) Nodes(types []ast.Node, f func(n ast.Node, push bool) (proceed bool)) {
mask := maskOf(types)
- for i := 0; i < len(in.events); {
+ for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
func (in *Inspector) WithStack(types []ast.Node, f func(n ast.Node, push bool, stack []ast.Node) (proceed bool)) {
mask := maskOf(types)
var stack []ast.Node
- for i := 0; i < len(in.events); {
+ for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
extent += int(f.End() - f.Pos())
}
// This estimate is based on the net/http package.
- capacity := extent * 33 / 100
- if capacity > 1e6 {
- capacity = 1e6 // impose some reasonable maximum
+ capacity := min(extent*33/100, 1e6) // impose some reasonable maximum (1M)
+
+ v := &visitor{
+ events: make([]event, 0, capacity),
+ stack: []item{{index: -1}}, // include an extra event so file nodes have a parent
+ }
+ for _, file := range files {
+ walk(v, edge.Invalid, -1, file)
}
- events := make([]event, 0, capacity)
+ return v.events
+}
- var stack []event
- stack = append(stack, event{}) // include an extra event so file nodes have a parent
- for _, f := range files {
- ast.Inspect(f, func(n ast.Node) bool {
- if n != nil {
- // push
- ev := event{
- node: n,
- typ: 0, // temporarily used to accumulate type bits of subtree
- index: len(events), // push event temporarily holds own index
- }
- stack = append(stack, ev)
- events = append(events, ev)
- } else {
- // pop
- top := len(stack) - 1
- ev := stack[top]
- typ := typeOf(ev.node)
- push := ev.index
- parent := top - 1
-
- events[push].typ = typ // set type of push
- stack[parent].typ |= typ | ev.typ // parent's typ contains push and pop's typs.
- events[push].index = len(events) // make push refer to pop
-
- stack = stack[:top]
- events = append(events, ev)
- }
- return true
- })
+type visitor struct {
+ events []event
+ stack []item
+}
+
+type item struct {
+ index int32 // index of current node's push event
+ parentIndex int32 // index of parent node's push event
+ typAccum uint64 // accumulated type bits of current node's descendents
+ edgeKindAndIndex int32 // edge.Kind and index, bit packed
+}
+
+func (v *visitor) push(ek edge.Kind, eindex int, node ast.Node) {
+ var (
+ index = int32(len(v.events))
+ parentIndex = v.stack[len(v.stack)-1].index
+ )
+ v.events = append(v.events, event{
+ node: node,
+ parent: parentIndex,
+ typ: typeOf(node),
+ index: 0, // (pop index is set later by visitor.pop)
+ })
+ v.stack = append(v.stack, item{
+ index: index,
+ parentIndex: parentIndex,
+ edgeKindAndIndex: packEdgeKindAndIndex(ek, eindex),
+ })
+
+ // 2B nodes ought to be enough for anyone!
+ if int32(len(v.events)) < 0 {
+ panic("event index exceeded int32")
}
- return events
+ // 32M elements in an []ast.Node ought to be enough for anyone!
+ if ek2, eindex2 := unpackEdgeKindAndIndex(packEdgeKindAndIndex(ek, eindex)); ek2 != ek || eindex2 != eindex {
+ panic("Node slice index exceeded uint25")
+ }
+}
+
+func (v *visitor) pop(node ast.Node) {
+ top := len(v.stack) - 1
+ current := v.stack[top]
+
+ push := &v.events[current.index]
+ parent := &v.stack[top-1]
+
+ push.index = int32(len(v.events)) // make push event refer to pop
+ parent.typAccum |= current.typAccum | push.typ // accumulate type bits into parent
+
+ v.stack = v.stack[:top]
+
+ v.events = append(v.events, event{
+ node: node,
+ typ: current.typAccum,
+ index: current.index,
+ parent: current.edgeKindAndIndex, // see [unpackEdgeKindAndIndex]
+ })
}
return func(yield func(ast.Node) bool) {
mask := maskOf(types)
- for i := 0; i < len(in.events); {
+ for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
mask := typeOf((N)(nil))
return func(yield func(N) bool) {
- for i := 0; i < len(in.events); {
+ for i := int32(0); i < int32(len(in.events)); {
ev := in.events[i]
if ev.index > i {
// push
import (
"go/ast"
"math"
+
+ _ "unsafe"
)
const (
return 0
}
+//go:linkname maskOf
func maskOf(nodes []ast.Node) uint64 {
- if nodes == nil {
+ if len(nodes) == 0 {
return math.MaxUint64 // match all node types
}
var mask uint64
--- /dev/null
+// 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 inspector
+
+// This file is a fork of ast.Inspect to reduce unnecessary dynamic
+// calls and to gather edge information.
+//
+// Consistency with the original is ensured by TestInspectAllNodes.
+
+import (
+ "fmt"
+ "go/ast"
+
+ "golang.org/x/tools/internal/astutil/edge"
+)
+
+func walkList[N ast.Node](v *visitor, ek edge.Kind, list []N) {
+ for i, node := range list {
+ walk(v, ek, i, node)
+ }
+}
+
+func walk(v *visitor, ek edge.Kind, index int, node ast.Node) {
+ v.push(ek, index, node)
+
+ // walk children
+ // (the order of the cases matches the order
+ // of the corresponding node types in ast.go)
+ switch n := node.(type) {
+ // Comments and fields
+ case *ast.Comment:
+ // nothing to do
+
+ case *ast.CommentGroup:
+ walkList(v, edge.CommentGroup_List, n.List)
+
+ case *ast.Field:
+ if n.Doc != nil {
+ walk(v, edge.Field_Doc, -1, n.Doc)
+ }
+ walkList(v, edge.Field_Names, n.Names)
+ if n.Type != nil {
+ walk(v, edge.Field_Type, -1, n.Type)
+ }
+ if n.Tag != nil {
+ walk(v, edge.Field_Tag, -1, n.Tag)
+ }
+ if n.Comment != nil {
+ walk(v, edge.Field_Comment, -1, n.Comment)
+ }
+
+ case *ast.FieldList:
+ walkList(v, edge.FieldList_List, n.List)
+
+ // Expressions
+ case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
+ // nothing to do
+
+ case *ast.Ellipsis:
+ if n.Elt != nil {
+ walk(v, edge.Ellipsis_Elt, -1, n.Elt)
+ }
+
+ case *ast.FuncLit:
+ walk(v, edge.FuncLit_Type, -1, n.Type)
+ walk(v, edge.FuncLit_Body, -1, n.Body)
+
+ case *ast.CompositeLit:
+ if n.Type != nil {
+ walk(v, edge.CompositeLit_Type, -1, n.Type)
+ }
+ walkList(v, edge.CompositeLit_Elts, n.Elts)
+
+ case *ast.ParenExpr:
+ walk(v, edge.ParenExpr_X, -1, n.X)
+
+ case *ast.SelectorExpr:
+ walk(v, edge.SelectorExpr_X, -1, n.X)
+ walk(v, edge.SelectorExpr_Sel, -1, n.Sel)
+
+ case *ast.IndexExpr:
+ walk(v, edge.IndexExpr_X, -1, n.X)
+ walk(v, edge.IndexExpr_Index, -1, n.Index)
+
+ case *ast.IndexListExpr:
+ walk(v, edge.IndexListExpr_X, -1, n.X)
+ walkList(v, edge.IndexListExpr_Indices, n.Indices)
+
+ case *ast.SliceExpr:
+ walk(v, edge.SliceExpr_X, -1, n.X)
+ if n.Low != nil {
+ walk(v, edge.SliceExpr_Low, -1, n.Low)
+ }
+ if n.High != nil {
+ walk(v, edge.SliceExpr_High, -1, n.High)
+ }
+ if n.Max != nil {
+ walk(v, edge.SliceExpr_Max, -1, n.Max)
+ }
+
+ case *ast.TypeAssertExpr:
+ walk(v, edge.TypeAssertExpr_X, -1, n.X)
+ if n.Type != nil {
+ walk(v, edge.TypeAssertExpr_Type, -1, n.Type)
+ }
+
+ case *ast.CallExpr:
+ walk(v, edge.CallExpr_Fun, -1, n.Fun)
+ walkList(v, edge.CallExpr_Args, n.Args)
+
+ case *ast.StarExpr:
+ walk(v, edge.StarExpr_X, -1, n.X)
+
+ case *ast.UnaryExpr:
+ walk(v, edge.UnaryExpr_X, -1, n.X)
+
+ case *ast.BinaryExpr:
+ walk(v, edge.BinaryExpr_X, -1, n.X)
+ walk(v, edge.BinaryExpr_Y, -1, n.Y)
+
+ case *ast.KeyValueExpr:
+ walk(v, edge.KeyValueExpr_Key, -1, n.Key)
+ walk(v, edge.KeyValueExpr_Value, -1, n.Value)
+
+ // Types
+ case *ast.ArrayType:
+ if n.Len != nil {
+ walk(v, edge.ArrayType_Len, -1, n.Len)
+ }
+ walk(v, edge.ArrayType_Elt, -1, n.Elt)
+
+ case *ast.StructType:
+ walk(v, edge.StructType_Fields, -1, n.Fields)
+
+ case *ast.FuncType:
+ if n.TypeParams != nil {
+ walk(v, edge.FuncType_TypeParams, -1, n.TypeParams)
+ }
+ if n.Params != nil {
+ walk(v, edge.FuncType_Params, -1, n.Params)
+ }
+ if n.Results != nil {
+ walk(v, edge.FuncType_Results, -1, n.Results)
+ }
+
+ case *ast.InterfaceType:
+ walk(v, edge.InterfaceType_Methods, -1, n.Methods)
+
+ case *ast.MapType:
+ walk(v, edge.MapType_Key, -1, n.Key)
+ walk(v, edge.MapType_Value, -1, n.Value)
+
+ case *ast.ChanType:
+ walk(v, edge.ChanType_Value, -1, n.Value)
+
+ // Statements
+ case *ast.BadStmt:
+ // nothing to do
+
+ case *ast.DeclStmt:
+ walk(v, edge.DeclStmt_Decl, -1, n.Decl)
+
+ case *ast.EmptyStmt:
+ // nothing to do
+
+ case *ast.LabeledStmt:
+ walk(v, edge.LabeledStmt_Label, -1, n.Label)
+ walk(v, edge.LabeledStmt_Stmt, -1, n.Stmt)
+
+ case *ast.ExprStmt:
+ walk(v, edge.ExprStmt_X, -1, n.X)
+
+ case *ast.SendStmt:
+ walk(v, edge.SendStmt_Chan, -1, n.Chan)
+ walk(v, edge.SendStmt_Value, -1, n.Value)
+
+ case *ast.IncDecStmt:
+ walk(v, edge.IncDecStmt_X, -1, n.X)
+
+ case *ast.AssignStmt:
+ walkList(v, edge.AssignStmt_Lhs, n.Lhs)
+ walkList(v, edge.AssignStmt_Rhs, n.Rhs)
+
+ case *ast.GoStmt:
+ walk(v, edge.GoStmt_Call, -1, n.Call)
+
+ case *ast.DeferStmt:
+ walk(v, edge.DeferStmt_Call, -1, n.Call)
+
+ case *ast.ReturnStmt:
+ walkList(v, edge.ReturnStmt_Results, n.Results)
+
+ case *ast.BranchStmt:
+ if n.Label != nil {
+ walk(v, edge.BranchStmt_Label, -1, n.Label)
+ }
+
+ case *ast.BlockStmt:
+ walkList(v, edge.BlockStmt_List, n.List)
+
+ case *ast.IfStmt:
+ if n.Init != nil {
+ walk(v, edge.IfStmt_Init, -1, n.Init)
+ }
+ walk(v, edge.IfStmt_Cond, -1, n.Cond)
+ walk(v, edge.IfStmt_Body, -1, n.Body)
+ if n.Else != nil {
+ walk(v, edge.IfStmt_Else, -1, n.Else)
+ }
+
+ case *ast.CaseClause:
+ walkList(v, edge.CaseClause_List, n.List)
+ walkList(v, edge.CaseClause_Body, n.Body)
+
+ case *ast.SwitchStmt:
+ if n.Init != nil {
+ walk(v, edge.SwitchStmt_Init, -1, n.Init)
+ }
+ if n.Tag != nil {
+ walk(v, edge.SwitchStmt_Tag, -1, n.Tag)
+ }
+ walk(v, edge.SwitchStmt_Body, -1, n.Body)
+
+ case *ast.TypeSwitchStmt:
+ if n.Init != nil {
+ walk(v, edge.TypeSwitchStmt_Init, -1, n.Init)
+ }
+ walk(v, edge.TypeSwitchStmt_Assign, -1, n.Assign)
+ walk(v, edge.TypeSwitchStmt_Body, -1, n.Body)
+
+ case *ast.CommClause:
+ if n.Comm != nil {
+ walk(v, edge.CommClause_Comm, -1, n.Comm)
+ }
+ walkList(v, edge.CommClause_Body, n.Body)
+
+ case *ast.SelectStmt:
+ walk(v, edge.SelectStmt_Body, -1, n.Body)
+
+ case *ast.ForStmt:
+ if n.Init != nil {
+ walk(v, edge.ForStmt_Init, -1, n.Init)
+ }
+ if n.Cond != nil {
+ walk(v, edge.ForStmt_Cond, -1, n.Cond)
+ }
+ if n.Post != nil {
+ walk(v, edge.ForStmt_Post, -1, n.Post)
+ }
+ walk(v, edge.ForStmt_Body, -1, n.Body)
+
+ case *ast.RangeStmt:
+ if n.Key != nil {
+ walk(v, edge.RangeStmt_Key, -1, n.Key)
+ }
+ if n.Value != nil {
+ walk(v, edge.RangeStmt_Value, -1, n.Value)
+ }
+ walk(v, edge.RangeStmt_X, -1, n.X)
+ walk(v, edge.RangeStmt_Body, -1, n.Body)
+
+ // Declarations
+ case *ast.ImportSpec:
+ if n.Doc != nil {
+ walk(v, edge.ImportSpec_Doc, -1, n.Doc)
+ }
+ if n.Name != nil {
+ walk(v, edge.ImportSpec_Name, -1, n.Name)
+ }
+ walk(v, edge.ImportSpec_Path, -1, n.Path)
+ if n.Comment != nil {
+ walk(v, edge.ImportSpec_Comment, -1, n.Comment)
+ }
+
+ case *ast.ValueSpec:
+ if n.Doc != nil {
+ walk(v, edge.ValueSpec_Doc, -1, n.Doc)
+ }
+ walkList(v, edge.ValueSpec_Names, n.Names)
+ if n.Type != nil {
+ walk(v, edge.ValueSpec_Type, -1, n.Type)
+ }
+ walkList(v, edge.ValueSpec_Values, n.Values)
+ if n.Comment != nil {
+ walk(v, edge.ValueSpec_Comment, -1, n.Comment)
+ }
+
+ case *ast.TypeSpec:
+ if n.Doc != nil {
+ walk(v, edge.TypeSpec_Doc, -1, n.Doc)
+ }
+ walk(v, edge.TypeSpec_Name, -1, n.Name)
+ if n.TypeParams != nil {
+ walk(v, edge.TypeSpec_TypeParams, -1, n.TypeParams)
+ }
+ walk(v, edge.TypeSpec_Type, -1, n.Type)
+ if n.Comment != nil {
+ walk(v, edge.TypeSpec_Comment, -1, n.Comment)
+ }
+
+ case *ast.BadDecl:
+ // nothing to do
+
+ case *ast.GenDecl:
+ if n.Doc != nil {
+ walk(v, edge.GenDecl_Doc, -1, n.Doc)
+ }
+ walkList(v, edge.GenDecl_Specs, n.Specs)
+
+ case *ast.FuncDecl:
+ if n.Doc != nil {
+ walk(v, edge.FuncDecl_Doc, -1, n.Doc)
+ }
+ if n.Recv != nil {
+ walk(v, edge.FuncDecl_Recv, -1, n.Recv)
+ }
+ walk(v, edge.FuncDecl_Name, -1, n.Name)
+ walk(v, edge.FuncDecl_Type, -1, n.Type)
+ if n.Body != nil {
+ walk(v, edge.FuncDecl_Body, -1, n.Body)
+ }
+
+ case *ast.File:
+ if n.Doc != nil {
+ walk(v, edge.File_Doc, -1, n.Doc)
+ }
+ walk(v, edge.File_Name, -1, n.Name)
+ walkList(v, edge.File_Decls, n.Decls)
+ // don't walk n.Comments - they have been
+ // visited already through the individual
+ // nodes
+
+ default:
+ // (includes *ast.Package)
+ panic(fmt.Sprintf("Walk: unexpected node type %T", n))
+ }
+
+ v.pop(node)
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package typeutil defines various utilities for types, such as Map,
-// a mapping from types.Type to any values.
-package typeutil // import "golang.org/x/tools/go/types/typeutil"
+// Package typeutil defines various utilities for types, such as [Map],
+// a hash table that maps [types.Type] to any value.
+package typeutil
import (
"bytes"
"fmt"
"go/types"
- "reflect"
+ "hash/maphash"
+ "unsafe"
"golang.org/x/tools/internal/typeparams"
)
// Map is a hash-table-based mapping from types (types.Type) to
-// arbitrary any values. The concrete types that implement
+// arbitrary values. The concrete types that implement
// the Type interface are pointers. Since they are not canonicalized,
// == cannot be used to check for equivalence, and thus we cannot
// simply use a Go map.
//
// Just as with map[K]V, a nil *Map is a valid empty map.
//
-// Not thread-safe.
+// Read-only map operations ([Map.At], [Map.Len], and so on) may
+// safely be called concurrently.
+//
+// TODO(adonovan): deprecate in favor of https://go.dev/issues/69420
+// and 69559, if the latter proposals for a generic hash-map type and
+// a types.Hash function are accepted.
type Map struct {
- hasher Hasher // shared by many Maps
table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused
length int // number of map entries
}
value any
}
-// SetHasher sets the hasher used by Map.
-//
-// All Hashers are functionally equivalent but contain internal state
-// used to cache the results of hashing previously seen types.
-//
-// A single Hasher created by MakeHasher() may be shared among many
-// Maps. This is recommended if the instances have many keys in
-// common, as it will amortize the cost of hash computation.
-//
-// A Hasher may grow without bound as new types are seen. Even when a
-// type is deleted from the map, the Hasher never shrinks, since other
-// types in the map may reference the deleted type indirectly.
+// SetHasher has no effect.
//
-// Hashers are not thread-safe, and read-only operations such as
-// Map.Lookup require updates to the hasher, so a full Mutex lock (not a
-// read-lock) is require around all Map operations if a shared
-// hasher is accessed from multiple threads.
-//
-// If SetHasher is not called, the Map will create a private hasher at
-// the first call to Insert.
-func (m *Map) SetHasher(hasher Hasher) {
- m.hasher = hasher
-}
+// It is a relic of an optimization that is no longer profitable. Do
+// not use [Hasher], [MakeHasher], or [SetHasher] in new code.
+func (m *Map) SetHasher(Hasher) {}
// Delete removes the entry with the given key, if any.
// It returns true if the entry was found.
func (m *Map) Delete(key types.Type) bool {
if m != nil && m.table != nil {
- hash := m.hasher.Hash(key)
+ hash := hash(key)
bucket := m.table[hash]
for i, e := range bucket {
if e.key != nil && types.Identical(key, e.key) {
// The result is nil if the entry is not present.
func (m *Map) At(key types.Type) any {
if m != nil && m.table != nil {
- for _, e := range m.table[m.hasher.Hash(key)] {
+ for _, e := range m.table[hash(key)] {
if e.key != nil && types.Identical(key, e.key) {
return e.value
}
// and returns the previous entry, if any.
func (m *Map) Set(key types.Type, value any) (prev any) {
if m.table != nil {
- hash := m.hasher.Hash(key)
+ hash := hash(key)
bucket := m.table[hash]
var hole *entry
for i, e := range bucket {
m.table[hash] = append(bucket, entry{key, value})
}
} else {
- if m.hasher.memo == nil {
- m.hasher = MakeHasher()
- }
- hash := m.hasher.Hash(key)
+ hash := hash(key)
m.table = map[uint32][]entry{hash: {entry{key, value}}}
}
return m.toString(false)
}
-////////////////////////////////////////////////////////////////////////
-// Hasher
-
-// A Hasher maps each type to its hash value.
-// For efficiency, a hasher uses memoization; thus its memory
-// footprint grows monotonically over time.
-// Hashers are not thread-safe.
-// Hashers have reference semantics.
-// Call MakeHasher to create a Hasher.
-type Hasher struct {
- memo map[types.Type]uint32
-
- // ptrMap records pointer identity.
- ptrMap map[any]uint32
-
- // sigTParams holds type parameters from the signature being hashed.
- // Signatures are considered identical modulo renaming of type parameters, so
- // within the scope of a signature type the identity of the signature's type
- // parameters is just their index.
- //
- // Since the language does not currently support referring to uninstantiated
- // generic types or functions, and instantiated signatures do not have type
- // parameter lists, we should never encounter a second non-empty type
- // parameter list when hashing a generic signature.
- sigTParams *types.TypeParamList
-}
+// -- Hasher --
-// MakeHasher returns a new Hasher instance.
-func MakeHasher() Hasher {
- return Hasher{
- memo: make(map[types.Type]uint32),
- ptrMap: make(map[any]uint32),
- sigTParams: nil,
- }
+// hash returns the hash of type t.
+// TODO(adonovan): replace by types.Hash when Go proposal #69420 is accepted.
+func hash(t types.Type) uint32 {
+ return theHasher.Hash(t)
}
+// A Hasher provides a [Hasher.Hash] method to map a type to its hash value.
+// Hashers are stateless, and all are equivalent.
+type Hasher struct{}
+
+var theHasher Hasher
+
+// MakeHasher returns Hasher{}.
+// Hashers are stateless; all are equivalent.
+func MakeHasher() Hasher { return theHasher }
+
// Hash computes a hash value for the given type t such that
// Identical(t, t') => Hash(t) == Hash(t').
func (h Hasher) Hash(t types.Type) uint32 {
- hash, ok := h.memo[t]
- if !ok {
- hash = h.hashFor(t)
- h.memo[t] = hash
- }
- return hash
+ return hasher{inGenericSig: false}.hash(t)
}
+// hasher holds the state of a single Hash traversal: whether we are
+// inside the signature of a generic function; this is used to
+// optimize [hasher.hashTypeParam].
+type hasher struct{ inGenericSig bool }
+
// hashString computes the Fowler–Noll–Vo hash of s.
func hashString(s string) uint32 {
var h uint32
return h
}
-// hashFor computes the hash of t.
-func (h Hasher) hashFor(t types.Type) uint32 {
+// hash computes the hash of t.
+func (h hasher) hash(t types.Type) uint32 {
// See Identical for rationale.
switch t := t.(type) {
case *types.Basic:
return uint32(t.Kind())
case *types.Alias:
- return h.Hash(types.Unalias(t))
+ return h.hash(types.Unalias(t))
case *types.Array:
- return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem())
+ return 9043 + 2*uint32(t.Len()) + 3*h.hash(t.Elem())
case *types.Slice:
- return 9049 + 2*h.Hash(t.Elem())
+ return 9049 + 2*h.hash(t.Elem())
case *types.Struct:
var hash uint32 = 9059
}
hash += hashString(t.Tag(i))
hash += hashString(f.Name()) // (ignore f.Pkg)
- hash += h.Hash(f.Type())
+ hash += h.hash(f.Type())
}
return hash
case *types.Pointer:
- return 9067 + 2*h.Hash(t.Elem())
+ return 9067 + 2*h.hash(t.Elem())
case *types.Signature:
var hash uint32 = 9091
hash *= 8863
}
- // Use a separate hasher for types inside of the signature, where type
- // parameter identity is modified to be (index, constraint). We must use a
- // new memo for this hasher as type identity may be affected by this
- // masking. For example, in func[T any](*T), the identity of *T depends on
- // whether we are mapping the argument in isolation, or recursively as part
- // of hashing the signature.
- //
- // We should never encounter a generic signature while hashing another
- // generic signature, but defensively set sigTParams only if h.mask is
- // unset.
tparams := t.TypeParams()
- if h.sigTParams == nil && tparams.Len() != 0 {
- h = Hasher{
- // There may be something more efficient than discarding the existing
- // memo, but it would require detecting whether types are 'tainted' by
- // references to type parameters.
- memo: make(map[types.Type]uint32),
- // Re-using ptrMap ensures that pointer identity is preserved in this
- // hasher.
- ptrMap: h.ptrMap,
- sigTParams: tparams,
- }
- }
+ if n := tparams.Len(); n > 0 {
+ h.inGenericSig = true // affects constraints, params, and results
- for i := 0; i < tparams.Len(); i++ {
- tparam := tparams.At(i)
- hash += 7 * h.Hash(tparam.Constraint())
+ for i := range n {
+ tparam := tparams.At(i)
+ hash += 7 * h.hash(tparam.Constraint())
+ }
}
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
return hash
case *types.Map:
- return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem())
+ return 9109 + 2*h.hash(t.Key()) + 3*h.hash(t.Elem())
case *types.Chan:
- return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem())
+ return 9127 + 2*uint32(t.Dir()) + 3*h.hash(t.Elem())
case *types.Named:
- hash := h.hashPtr(t.Obj())
+ hash := h.hashTypeName(t.Obj())
targs := t.TypeArgs()
for i := 0; i < targs.Len(); i++ {
targ := targs.At(i)
- hash += 2 * h.Hash(targ)
+ hash += 2 * h.hash(targ)
}
return hash
panic(fmt.Sprintf("%T: %v", t, t))
}
-func (h Hasher) hashTuple(tuple *types.Tuple) uint32 {
+func (h hasher) hashTuple(tuple *types.Tuple) uint32 {
// See go/types.identicalTypes for rationale.
n := tuple.Len()
hash := 9137 + 2*uint32(n)
- for i := 0; i < n; i++ {
- hash += 3 * h.Hash(tuple.At(i).Type())
+ for i := range n {
+ hash += 3 * h.hash(tuple.At(i).Type())
}
return hash
}
-func (h Hasher) hashUnion(t *types.Union) uint32 {
+func (h hasher) hashUnion(t *types.Union) uint32 {
// Hash type restrictions.
terms, err := typeparams.UnionTermSet(t)
// if err != nil t has invalid type restrictions. Fall back on a non-zero
return h.hashTermSet(terms)
}
-func (h Hasher) hashTermSet(terms []*types.Term) uint32 {
+func (h hasher) hashTermSet(terms []*types.Term) uint32 {
hash := 9157 + 2*uint32(len(terms))
for _, term := range terms {
// term order is not significant.
- termHash := h.Hash(term.Type())
+ termHash := h.hash(term.Type())
if term.Tilde() {
termHash *= 9161
}
return hash
}
-// hashTypeParam returns a hash of the type parameter t, with a hash value
-// depending on whether t is contained in h.sigTParams.
-//
-// If h.sigTParams is set and contains t, then we are in the process of hashing
-// a signature, and the hash value of t must depend only on t's index and
-// constraint: signatures are considered identical modulo type parameter
-// renaming. To avoid infinite recursion, we only hash the type parameter
-// index, and rely on types.Identical to handle signatures where constraints
-// are not identical.
-//
-// Otherwise the hash of t depends only on t's pointer identity.
-func (h Hasher) hashTypeParam(t *types.TypeParam) uint32 {
- if h.sigTParams != nil {
- i := t.Index()
- if i >= 0 && i < h.sigTParams.Len() && t == h.sigTParams.At(i) {
- return 9173 + 3*uint32(i)
- }
+// hashTypeParam returns the hash of a type parameter.
+func (h hasher) hashTypeParam(t *types.TypeParam) uint32 {
+ // Within the signature of a generic function, TypeParams are
+ // identical if they have the same index and constraint, so we
+ // hash them based on index.
+ //
+ // When we are outside a generic function, free TypeParams are
+ // identical iff they are the same object, so we can use a
+ // more discriminating hash consistent with object identity.
+ // This optimization saves [Map] about 4% when hashing all the
+ // types.Info.Types in the forward closure of net/http.
+ if !h.inGenericSig {
+ // Optimization: outside a generic function signature,
+ // use a more discrimating hash consistent with object identity.
+ return h.hashTypeName(t.Obj())
}
- return h.hashPtr(t.Obj())
+ return 9173 + 3*uint32(t.Index())
}
-// hashPtr hashes the pointer identity of ptr. It uses h.ptrMap to ensure that
-// pointers values are not dependent on the GC.
-func (h Hasher) hashPtr(ptr any) uint32 {
- if hash, ok := h.ptrMap[ptr]; ok {
- return hash
+var theSeed = maphash.MakeSeed()
+
+// hashTypeName hashes the pointer of tname.
+func (hasher) hashTypeName(tname *types.TypeName) uint32 {
+ // Since types.Identical uses == to compare TypeNames,
+ // the Hash function uses maphash.Comparable.
+ // TODO(adonovan): or will, when it becomes available in go1.24.
+ // In the meantime we use the pointer's numeric value.
+ //
+ // hash := maphash.Comparable(theSeed, tname)
+ //
+ // (Another approach would be to hash the name and package
+ // path, and whether or not it is a package-level typename. It
+ // is rare for a package to define multiple local types with
+ // the same name.)
+ ptr := uintptr(unsafe.Pointer(tname))
+ if unsafe.Sizeof(ptr) == 8 {
+ hash := uint64(ptr)
+ return uint32(hash ^ (hash >> 32))
+ } else {
+ return uint32(ptr)
}
- hash := uint32(reflect.ValueOf(ptr).Pointer())
- h.ptrMap[ptr] = hash
- return hash
}
// shallowHash computes a hash of t without looking at any of its
// include m itself; there is no mention of the named type X that
// might help us break the cycle.
// (See comment in go/types.identical, case *Interface, for more.)
-func (h Hasher) shallowHash(t types.Type) uint32 {
+func (h hasher) shallowHash(t types.Type) uint32 {
// t is the type of an interface method (Signature),
// its params or results (Tuples), or their immediate
// elements (mostly Slice, Pointer, Basic, Named),
case *types.Tuple:
n := t.Len()
hash := 9137 + 2*uint32(n)
- for i := 0; i < n; i++ {
+ for i := range n {
hash += 53471161 * h.shallowHash(t.At(i).Type())
}
return hash
return 9127
case *types.Named:
- return h.hashPtr(t.Obj())
+ return h.hashTypeName(t.Obj())
case *types.TypeParam:
- return h.hashPtr(t.Obj())
+ return h.hashTypeParam(t)
}
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
}
import (
"bytes"
+ "cmp"
"fmt"
"go/ast"
+ "go/printer"
"go/scanner"
"go/token"
"go/types"
- "os"
pathpkg "path"
+ "slices"
+ "strings"
"golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/internal/typesinternal"
)
func TypeErrorEndPos(fset *token.FileSet, src []byte, start token.Pos) token.Pos {
return end
}
-// StmtToInsertVarBefore returns the ast.Stmt before which we can
-// safely insert a new var declaration, or nil if the path denotes a
-// node outside any statement.
-//
-// Basic Example:
-//
-// z := 1
-// y := z + x
-//
-// If x is undeclared, then this function would return `y := z + x`, so that we
-// can insert `x := ` on the line before `y := z + x`.
-//
-// If stmt example:
-//
-// if z == 1 {
-// } else if z == y {}
-//
-// If y is undeclared, then this function would return `if z == 1 {`, because we cannot
-// insert a statement between an if and an else if statement. As a result, we need to find
-// the top of the if chain to insert `y := ` before.
-func StmtToInsertVarBefore(path []ast.Node) ast.Stmt {
- enclosingIndex := -1
- for i, p := range path {
- if _, ok := p.(ast.Stmt); ok {
- enclosingIndex = i
- break
- }
- }
- if enclosingIndex == -1 {
- return nil // no enclosing statement: outside function
- }
- enclosingStmt := path[enclosingIndex]
- switch enclosingStmt.(type) {
- case *ast.IfStmt:
- // The enclosingStmt is inside of the if declaration,
- // We need to check if we are in an else-if stmt and
- // get the base if statement.
- // TODO(adonovan): for non-constants, it may be preferable
- // to add the decl as the Init field of the innermost
- // enclosing ast.IfStmt.
- return baseIfStmt(path, enclosingIndex)
- case *ast.CaseClause:
- // Get the enclosing switch stmt if the enclosingStmt is
- // inside of the case statement.
- for i := enclosingIndex + 1; i < len(path); i++ {
- if node, ok := path[i].(*ast.SwitchStmt); ok {
- return node
- } else if node, ok := path[i].(*ast.TypeSwitchStmt); ok {
- return node
- }
- }
- }
- if len(path) <= enclosingIndex+1 {
- return enclosingStmt.(ast.Stmt)
- }
- // Check if the enclosing statement is inside another node.
- switch expr := path[enclosingIndex+1].(type) {
- case *ast.IfStmt:
- // Get the base if statement.
- return baseIfStmt(path, enclosingIndex+1)
- case *ast.ForStmt:
- if expr.Init == enclosingStmt || expr.Post == enclosingStmt {
- return expr
- }
- case *ast.SwitchStmt, *ast.TypeSwitchStmt:
- return expr.(ast.Stmt)
- }
- return enclosingStmt.(ast.Stmt)
-}
-
-// baseIfStmt walks up the if/else-if chain until we get to
-// the top of the current if chain.
-func baseIfStmt(path []ast.Node, index int) ast.Stmt {
- stmt := path[index]
- for i := index + 1; i < len(path); i++ {
- if node, ok := path[i].(*ast.IfStmt); ok && node.Else == stmt {
- stmt = node
- continue
- }
- break
- }
- return stmt.(ast.Stmt)
-}
-
// WalkASTWithParent walks the AST rooted at n. The semantics are
// similar to ast.Inspect except it does not call f(nil).
func WalkASTWithParent(n ast.Node, f func(n ast.Node, parent ast.Node) bool) {
return types.AssignableTo(want, got)
}
-// MakeReadFile returns a simple implementation of the Pass.ReadFile function.
-func MakeReadFile(pass *analysis.Pass) func(filename string) ([]byte, error) {
+// 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 os.ReadFile(filename)
+ 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 slicesContains(pass.OtherFiles, filename) ||
- slicesContains(pass.IgnoredFiles, filename) {
+ if slices.Contains(pass.OtherFiles, filename) ||
+ slices.Contains(pass.IgnoredFiles, filename) {
return nil
}
for _, f := range pass.Files {
return fmt.Errorf("Pass.ReadFile: %s is not among OtherFiles, IgnoredFiles, or names of Files", filename)
}
-// TODO(adonovan): use go1.21 slices.Contains.
-func slicesContains[S ~[]E, E comparable](slice S, x E) bool {
- for _, elem := range slice {
- if elem == x {
- return true
- }
- }
- return false
-}
-
// AddImport checks whether this file already imports pkgpath and
// that import is in scope at pos. If so, it returns the name under
// which it was imported and a zero edit. Otherwise, it adds a new
// import of pkgpath, using a name derived from the preferred name,
-// and returns the chosen name along with the edit for the new import.
+// and returns the chosen name, a prefix to be concatenated with member
+// to form a qualified name, and the edit for the new import.
+//
+// In the special case that pkgpath is dot-imported then member, the
+// identifer for which the import is being added, is consulted. If
+// member is not shadowed at pos, AddImport returns (".", "", nil).
+// (AddImport accepts the caller's implicit claim that the imported
+// package declares member.)
//
// It does not mutate its arguments.
-func AddImport(info *types.Info, file *ast.File, pos token.Pos, pkgpath, preferredName string) (name string, newImport []analysis.TextEdit) {
+func AddImport(info *types.Info, file *ast.File, preferredName, pkgpath, member string, pos token.Pos) (name, prefix string, newImport []analysis.TextEdit) {
// Find innermost enclosing lexical block.
scope := info.Scopes[file].Innermost(pos)
if scope == nil {
// Is there an existing import of this package?
// If so, are we in its scope? (not shadowed)
for _, spec := range file.Imports {
- pkgname, ok := importedPkgName(info, spec)
- if ok && pkgname.Imported().Path() == pkgpath {
- if _, obj := scope.LookupParent(pkgname.Name(), pos); obj == pkgname {
- return pkgname.Name(), nil
+ pkgname := info.PkgNameOf(spec)
+ if pkgname != nil && pkgname.Imported().Path() == pkgpath {
+ name = pkgname.Name()
+ if name == "." {
+ // The scope of ident must be the file scope.
+ if s, _ := scope.LookupParent(member, pos); s == info.Scopes[file] {
+ return name, "", nil
+ }
+ } else if _, obj := scope.LookupParent(name, pos); obj == pkgname {
+ return name, name + ".", nil
}
}
}
newName = fmt.Sprintf("%s%d", preferredName, i)
}
- // For now, keep it real simple: create a new import
- // declaration before the first existing declaration (which
- // must exist), including its comments, and let goimports tidy it up.
+ // Create a new import declaration either before the first existing
+ // declaration (which must exist), including its comments; or
+ // inside the declaration, if it is an import group.
//
// Use a renaming import whenever the preferred name is not
// available, or the chosen name does not match the last
// segment of its path.
- newText := fmt.Sprintf("import %q\n\n", pkgpath)
+ newText := fmt.Sprintf("%q", pkgpath)
if newName != preferredName || newName != pathpkg.Base(pkgpath) {
- newText = fmt.Sprintf("import %s %q\n\n", newName, pkgpath)
+ newText = fmt.Sprintf("%s %q", newName, pkgpath)
}
decl0 := file.Decls[0]
var before ast.Node = decl0
before = decl0.Doc
}
}
- return newName, []analysis.TextEdit{{
- Pos: before.Pos(),
- End: before.Pos(),
+ // If the first decl is an import group, add this new import at the end.
+ if gd, ok := before.(*ast.GenDecl); ok && gd.Tok == token.IMPORT && gd.Rparen.IsValid() {
+ pos = gd.Rparen
+ newText = "\t" + newText + "\n"
+ } else {
+ pos = before.Pos()
+ newText = "import " + newText + "\n\n"
+ }
+ return newName, newName + ".", []analysis.TextEdit{{
+ Pos: pos,
+ End: pos,
NewText: []byte(newText),
}}
}
-// importedPkgName returns the PkgName object declared by an ImportSpec.
-// TODO(adonovan): use go1.22's Info.PkgNameOf.
-func importedPkgName(info *types.Info, imp *ast.ImportSpec) (*types.PkgName, bool) {
- var obj types.Object
- if imp.Name != nil {
- obj = info.Defs[imp.Name]
- } else {
- obj = info.Implicits[imp]
+// Format returns a string representation of the expression e.
+func Format(fset *token.FileSet, e ast.Expr) string {
+ var buf strings.Builder
+ printer.Fprint(&buf, fset, e) // ignore errors
+ return buf.String()
+}
+
+// Imports returns true if path is imported by pkg.
+func Imports(pkg *types.Package, path string) bool {
+ for _, imp := range pkg.Imports() {
+ if imp.Path() == path {
+ return true
+ }
+ }
+ return false
+}
+
+// IsTypeNamed reports whether t is (or is an alias for) a
+// package-level defined type with the given package path and one of
+// the given names. It returns false if t is nil.
+//
+// This function avoids allocating the concatenation of "pkg.Name",
+// which is important for the performance of syntax matching.
+func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool {
+ if named, ok := types.Unalias(t).(*types.Named); ok {
+ tname := named.Obj()
+ return tname != nil &&
+ typesinternal.IsPackageLevel(tname) &&
+ tname.Pkg().Path() == pkgPath &&
+ slices.Contains(names, tname.Name())
+ }
+ return false
+}
+
+// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a
+// package-level defined type with the given package path and one of the given
+// names. It returns false if t is not a pointer type.
+func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool {
+ r := typesinternal.Unpointer(t)
+ if r == t {
+ return false
+ }
+ return IsTypeNamed(r, pkgPath, names...)
+}
+
+// IsFunctionNamed reports whether obj is a package-level function
+// defined in the given package and has one of the given names.
+// It returns false if obj is nil.
+//
+// This function avoids allocating the concatenation of "pkg.Name",
+// which is important for the performance of syntax matching.
+func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool {
+ f, ok := obj.(*types.Func)
+ return ok &&
+ typesinternal.IsPackageLevel(obj) &&
+ f.Pkg().Path() == pkgPath &&
+ f.Type().(*types.Signature).Recv() == nil &&
+ slices.Contains(names, f.Name())
+}
+
+// IsMethodNamed reports whether obj is a method defined on a
+// package-level type with the given package and type name, and has
+// one of the given names. It returns false if obj is nil.
+//
+// This function avoids allocating the concatenation of "pkg.TypeName.Name",
+// which is important for the performance of syntax matching.
+func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool {
+ if fn, ok := obj.(*types.Func); ok {
+ if recv := fn.Type().(*types.Signature).Recv(); recv != nil {
+ _, T := typesinternal.ReceiverNamed(recv)
+ return T != nil &&
+ IsTypeNamed(T, pkgPath, typeName) &&
+ slices.Contains(names, fn.Name())
+ }
+ }
+ return false
+}
+
+// ValidateFixes validates the set of fixes for a single diagnostic.
+// Any error indicates a bug in the originating analyzer.
+//
+// It updates fixes so that fixes[*].End.IsValid().
+//
+// It may be used as part of an analysis driver implementation.
+func ValidateFixes(fset *token.FileSet, a *analysis.Analyzer, fixes []analysis.SuggestedFix) error {
+ fixMessages := make(map[string]bool)
+ for i := range fixes {
+ fix := &fixes[i]
+ if fixMessages[fix.Message] {
+ return fmt.Errorf("analyzer %q suggests two fixes with same Message (%s)", a.Name, fix.Message)
+ }
+ fixMessages[fix.Message] = true
+ if err := validateFix(fset, fix); err != nil {
+ return fmt.Errorf("analyzer %q suggests invalid fix (%s): %v", a.Name, fix.Message, err)
+ }
+ }
+ return nil
+}
+
+// validateFix validates a single fix.
+// Any error indicates a bug in the originating analyzer.
+//
+// It updates fix so that fix.End.IsValid().
+func validateFix(fset *token.FileSet, fix *analysis.SuggestedFix) error {
+
+ // Stably sort edits by Pos. This ordering puts insertions
+ // (end = start) before deletions (end > start) at the same
+ // point, but uses a stable sort to preserve the order of
+ // multiple insertions at the same point.
+ slices.SortStableFunc(fix.TextEdits, func(x, y analysis.TextEdit) int {
+ if sign := cmp.Compare(x.Pos, y.Pos); sign != 0 {
+ return sign
+ }
+ return cmp.Compare(x.End, y.End)
+ })
+
+ var prev *analysis.TextEdit
+ for i := range fix.TextEdits {
+ edit := &fix.TextEdits[i]
+
+ // Validate edit individually.
+ start := edit.Pos
+ file := fset.File(start)
+ if file == nil {
+ return fmt.Errorf("missing file info for pos (%v)", edit.Pos)
+ }
+ if end := edit.End; end.IsValid() {
+ if end < start {
+ return fmt.Errorf("pos (%v) > end (%v)", edit.Pos, edit.End)
+ }
+ endFile := fset.File(end)
+ if endFile == nil {
+ return fmt.Errorf("malformed end position %v", end)
+ }
+ if endFile != file {
+ return fmt.Errorf("edit spans files %v and %v", file.Name(), endFile.Name())
+ }
+ } else {
+ edit.End = start // update the SuggestedFix
+ }
+ if eof := token.Pos(file.Base() + file.Size()); edit.End > eof {
+ return fmt.Errorf("end is (%v) beyond end of file (%v)", edit.End, eof)
+ }
+
+ // Validate the sequence of edits:
+ // properly ordered, no overlapping deletions
+ if prev != nil && edit.Pos < prev.End {
+ xpos := fset.Position(prev.Pos)
+ xend := fset.Position(prev.End)
+ ypos := fset.Position(edit.Pos)
+ yend := fset.Position(edit.End)
+ return fmt.Errorf("overlapping edits to %s (%d:%d-%d:%d and %d:%d-%d:%d)",
+ xpos.Filename,
+ xpos.Line, xpos.Column,
+ xend.Line, xend.Column,
+ ypos.Line, ypos.Column,
+ yend.Line, yend.Column,
+ )
+ }
+ prev = edit
+ }
+
+ return nil
+}
+
+// CanImport reports whether one package is allowed to import another.
+//
+// TODO(adonovan): allow customization of the accessibility relation
+// (e.g. for Bazel).
+func CanImport(from, to string) bool {
+ // TODO(adonovan): better segment hygiene.
+ if to == "internal" || strings.HasPrefix(to, "internal/") {
+ // Special case: only std packages may import internal/...
+ // We can't reliably know whether we're in std, so we
+ // use a heuristic on the first segment.
+ first, _, _ := strings.Cut(from, "/")
+ if strings.Contains(first, ".") {
+ return false // example.com/foo ∉ std
+ }
+ if first == "testdata" {
+ return false // testdata/foo ∉ std
+ }
+ }
+ if strings.HasSuffix(to, "/internal") {
+ return strings.HasPrefix(from, to[:len(to)-len("/internal")])
+ }
+ if i := strings.LastIndex(to, "/internal/"); i >= 0 {
+ return strings.HasPrefix(from, to[:i])
}
- pkgname, ok := obj.(*types.PkgName)
- return pkgname, ok
+ return true
}
--- /dev/null
+// 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 edge defines identifiers for each field of an ast.Node
+// struct type that refers to another Node.
+package edge
+
+import (
+ "fmt"
+ "go/ast"
+ "reflect"
+)
+
+// A Kind describes a field of an ast.Node struct.
+type Kind uint8
+
+// String returns a description of the edge kind.
+func (k Kind) String() string {
+ if k == Invalid {
+ return "<invalid>"
+ }
+ info := fieldInfos[k]
+ return fmt.Sprintf("%v.%s", info.nodeType.Elem().Name(), info.name)
+}
+
+// NodeType returns the pointer-to-struct type of the ast.Node implementation.
+func (k Kind) NodeType() reflect.Type { return fieldInfos[k].nodeType }
+
+// FieldName returns the name of the field.
+func (k Kind) FieldName() string { return fieldInfos[k].name }
+
+// FieldType returns the declared type of the field.
+func (k Kind) FieldType() reflect.Type { return fieldInfos[k].fieldType }
+
+// Get returns the direct child of n identified by (k, idx).
+// n's type must match k.NodeType().
+// idx must be a valid slice index, or -1 for a non-slice.
+func (k Kind) Get(n ast.Node, idx int) ast.Node {
+ if k.NodeType() != reflect.TypeOf(n) {
+ panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n))
+ }
+ v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index)
+ if idx != -1 {
+ v = v.Index(idx) // asserts valid index
+ } else {
+ // (The type assertion below asserts that v is not a slice.)
+ }
+ return v.Interface().(ast.Node) // may be nil
+}
+
+const (
+ Invalid Kind = iota // for nodes at the root of the traversal
+
+ // Kinds are sorted alphabetically.
+ // Numbering is not stable.
+ // Each is named Type_Field, where Type is the
+ // ast.Node struct type and Field is the name of the field
+
+ ArrayType_Elt
+ ArrayType_Len
+ AssignStmt_Lhs
+ AssignStmt_Rhs
+ BinaryExpr_X
+ BinaryExpr_Y
+ BlockStmt_List
+ BranchStmt_Label
+ CallExpr_Args
+ CallExpr_Fun
+ CaseClause_Body
+ CaseClause_List
+ ChanType_Value
+ CommClause_Body
+ CommClause_Comm
+ CommentGroup_List
+ CompositeLit_Elts
+ CompositeLit_Type
+ DeclStmt_Decl
+ DeferStmt_Call
+ Ellipsis_Elt
+ ExprStmt_X
+ FieldList_List
+ Field_Comment
+ Field_Doc
+ Field_Names
+ Field_Tag
+ Field_Type
+ File_Decls
+ File_Doc
+ File_Name
+ ForStmt_Body
+ ForStmt_Cond
+ ForStmt_Init
+ ForStmt_Post
+ FuncDecl_Body
+ FuncDecl_Doc
+ FuncDecl_Name
+ FuncDecl_Recv
+ FuncDecl_Type
+ FuncLit_Body
+ FuncLit_Type
+ FuncType_Params
+ FuncType_Results
+ FuncType_TypeParams
+ GenDecl_Doc
+ GenDecl_Specs
+ GoStmt_Call
+ IfStmt_Body
+ IfStmt_Cond
+ IfStmt_Else
+ IfStmt_Init
+ ImportSpec_Comment
+ ImportSpec_Doc
+ ImportSpec_Name
+ ImportSpec_Path
+ IncDecStmt_X
+ IndexExpr_Index
+ IndexExpr_X
+ IndexListExpr_Indices
+ IndexListExpr_X
+ InterfaceType_Methods
+ KeyValueExpr_Key
+ KeyValueExpr_Value
+ LabeledStmt_Label
+ LabeledStmt_Stmt
+ MapType_Key
+ MapType_Value
+ ParenExpr_X
+ RangeStmt_Body
+ RangeStmt_Key
+ RangeStmt_Value
+ RangeStmt_X
+ ReturnStmt_Results
+ SelectStmt_Body
+ SelectorExpr_Sel
+ SelectorExpr_X
+ SendStmt_Chan
+ SendStmt_Value
+ SliceExpr_High
+ SliceExpr_Low
+ SliceExpr_Max
+ SliceExpr_X
+ StarExpr_X
+ StructType_Fields
+ SwitchStmt_Body
+ SwitchStmt_Init
+ SwitchStmt_Tag
+ TypeAssertExpr_Type
+ TypeAssertExpr_X
+ TypeSpec_Comment
+ TypeSpec_Doc
+ TypeSpec_Name
+ TypeSpec_Type
+ TypeSpec_TypeParams
+ TypeSwitchStmt_Assign
+ TypeSwitchStmt_Body
+ TypeSwitchStmt_Init
+ UnaryExpr_X
+ ValueSpec_Comment
+ ValueSpec_Doc
+ ValueSpec_Names
+ ValueSpec_Type
+ ValueSpec_Values
+
+ maxKind
+)
+
+// Assert that the encoding fits in 7 bits,
+// as the inspector relies on this.
+// (We are currently at 104.)
+var _ = [1 << 7]struct{}{}[maxKind]
+
+type fieldInfo struct {
+ nodeType reflect.Type // pointer-to-struct type of ast.Node implementation
+ name string
+ index int
+ fieldType reflect.Type
+}
+
+func info[N ast.Node](fieldName string) fieldInfo {
+ nodePtrType := reflect.TypeFor[N]()
+ f, ok := nodePtrType.Elem().FieldByName(fieldName)
+ if !ok {
+ panic(fieldName)
+ }
+ return fieldInfo{nodePtrType, fieldName, f.Index[0], f.Type}
+}
+
+var fieldInfos = [...]fieldInfo{
+ Invalid: {},
+ ArrayType_Elt: info[*ast.ArrayType]("Elt"),
+ ArrayType_Len: info[*ast.ArrayType]("Len"),
+ AssignStmt_Lhs: info[*ast.AssignStmt]("Lhs"),
+ AssignStmt_Rhs: info[*ast.AssignStmt]("Rhs"),
+ BinaryExpr_X: info[*ast.BinaryExpr]("X"),
+ BinaryExpr_Y: info[*ast.BinaryExpr]("Y"),
+ BlockStmt_List: info[*ast.BlockStmt]("List"),
+ BranchStmt_Label: info[*ast.BranchStmt]("Label"),
+ CallExpr_Args: info[*ast.CallExpr]("Args"),
+ CallExpr_Fun: info[*ast.CallExpr]("Fun"),
+ CaseClause_Body: info[*ast.CaseClause]("Body"),
+ CaseClause_List: info[*ast.CaseClause]("List"),
+ ChanType_Value: info[*ast.ChanType]("Value"),
+ CommClause_Body: info[*ast.CommClause]("Body"),
+ CommClause_Comm: info[*ast.CommClause]("Comm"),
+ CommentGroup_List: info[*ast.CommentGroup]("List"),
+ CompositeLit_Elts: info[*ast.CompositeLit]("Elts"),
+ CompositeLit_Type: info[*ast.CompositeLit]("Type"),
+ DeclStmt_Decl: info[*ast.DeclStmt]("Decl"),
+ DeferStmt_Call: info[*ast.DeferStmt]("Call"),
+ Ellipsis_Elt: info[*ast.Ellipsis]("Elt"),
+ ExprStmt_X: info[*ast.ExprStmt]("X"),
+ FieldList_List: info[*ast.FieldList]("List"),
+ Field_Comment: info[*ast.Field]("Comment"),
+ Field_Doc: info[*ast.Field]("Doc"),
+ Field_Names: info[*ast.Field]("Names"),
+ Field_Tag: info[*ast.Field]("Tag"),
+ Field_Type: info[*ast.Field]("Type"),
+ File_Decls: info[*ast.File]("Decls"),
+ File_Doc: info[*ast.File]("Doc"),
+ File_Name: info[*ast.File]("Name"),
+ ForStmt_Body: info[*ast.ForStmt]("Body"),
+ ForStmt_Cond: info[*ast.ForStmt]("Cond"),
+ ForStmt_Init: info[*ast.ForStmt]("Init"),
+ ForStmt_Post: info[*ast.ForStmt]("Post"),
+ FuncDecl_Body: info[*ast.FuncDecl]("Body"),
+ FuncDecl_Doc: info[*ast.FuncDecl]("Doc"),
+ FuncDecl_Name: info[*ast.FuncDecl]("Name"),
+ FuncDecl_Recv: info[*ast.FuncDecl]("Recv"),
+ FuncDecl_Type: info[*ast.FuncDecl]("Type"),
+ FuncLit_Body: info[*ast.FuncLit]("Body"),
+ FuncLit_Type: info[*ast.FuncLit]("Type"),
+ FuncType_Params: info[*ast.FuncType]("Params"),
+ FuncType_Results: info[*ast.FuncType]("Results"),
+ FuncType_TypeParams: info[*ast.FuncType]("TypeParams"),
+ GenDecl_Doc: info[*ast.GenDecl]("Doc"),
+ GenDecl_Specs: info[*ast.GenDecl]("Specs"),
+ GoStmt_Call: info[*ast.GoStmt]("Call"),
+ IfStmt_Body: info[*ast.IfStmt]("Body"),
+ IfStmt_Cond: info[*ast.IfStmt]("Cond"),
+ IfStmt_Else: info[*ast.IfStmt]("Else"),
+ IfStmt_Init: info[*ast.IfStmt]("Init"),
+ ImportSpec_Comment: info[*ast.ImportSpec]("Comment"),
+ ImportSpec_Doc: info[*ast.ImportSpec]("Doc"),
+ ImportSpec_Name: info[*ast.ImportSpec]("Name"),
+ ImportSpec_Path: info[*ast.ImportSpec]("Path"),
+ IncDecStmt_X: info[*ast.IncDecStmt]("X"),
+ IndexExpr_Index: info[*ast.IndexExpr]("Index"),
+ IndexExpr_X: info[*ast.IndexExpr]("X"),
+ IndexListExpr_Indices: info[*ast.IndexListExpr]("Indices"),
+ IndexListExpr_X: info[*ast.IndexListExpr]("X"),
+ InterfaceType_Methods: info[*ast.InterfaceType]("Methods"),
+ KeyValueExpr_Key: info[*ast.KeyValueExpr]("Key"),
+ KeyValueExpr_Value: info[*ast.KeyValueExpr]("Value"),
+ LabeledStmt_Label: info[*ast.LabeledStmt]("Label"),
+ LabeledStmt_Stmt: info[*ast.LabeledStmt]("Stmt"),
+ MapType_Key: info[*ast.MapType]("Key"),
+ MapType_Value: info[*ast.MapType]("Value"),
+ ParenExpr_X: info[*ast.ParenExpr]("X"),
+ RangeStmt_Body: info[*ast.RangeStmt]("Body"),
+ RangeStmt_Key: info[*ast.RangeStmt]("Key"),
+ RangeStmt_Value: info[*ast.RangeStmt]("Value"),
+ RangeStmt_X: info[*ast.RangeStmt]("X"),
+ ReturnStmt_Results: info[*ast.ReturnStmt]("Results"),
+ SelectStmt_Body: info[*ast.SelectStmt]("Body"),
+ SelectorExpr_Sel: info[*ast.SelectorExpr]("Sel"),
+ SelectorExpr_X: info[*ast.SelectorExpr]("X"),
+ SendStmt_Chan: info[*ast.SendStmt]("Chan"),
+ SendStmt_Value: info[*ast.SendStmt]("Value"),
+ SliceExpr_High: info[*ast.SliceExpr]("High"),
+ SliceExpr_Low: info[*ast.SliceExpr]("Low"),
+ SliceExpr_Max: info[*ast.SliceExpr]("Max"),
+ SliceExpr_X: info[*ast.SliceExpr]("X"),
+ StarExpr_X: info[*ast.StarExpr]("X"),
+ StructType_Fields: info[*ast.StructType]("Fields"),
+ SwitchStmt_Body: info[*ast.SwitchStmt]("Body"),
+ SwitchStmt_Init: info[*ast.SwitchStmt]("Init"),
+ SwitchStmt_Tag: info[*ast.SwitchStmt]("Tag"),
+ TypeAssertExpr_Type: info[*ast.TypeAssertExpr]("Type"),
+ TypeAssertExpr_X: info[*ast.TypeAssertExpr]("X"),
+ TypeSpec_Comment: info[*ast.TypeSpec]("Comment"),
+ TypeSpec_Doc: info[*ast.TypeSpec]("Doc"),
+ TypeSpec_Name: info[*ast.TypeSpec]("Name"),
+ TypeSpec_Type: info[*ast.TypeSpec]("Type"),
+ TypeSpec_TypeParams: info[*ast.TypeSpec]("TypeParams"),
+ TypeSwitchStmt_Assign: info[*ast.TypeSwitchStmt]("Assign"),
+ TypeSwitchStmt_Body: info[*ast.TypeSwitchStmt]("Body"),
+ TypeSwitchStmt_Init: info[*ast.TypeSwitchStmt]("Init"),
+ UnaryExpr_X: info[*ast.UnaryExpr]("X"),
+ ValueSpec_Comment: info[*ast.ValueSpec]("Comment"),
+ ValueSpec_Doc: info[*ast.ValueSpec]("Doc"),
+ ValueSpec_Names: info[*ast.ValueSpec]("Names"),
+ ValueSpec_Type: info[*ast.ValueSpec]("Type"),
+ ValueSpec_Values: info[*ast.ValueSpec]("Values"),
+}
--- /dev/null
+// Copyright 2024 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 fmtstr defines a parser for format strings as used by [fmt.Printf].
+package fmtstr
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "unicode/utf8"
+)
+
+// Operation holds the parsed representation of a printf operation such as "%3.*[4]d".
+// It is constructed by [Parse].
+type Operation struct {
+ Text string // full text of the operation, e.g. "%[2]*.3d"
+ Verb Verb // verb specifier, guaranteed to exist, e.g., 'd' in '%[1]d'
+ Range Range // the range of Text within the overall format string
+ Flags string // formatting flags, e.g. "-0"
+ Width Size // width specifier, e.g., '3' in '%3d'
+ Prec Size // precision specifier, e.g., '.4' in '%.4f'
+}
+
+// Size describes an optional width or precision in a format operation.
+// It may represent no value, a literal number, an asterisk, or an indexed asterisk.
+type Size struct {
+ // At most one of these two fields is non-negative.
+ Fixed int // e.g. 4 from "%4d", otherwise -1
+ Dynamic int // index of argument providing dynamic size (e.g. %*d or %[3]*d), otherwise -1
+
+ Index int // If the width or precision uses an indexed argument (e.g. 2 in %[2]*d), this is the index, otherwise -1
+ Range Range // position of the size specifier within the operation
+}
+
+// Verb represents the verb character of a format operation (e.g., 'd', 's', 'f').
+// It also includes positional information and any explicit argument indexing.
+type Verb struct {
+ Verb rune
+ Range Range // positional range of the verb in the format string
+ Index int // index of an indexed argument, (e.g. 2 in %[2]d), otherwise -1
+ ArgIndex int // argument index (0-based) associated with this verb, relative to CallExpr
+}
+
+// byte offsets of format string
+type Range struct {
+ Start, End int
+}
+
+// Parse takes a format string and its index in the printf-like call,
+// parses out all format operations, returns a slice of parsed
+// [Operation] which describes flags, width, precision, verb, and argument indexing,
+// or an error if parsing fails.
+//
+// All error messages are in predicate form ("call has a problem")
+// so that they may be affixed into a subject ("log.Printf ").
+//
+// The flags will only be a subset of ['#', '0', '+', '-', ' '].
+// It does not perform any validation of verbs, nor the
+// existence of corresponding arguments (obviously it can't). The provided format string may differ
+// from the one in CallExpr, such as a concatenated string or a string
+// referred to by the argument in the CallExpr.
+func Parse(format string, idx int) ([]*Operation, error) {
+ if !strings.Contains(format, "%") {
+ return nil, fmt.Errorf("call has arguments but no formatting directives")
+ }
+
+ firstArg := idx + 1 // Arguments are immediately after format string.
+ argNum := firstArg
+ var operations []*Operation
+ for i, w := 0, 0; i < len(format); i += w {
+ w = 1
+ if format[i] != '%' {
+ continue
+ }
+ state, err := parseOperation(format[i:], firstArg, argNum)
+ if err != nil {
+ return nil, err
+ }
+
+ state.operation.addOffset(i)
+ operations = append(operations, state.operation)
+
+ w = len(state.operation.Text)
+ // Do not waste an argument for '%'.
+ if state.operation.Verb.Verb != '%' {
+ argNum = state.argNum + 1
+ }
+ }
+ return operations, nil
+}
+
+// Internal parsing state to operation.
+type state struct {
+ operation *Operation
+ firstArg int // index of the first argument after the format string
+ argNum int // which argument we're expecting to format now
+ hasIndex bool // whether the argument is indexed
+ index int // the encountered index
+ indexPos int // the encountered index's offset
+ indexPending bool // whether we have an indexed argument that has not resolved
+ nbytes int // number of bytes of the format string consumed
+}
+
+// parseOperation parses one format operation starting at the given substring `format`,
+// which should begin with '%'. It returns a fully populated state or an error
+// if the operation is malformed. The firstArg and argNum parameters help determine how
+// arguments map to this operation.
+//
+// Parse sequence: '%' -> flags -> {[N]* or width} -> .{[N]* or precision} -> [N] -> verb.
+func parseOperation(format string, firstArg, argNum int) (*state, error) {
+ state := &state{
+ operation: &Operation{
+ Text: format,
+ Width: Size{
+ Fixed: -1,
+ Dynamic: -1,
+ Index: -1,
+ },
+ Prec: Size{
+ Fixed: -1,
+ Dynamic: -1,
+ Index: -1,
+ },
+ },
+ firstArg: firstArg,
+ argNum: argNum,
+ hasIndex: false,
+ index: 0,
+ indexPos: 0,
+ indexPending: false,
+ nbytes: len("%"), // There's guaranteed to be a percent sign.
+ }
+ // There may be flags.
+ state.parseFlags()
+ // There may be an index.
+ if err := state.parseIndex(); err != nil {
+ return nil, err
+ }
+ // There may be a width.
+ state.parseSize(Width)
+ // There may be a precision.
+ if err := state.parsePrecision(); err != nil {
+ return nil, err
+ }
+ // Now a verb, possibly prefixed by an index (which we may already have).
+ if !state.indexPending {
+ if err := state.parseIndex(); err != nil {
+ return nil, err
+ }
+ }
+ if state.nbytes == len(state.operation.Text) {
+ return nil, fmt.Errorf("format %s is missing verb at end of string", state.operation.Text)
+ }
+ verb, w := utf8.DecodeRuneInString(state.operation.Text[state.nbytes:])
+
+ // Ensure there must be a verb.
+ if state.indexPending {
+ state.operation.Verb = Verb{
+ Verb: verb,
+ Range: Range{
+ Start: state.indexPos,
+ End: state.nbytes + w,
+ },
+ Index: state.index,
+ ArgIndex: state.argNum,
+ }
+ } else {
+ state.operation.Verb = Verb{
+ Verb: verb,
+ Range: Range{
+ Start: state.nbytes,
+ End: state.nbytes + w,
+ },
+ Index: -1,
+ ArgIndex: state.argNum,
+ }
+ }
+
+ state.nbytes += w
+ state.operation.Text = state.operation.Text[:state.nbytes]
+ return state, nil
+}
+
+// addOffset adjusts the recorded positions in Verb, Width, Prec, and the
+// operation's overall Range to be relative to the position in the full format string.
+func (s *Operation) addOffset(parsedLen int) {
+ s.Verb.Range.Start += parsedLen
+ s.Verb.Range.End += parsedLen
+
+ s.Range.Start = parsedLen
+ s.Range.End = s.Verb.Range.End
+
+ // one of Fixed or Dynamic is non-negative means existence.
+ if s.Prec.Fixed != -1 || s.Prec.Dynamic != -1 {
+ s.Prec.Range.Start += parsedLen
+ s.Prec.Range.End += parsedLen
+ }
+ if s.Width.Fixed != -1 || s.Width.Dynamic != -1 {
+ s.Width.Range.Start += parsedLen
+ s.Width.Range.End += parsedLen
+ }
+}
+
+// parseFlags accepts any printf flags.
+func (s *state) parseFlags() {
+ s.operation.Flags = prefixOf(s.operation.Text[s.nbytes:], "#0+- ")
+ s.nbytes += len(s.operation.Flags)
+}
+
+// prefixOf returns the prefix of s composed only of runes from the specified set.
+func prefixOf(s, set string) string {
+ rest := strings.TrimLeft(s, set)
+ return s[:len(s)-len(rest)]
+}
+
+// parseIndex parses an argument index of the form "[n]" that can appear
+// in a printf operation (e.g., "%[2]d"). Returns an error if syntax is
+// malformed or index is invalid.
+func (s *state) parseIndex() error {
+ if s.nbytes == len(s.operation.Text) || s.operation.Text[s.nbytes] != '[' {
+ return nil
+ }
+ // Argument index present.
+ s.nbytes++ // skip '['
+ start := s.nbytes
+ if num, ok := s.scanNum(); ok {
+ // Later consumed/stored by a '*' or verb.
+ s.index = num
+ s.indexPos = start - 1
+ }
+
+ ok := true
+ if s.nbytes == len(s.operation.Text) || s.nbytes == start || s.operation.Text[s.nbytes] != ']' {
+ ok = false // syntax error is either missing "]" or invalid index.
+ s.nbytes = strings.Index(s.operation.Text[start:], "]")
+ if s.nbytes < 0 {
+ return fmt.Errorf("format %s is missing closing ]", s.operation.Text)
+ }
+ s.nbytes = s.nbytes + start
+ }
+ arg32, err := strconv.ParseInt(s.operation.Text[start:s.nbytes], 10, 32)
+ if err != nil || !ok || arg32 <= 0 {
+ return fmt.Errorf("format has invalid argument index [%s]", s.operation.Text[start:s.nbytes])
+ }
+
+ s.nbytes++ // skip ']'
+ arg := int(arg32)
+ arg += s.firstArg - 1 // We want to zero-index the actual arguments.
+ s.argNum = arg
+ s.hasIndex = true
+ s.indexPending = true
+ return nil
+}
+
+// scanNum advances through a decimal number if present, which represents a [Size] or [Index].
+func (s *state) scanNum() (int, bool) {
+ start := s.nbytes
+ for ; s.nbytes < len(s.operation.Text); s.nbytes++ {
+ c := s.operation.Text[s.nbytes]
+ if c < '0' || '9' < c {
+ if start < s.nbytes {
+ num, _ := strconv.ParseInt(s.operation.Text[start:s.nbytes], 10, 32)
+ return int(num), true
+ } else {
+ return 0, false
+ }
+ }
+ }
+ return 0, false
+}
+
+type sizeType int
+
+const (
+ Width sizeType = iota
+ Precision
+)
+
+// parseSize parses a width or precision specifier. It handles literal numeric
+// values (e.g., "%3d"), asterisk values (e.g., "%*d"), or indexed asterisk values (e.g., "%[2]*d").
+func (s *state) parseSize(kind sizeType) {
+ if s.nbytes < len(s.operation.Text) && s.operation.Text[s.nbytes] == '*' {
+ s.nbytes++
+ if s.indexPending {
+ // Absorb it.
+ s.indexPending = false
+ size := Size{
+ Fixed: -1,
+ Dynamic: s.argNum,
+ Index: s.index,
+ Range: Range{
+ Start: s.indexPos,
+ End: s.nbytes,
+ },
+ }
+ switch kind {
+ case Width:
+ s.operation.Width = size
+ case Precision:
+ // Include the leading '.'.
+ size.Range.Start -= len(".")
+ s.operation.Prec = size
+ default:
+ panic(kind)
+ }
+ } else {
+ // Non-indexed asterisk: "%*d".
+ size := Size{
+ Dynamic: s.argNum,
+ Index: -1,
+ Fixed: -1,
+ Range: Range{
+ Start: s.nbytes - 1,
+ End: s.nbytes,
+ },
+ }
+ switch kind {
+ case Width:
+ s.operation.Width = size
+ case Precision:
+ // For precision, include the '.' in the range.
+ size.Range.Start -= 1
+ s.operation.Prec = size
+ default:
+ panic(kind)
+ }
+ }
+ s.argNum++
+ } else { // Literal number, e.g. "%10d"
+ start := s.nbytes
+ if num, ok := s.scanNum(); ok {
+ size := Size{
+ Fixed: num,
+ Index: -1,
+ Dynamic: -1,
+ Range: Range{
+ Start: start,
+ End: s.nbytes,
+ },
+ }
+ switch kind {
+ case Width:
+ s.operation.Width = size
+ case Precision:
+ // Include the leading '.'.
+ size.Range.Start -= 1
+ s.operation.Prec = size
+ default:
+ panic(kind)
+ }
+ }
+ }
+}
+
+// parsePrecision checks if there's a precision specified after a '.' character.
+// If found, it may also parse an index or an asterisk. Returns an error if any index
+// parsing fails.
+func (s *state) parsePrecision() error {
+ // If there's a period, there may be a precision.
+ if s.nbytes < len(s.operation.Text) && s.operation.Text[s.nbytes] == '.' {
+ s.nbytes++
+ if err := s.parseIndex(); err != nil {
+ return err
+ }
+ s.parseSize(Precision)
+ }
+ return nil
+}
{"ErrTooLarge", Var, 0},
{"Fields", Func, 0},
{"FieldsFunc", Func, 0},
+ {"FieldsFuncSeq", Func, 24},
+ {"FieldsSeq", Func, 24},
{"HasPrefix", Func, 0},
{"HasSuffix", Func, 0},
{"Index", Func, 0},
{"LastIndexAny", Func, 0},
{"LastIndexByte", Func, 5},
{"LastIndexFunc", Func, 0},
+ {"Lines", Func, 24},
{"Map", Func, 0},
{"MinRead", Const, 0},
{"NewBuffer", Func, 0},
{"Split", Func, 0},
{"SplitAfter", Func, 0},
{"SplitAfterN", Func, 0},
+ {"SplitAfterSeq", Func, 24},
{"SplitN", Func, 0},
+ {"SplitSeq", Func, 24},
{"Title", Func, 0},
{"ToLower", Func, 0},
{"ToLowerSpecial", Func, 0},
{"NewCTR", Func, 0},
{"NewGCM", Func, 2},
{"NewGCMWithNonceSize", Func, 5},
+ {"NewGCMWithRandomNonce", Func, 24},
{"NewGCMWithTagSize", Func, 11},
{"NewOFB", Func, 0},
{"Stream", Type, 0},
{"Unmarshal", Func, 0},
{"UnmarshalCompressed", Func, 15},
},
+ "crypto/fips140": {
+ {"Enabled", Func, 24},
+ },
+ "crypto/hkdf": {
+ {"Expand", Func, 24},
+ {"Extract", Func, 24},
+ {"Key", Func, 24},
+ },
"crypto/hmac": {
{"Equal", Func, 1},
{"New", Func, 0},
{"Size", Const, 0},
{"Sum", Func, 2},
},
+ "crypto/mlkem": {
+ {"(*DecapsulationKey1024).Bytes", Method, 24},
+ {"(*DecapsulationKey1024).Decapsulate", Method, 24},
+ {"(*DecapsulationKey1024).EncapsulationKey", Method, 24},
+ {"(*DecapsulationKey768).Bytes", Method, 24},
+ {"(*DecapsulationKey768).Decapsulate", Method, 24},
+ {"(*DecapsulationKey768).EncapsulationKey", Method, 24},
+ {"(*EncapsulationKey1024).Bytes", Method, 24},
+ {"(*EncapsulationKey1024).Encapsulate", Method, 24},
+ {"(*EncapsulationKey768).Bytes", Method, 24},
+ {"(*EncapsulationKey768).Encapsulate", Method, 24},
+ {"CiphertextSize1024", Const, 24},
+ {"CiphertextSize768", Const, 24},
+ {"DecapsulationKey1024", Type, 24},
+ {"DecapsulationKey768", Type, 24},
+ {"EncapsulationKey1024", Type, 24},
+ {"EncapsulationKey768", Type, 24},
+ {"EncapsulationKeySize1024", Const, 24},
+ {"EncapsulationKeySize768", Const, 24},
+ {"GenerateKey1024", Func, 24},
+ {"GenerateKey768", Func, 24},
+ {"NewDecapsulationKey1024", Func, 24},
+ {"NewDecapsulationKey768", Func, 24},
+ {"NewEncapsulationKey1024", Func, 24},
+ {"NewEncapsulationKey768", Func, 24},
+ {"SeedSize", Const, 24},
+ {"SharedKeySize", Const, 24},
+ },
+ "crypto/pbkdf2": {
+ {"Key", Func, 24},
+ },
"crypto/rand": {
{"Int", Func, 0},
{"Prime", Func, 0},
{"Read", Func, 0},
{"Reader", Var, 0},
+ {"Text", Func, 24},
},
"crypto/rc4": {
{"(*Cipher).Reset", Method, 0},
{"Sum224", Func, 2},
{"Sum256", Func, 2},
},
+ "crypto/sha3": {
+ {"(*SHA3).AppendBinary", Method, 24},
+ {"(*SHA3).BlockSize", Method, 24},
+ {"(*SHA3).MarshalBinary", Method, 24},
+ {"(*SHA3).Reset", Method, 24},
+ {"(*SHA3).Size", Method, 24},
+ {"(*SHA3).Sum", Method, 24},
+ {"(*SHA3).UnmarshalBinary", Method, 24},
+ {"(*SHA3).Write", Method, 24},
+ {"(*SHAKE).AppendBinary", Method, 24},
+ {"(*SHAKE).BlockSize", Method, 24},
+ {"(*SHAKE).MarshalBinary", Method, 24},
+ {"(*SHAKE).Read", Method, 24},
+ {"(*SHAKE).Reset", Method, 24},
+ {"(*SHAKE).UnmarshalBinary", Method, 24},
+ {"(*SHAKE).Write", Method, 24},
+ {"New224", Func, 24},
+ {"New256", Func, 24},
+ {"New384", Func, 24},
+ {"New512", Func, 24},
+ {"NewCSHAKE128", Func, 24},
+ {"NewCSHAKE256", Func, 24},
+ {"NewSHAKE128", Func, 24},
+ {"NewSHAKE256", Func, 24},
+ {"SHA3", Type, 24},
+ {"SHAKE", Type, 24},
+ {"Sum224", Func, 24},
+ {"Sum256", Func, 24},
+ {"Sum384", Func, 24},
+ {"Sum512", Func, 24},
+ {"SumSHAKE128", Func, 24},
+ {"SumSHAKE256", Func, 24},
+ },
"crypto/sha512": {
{"BlockSize", Const, 0},
{"New", Func, 0},
{"ConstantTimeEq", Func, 0},
{"ConstantTimeLessOrEq", Func, 2},
{"ConstantTimeSelect", Func, 0},
+ {"WithDataIndependentTiming", Func, 24},
{"XORBytes", Func, 20},
},
"crypto/tls": {
{"ClientHelloInfo", Type, 4},
{"ClientHelloInfo.CipherSuites", Field, 4},
{"ClientHelloInfo.Conn", Field, 8},
+ {"ClientHelloInfo.Extensions", Field, 24},
{"ClientHelloInfo.ServerName", Field, 4},
{"ClientHelloInfo.SignatureSchemes", Field, 8},
{"ClientHelloInfo.SupportedCurves", Field, 4},
{"Config.CurvePreferences", Field, 3},
{"Config.DynamicRecordSizingDisabled", Field, 7},
{"Config.EncryptedClientHelloConfigList", Field, 23},
+ {"Config.EncryptedClientHelloKeys", Field, 24},
{"Config.EncryptedClientHelloRejectionVerify", Field, 23},
{"Config.GetCertificate", Field, 4},
{"Config.GetClientCertificate", Field, 8},
{"ECHRejectionError", Type, 23},
{"ECHRejectionError.RetryConfigList", Field, 23},
{"Ed25519", Const, 13},
+ {"EncryptedClientHelloKey", Type, 24},
+ {"EncryptedClientHelloKey.Config", Field, 24},
+ {"EncryptedClientHelloKey.PrivateKey", Field, 24},
+ {"EncryptedClientHelloKey.SendAsRetry", Field, 24},
{"InsecureCipherSuites", Func, 14},
{"Listen", Func, 0},
{"LoadX509KeyPair", Func, 0},
{"VersionTLS12", Const, 2},
{"VersionTLS13", Const, 12},
{"X25519", Const, 8},
+ {"X25519MLKEM768", Const, 24},
{"X509KeyPair", Func, 0},
},
"crypto/x509": {
{"(ConstraintViolationError).Error", Method, 0},
{"(HostnameError).Error", Method, 0},
{"(InsecureAlgorithmError).Error", Method, 6},
+ {"(OID).AppendBinary", Method, 24},
+ {"(OID).AppendText", Method, 24},
{"(OID).Equal", Method, 22},
{"(OID).EqualASN1OID", Method, 22},
{"(OID).MarshalBinary", Method, 23},
{"Certificate.Extensions", Field, 2},
{"Certificate.ExtraExtensions", Field, 2},
{"Certificate.IPAddresses", Field, 1},
+ {"Certificate.InhibitAnyPolicy", Field, 24},
+ {"Certificate.InhibitAnyPolicyZero", Field, 24},
+ {"Certificate.InhibitPolicyMapping", Field, 24},
+ {"Certificate.InhibitPolicyMappingZero", Field, 24},
{"Certificate.IsCA", Field, 0},
{"Certificate.Issuer", Field, 0},
{"Certificate.IssuingCertificateURL", Field, 2},
{"Certificate.PermittedURIDomains", Field, 10},
{"Certificate.Policies", Field, 22},
{"Certificate.PolicyIdentifiers", Field, 0},
+ {"Certificate.PolicyMappings", Field, 24},
{"Certificate.PublicKey", Field, 0},
{"Certificate.PublicKeyAlgorithm", Field, 0},
{"Certificate.Raw", Field, 0},
{"Certificate.RawSubject", Field, 0},
{"Certificate.RawSubjectPublicKeyInfo", Field, 0},
{"Certificate.RawTBSCertificate", Field, 0},
+ {"Certificate.RequireExplicitPolicy", Field, 24},
+ {"Certificate.RequireExplicitPolicyZero", Field, 24},
{"Certificate.SerialNumber", Field, 0},
{"Certificate.Signature", Field, 0},
{"Certificate.SignatureAlgorithm", Field, 0},
{"NameConstraintsWithoutSANs", Const, 10},
{"NameMismatch", Const, 8},
{"NewCertPool", Func, 0},
+ {"NoValidChains", Const, 24},
{"NotAuthorizedToSign", Const, 0},
{"OID", Type, 22},
{"OIDFromInts", Func, 22},
{"ParsePKCS8PrivateKey", Func, 0},
{"ParsePKIXPublicKey", Func, 0},
{"ParseRevocationList", Func, 19},
+ {"PolicyMapping", Type, 24},
+ {"PolicyMapping.IssuerDomainPolicy", Field, 24},
+ {"PolicyMapping.SubjectDomainPolicy", Field, 24},
{"PublicKeyAlgorithm", Type, 0},
{"PureEd25519", Const, 13},
{"RSA", Const, 0},
{"UnknownPublicKeyAlgorithm", Const, 0},
{"UnknownSignatureAlgorithm", Const, 0},
{"VerifyOptions", Type, 0},
+ {"VerifyOptions.CertificatePolicies", Field, 24},
{"VerifyOptions.CurrentTime", Field, 0},
{"VerifyOptions.DNSName", Field, 0},
{"VerifyOptions.Intermediates", Field, 0},
{"(*File).DynString", Method, 1},
{"(*File).DynValue", Method, 21},
{"(*File).DynamicSymbols", Method, 4},
+ {"(*File).DynamicVersionNeeds", Method, 24},
+ {"(*File).DynamicVersions", Method, 24},
{"(*File).ImportedLibraries", Method, 0},
{"(*File).ImportedSymbols", Method, 0},
{"(*File).Section", Method, 0},
{"(Type).String", Method, 0},
{"(Version).GoString", Method, 0},
{"(Version).String", Method, 0},
+ {"(VersionIndex).Index", Method, 24},
+ {"(VersionIndex).IsHidden", Method, 24},
{"ARM_MAGIC_TRAMP_NUMBER", Const, 0},
{"COMPRESS_HIOS", Const, 6},
{"COMPRESS_HIPROC", Const, 6},
{"DynFlag", Type, 0},
{"DynFlag1", Type, 21},
{"DynTag", Type, 0},
+ {"DynamicVersion", Type, 24},
+ {"DynamicVersion.Deps", Field, 24},
+ {"DynamicVersion.Flags", Field, 24},
+ {"DynamicVersion.Index", Field, 24},
+ {"DynamicVersion.Name", Field, 24},
+ {"DynamicVersionDep", Type, 24},
+ {"DynamicVersionDep.Dep", Field, 24},
+ {"DynamicVersionDep.Flags", Field, 24},
+ {"DynamicVersionDep.Index", Field, 24},
+ {"DynamicVersionFlag", Type, 24},
+ {"DynamicVersionNeed", Type, 24},
+ {"DynamicVersionNeed.Name", Field, 24},
+ {"DynamicVersionNeed.Needs", Field, 24},
{"EI_ABIVERSION", Const, 0},
{"EI_CLASS", Const, 0},
{"EI_DATA", Const, 0},
{"SymType", Type, 0},
{"SymVis", Type, 0},
{"Symbol", Type, 0},
+ {"Symbol.HasVersion", Field, 24},
{"Symbol.Info", Field, 0},
{"Symbol.Library", Field, 13},
{"Symbol.Name", Field, 0},
{"Symbol.Size", Field, 0},
{"Symbol.Value", Field, 0},
{"Symbol.Version", Field, 13},
+ {"Symbol.VersionIndex", Field, 24},
{"Type", Type, 0},
+ {"VER_FLG_BASE", Const, 24},
+ {"VER_FLG_INFO", Const, 24},
+ {"VER_FLG_WEAK", Const, 24},
{"Version", Type, 0},
+ {"VersionIndex", Type, 24},
},
"debug/gosym": {
{"(*DecodingError).Error", Method, 0},
{"FS", Type, 16},
},
"encoding": {
+ {"BinaryAppender", Type, 24},
{"BinaryMarshaler", Type, 2},
{"BinaryUnmarshaler", Type, 2},
+ {"TextAppender", Type, 24},
{"TextMarshaler", Type, 2},
{"TextUnmarshaler", Type, 2},
},
{"(*Interface).Complete", Method, 5},
{"(*Interface).Embedded", Method, 5},
{"(*Interface).EmbeddedType", Method, 11},
+ {"(*Interface).EmbeddedTypes", Method, 24},
{"(*Interface).Empty", Method, 5},
{"(*Interface).ExplicitMethod", Method, 5},
+ {"(*Interface).ExplicitMethods", Method, 24},
{"(*Interface).IsComparable", Method, 18},
{"(*Interface).IsImplicit", Method, 18},
{"(*Interface).IsMethodSet", Method, 18},
{"(*Interface).MarkImplicit", Method, 18},
{"(*Interface).Method", Method, 5},
+ {"(*Interface).Methods", Method, 24},
{"(*Interface).NumEmbeddeds", Method, 5},
{"(*Interface).NumExplicitMethods", Method, 5},
{"(*Interface).NumMethods", Method, 5},
{"(*MethodSet).At", Method, 5},
{"(*MethodSet).Len", Method, 5},
{"(*MethodSet).Lookup", Method, 5},
+ {"(*MethodSet).Methods", Method, 24},
{"(*MethodSet).String", Method, 5},
{"(*Named).AddMethod", Method, 5},
{"(*Named).Method", Method, 5},
+ {"(*Named).Methods", Method, 24},
{"(*Named).NumMethods", Method, 5},
{"(*Named).Obj", Method, 5},
{"(*Named).Origin", Method, 18},
{"(*Pointer).String", Method, 5},
{"(*Pointer).Underlying", Method, 5},
{"(*Scope).Child", Method, 5},
+ {"(*Scope).Children", Method, 24},
{"(*Scope).Contains", Method, 5},
{"(*Scope).End", Method, 5},
{"(*Scope).Innermost", Method, 5},
{"(*StdSizes).Offsetsof", Method, 5},
{"(*StdSizes).Sizeof", Method, 5},
{"(*Struct).Field", Method, 5},
+ {"(*Struct).Fields", Method, 24},
{"(*Struct).NumFields", Method, 5},
{"(*Struct).String", Method, 5},
{"(*Struct).Tag", Method, 5},
{"(*Tuple).Len", Method, 5},
{"(*Tuple).String", Method, 5},
{"(*Tuple).Underlying", Method, 5},
+ {"(*Tuple).Variables", Method, 24},
{"(*TypeList).At", Method, 18},
{"(*TypeList).Len", Method, 18},
+ {"(*TypeList).Types", Method, 24},
{"(*TypeName).Exported", Method, 5},
{"(*TypeName).Id", Method, 5},
{"(*TypeName).IsAlias", Method, 9},
{"(*TypeParam).Underlying", Method, 18},
{"(*TypeParamList).At", Method, 18},
{"(*TypeParamList).Len", Method, 18},
+ {"(*TypeParamList).TypeParams", Method, 24},
{"(*Union).Len", Method, 18},
{"(*Union).String", Method, 18},
{"(*Union).Term", Method, 18},
+ {"(*Union).Terms", Method, 24},
{"(*Union).Underlying", Method, 18},
{"(*Var).Anonymous", Method, 5},
{"(*Var).Embedded", Method, 11},
{"(*Hash).WriteByte", Method, 14},
{"(*Hash).WriteString", Method, 14},
{"Bytes", Func, 19},
+ {"Comparable", Func, 24},
{"Hash", Type, 14},
{"MakeSeed", Func, 14},
{"Seed", Type, 14},
{"String", Func, 19},
+ {"WriteComparable", Func, 24},
},
"html": {
{"EscapeString", Func, 0},
{"(*JSONHandler).WithGroup", Method, 21},
{"(*Level).UnmarshalJSON", Method, 21},
{"(*Level).UnmarshalText", Method, 21},
+ {"(*LevelVar).AppendText", Method, 24},
{"(*LevelVar).Level", Method, 21},
{"(*LevelVar).MarshalText", Method, 21},
{"(*LevelVar).Set", Method, 21},
{"(Attr).Equal", Method, 21},
{"(Attr).String", Method, 21},
{"(Kind).String", Method, 21},
+ {"(Level).AppendText", Method, 24},
{"(Level).Level", Method, 21},
{"(Level).MarshalJSON", Method, 21},
{"(Level).MarshalText", Method, 21},
{"Debug", Func, 21},
{"DebugContext", Func, 21},
{"Default", Func, 21},
+ {"DiscardHandler", Var, 24},
{"Duration", Func, 21},
{"DurationValue", Func, 21},
{"Error", Func, 21},
{"(*Float).Acc", Method, 5},
{"(*Float).Add", Method, 5},
{"(*Float).Append", Method, 5},
+ {"(*Float).AppendText", Method, 24},
{"(*Float).Cmp", Method, 5},
{"(*Float).Copy", Method, 5},
{"(*Float).Float32", Method, 5},
{"(*Int).And", Method, 0},
{"(*Int).AndNot", Method, 0},
{"(*Int).Append", Method, 6},
+ {"(*Int).AppendText", Method, 24},
{"(*Int).Binomial", Method, 0},
{"(*Int).Bit", Method, 0},
{"(*Int).BitLen", Method, 0},
{"(*Int).Xor", Method, 0},
{"(*Rat).Abs", Method, 0},
{"(*Rat).Add", Method, 0},
+ {"(*Rat).AppendText", Method, 24},
{"(*Rat).Cmp", Method, 0},
{"(*Rat).Denom", Method, 0},
{"(*Rat).Float32", Method, 4},
{"Zipf", Type, 0},
},
"math/rand/v2": {
+ {"(*ChaCha8).AppendBinary", Method, 24},
{"(*ChaCha8).MarshalBinary", Method, 22},
{"(*ChaCha8).Read", Method, 23},
{"(*ChaCha8).Seed", Method, 22},
{"(*ChaCha8).Uint64", Method, 22},
{"(*ChaCha8).UnmarshalBinary", Method, 22},
+ {"(*PCG).AppendBinary", Method, 24},
{"(*PCG).MarshalBinary", Method, 22},
{"(*PCG).Seed", Method, 22},
{"(*PCG).Uint64", Method, 22},
{"(*UnixListener).SyscallConn", Method, 10},
{"(Flags).String", Method, 0},
{"(HardwareAddr).String", Method, 0},
+ {"(IP).AppendText", Method, 24},
{"(IP).DefaultMask", Method, 0},
{"(IP).Equal", Method, 0},
{"(IP).IsGlobalUnicast", Method, 0},
{"(*MaxBytesError).Error", Method, 19},
{"(*ProtocolError).Error", Method, 0},
{"(*ProtocolError).Is", Method, 21},
+ {"(*Protocols).SetHTTP1", Method, 24},
+ {"(*Protocols).SetHTTP2", Method, 24},
+ {"(*Protocols).SetUnencryptedHTTP2", Method, 24},
{"(*Request).AddCookie", Method, 0},
{"(*Request).BasicAuth", Method, 4},
{"(*Request).Clone", Method, 13},
{"(Header).Values", Method, 14},
{"(Header).Write", Method, 0},
{"(Header).WriteSubset", Method, 0},
+ {"(Protocols).HTTP1", Method, 24},
+ {"(Protocols).HTTP2", Method, 24},
+ {"(Protocols).String", Method, 24},
+ {"(Protocols).UnencryptedHTTP2", Method, 24},
{"AllowQuerySemicolons", Func, 17},
{"CanonicalHeaderKey", Func, 0},
{"Client", Type, 0},
{"FileSystem", Type, 0},
{"Flusher", Type, 0},
{"Get", Func, 0},
+ {"HTTP2Config", Type, 24},
+ {"HTTP2Config.CountError", Field, 24},
+ {"HTTP2Config.MaxConcurrentStreams", Field, 24},
+ {"HTTP2Config.MaxDecoderHeaderTableSize", Field, 24},
+ {"HTTP2Config.MaxEncoderHeaderTableSize", Field, 24},
+ {"HTTP2Config.MaxReadFrameSize", Field, 24},
+ {"HTTP2Config.MaxReceiveBufferPerConnection", Field, 24},
+ {"HTTP2Config.MaxReceiveBufferPerStream", Field, 24},
+ {"HTTP2Config.PermitProhibitedCipherSuites", Field, 24},
+ {"HTTP2Config.PingTimeout", Field, 24},
+ {"HTTP2Config.SendPingTimeout", Field, 24},
+ {"HTTP2Config.WriteByteTimeout", Field, 24},
{"Handle", Func, 0},
{"HandleFunc", Func, 0},
{"Handler", Type, 0},
{"PostForm", Func, 0},
{"ProtocolError", Type, 0},
{"ProtocolError.ErrorString", Field, 0},
+ {"Protocols", Type, 24},
{"ProxyFromEnvironment", Func, 0},
{"ProxyURL", Func, 0},
{"PushOptions", Type, 8},
{"Server.ConnState", Field, 3},
{"Server.DisableGeneralOptionsHandler", Field, 20},
{"Server.ErrorLog", Field, 3},
+ {"Server.HTTP2", Field, 24},
{"Server.Handler", Field, 0},
{"Server.IdleTimeout", Field, 8},
{"Server.MaxHeaderBytes", Field, 0},
+ {"Server.Protocols", Field, 24},
{"Server.ReadHeaderTimeout", Field, 8},
{"Server.ReadTimeout", Field, 0},
{"Server.TLSConfig", Field, 0},
{"Transport.ExpectContinueTimeout", Field, 6},
{"Transport.ForceAttemptHTTP2", Field, 13},
{"Transport.GetProxyConnectHeader", Field, 16},
+ {"Transport.HTTP2", Field, 24},
{"Transport.IdleConnTimeout", Field, 7},
{"Transport.MaxConnsPerHost", Field, 11},
{"Transport.MaxIdleConns", Field, 7},
{"Transport.MaxIdleConnsPerHost", Field, 0},
{"Transport.MaxResponseHeaderBytes", Field, 7},
{"Transport.OnProxyConnectResponse", Field, 20},
+ {"Transport.Protocols", Field, 24},
{"Transport.Proxy", Field, 0},
{"Transport.ProxyConnectHeader", Field, 8},
{"Transport.ReadBufferSize", Field, 13},
{"(*AddrPort).UnmarshalText", Method, 18},
{"(*Prefix).UnmarshalBinary", Method, 18},
{"(*Prefix).UnmarshalText", Method, 18},
+ {"(Addr).AppendBinary", Method, 24},
+ {"(Addr).AppendText", Method, 24},
{"(Addr).AppendTo", Method, 18},
{"(Addr).As16", Method, 18},
{"(Addr).As4", Method, 18},
{"(Addr).WithZone", Method, 18},
{"(Addr).Zone", Method, 18},
{"(AddrPort).Addr", Method, 18},
+ {"(AddrPort).AppendBinary", Method, 24},
+ {"(AddrPort).AppendText", Method, 24},
{"(AddrPort).AppendTo", Method, 18},
{"(AddrPort).Compare", Method, 22},
{"(AddrPort).IsValid", Method, 18},
{"(AddrPort).Port", Method, 18},
{"(AddrPort).String", Method, 18},
{"(Prefix).Addr", Method, 18},
+ {"(Prefix).AppendBinary", Method, 24},
+ {"(Prefix).AppendText", Method, 24},
{"(Prefix).AppendTo", Method, 18},
{"(Prefix).Bits", Method, 18},
{"(Prefix).Contains", Method, 18},
{"(*Error).Temporary", Method, 6},
{"(*Error).Timeout", Method, 6},
{"(*Error).Unwrap", Method, 13},
+ {"(*URL).AppendBinary", Method, 24},
{"(*URL).EscapedFragment", Method, 15},
{"(*URL).EscapedPath", Method, 5},
{"(*URL).Hostname", Method, 8},
{"(*ProcessState).SysUsage", Method, 0},
{"(*ProcessState).SystemTime", Method, 0},
{"(*ProcessState).UserTime", Method, 0},
+ {"(*Root).Close", Method, 24},
+ {"(*Root).Create", Method, 24},
+ {"(*Root).FS", Method, 24},
+ {"(*Root).Lstat", Method, 24},
+ {"(*Root).Mkdir", Method, 24},
+ {"(*Root).Name", Method, 24},
+ {"(*Root).Open", Method, 24},
+ {"(*Root).OpenFile", Method, 24},
+ {"(*Root).OpenRoot", Method, 24},
+ {"(*Root).Remove", Method, 24},
+ {"(*Root).Stat", Method, 24},
{"(*SyscallError).Error", Method, 0},
{"(*SyscallError).Timeout", Method, 10},
{"(*SyscallError).Unwrap", Method, 13},
{"O_WRONLY", Const, 0},
{"Open", Func, 0},
{"OpenFile", Func, 0},
+ {"OpenInRoot", Func, 24},
+ {"OpenRoot", Func, 24},
{"PathError", Type, 0},
{"PathError.Err", Field, 0},
{"PathError.Op", Field, 0},
{"Remove", Func, 0},
{"RemoveAll", Func, 0},
{"Rename", Func, 0},
+ {"Root", Type, 24},
{"SEEK_CUR", Const, 0},
{"SEEK_END", Const, 0},
{"SEEK_SET", Const, 0},
{"Zero", Func, 0},
},
"regexp": {
+ {"(*Regexp).AppendText", Method, 24},
{"(*Regexp).Copy", Method, 6},
{"(*Regexp).Expand", Method, 0},
{"(*Regexp).ExpandString", Method, 0},
{"(*StackRecord).Stack", Method, 0},
{"(*TypeAssertionError).Error", Method, 0},
{"(*TypeAssertionError).RuntimeError", Method, 0},
+ {"(Cleanup).Stop", Method, 24},
+ {"AddCleanup", Func, 24},
{"BlockProfile", Func, 1},
{"BlockProfileRecord", Type, 1},
{"BlockProfileRecord.Count", Field, 1},
{"Caller", Func, 0},
{"Callers", Func, 0},
{"CallersFrames", Func, 7},
+ {"Cleanup", Type, 24},
{"Compiler", Const, 0},
{"Error", Type, 0},
{"Frame", Type, 7},
{"EqualFold", Func, 0},
{"Fields", Func, 0},
{"FieldsFunc", Func, 0},
+ {"FieldsFuncSeq", Func, 24},
+ {"FieldsSeq", Func, 24},
{"HasPrefix", Func, 0},
{"HasSuffix", Func, 0},
{"Index", Func, 0},
{"LastIndexAny", Func, 0},
{"LastIndexByte", Func, 5},
{"LastIndexFunc", Func, 0},
+ {"Lines", Func, 24},
{"Map", Func, 0},
{"NewReader", Func, 0},
{"NewReplacer", Func, 0},
{"Split", Func, 0},
{"SplitAfter", Func, 0},
{"SplitAfterN", Func, 0},
+ {"SplitAfterSeq", Func, 24},
{"SplitN", Func, 0},
+ {"SplitSeq", Func, 24},
{"Title", Func, 0},
{"ToLower", Func, 0},
{"ToLowerSpecial", Func, 0},
{"ValueOf", Func, 0},
},
"testing": {
+ {"(*B).Chdir", Method, 24},
{"(*B).Cleanup", Method, 14},
+ {"(*B).Context", Method, 24},
{"(*B).Elapsed", Method, 20},
{"(*B).Error", Method, 0},
{"(*B).Errorf", Method, 0},
{"(*B).Helper", Method, 9},
{"(*B).Log", Method, 0},
{"(*B).Logf", Method, 0},
+ {"(*B).Loop", Method, 24},
{"(*B).Name", Method, 8},
{"(*B).ReportAllocs", Method, 1},
{"(*B).ReportMetric", Method, 13},
{"(*B).StopTimer", Method, 0},
{"(*B).TempDir", Method, 15},
{"(*F).Add", Method, 18},
+ {"(*F).Chdir", Method, 24},
{"(*F).Cleanup", Method, 18},
+ {"(*F).Context", Method, 24},
{"(*F).Error", Method, 18},
{"(*F).Errorf", Method, 18},
{"(*F).Fail", Method, 18},
{"(*F).TempDir", Method, 18},
{"(*M).Run", Method, 4},
{"(*PB).Next", Method, 3},
+ {"(*T).Chdir", Method, 24},
{"(*T).Cleanup", Method, 14},
+ {"(*T).Context", Method, 24},
{"(*T).Deadline", Method, 15},
{"(*T).Error", Method, 0},
{"(*T).Errorf", Method, 0},
{"(Time).Add", Method, 0},
{"(Time).AddDate", Method, 0},
{"(Time).After", Method, 0},
+ {"(Time).AppendBinary", Method, 24},
{"(Time).AppendFormat", Method, 5},
+ {"(Time).AppendText", Method, 24},
{"(Time).Before", Method, 0},
{"(Time).Clock", Method, 0},
{"(Time).Compare", Method, 20},
{"String", Func, 0},
{"StringData", Func, 0},
},
+ "weak": {
+ {"(Pointer).Value", Method, 24},
+ {"Make", Func, 24},
+ {"Pointer", Type, 24},
+ },
}
_, ok := types.Unalias(t).(*types.TypeParam)
return ok
}
-
-// GenericAssignableTo is a generalization of types.AssignableTo that
-// implements the following rule for uninstantiated generic types:
-//
-// If V and T are generic named types, then V is considered assignable to T if,
-// for every possible instantiation of V[A_1, ..., A_N], the instantiation
-// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N].
-//
-// If T has structural constraints, they must be satisfied by V.
-//
-// For example, consider the following type declarations:
-//
-// type Interface[T any] interface {
-// Accept(T)
-// }
-//
-// type Container[T any] struct {
-// Element T
-// }
-//
-// func (c Container[T]) Accept(t T) { c.Element = t }
-//
-// In this case, GenericAssignableTo reports that instantiations of Container
-// are assignable to the corresponding instantiation of Interface.
-func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool {
- V = types.Unalias(V)
- T = types.Unalias(T)
-
- // If V and T are not both named, or do not have matching non-empty type
- // parameter lists, fall back on types.AssignableTo.
-
- VN, Vnamed := V.(*types.Named)
- TN, Tnamed := T.(*types.Named)
- if !Vnamed || !Tnamed {
- return types.AssignableTo(V, T)
- }
-
- vtparams := VN.TypeParams()
- ttparams := TN.TypeParams()
- if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 {
- return types.AssignableTo(V, T)
- }
-
- // V and T have the same (non-zero) number of type params. Instantiate both
- // with the type parameters of V. This must always succeed for V, and will
- // succeed for T if and only if the type set of each type parameter of V is a
- // subset of the type set of the corresponding type parameter of T, meaning
- // that every instantiation of V corresponds to a valid instantiation of T.
-
- // Minor optimization: ensure we share a context across the two
- // instantiations below.
- if ctxt == nil {
- ctxt = types.NewContext()
- }
-
- var targs []types.Type
- for i := 0; i < vtparams.Len(); i++ {
- targs = append(targs, vtparams.At(i))
- }
-
- vinst, err := types.Instantiate(ctxt, V, targs, true)
- if err != nil {
- panic("type parameters should satisfy their own constraints")
- }
-
- tinst, err := types.Instantiate(ctxt, T, targs, true)
- if err != nil {
- return false
- }
-
- return types.AssignableTo(vinst, tinst)
-}
//
// NormalTerms makes no guarantees about the order of terms, except that it
// is deterministic.
-func NormalTerms(typ types.Type) ([]*types.Term, error) {
- switch typ := typ.Underlying().(type) {
+func NormalTerms(T types.Type) ([]*types.Term, error) {
+ // typeSetOf(T) == typeSetOf(Unalias(T))
+ typ := types.Unalias(T)
+ if named, ok := typ.(*types.Named); ok {
+ typ = named.Underlying()
+ }
+ switch typ := typ.(type) {
case *types.TypeParam:
return StructuralTerms(typ)
case *types.Union:
case *types.Interface:
return InterfaceTermSet(typ)
default:
- return []*types.Term{types.NewTerm(false, typ)}, nil
+ return []*types.Term{types.NewTerm(false, T)}, nil
}
}
// var _ = string(x)
InvalidConversion
- // InvalidUntypedConversion occurs when an there is no valid implicit
+ // InvalidUntypedConversion occurs when there is no valid implicit
// conversion from an untyped value satisfying the type constraints of the
// context in which it is used.
//
--- /dev/null
+// Copyright 2024 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 typesinternal
+
+import (
+ "go/ast"
+ "go/types"
+ "strconv"
+)
+
+// FileQualifier returns a [types.Qualifier] function that qualifies
+// imported symbols appropriately based on the import environment of a given
+// file.
+// If the same package is imported multiple times, the last appearance is
+// recorded.
+func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier {
+ // Construct mapping of import paths to their defined names.
+ // It is only necessary to look at renaming imports.
+ imports := make(map[string]string)
+ for _, imp := range f.Imports {
+ if imp.Name != nil && imp.Name.Name != "_" {
+ path, _ := strconv.Unquote(imp.Path.Value)
+ imports[path] = imp.Name.Name
+ }
+ }
+
+ // Define qualifier to replace full package paths with names of the imports.
+ return func(p *types.Package) string {
+ if p == nil || p == pkg {
+ return ""
+ }
+
+ if name, ok := imports[p.Path()]; ok {
+ if name == "." {
+ return ""
+ } else {
+ return name
+ }
+ }
+
+ // If there is no local renaming, fall back to the package name.
+ return p.Name()
+ }
+}
// ReceiverNamed returns the named type (if any) associated with the
// type of recv, which may be of the form N or *N, or aliases thereof.
// It also reports whether a Pointer was present.
+//
+// The named result may be nil if recv is from a method on an
+// anonymous interface or struct types or in ill-typed code.
func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) {
t := recv.Type()
if ptr, ok := types.Unalias(t).(*types.Pointer); ok {
type NamedOrAlias interface {
types.Type
Obj() *types.TypeName
+ // TODO(hxjiang): add method TypeArgs() *types.TypeList after stop supporting go1.22.
}
// TypeParams is a light shim around t.TypeParams().
}
return t
}
+
+// IsPackageLevel reports whether obj is a package-level symbol.
+func IsPackageLevel(obj types.Object) bool {
+ return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope()
+}
--- /dev/null
+// Copyright 2024 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 typesinternal
+
+// TODO(adonovan): when CL 645115 lands, define the go1.25 version of
+// this API that actually does something.
+
+import "go/types"
+
+type VarKind uint8
+
+const (
+ _ VarKind = iota // (not meaningful)
+ PackageVar // a package-level variable
+ LocalVar // a local variable
+ RecvVar // a method receiver variable
+ ParamVar // a function parameter variable
+ ResultVar // a function result variable
+ FieldVar // a struct field
+)
+
+func (kind VarKind) String() string {
+ return [...]string{
+ 0: "VarKind(0)",
+ PackageVar: "PackageVar",
+ LocalVar: "LocalVar",
+ RecvVar: "RecvVar",
+ ParamVar: "ParamVar",
+ ResultVar: "ResultVar",
+ FieldVar: "FieldVar",
+ }[kind]
+}
+
+// GetVarKind returns an invalid VarKind.
+func GetVarKind(v *types.Var) VarKind { return 0 }
+
+// SetVarKind has no effect.
+func SetVarKind(v *types.Var, kind VarKind) {}
"go/ast"
"go/token"
"go/types"
- "strconv"
"strings"
)
-// ZeroString returns the string representation of the "zero" value of the type t.
+// ZeroString returns the string representation of the zero value for any type t.
+// The boolean result indicates whether the type is or contains an invalid type
+// or a non-basic (constraint) interface type.
+//
+// Even for invalid input types, ZeroString may return a partially correct
+// string representation. The caller should use the returned isValid boolean
+// to determine the validity of the expression.
+//
+// When assigning to a wider type (such as 'any'), it's the caller's
+// responsibility to handle any necessary type conversions.
+//
// This string can be used on the right-hand side of an assignment where the
// left-hand side has that explicit type.
+// References to named types are qualified by an appropriate (optional)
+// qualifier function.
// Exception: This does not apply to tuples. Their string representation is
// informational only and cannot be used in an assignment.
-// When assigning to a wider type (such as 'any'), it's the caller's
-// responsibility to handle any necessary type conversions.
+//
// See [ZeroExpr] for a variant that returns an [ast.Expr].
-func ZeroString(t types.Type, qf types.Qualifier) string {
+func ZeroString(t types.Type, qual types.Qualifier) (_ string, isValid bool) {
switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
- return "false"
+ return "false", true
case t.Info()&types.IsNumeric != 0:
- return "0"
+ return "0", true
case t.Info()&types.IsString != 0:
- return `""`
+ return `""`, true
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
- return "nil"
+ return "nil", true
+ case t.Kind() == types.Invalid:
+ return "invalid", false
default:
- panic(fmt.Sprint("ZeroString for unexpected type:", t))
+ panic(fmt.Sprintf("ZeroString for unexpected type %v", t))
}
- case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
- return "nil"
+ case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
+ return "nil", true
+
+ case *types.Interface:
+ if !t.IsMethodSet() {
+ return "invalid", false
+ }
+ return "nil", true
- case *types.Named, *types.Alias:
+ case *types.Named:
switch under := t.Underlying().(type) {
case *types.Struct, *types.Array:
- return types.TypeString(t, qf) + "{}"
+ return types.TypeString(t, qual) + "{}", true
+ default:
+ return ZeroString(under, qual)
+ }
+
+ case *types.Alias:
+ switch t.Underlying().(type) {
+ case *types.Struct, *types.Array:
+ return types.TypeString(t, qual) + "{}", true
default:
- return ZeroString(under, qf)
+ // A type parameter can have alias but alias type's underlying type
+ // can never be a type parameter.
+ // Use types.Unalias to preserve the info of type parameter instead
+ // of call Underlying() going right through and get the underlying
+ // type of the type parameter which is always an interface.
+ return ZeroString(types.Unalias(t), qual)
}
case *types.Array, *types.Struct:
- return types.TypeString(t, qf) + "{}"
+ return types.TypeString(t, qual) + "{}", true
case *types.TypeParam:
// Assumes func new is not shadowed.
- return "*new(" + types.TypeString(t, qf) + ")"
+ return "*new(" + types.TypeString(t, qual) + ")", true
case *types.Tuple:
// Tuples are not normal values.
// We are currently format as "(t[0], ..., t[n])". Could be something else.
+ isValid := true
components := make([]string, t.Len())
for i := 0; i < t.Len(); i++ {
- components[i] = ZeroString(t.At(i).Type(), qf)
+ comp, ok := ZeroString(t.At(i).Type(), qual)
+
+ components[i] = comp
+ isValid = isValid && ok
}
- return "(" + strings.Join(components, ", ") + ")"
+ return "(" + strings.Join(components, ", ") + ")", isValid
case *types.Union:
// Variables of these types cannot be created, so it makes
}
}
-// ZeroExpr returns the ast.Expr representation of the "zero" value of the type t.
-// ZeroExpr is defined for types that are suitable for variables.
-// It may panic for other types such as Tuple or Union.
+// ZeroExpr returns the ast.Expr representation of the zero value for any type t.
+// The boolean result indicates whether the type is or contains an invalid type
+// or a non-basic (constraint) interface type.
+//
+// Even for invalid input types, ZeroExpr may return a partially correct ast.Expr
+// representation. The caller should use the returned isValid boolean to determine
+// the validity of the expression.
+//
+// This function is designed for types suitable for variables and should not be
+// used with Tuple or Union types.References to named types are qualified by an
+// appropriate (optional) qualifier function.
+//
// See [ZeroString] for a variant that returns a string.
-func ZeroExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- switch t := typ.(type) {
+func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) {
+ switch t := t.(type) {
case *types.Basic:
switch {
case t.Info()&types.IsBoolean != 0:
- return &ast.Ident{Name: "false"}
+ return &ast.Ident{Name: "false"}, true
case t.Info()&types.IsNumeric != 0:
- return &ast.BasicLit{Kind: token.INT, Value: "0"}
+ return &ast.BasicLit{Kind: token.INT, Value: "0"}, true
case t.Info()&types.IsString != 0:
- return &ast.BasicLit{Kind: token.STRING, Value: `""`}
+ return &ast.BasicLit{Kind: token.STRING, Value: `""`}, true
case t.Kind() == types.UnsafePointer:
fallthrough
case t.Kind() == types.UntypedNil:
- return ast.NewIdent("nil")
+ return ast.NewIdent("nil"), true
+ case t.Kind() == types.Invalid:
+ return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false
default:
- panic(fmt.Sprint("ZeroExpr for unexpected type:", t))
+ panic(fmt.Sprintf("ZeroExpr for unexpected type %v", t))
}
- case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
- return ast.NewIdent("nil")
+ case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature:
+ return ast.NewIdent("nil"), true
+
+ case *types.Interface:
+ if !t.IsMethodSet() {
+ return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false
+ }
+ return ast.NewIdent("nil"), true
- case *types.Named, *types.Alias:
+ case *types.Named:
switch under := t.Underlying().(type) {
case *types.Struct, *types.Array:
return &ast.CompositeLit{
- Type: TypeExpr(f, pkg, typ),
- }
+ Type: TypeExpr(t, qual),
+ }, true
default:
- return ZeroExpr(f, pkg, under)
+ return ZeroExpr(under, qual)
+ }
+
+ case *types.Alias:
+ switch t.Underlying().(type) {
+ case *types.Struct, *types.Array:
+ return &ast.CompositeLit{
+ Type: TypeExpr(t, qual),
+ }, true
+ default:
+ return ZeroExpr(types.Unalias(t), qual)
}
case *types.Array, *types.Struct:
return &ast.CompositeLit{
- Type: TypeExpr(f, pkg, typ),
- }
+ Type: TypeExpr(t, qual),
+ }, true
case *types.TypeParam:
return &ast.StarExpr{ // *new(T)
ast.NewIdent(t.Obj().Name()),
},
},
- }
+ }, true
case *types.Tuple:
// Unlike ZeroString, there is no ast.Expr can express tuple by
}
// TypeExpr returns syntax for the specified type. References to named types
-// from packages other than pkg are qualified by an appropriate package name, as
-// defined by the import environment of file.
+// are qualified by an appropriate (optional) qualifier function.
// It may panic for types such as Tuple or Union.
-func TypeExpr(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- switch t := typ.(type) {
+func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr {
+ switch t := t.(type) {
case *types.Basic:
switch t.Kind() {
case types.UnsafePointer:
- // TODO(hxjiang): replace the implementation with types.Qualifier.
- return &ast.SelectorExpr{X: ast.NewIdent("unsafe"), Sel: ast.NewIdent("Pointer")}
+ return &ast.SelectorExpr{X: ast.NewIdent(qual(types.NewPackage("unsafe", "unsafe"))), Sel: ast.NewIdent("Pointer")}
default:
return ast.NewIdent(t.Name())
}
case *types.Pointer:
return &ast.UnaryExpr{
Op: token.MUL,
- X: TypeExpr(f, pkg, t.Elem()),
+ X: TypeExpr(t.Elem(), qual),
}
case *types.Array:
Kind: token.INT,
Value: fmt.Sprintf("%d", t.Len()),
},
- Elt: TypeExpr(f, pkg, t.Elem()),
+ Elt: TypeExpr(t.Elem(), qual),
}
case *types.Slice:
return &ast.ArrayType{
- Elt: TypeExpr(f, pkg, t.Elem()),
+ Elt: TypeExpr(t.Elem(), qual),
}
case *types.Map:
return &ast.MapType{
- Key: TypeExpr(f, pkg, t.Key()),
- Value: TypeExpr(f, pkg, t.Elem()),
+ Key: TypeExpr(t.Key(), qual),
+ Value: TypeExpr(t.Elem(), qual),
}
case *types.Chan:
}
return &ast.ChanType{
Dir: dir,
- Value: TypeExpr(f, pkg, t.Elem()),
+ Value: TypeExpr(t.Elem(), qual),
}
case *types.Signature:
var params []*ast.Field
for i := 0; i < t.Params().Len(); i++ {
params = append(params, &ast.Field{
- Type: TypeExpr(f, pkg, t.Params().At(i).Type()),
+ Type: TypeExpr(t.Params().At(i).Type(), qual),
Names: []*ast.Ident{
{
Name: t.Params().At(i).Name(),
var returns []*ast.Field
for i := 0; i < t.Results().Len(); i++ {
returns = append(returns, &ast.Field{
- Type: TypeExpr(f, pkg, t.Results().At(i).Type()),
+ Type: TypeExpr(t.Results().At(i).Type(), qual),
})
}
return &ast.FuncType{
},
}
- case interface{ Obj() *types.TypeName }: // *types.{Alias,Named,TypeParam}
- switch t.Obj().Pkg() {
- case pkg, nil:
- return ast.NewIdent(t.Obj().Name())
- }
- pkgName := t.Obj().Pkg().Name()
-
- // TODO(hxjiang): replace the implementation with types.Qualifier.
- // If the file already imports the package under another name, use that.
- for _, cand := range f.Imports {
- if path, _ := strconv.Unquote(cand.Path.Value); path == t.Obj().Pkg().Path() {
- if cand.Name != nil && cand.Name.Name != "" {
- pkgName = cand.Name.Name
- }
- }
- }
- if pkgName == "." {
+ case *types.TypeParam:
+ pkgName := qual(t.Obj().Pkg())
+ if pkgName == "" || t.Obj().Pkg() == nil {
return ast.NewIdent(t.Obj().Name())
}
return &ast.SelectorExpr{
Sel: ast.NewIdent(t.Obj().Name()),
}
+ // types.TypeParam also implements interface NamedOrAlias. To differentiate,
+ // case TypeParam need to be present before case NamedOrAlias.
+ // TODO(hxjiang): remove this comment once TypeArgs() is added to interface
+ // NamedOrAlias.
+ case NamedOrAlias:
+ var expr ast.Expr = ast.NewIdent(t.Obj().Name())
+ if pkgName := qual(t.Obj().Pkg()); pkgName != "." && pkgName != "" {
+ expr = &ast.SelectorExpr{
+ X: ast.NewIdent(pkgName),
+ Sel: expr.(*ast.Ident),
+ }
+ }
+
+ // TODO(hxjiang): call t.TypeArgs after adding method TypeArgs() to
+ // typesinternal.NamedOrAlias.
+ if hasTypeArgs, ok := t.(interface{ TypeArgs() *types.TypeList }); ok {
+ if typeArgs := hasTypeArgs.TypeArgs(); typeArgs != nil && typeArgs.Len() > 0 {
+ var indices []ast.Expr
+ for i := range typeArgs.Len() {
+ indices = append(indices, TypeExpr(typeArgs.At(i), qual))
+ }
+ expr = &ast.IndexListExpr{
+ X: expr,
+ Indices: indices,
+ }
+ }
+ }
+
+ return expr
+
case *types.Struct:
return ast.NewIdent(t.String())
return ast.NewIdent(t.String())
case *types.Union:
- // TODO(hxjiang): handle the union through syntax (~A | ... | ~Z).
- // Remove nil check when calling typesinternal.TypeExpr.
- return nil
+ if t.Len() == 0 {
+ panic("Union type should have at least one term")
+ }
+ // Same as go/ast, the return expression will put last term in the
+ // Y field at topmost level of BinaryExpr.
+ // For union of type "float32 | float64 | int64", the structure looks
+ // similar to:
+ // {
+ // X: {
+ // X: float32,
+ // Op: |
+ // Y: float64,
+ // }
+ // Op: |,
+ // Y: int64,
+ // }
+ var union ast.Expr
+ for i := range t.Len() {
+ term := t.Term(i)
+ termExpr := TypeExpr(term.Type(), qual)
+ if term.Tilde() {
+ termExpr = &ast.UnaryExpr{
+ Op: token.TILDE,
+ X: termExpr,
+ }
+ }
+ if i == 0 {
+ union = termExpr
+ } else {
+ union = &ast.BinaryExpr{
+ X: union,
+ Op: token.OR,
+ Y: termExpr,
+ }
+ }
+ }
+ return union
case *types.Tuple:
panic("invalid input type types.Tuple")
# github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd
## explicit; go 1.13
github.com/ianlancetaylor/demangle
-# golang.org/x/arch v0.12.0
+# golang.org/x/arch v0.14.0
## explicit; go 1.18
golang.org/x/arch/arm/armasm
golang.org/x/arch/arm64/arm64asm
golang.org/x/arch/riscv64/riscv64asm
golang.org/x/arch/s390x/s390xasm
golang.org/x/arch/x86/x86asm
-# golang.org/x/build v0.0.0-20241205234318-b850320af2a4
+# golang.org/x/build v0.0.0-20250211223606-a5e3f75caa63
## explicit; go 1.22.0
golang.org/x/build/relnote
-# golang.org/x/mod v0.22.0
+# golang.org/x/mod v0.23.0
## explicit; go 1.22.0
golang.org/x/mod/internal/lazyregexp
golang.org/x/mod/modfile
golang.org/x/mod/sumdb/note
golang.org/x/mod/sumdb/tlog
golang.org/x/mod/zip
-# golang.org/x/sync v0.10.0
+# golang.org/x/sync v0.11.0
## explicit; go 1.18
golang.org/x/sync/errgroup
golang.org/x/sync/semaphore
-# golang.org/x/sys v0.28.0
+# golang.org/x/sys v0.30.0
## explicit; go 1.18
golang.org/x/sys/plan9
golang.org/x/sys/unix
golang.org/x/sys/windows
-# golang.org/x/telemetry v0.0.0-20241204182053-c0ac0e154df3
+# golang.org/x/telemetry v0.0.0-20250212145848-75305293b65a
## explicit; go 1.22.0
golang.org/x/telemetry
golang.org/x/telemetry/counter
golang.org/x/telemetry/internal/mmap
golang.org/x/telemetry/internal/telemetry
golang.org/x/telemetry/internal/upload
-# golang.org/x/term v0.27.0
+# golang.org/x/term v0.29.0
## explicit; go 1.18
golang.org/x/term
-# golang.org/x/text v0.21.0
+# golang.org/x/text v0.22.0
## explicit; go 1.18
golang.org/x/text/cases
golang.org/x/text/internal
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.28.1-0.20250131145412-98746475647e
+# golang.org/x/tools v0.30.1-0.20250212161021-f9aad7054b5f
## explicit; go 1.22.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover
golang.org/x/tools/go/types/typeutil
golang.org/x/tools/internal/aliases
golang.org/x/tools/internal/analysisinternal
+golang.org/x/tools/internal/astutil/edge
golang.org/x/tools/internal/bisect
golang.org/x/tools/internal/facts
+golang.org/x/tools/internal/fmtstr
golang.org/x/tools/internal/stdlib
golang.org/x/tools/internal/typeparams
golang.org/x/tools/internal/typesinternal
// Bad argument reorderings.
Printf("%[xd", 3) // ERROR "Printf format %\[xd is missing closing \]"
Printf("%[x]d x", 3) // ERROR "Printf format has invalid argument index \[x\]"
- Printf("%[3]*s x", "hi", 2) // ERROR "Printf format has invalid argument index \[3\]"
- _ = fmt.Sprintf("%[3]d x", 2) // ERROR "Sprintf format has invalid argument index \[3\]"
+ Printf("%[3]*s x", "hi", 2) // ERROR "Printf format %\[3\]\*s reads arg #3, but call has 2 args"
+ _ = fmt.Sprintf("%[3]d x", 2) // ERROR "Sprintf format %\[3\]d reads arg #3, but call has 1 arg"
Printf("%[2]*.[1]*[3]d x", 2, "hi", 4) // ERROR "Printf format %\[2]\*\.\[1\]\*\[3\]d uses non-int \x22hi\x22 as argument of \*"
Printf("%[0]s x", "arg1") // ERROR "Printf format has invalid argument index \[0\]"
Printf("%[0]d x", 1) // ERROR "Printf format has invalid argument index \[0\]"
module std
-go 1.24
+go 1.25
require (
- golang.org/x/crypto v0.30.0
- golang.org/x/net v0.32.1-0.20250121202134-9a960c88dd98
+ golang.org/x/crypto v0.33.1-0.20250210163342-e47973b1c108
+ golang.org/x/net v0.34.1-0.20250123000230-c72e89d6a9e4
)
require (
- golang.org/x/sys v0.28.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
)
-golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
-golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/net v0.32.1-0.20250121202134-9a960c88dd98 h1:36bTiCRO7f/J3t+LumnLTJDXqxsp1x6Q7754SsRD9u4=
-golang.org/x/net v0.32.1-0.20250121202134-9a960c88dd98/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/crypto v0.33.1-0.20250210163342-e47973b1c108 h1:FwaGHNRX5GDt6vHr+Ly+yRTs0ADe4xTlGOzwaga4ZOs=
+golang.org/x/crypto v0.33.1-0.20250210163342-e47973b1c108/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
+golang.org/x/net v0.34.1-0.20250123000230-c72e89d6a9e4 h1:guLo+MhruvDNVBe2ssFzu5BGn4pc0G1xx6TqTHK+MnE=
+golang.org/x/net v0.34.1-0.20250123000230-c72e89d6a9e4/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
return conf
}
-// configFromServer merges configuration settings from h2 and h2.t1.HTTP2
+// configFromTransport merges configuration settings from h2 and h2.t1.HTTP2
// (the net/http Transport).
func http2configFromTransport(h2 *http2Transport) http2http2Config {
conf := http2http2Config{
http2fillNetHTTPConfig(conf, srv.HTTP2)
}
-// fillNetHTTPServerConfig sets fields in conf from tr.HTTP2.
+// fillNetHTTPTransportConfig sets fields in conf from tr.HTTP2.
func http2fillNetHTTPTransportConfig(conf *http2http2Config, tr *Transport) {
http2fillNetHTTPConfig(conf, tr.HTTP2)
}
doNotReuse bool // whether conn is marked to not be reused for any future requests
closing bool
closed bool
+ closedOnIdle bool // true if conn was closed for idleness
seenSettings bool // true if we've seen a settings frame, false otherwise
seenSettingsChan chan struct{} // closed when seenSettings is true or frame reading fails
wantSettingsAck bool // we sent a SETTINGS frame and haven't heard back
// If this connection has never been used for a request and is closed,
// then let it take a request (which will fail).
+ // If the conn was closed for idleness, we're racing the idle timer;
+ // don't try to use the conn. (Issue #70515.)
//
// This avoids a situation where an error early in a connection's lifetime
// goes unreported.
- if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed {
+ if cc.nextStreamID == 1 && cc.streamsReserved == 0 && cc.closed && !cc.closedOnIdle {
st.canTakeNewRequest = true
}
return
}
cc.closed = true
+ cc.closedOnIdle = true
nextID := cc.nextStreamID
// TODO: do clients send GOAWAY too? maybe? Just Close:
cc.mu.Unlock()
// This avoids a situation where new connections are constantly created,
// added to the pool, fail, and are removed from the pool, without any error
// being surfaced to the user.
- const unusedWaitTime = 5 * time.Second
+ unusedWaitTime := 5 * time.Second
+ if cc.idleTimeout > 0 && unusedWaitTime > cc.idleTimeout {
+ unusedWaitTime = cc.idleTimeout
+ }
idleTime := cc.t.now().Sub(cc.lastActive)
- if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime {
+ if atomic.LoadUint32(&cc.atomicReused) == 0 && idleTime < unusedWaitTime && !cc.closedOnIdle {
cc.idleTimer = cc.t.afterFunc(unusedWaitTime-idleTime, func() {
cc.t.connPool().MarkDead(cc)
})
HasSSSE3 bool // Supplemental streaming SIMD extension 3
HasSSE41 bool // Streaming SIMD extension 4 and 4.1
HasSSE42 bool // Streaming SIMD extension 4 and 4.2
+ HasAVXIFMA bool // Advanced vector extension Integer Fused Multiply Add
+ HasAVXVNNI bool // Advanced vector extension Vector Neural Network Instructions
+ HasAVXVNNIInt8 bool // Advanced vector extension Vector Neural Network Int8 instructions
_ CacheLinePad
}
{Name: "sse41", Feature: &X86.HasSSE41},
{Name: "sse42", Feature: &X86.HasSSE42},
{Name: "ssse3", Feature: &X86.HasSSSE3},
+ {Name: "avxifma", Feature: &X86.HasAVXIFMA},
+ {Name: "avxvnni", Feature: &X86.HasAVXVNNI},
+ {Name: "avxvnniint8", Feature: &X86.HasAVXVNNIInt8},
// These capabilities should always be enabled on amd64:
{Name: "sse2", Feature: &X86.HasSSE2, Required: runtime.GOARCH == "amd64"},
return
}
- _, ebx7, ecx7, edx7 := cpuid(7, 0)
+ eax7, ebx7, ecx7, edx7 := cpuid(7, 0)
X86.HasBMI1 = isSet(3, ebx7)
X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX
X86.HasBMI2 = isSet(8, ebx7)
X86.HasAVX512VAES = isSet(9, ecx7)
X86.HasAVX512VBMI2 = isSet(6, ecx7)
X86.HasAVX512BITALG = isSet(12, ecx7)
-
- eax71, _, _, _ := cpuid(7, 1)
- X86.HasAVX512BF16 = isSet(5, eax71)
}
X86.HasAMXTile = isSet(24, edx7)
X86.HasAMXInt8 = isSet(25, edx7)
X86.HasAMXBF16 = isSet(22, edx7)
+
+ // These features depend on the second level of extended features.
+ if eax7 >= 1 {
+ eax71, _, _, edx71 := cpuid(7, 1)
+ if X86.HasAVX512 {
+ X86.HasAVX512BF16 = isSet(5, eax71)
+ }
+ if X86.HasAVX {
+ X86.HasAVXIFMA = isSet(23, eax71)
+ X86.HasAVXVNNI = isSet(4, eax71)
+ X86.HasAVXVNNIInt8 = isSet(4, edx71)
+ }
+ }
}
func isSet(bitpos uint, value uint32) bool {
-# golang.org/x/crypto v0.30.0
+# golang.org/x/crypto v0.33.1-0.20250210163342-e47973b1c108
## explicit; go 1.20
golang.org/x/crypto/chacha20
golang.org/x/crypto/chacha20poly1305
golang.org/x/crypto/cryptobyte/asn1
golang.org/x/crypto/internal/alias
golang.org/x/crypto/internal/poly1305
-# golang.org/x/net v0.32.1-0.20250121202134-9a960c88dd98
+# golang.org/x/net v0.34.1-0.20250123000230-c72e89d6a9e4
## explicit; go 1.18
golang.org/x/net/dns/dnsmessage
golang.org/x/net/http/httpguts
golang.org/x/net/idna
golang.org/x/net/lif
golang.org/x/net/nettest
-# golang.org/x/sys v0.28.0
+# golang.org/x/sys v0.30.0
## explicit; go 1.18
golang.org/x/sys/cpu
-# golang.org/x/text v0.21.0
+# golang.org/x/text v0.22.0
## explicit; go 1.18
golang.org/x/text/secure/bidirule
golang.org/x/text/transform