system service: unset NOTIFY_SOCKET

Unset the NOTIFY_SOCKET environment variable after sending the MAIN_PID
and READY message.  This avoids any unintentional side-effects of other
code paths using the socket assuming they'd run in a non-server
short-lived Podman process.

Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2023-08-16 15:07:25 +02:00
parent 60e58f0594
commit 7a94f8c123
35 changed files with 1690 additions and 300 deletions

10
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/containernetworking/cni v1.1.2
github.com/containernetworking/plugins v1.3.0
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6
github.com/containers/common v0.55.1-0.20230816154734-519ed7fea9bd
github.com/containers/common v0.55.1-0.20230824140149-b27c2ba2b7e1
github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa
github.com/containers/libhvee v0.4.0
@ -43,7 +43,7 @@ require (
github.com/mattn/go-sqlite3 v1.14.17
github.com/moby/term v0.5.0
github.com/nxadm/tail v1.4.8
github.com/onsi/ginkgo/v2 v2.11.0
github.com/onsi/ginkgo/v2 v2.12.0
github.com/onsi/gomega v1.27.10
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc4
@ -132,7 +132,7 @@ require (
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jinzhu/copier v0.3.5 // indirect
github.com/jinzhu/copier v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
@ -185,9 +185,9 @@ require (
go.opentelemetry.io/otel/trace v1.16.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.12.0 // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/oauth2 v0.11.0 // indirect
golang.org/x/tools v0.9.3 // indirect
golang.org/x/tools v0.12.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.56.2 // indirect

20
go.sum
View File

@ -246,8 +246,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q
github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6 h1:K/S8SFQsnnNTF0Ws58SrBD9L0EuClzAG8Zp08d7+6AA=
github.com/containers/buildah v1.31.1-0.20230722114901-5ece066f82c6/go.mod h1:0sptTFBBtSznLqoTh80DfvMOCNbdRsNRgVOKhBhrupA=
github.com/containers/common v0.55.1-0.20230816154734-519ed7fea9bd h1:fdpl099M/XfhB+yYACXFcW7Dsaz+1oHs57hF78SR9wI=
github.com/containers/common v0.55.1-0.20230816154734-519ed7fea9bd/go.mod h1:wtIdVQKHf4U+UfIz9B1htNZqqEeMNysQOevHNiNrru0=
github.com/containers/common v0.55.1-0.20230824140149-b27c2ba2b7e1 h1:MfMnYctOK+QlYQudW88avvOzU/kBVWdqs+w40inJeKM=
github.com/containers/common v0.55.1-0.20230824140149-b27c2ba2b7e1/go.mod h1:1ZeaFdUqfLR9HE45ZacZwO3m4y5UOWP/DY9oZlTOYlI=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.26.1-0.20230807184415-3fb422379cfa h1:wDfVQtc6ik2MvsUmu/YRSyBAE5YUxdjcEDtuT1q2KDo=
@ -637,8 +637,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548 h1:dYTbLf4m0a5u0KLmPfB6mgxbcV7588bOCx79hxa5Sr4=
@ -787,8 +787,8 @@ github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI=
github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -1142,8 +1142,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1376,8 +1376,8 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -167,7 +167,8 @@ func newServer(runtime *libpod.Runtime, listener net.Listener, opts entities.Ser
// setupSystemd notifies systemd API service is ready
// If the NOTIFY_SOCKET is set, communicate the PID and readiness, and unset INVOCATION_ID
// so conmon and containers are in the correct cgroup.
// so conmon and containers are in the correct cgroup. Also unset NOTIFY_SOCKET
// to avoid any further usage of the socket.
func (s *APIServer) setupSystemd() {
if _, found := os.LookupEnv("NOTIFY_SOCKET"); !found {
return
@ -176,13 +177,16 @@ func (s *APIServer) setupSystemd() {
payload := fmt.Sprintf("MAINPID=%d\n", os.Getpid())
payload += daemon.SdNotifyReady
if sent, err := daemon.SdNotify(true, payload); err != nil {
logrus.Error("API service failed to notify systemd of Conmon PID: " + err.Error())
logrus.Errorf("API service failed to notify systemd of Conmon PID: %v", err)
} else if !sent {
logrus.Warn("API service unable to successfully send SDNotify")
}
if err := os.Unsetenv("INVOCATION_ID"); err != nil {
logrus.Error("API service failed unsetting INVOCATION_ID: " + err.Error())
logrus.Errorf("API service failed unsetting INVOCATION_ID: %v", err)
}
if err := os.Unsetenv("NOTIFY_SOCKET"); err != nil {
logrus.Errorf("API service failed unsetting NOTIFY_SOCKET: %v", err)
}
}

View File

@ -4,6 +4,8 @@
#
load helpers
load helpers.network
load helpers.registry
# Shared throughout this module: PID of socat process, and path to its log
_SOCAT_PID=
@ -503,4 +505,85 @@ none | false | false | 0
run_podman rmi $(pause_image)
}
@test "podman pull - EXTEND_TIMEOUT_USEC" {
# Make sure that Podman extends the start timeout via DBUS when running
# inside a systemd unit (i.e., with NOTIFY_SOCKET set). Extending the
# timout works by continuously sending EXTEND_TIMEOUT_USEC; Podman does
# this at most 10 times, adding up to ~5min.
image_on_local_registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}/name:tag
registry_flags="--tls-verify=false --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}"
start_registry
export NOTIFY_SOCKET=$PODMAN_TMPDIR/notify.sock
_start_socat
run_podman push $registry_flags $IMAGE $image_on_local_registry
run_podman pull $registry_flags $image_on_local_registry
is "${lines[1]}" "Pulling image $image_on_local_registry inside systemd: setting pull timeout to 5m0s" "NOTIFY_SOCKET is passed to container"
run cat $_SOCAT_LOG
# The 'echo's help us debug failed runs
echo "socat log:"
echo "$output"
is "$output" "EXTEND_TIMEOUT_USEC=30000000"
run_podman rmi $image_on_local_registry
_stop_socat
}
@test "podman system service" {
# This test makes sure that podman-system-service uses the NOTIFY_SOCKET
# correctly and that it unsets it after sending the expected MAINPID and
# READY message by making sure no EXTEND_TIMEOUT_USEC is sent on pull.
# Start a local registry and pre-populate it with an image we'll pull later on.
image_on_local_registry=localhost:${PODMAN_LOGIN_REGISTRY_PORT}/name:tag
registry_flags="--tls-verify=false --creds ${PODMAN_LOGIN_USER}:${PODMAN_LOGIN_PASS}"
start_registry
run_podman push $registry_flags $IMAGE $image_on_local_registry
export NOTIFY_SOCKET=$PODMAN_TMPDIR/notify.sock
podman_socket="unix://$PODMAN_TMPDIR/podman.sock"
envfile=$PODMAN_TMPDIR/envfile
_start_socat
(timeout --foreground -v --kill=10 30 $PODMAN system service -t0 $podman_socket &)
wait_for_file $_SOCAT_LOG
local timeout=10
while [[ $timeout -gt 0 ]]; do
run cat $_SOCAT_LOG
# The 'echo's help us debug failed runs
echo "socat log:"
echo "$output"
if [[ "$output" =~ "READY=1" ]]; then
break
fi
timeout=$((timeout - 1))
assert $timeout -gt 0 "Timed out waiting for podman-system-service to send expected data over NOTIFY_SOCKET"
sleep 0.5
done
assert "$output" =~ "MAINPID=.*
READY=1" "podman-system-service sends expected data over NOTIFY_SOCKET"
mainpid=${lines[0]:8}
# Now pull remotely and make sure that the service does _not_ extend the
# timeout; the NOTIFY_SOCKET should be unset at that point.
run_podman --url $podman_socket pull $registry_flags $image_on_local_registry
run cat $_SOCAT_LOG
# The 'echo's help us debug failed runs
echo "socat log:"
echo "$output"
assert "$output" !~ "EXTEND_TIMEOUT_USEC="
# Give the system-service 5sec to terminate before killing it.
/bin/kill --timeout 5000 KILL --signal TERM $mainpid
run_podman rmi $image_on_local_registry
_stop_socat
}
# vim: filetype=sh

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"net"
"os"
"strings"
"time"
@ -150,10 +151,15 @@ type CopyOptions struct {
// Additional tags when creating or copying a docker-archive.
dockerArchiveAdditionalTags []reference.NamedTagged
// If set it points to a NOTIFY_SOCKET the copier will use to extend
// the systemd timeout while copying.
extendTimeoutSocket string
}
// copier is an internal helper to conveniently copy images.
type copier struct {
extendTimeoutSocket string
imageCopyOptions copy.Options
retryOptions retry.Options
systemContext *types.SystemContext
@ -208,7 +214,7 @@ func getDockerAuthConfig(name, passwd, creds, idToken string) (*types.DockerAuth
// counterparts of the specified system context. Please make sure to call
// `(*copier).close()`.
func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) {
c := copier{}
c := copier{extendTimeoutSocket: options.extendTimeoutSocket}
c.systemContext = r.systemContextCopy()
if options.SourceLookupReferenceFunc != nil {
@ -333,6 +339,61 @@ func (c *copier) close() error {
func (c *copier) copy(ctx context.Context, source, destination types.ImageReference) ([]byte, error) {
logrus.Debugf("Copying source image %s to destination image %s", source.StringWithinTransport(), destination.StringWithinTransport())
// Avoid running out of time when running inside a systemd unit by
// regularly increasing the timeout.
if c.extendTimeoutSocket != "" {
socketAddr := &net.UnixAddr{
Name: c.extendTimeoutSocket,
Net: "unixgram",
}
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
if err != nil {
return nil, err
}
defer conn.Close()
numExtensions := 10
extension := 30 * time.Second
timerFrequency := 25 * time.Second // Fire the timer at a higher frequency to avoid a race
timer := time.NewTicker(timerFrequency)
socketCtx, cancel := context.WithCancel(ctx)
defer cancel()
defer timer.Stop()
fmt.Fprintf(c.imageCopyOptions.ReportWriter, "Pulling image %s inside systemd: setting pull timeout to %s\n", source.DockerReference(), time.Duration(numExtensions)*extension)
// From `man systemd.service(5)`:
//
// "If a service of Type=notify/Type=notify-reload sends "EXTEND_TIMEOUT_USEC=...", this may cause
// the start time to be extended beyond TimeoutStartSec=. The first receipt of this message must
// occur before TimeoutStartSec= is exceeded, and once the start time has extended beyond
// TimeoutStartSec=, the service manager will allow the service to continue to start, provided the
// service repeats "EXTEND_TIMEOUT_USEC=..." within the interval specified until the service startup
// status is finished by "READY=1"."
extendValue := []byte(fmt.Sprintf("EXTEND_TIMEOUT_USEC=%d", extension.Microseconds()))
extendTimeout := func() {
if _, err := conn.Write(extendValue); err != nil {
logrus.Errorf("Increasing EXTEND_TIMEOUT_USEC failed: %v", err)
}
numExtensions--
}
extendTimeout()
go func() {
for {
select {
case <-socketCtx.Done():
return
case <-timer.C:
if numExtensions == 0 {
return
}
extendTimeout()
}
}
}()
}
var err error
if c.sourceLookup != nil {

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"os"
"runtime"
"strings"
"time"
@ -592,6 +593,9 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
return nil
}
if socketPath, ok := os.LookupEnv("NOTIFY_SOCKET"); ok {
options.extendTimeoutSocket = socketPath
}
c, err := r.newCopier(&options.CopyOptions)
if err != nil {
return nil, err

View File

@ -8,6 +8,12 @@ import (
// add the default address. Note: this will also add ::1 as a side
// effect.
func setupLoopback(namespacePath string) error {
// The jexec wrapper runs the ifconfig command inside the jail.
// Try to run the command using ifconfig's -j flag (supported in 13.3 and later)
if err := exec.Command("ifconfig", "-j", namespacePath, "lo0", "inet", "127.0.0.1").Run(); err == nil {
return nil
}
// Fall back to using the jexec wrapper to run the ifconfig command
// inside the jail.
return exec.Command("jexec", namespacePath, "ifconfig", "lo0", "inet", "127.0.0.1").Run()
}

2
vendor/github.com/jinzhu/copier/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
.idea/
ttt/

View File

@ -30,7 +30,7 @@ type User struct {
Name string
Role string
Age int32
EmployeCode int64 `copier:"EmployeNum"` // specify field name
EmployeeCode int64 `copier:"EmployeeNum"` // specify field name
// Explicitly ignored in the destination struct.
Salary int
@ -53,7 +53,7 @@ type Employee struct {
Salary int `copier:"-"`
DoubleAge int32
EmployeId int64 `copier:"EmployeNum"` // specify field name
EmployeeId int64 `copier:"EmployeeNum"` // specify field name
SuperRole string
}
@ -86,7 +86,7 @@ func main() {
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRole: "Super Admin"}
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"}
// }
// Copy slice to slice
@ -95,8 +95,8 @@ func main() {
fmt.Printf("%#v \n", employees)
// []Employee{
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeId: 0, SuperRole: "Super Admin"},
// {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeId: 0, SuperRole: "Super Dev"},
// {Name: "Jinzhu", Age: 18, Salary:0, DoubleAge: 36, EmployeeId: 0, SuperRole: "Super Admin"},
// {Name: "jinzhu 2", Age: 30, Salary:0, DoubleAge: 60, EmployeeId: 0, SuperRole: "Super Dev"},
// }
// Copy map to map

View File

@ -3,10 +3,10 @@ package copier
import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"reflect"
"strings"
"sync"
"unicode"
)
@ -38,14 +38,34 @@ type Option struct {
// setting this value to true will ignore copying zero values of all the fields, including bools, as well as a
// struct having all it's fields set to their zero values respectively (see IsZero() in reflect/value.go)
IgnoreEmpty bool
CaseSensitive bool
DeepCopy bool
Converters []TypeConverter
// Custom field name mappings to copy values with different names in `fromValue` and `toValue` types.
// Examples can be found in `copier_field_name_mapping_test.go`.
FieldNameMapping []FieldNameMapping
}
func (opt Option) converters() map[converterPair]TypeConverter {
var converters = map[converterPair]TypeConverter{}
// save converters into map for faster lookup
for i := range opt.Converters {
pair := converterPair{
SrcType: reflect.TypeOf(opt.Converters[i].SrcType),
DstType: reflect.TypeOf(opt.Converters[i].DstType),
}
converters[pair] = opt.Converters[i]
}
return converters
}
type TypeConverter struct {
SrcType interface{}
DstType interface{}
Fn func(src interface{}) (interface{}, error)
Fn func(src interface{}) (dst interface{}, err error)
}
type converterPair struct {
@ -53,6 +73,27 @@ type converterPair struct {
DstType reflect.Type
}
func (opt Option) fieldNameMapping() map[converterPair]FieldNameMapping {
var mapping = map[converterPair]FieldNameMapping{}
for i := range opt.FieldNameMapping {
pair := converterPair{
SrcType: reflect.TypeOf(opt.FieldNameMapping[i].SrcType),
DstType: reflect.TypeOf(opt.FieldNameMapping[i].DstType),
}
mapping[pair] = opt.FieldNameMapping[i]
}
return mapping
}
type FieldNameMapping struct {
SrcType interface{}
DstType interface{}
Mapping map[string]string
}
// Tag Flags
type flags struct {
BitFlags map[string]uint8
@ -82,23 +123,10 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
amount = 1
from = indirect(reflect.ValueOf(fromValue))
to = indirect(reflect.ValueOf(toValue))
converters map[converterPair]TypeConverter
converters = opt.converters()
mappings = opt.fieldNameMapping()
)
// save convertes into map for faster lookup
for i := range opt.Converters {
if converters == nil {
converters = make(map[converterPair]TypeConverter)
}
pair := converterPair{
SrcType: reflect.TypeOf(opt.Converters[i].SrcType),
DstType: reflect.TypeOf(opt.Converters[i].DstType),
}
converters[pair] = opt.Converters[i]
}
if !to.CanAddr() {
return ErrInvalidCopyDestination
}
@ -147,7 +175,11 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
for _, k := range from.MapKeys() {
toKey := indirect(reflect.New(toType.Key()))
if !set(toKey, k, opt.DeepCopy, converters) {
isSet, err := set(toKey, k, opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
return fmt.Errorf("%w map, old key: %v, new key: %v", ErrNotSupported, k.Type(), toType.Key())
}
@ -156,7 +188,11 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
elemType, _ = indirectType(elemType)
}
toValue := indirect(reflect.New(elemType))
if !set(toValue, from.MapIndex(k), opt.DeepCopy, converters) {
isSet, err = set(toValue, from.MapIndex(k), opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
if err = copier(toValue.Addr().Interface(), from.MapIndex(k).Interface(), opt); err != nil {
return err
}
@ -174,18 +210,21 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
return
}
if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice && fromType.ConvertibleTo(toType) {
if from.Kind() == reflect.Slice && to.Kind() == reflect.Slice {
if to.IsNil() {
slice := reflect.MakeSlice(reflect.SliceOf(to.Type().Elem()), from.Len(), from.Cap())
to.Set(slice)
}
if fromType.ConvertibleTo(toType) {
for i := 0; i < from.Len(); i++ {
if to.Len() < i+1 {
to.Set(reflect.Append(to, reflect.New(to.Type().Elem()).Elem()))
}
if !set(to.Index(i), from.Index(i), opt.DeepCopy, converters) {
isSet, err := set(to.Index(i), from.Index(i), opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
// ignore error while copy slice element
err = copier(to.Index(i).Addr().Interface(), from.Index(i).Interface(), opt)
if err != nil {
@ -195,12 +234,20 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
}
return
}
}
if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct {
// skip not supported type
return
}
if len(converters) > 0 {
if ok, e := set(to, from, opt.DeepCopy, converters); e == nil && ok {
// converter supported
return
}
}
if from.Kind() == reflect.Slice || to.Kind() == reflect.Slice {
isSlice = true
if from.Kind() == reflect.Slice {
@ -225,6 +272,27 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
dest = indirect(to)
}
if len(converters) > 0 {
if ok, e := set(dest, source, opt.DeepCopy, converters); e == nil && ok {
if isSlice {
// FIXME: maybe should check the other types?
if to.Type().Elem().Kind() == reflect.Ptr {
to.Index(i).Set(dest.Addr())
} else {
if to.Len() < i+1 {
reflect.Append(to, dest)
} else {
to.Index(i).Set(dest)
}
}
} else {
to.Set(dest)
}
continue
}
}
destKind := dest.Kind()
initDest := false
if destKind == reflect.Interface {
@ -248,19 +316,22 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
name := field.Name
// Get bit flags for field
fieldFlags, _ := flgs.BitFlags[name]
fieldFlags := flgs.BitFlags[name]
// Check if we should ignore copying
if (fieldFlags & tagIgnore) != 0 {
continue
}
srcFieldName, destFieldName := getFieldName(name, flgs)
if fromField := source.FieldByName(srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) {
fieldNamesMapping := getFieldNamesMapping(mappings, fromType, toType)
srcFieldName, destFieldName := getFieldName(name, flgs, fieldNamesMapping)
if fromField := fieldByNameOrZeroValue(source, srcFieldName); fromField.IsValid() && !shouldIgnore(fromField, opt.IgnoreEmpty) {
// process for nested anonymous field
destFieldNotSet := false
if f, ok := dest.Type().FieldByName(destFieldName); ok {
for idx := range f.Index {
// only initialize parent embedded struct pointer in the path
for idx := range f.Index[:len(f.Index)-1] {
destField := dest.FieldByIndex(f.Index[:idx+1])
if destField.Kind() != reflect.Ptr {
@ -285,10 +356,14 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
break
}
toField := dest.FieldByName(destFieldName)
toField := fieldByName(dest, destFieldName, opt.CaseSensitive)
if toField.IsValid() {
if toField.CanSet() {
if !set(toField, fromField, opt.DeepCopy, converters) {
isSet, err := set(toField, fromField, opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
if err := copier(toField.Addr().Interface(), fromField.Interface(), opt); err != nil {
return err
}
@ -317,7 +392,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
// Copy from from method to dest field
for _, field := range deepFields(toType) {
name := field.Name
srcFieldName, destFieldName := getFieldName(name, flgs)
srcFieldName, destFieldName := getFieldName(name, flgs, getFieldNamesMapping(mappings, fromType, toType))
var fromMethod reflect.Value
if source.CanAddr() {
@ -327,7 +402,7 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
}
if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 && !shouldIgnore(fromMethod, opt.IgnoreEmpty) {
if toField := dest.FieldByName(destFieldName); toField.IsValid() && toField.CanSet() {
if toField := fieldByName(dest, destFieldName, opt.CaseSensitive); toField.IsValid() && toField.CanSet() {
values := fromMethod.Call([]reflect.Value{})
if len(values) >= 1 {
set(toField, values[0], opt.DeepCopy, converters)
@ -342,7 +417,11 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
if to.Len() < i+1 {
to.Set(reflect.Append(to, dest.Addr()))
} else {
if !set(to.Index(i), dest.Addr(), opt.DeepCopy, converters) {
isSet, err := set(to.Index(i), dest.Addr(), opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
// ignore error while copy slice element
err = copier(to.Index(i).Addr().Interface(), dest.Addr().Interface(), opt)
if err != nil {
@ -354,7 +433,11 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
if to.Len() < i+1 {
to.Set(reflect.Append(to, dest))
} else {
if !set(to.Index(i), dest, opt.DeepCopy, converters) {
isSet, err := set(to.Index(i), dest, opt.DeepCopy, converters)
if err != nil {
return err
}
if !isSet {
// ignore error while copy slice element
err = copier(to.Index(i).Addr().Interface(), dest.Interface(), opt)
if err != nil {
@ -373,6 +456,31 @@ func copier(toValue interface{}, fromValue interface{}, opt Option) (err error)
return
}
func getFieldNamesMapping(mappings map[converterPair]FieldNameMapping, fromType reflect.Type, toType reflect.Type) map[string]string {
var fieldNamesMapping map[string]string
if len(mappings) > 0 {
pair := converterPair{
SrcType: fromType,
DstType: toType,
}
if v, ok := mappings[pair]; ok {
fieldNamesMapping = v.Mapping
}
}
return fieldNamesMapping
}
func fieldByNameOrZeroValue(source reflect.Value, fieldName string) (value reflect.Value) {
defer func() {
if err := recover(); err != nil {
value = reflect.Value{}
}
}()
return source.FieldByName(fieldName)
}
func copyUnexportedStructFields(to, from reflect.Value) {
if from.Kind() != reflect.Struct || to.Kind() != reflect.Struct || !from.Type().AssignableTo(to.Type()) {
return
@ -392,14 +500,20 @@ func copyUnexportedStructFields(to, from reflect.Value) {
}
func shouldIgnore(v reflect.Value, ignoreEmpty bool) bool {
if !ignoreEmpty {
return false
return ignoreEmpty && v.IsZero()
}
return v.IsZero()
}
var deepFieldsLock sync.RWMutex
var deepFieldsMap = make(map[reflect.Type][]reflect.StructField)
func deepFields(reflectType reflect.Type) []reflect.StructField {
deepFieldsLock.RLock()
cache, ok := deepFieldsMap[reflectType]
deepFieldsLock.RUnlock()
if ok {
return cache
}
var res []reflect.StructField
if reflectType, _ = indirectType(reflectType); reflectType.Kind() == reflect.Struct {
fields := make([]reflect.StructField, 0, reflectType.NumField())
@ -416,11 +530,13 @@ func deepFields(reflectType reflect.Type) []reflect.StructField {
}
}
}
return fields
res = fields
}
return nil
deepFieldsLock.Lock()
deepFieldsMap[reflectType] = res
deepFieldsLock.Unlock()
return res
}
func indirect(reflectValue reflect.Value) reflect.Value {
@ -438,30 +554,32 @@ func indirectType(reflectType reflect.Type) (_ reflect.Type, isPtr bool) {
return reflectType, isPtr
}
func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) bool {
if from.IsValid() {
func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]TypeConverter) (bool, error) {
if !from.IsValid() {
return true, nil
}
if ok, err := lookupAndCopyWithConverter(to, from, converters); err != nil {
return false
return false, err
} else if ok {
return true
return true, nil
}
if to.Kind() == reflect.Ptr {
// set `to` to nil if from is nil
if from.Kind() == reflect.Ptr && from.IsNil() {
to.Set(reflect.Zero(to.Type()))
return true
return true, nil
} else if to.IsNil() {
// `from` -> `to`
// sql.NullString -> *string
if fromValuer, ok := driverValuer(from); ok {
v, err := fromValuer.Value()
if err != nil {
return false
return true, nil
}
// if `from` is not valid do nothing with `to`
if v == nil {
return true
return true, nil
}
}
// allocate new `to` variable with default value (eg. *string -> new(string))
@ -480,10 +598,10 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ
}
}
if from.Kind() == reflect.Ptr && from.IsNil() {
return true
return true, nil
}
if toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice {
return false
if _, ok := to.Addr().Interface().(sql.Scanner); !ok && (toKind == reflect.Struct || toKind == reflect.Map || toKind == reflect.Slice) {
return false, nil
}
}
@ -495,7 +613,7 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ
if from.Kind() == reflect.Ptr {
// if `from` is nil do nothing with `to`
if from.IsNil() {
return true
return true, nil
}
// depointer `from`
from = indirect(from)
@ -505,31 +623,32 @@ func set(to, from reflect.Value, deepCopy bool, converters map[converterPair]Typ
// set `to` by invoking method Scan(`from`)
err := toScanner.Scan(from.Interface())
if err != nil {
return false
return false, nil
}
} else if fromValuer, ok := driverValuer(from); ok {
// `from` -> `to`
// sql.NullString -> string
v, err := fromValuer.Value()
if err != nil {
return false
return false, nil
}
// if `from` is not valid do nothing with `to`
if v == nil {
return true
return true, nil
}
rv := reflect.ValueOf(v)
if rv.Type().AssignableTo(to.Type()) {
to.Set(rv)
} else if to.CanSet() && rv.Type().ConvertibleTo(to.Type()) {
to.Set(rv.Convert(to.Type()))
}
} else if from.Kind() == reflect.Ptr {
return set(to, from.Elem(), deepCopy, converters)
} else {
return false
}
return false, nil
}
return true
return true, nil
}
// lookupAndCopyWithConverter looks up the type pair, on success the TypeConverter Fn func is called to copy src to dst field.
@ -541,7 +660,6 @@ func lookupAndCopyWithConverter(to, from reflect.Value, converters map[converter
if cnv, ok := converters[pair]; ok {
result, err := cnv.Fn(from.Interface())
if err != nil {
return false, err
}
@ -574,7 +692,7 @@ func parseTags(tag string) (flg uint8, name string, err error) {
if unicode.IsUpper([]rune(t)[0]) {
name = strings.TrimSpace(t)
} else {
err = errors.New("copier field name tag must be start upper case")
err = ErrFieldNameTagStartNotUpperCase
}
}
}
@ -651,8 +769,14 @@ func checkBitFlags(flagsList map[string]uint8) (err error) {
return
}
func getFieldName(fieldName string, flgs flags) (srcFieldName string, destFieldName string) {
func getFieldName(fieldName string, flgs flags, fieldNameMapping map[string]string) (srcFieldName string, destFieldName string) {
// get dest field name
if name, ok := fieldNameMapping[fieldName]; ok {
srcFieldName = fieldName
destFieldName = name
return
}
if srcTagName, ok := flgs.SrcNames.FieldNameToTag[fieldName]; ok {
destFieldName = srcTagName
if destTagName, ok := flgs.DestNames.TagToFieldName[srcTagName]; ok {
@ -686,7 +810,6 @@ func getFieldName(fieldName string, flgs flags) (srcFieldName string, destFieldN
}
func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) {
if !v.CanAddr() {
i, ok = v.Interface().(driver.Valuer)
return
@ -695,3 +818,11 @@ func driverValuer(v reflect.Value) (i driver.Valuer, ok bool) {
i, ok = v.Addr().Interface().(driver.Valuer)
return
}
func fieldByName(v reflect.Value, name string, caseSensitive bool) reflect.Value {
if caseSensitive {
return v.FieldByName(name)
}
return v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
}

View File

@ -3,8 +3,9 @@ package copier
import "errors"
var (
ErrInvalidCopyDestination = errors.New("copy destination is invalid")
ErrInvalidCopyFrom = errors.New("copy from is invalid")
ErrInvalidCopyDestination = errors.New("copy destination must be non-nil and addressable")
ErrInvalidCopyFrom = errors.New("copy from must be non-nil and addressable")
ErrMapKeyNotMatch = errors.New("map's key type doesn't match")
ErrNotSupported = errors.New("not supported")
ErrFieldNameTagStartNotUpperCase = errors.New("copier field name tag must be start upper case")
)

View File

@ -1,3 +1,17 @@
## 2.12.0
### Features
- feat: allow MustPassRepeatedly decorator to be set at suite level (#1266) [05de518]
### Fixes
- fix-errors-in-readme (#1244) [27c2f5d]
### Maintenance
Various chores/dependency bumps.
## 2.11.0
In prior versions of Ginkgo specs the CLI filter flags (e.g. `--focus`, `--label-filter`) would _override_ any programmatic focus. This behavior has proved surprising and confusing in at least the following ways:

View File

@ -15,7 +15,7 @@ import (
...
)
Describe("Checking books out of the library", Label("library"), func() {
var _ = Describe("Checking books out of the library", Label("library"), func() {
var library *libraries.Library
var book *books.Book
var valjean *users.User
@ -50,7 +50,7 @@ Describe("Checking books out of the library", Label("library"), func() {
It("tells the user", func(ctx SpecContext) {
err := valjean.Checkout(ctx, library, "Les Miserables")
Expect(error).To(MatchError("Les Miserables is currently checked out"))
Expect(err).To(MatchError("Les Miserables is currently checked out"))
}, SpecTimeout(time.Second * 5))
It("lets the user place a hold and get notified later", func(ctx SpecContext) {
@ -74,7 +74,7 @@ Describe("Checking books out of the library", Label("library"), func() {
When("the library does not have the book in question", func() {
It("tells the reader the book is unavailable", func(ctx SpecContext) {
err := valjean.Checkout(ctx, library, "Les Miserables")
Expect(error).To(MatchError("Les Miserables is not in the library catalog"))
Expect(err).To(MatchError("Les Miserables is not in the library catalog"))
}, SpecTimeout(time.Second * 5))
})
})

View File

@ -244,9 +244,7 @@ func labelFromCallExpr(ce *ast.CallExpr) []string {
}
if id.Name == "Label" {
ls := extractLabels(expr)
for _, label := range ls {
labels = append(labels, label)
}
labels = append(labels, ls...)
}
}
}

View File

@ -321,7 +321,10 @@ func (g *group) run(specs Specs) {
if !skip {
var maxAttempts = 1
if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 {
if g.suite.config.MustPassRepeatedly > 0 {
maxAttempts = g.suite.config.MustPassRepeatedly
g.suite.currentSpecReport.MaxMustPassRepeatedly = maxAttempts
} else if g.suite.currentSpecReport.MaxMustPassRepeatedly > 0 {
maxAttempts = max(1, spec.MustPassRepeatedly())
} else if g.suite.config.FlakeAttempts > 0 {
maxAttempts = g.suite.config.FlakeAttempts

View File

@ -600,9 +600,7 @@ type Nodes []Node
func (n Nodes) CopyAppend(nodes ...Node) Nodes {
numN := len(n)
out := make(Nodes, numN+len(nodes))
for i, node := range n {
out[i] = node
}
copy(out, n)
for j, node := range nodes {
out[numN+j] = node
}

View File

@ -27,6 +27,7 @@ type SuiteConfig struct {
FailOnPending bool
FailFast bool
FlakeAttempts int
MustPassRepeatedly int
DryRun bool
PollProgressAfter time.Duration
PollProgressInterval time.Duration

View File

@ -453,8 +453,8 @@ func (g ginkgoErrors) InvalidEntryDescription(cl CodeLocation) error {
func (g ginkgoErrors) MissingParametersForTableFunction(cl CodeLocation) error {
return GinkgoError{
Heading: fmt.Sprintf("No parameters have been passed to the Table Function"),
Message: fmt.Sprintf("The Table Function expected at least 1 parameter"),
Heading: "No parameters have been passed to the Table Function",
Message: "The Table Function expected at least 1 parameter",
CodeLocation: cl,
DocLink: "table-specs",
}

View File

@ -97,9 +97,7 @@ func (report Report) Add(other Report) Report {
report.RunTime = report.EndTime.Sub(report.StartTime)
reports := make(SpecReports, len(report.SpecReports)+len(other.SpecReports))
for i := range report.SpecReports {
reports[i] = report.SpecReports[i]
}
copy(reports, report.SpecReports)
offset := len(report.SpecReports)
for i := range other.SpecReports {
reports[i+offset] = other.SpecReports[i]

View File

@ -1,3 +1,3 @@
package types
const VERSION = "2.11.0"
const VERSION = "2.12.0"

View File

@ -140,7 +140,7 @@ func Compare(v, w string) int {
// Max canonicalizes its arguments and then returns the version string
// that compares greater.
//
// Deprecated: use Compare instead. In most cases, returning a canonicalized
// Deprecated: use [Compare] instead. In most cases, returning a canonicalized
// version is not expected or desired.
func Max(v, w string) string {
v = Canonical(v)
@ -151,7 +151,7 @@ func Max(v, w string) string {
return w
}
// ByVersion implements sort.Interface for sorting semantic version strings.
// ByVersion implements [sort.Interface] for sorting semantic version strings.
type ByVersion []string
func (vs ByVersion) Len() int { return len(vs) }
@ -164,7 +164,7 @@ func (vs ByVersion) Less(i, j int) bool {
return vs[i] < vs[j]
}
// Sort sorts a list of semantic version strings using ByVersion.
// Sort sorts a list of semantic version strings using [ByVersion].
func Sort(list []string) {
sort.Sort(ByVersion(list))
}

View File

@ -20,45 +20,45 @@
//
// # Verifying Notes
//
// A Verifier allows verification of signatures by one server public key.
// A [Verifier] allows verification of signatures by one server public key.
// It can report the name of the server and the uint32 hash of the key,
// and it can verify a purported signature by that key.
//
// The standard implementation of a Verifier is constructed
// by NewVerifier starting from a verifier key, which is a
// by [NewVerifier] starting from a verifier key, which is a
// plain text string of the form "<name>+<hash>+<keydata>".
//
// A Verifiers allows looking up a Verifier by the combination
// A [Verifiers] allows looking up a Verifier by the combination
// of server name and key hash.
//
// The standard implementation of a Verifiers is constructed
// by VerifierList from a list of known verifiers.
//
// A Note represents a text with one or more signatures.
// A [Note] represents a text with one or more signatures.
// An implementation can reject a note with too many signatures
// (for example, more than 100 signatures).
//
// A Signature represents a signature on a note, verified or not.
// A [Signature] represents a signature on a note, verified or not.
//
// The Open function takes as input a signed message
// The [Open] function takes as input a signed message
// and a set of known verifiers. It decodes and verifies
// the message signatures and returns a Note structure
// the message signatures and returns a [Note] structure
// containing the message text and (verified or unverified) signatures.
//
// # Signing Notes
//
// A Signer allows signing a text with a given key.
// A [Signer] allows signing a text with a given key.
// It can report the name of the server and the hash of the key
// and can sign a raw text using that key.
//
// The standard implementation of a Signer is constructed
// by NewSigner starting from an encoded signer key, which is a
// by [NewSigner] starting from an encoded signer key, which is a
// plain text string of the form "PRIVATE+KEY+<name>+<hash>+<keydata>".
// Anyone with an encoded signer key can sign messages using that key,
// so it must be kept secret. The encoding begins with the literal text
// "PRIVATE+KEY" to avoid confusion with the public server key.
//
// The Sign function takes as input a Note and a list of Signers
// The [Sign] function takes as input a Note and a list of Signers
// and returns an encoded, signed message.
//
// # Signed Note Format
@ -88,7 +88,7 @@
// although doing so will require deploying the new algorithms to all clients
// before starting to depend on them for signatures.
//
// The GenerateKey function generates and returns a new signer
// The [GenerateKey] function generates and returns a new signer
// and corresponding verifier.
//
// # Example
@ -123,9 +123,9 @@
// base URLs, the only syntactic requirement is that they
// not contain spaces or newlines).
//
// If Open is given access to a Verifiers including the
// Verifier for this key, then it will succeed at verifying
// the encoded message and returning the parsed Note:
// If [Open] is given access to a [Verifiers] including the
// [Verifier] for this key, then it will succeed at verifying
// the encoded message and returning the parsed [Note]:
//
// vkey := "PeterNeumann+c74f20a3+ARpc2QcUPDhMQegwxbzhKqiBfsVkmqq/LDE4izWy10TW"
// msg := []byte("If you think cryptography is the answer to your problem,\n" +
@ -238,7 +238,7 @@ func isValidName(name string) bool {
return name != "" && utf8.ValidString(name) && strings.IndexFunc(name, unicode.IsSpace) < 0 && !strings.Contains(name, "+")
}
// NewVerifier construct a new Verifier from an encoded verifier key.
// NewVerifier construct a new [Verifier] from an encoded verifier key.
func NewVerifier(vkey string) (Verifier, error) {
name, vkey := chop(vkey, "+")
hash16, key64 := chop(vkey, "+")
@ -295,7 +295,7 @@ func (v *verifier) Name() string { return v.name }
func (v *verifier) KeyHash() uint32 { return v.hash }
func (v *verifier) Verify(msg, sig []byte) bool { return v.verify(msg, sig) }
// NewSigner constructs a new Signer from an encoded signer key.
// NewSigner constructs a new [Signer] from an encoded signer key.
func NewSigner(skey string) (Signer, error) {
priv1, skey := chop(skey, "+")
priv2, skey := chop(skey, "+")
@ -409,7 +409,7 @@ func (e *UnknownVerifierError) Error() string {
}
// An ambiguousVerifierError indicates that the given name and hash
// match multiple keys passed to VerifierList.
// match multiple keys passed to [VerifierList].
// (If this happens, some malicious actor has taken control of the
// verifier list, at which point we may as well give up entirely,
// but we diagnose the problem instead.)
@ -422,7 +422,7 @@ func (e *ambiguousVerifierError) Error() string {
return fmt.Sprintf("ambiguous key %s+%08x", e.name, e.hash)
}
// VerifierList returns a Verifiers implementation that uses the given list of verifiers.
// VerifierList returns a [Verifiers] implementation that uses the given list of verifiers.
func VerifierList(list ...Verifier) Verifiers {
m := make(verifierMap)
for _, v := range list {
@ -510,7 +510,7 @@ var (
// If known.Verifier returns any other error, Open returns that error.
//
// If no known verifier has signed an otherwise valid note,
// Open returns an UnverifiedNoteError.
// Open returns an [UnverifiedNoteError].
// In this case, the unverified note can be fetched from inside the error.
func Open(msg []byte, known Verifiers) (*Note, error) {
if known == nil {

View File

@ -64,8 +64,9 @@ type event struct {
// depth-first order. It calls f(n) for each node n before it visits
// n's children.
//
// The complete traversal sequence is determined by ast.Inspect.
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// events. The function f is called only for nodes whose type
// matches an element of the types slice.
func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// Because it avoids postorder calls to f, and the pruning
@ -97,6 +98,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) {
// of the non-nil children of the node, followed by a call of
// f(n, false).
//
// The complete traversal sequence is determined by ast.Inspect.
// The types argument, if non-empty, enables type-based filtering of
// events. The function f if is called only for nodes whose type
// matches an element of the types slice.

View File

@ -671,6 +671,9 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse
// Temporary work-around for golang/go#39986. Parse filenames out of
// error messages. This happens if there are unrecoverable syntax
// errors in the source, so we can't match on a specific error message.
//
// TODO(rfindley): remove this heuristic, in favor of considering
// InvalidGoFiles from the list driver.
if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) {
addFilenameFromPos := func(pos string) bool {
split := strings.Split(pos, ":")

View File

@ -630,7 +630,7 @@ func newLoader(cfg *Config) *loader {
return ld
}
// refine connects the supplied packages into a graph and then adds type and
// refine connects the supplied packages into a graph and then adds type
// and syntax information as requested by the LoadMode.
func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
roots := response.Roots
@ -1043,6 +1043,9 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
Error: appendError,
Sizes: ld.sizes,
}
if lpkg.Module != nil && lpkg.Module.GoVersion != "" {
typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion)
}
if (ld.Mode & typecheckCgo) != 0 {
if !typesinternal.SetUsesCgo(tc) {
appendError(Error{

View File

@ -0,0 +1,824 @@
// Copyright 2018 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 objectpath defines a naming scheme for types.Objects
// (that is, named entities in Go programs) relative to their enclosing
// package.
//
// Type-checker objects are canonical, so they are usually identified by
// their address in memory (a pointer), but a pointer has meaning only
// within one address space. By contrast, objectpath names allow the
// identity of an object to be sent from one program to another,
// establishing a correspondence between types.Object variables that are
// distinct but logically equivalent.
//
// A single object may have multiple paths. In this example,
//
// type A struct{ X int }
// type B A
//
// the field X has two paths due to its membership of both A and B.
// The For(obj) function always returns one of these paths, arbitrarily
// but consistently.
package objectpath
import (
"fmt"
"go/types"
"sort"
"strconv"
"strings"
_ "unsafe"
"golang.org/x/tools/internal/typeparams"
)
// A Path is an opaque name that identifies a types.Object
// relative to its package. Conceptually, the name consists of a
// sequence of destructuring operations applied to the package scope
// to obtain the original object.
// The name does not include the package itself.
type Path string
// Encoding
//
// An object path is a textual and (with training) human-readable encoding
// of a sequence of destructuring operators, starting from a types.Package.
// The sequences represent a path through the package/object/type graph.
// We classify these operators by their type:
//
// PO package->object Package.Scope.Lookup
// OT object->type Object.Type
// TT type->type Type.{Elem,Key,Params,Results,Underlying} [EKPRU]
// TO type->object Type.{At,Field,Method,Obj} [AFMO]
//
// All valid paths start with a package and end at an object
// and thus may be defined by the regular language:
//
// objectpath = PO (OT TT* TO)*
//
// The concrete encoding follows directly:
// - The only PO operator is Package.Scope.Lookup, which requires an identifier.
// - The only OT operator is Object.Type,
// which we encode as '.' because dot cannot appear in an identifier.
// - The TT operators are encoded as [EKPRUTC];
// one of these (TypeParam) requires an integer operand,
// which is encoded as a string of decimal digits.
// - The TO operators are encoded as [AFMO];
// three of these (At,Field,Method) require an integer operand,
// which is encoded as a string of decimal digits.
// These indices are stable across different representations
// of the same package, even source and export data.
// The indices used are implementation specific and may not correspond to
// the argument to the go/types function.
//
// In the example below,
//
// package p
//
// type T interface {
// f() (a string, b struct{ X int })
// }
//
// field X has the path "T.UM0.RA1.F0",
// representing the following sequence of operations:
//
// p.Lookup("T") T
// .Type().Underlying().Method(0). f
// .Type().Results().At(1) b
// .Type().Field(0) X
//
// The encoding is not maximally compact---every R or P is
// followed by an A, for example---but this simplifies the
// encoder and decoder.
const (
// object->type operators
opType = '.' // .Type() (Object)
// type->type operators
opElem = 'E' // .Elem() (Pointer, Slice, Array, Chan, Map)
opKey = 'K' // .Key() (Map)
opParams = 'P' // .Params() (Signature)
opResults = 'R' // .Results() (Signature)
opUnderlying = 'U' // .Underlying() (Named)
opTypeParam = 'T' // .TypeParams.At(i) (Named, Signature)
opConstraint = 'C' // .Constraint() (TypeParam)
// type->object operators
opAt = 'A' // .At(i) (Tuple)
opField = 'F' // .Field(i) (Struct)
opMethod = 'M' // .Method(i) (Named or Interface; not Struct: "promoted" names are ignored)
opObj = 'O' // .Obj() (Named, TypeParam)
)
// For is equivalent to new(Encoder).For(obj).
//
// It may be more efficient to reuse a single Encoder across several calls.
func For(obj types.Object) (Path, error) {
return new(Encoder).For(obj)
}
// An Encoder amortizes the cost of encoding the paths of multiple objects.
// The zero value of an Encoder is ready to use.
type Encoder struct {
scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects
namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
skipMethodSorting bool
}
// Exposed to gopls via golang.org/x/tools/internal/typesinternal
// TODO(golang/go#61443): eliminate this parameter one way or the other.
//
//go:linkname skipMethodSorting
func skipMethodSorting(enc *Encoder) {
enc.skipMethodSorting = true
}
// For returns the path to an object relative to its package,
// or an error if the object is not accessible from the package's Scope.
//
// The For function guarantees to return a path only for the following objects:
// - package-level types
// - exported package-level non-types
// - methods
// - parameter and result variables
// - struct fields
// These objects are sufficient to define the API of their package.
// The objects described by a package's export data are drawn from this set.
//
// The set of objects accessible from a package's Scope depends on
// whether the package was produced by type-checking syntax, or
// reading export data; the latter may have a smaller Scope since
// export data trims objects that are not reachable from an exported
// declaration. For example, the For function will return a path for
// an exported method of an unexported type that is not reachable
// from any public declaration; this path will cause the Object
// function to fail if called on a package loaded from export data.
// TODO(adonovan): is this a bug or feature? Should this package
// compute accessibility in the same way?
//
// For does not return a path for predeclared names, imported package
// names, local names, and unexported package-level names (except
// types).
//
// Example: given this definition,
//
// package p
//
// type T interface {
// f() (a string, b struct{ X int })
// }
//
// For(X) would return a path that denotes the following sequence of operations:
//
// p.Scope().Lookup("T") (TypeName T)
// .Type().Underlying().Method(0). (method Func f)
// .Type().Results().At(1) (field Var b)
// .Type().Field(0) (field Var X)
//
// where p is the package (*types.Package) to which X belongs.
func (enc *Encoder) For(obj types.Object) (Path, error) {
pkg := obj.Pkg()
// This table lists the cases of interest.
//
// Object Action
// ------ ------
// nil reject
// builtin reject
// pkgname reject
// label reject
// var
// package-level accept
// func param/result accept
// local reject
// struct field accept
// const
// package-level accept
// local reject
// func
// package-level accept
// init functions reject
// concrete method accept
// interface method accept
// type
// package-level accept
// local reject
//
// The only accessible package-level objects are members of pkg itself.
//
// The cases are handled in four steps:
//
// 1. reject nil and builtin
// 2. accept package-level objects
// 3. reject obviously invalid objects
// 4. search the API for the path to the param/result/field/method.
// 1. reference to nil or builtin?
if pkg == nil {
return "", fmt.Errorf("predeclared %s has no path", obj)
}
scope := pkg.Scope()
// 2. package-level object?
if scope.Lookup(obj.Name()) == obj {
// Only exported objects (and non-exported types) have a path.
// Non-exported types may be referenced by other objects.
if _, ok := obj.(*types.TypeName); !ok && !obj.Exported() {
return "", fmt.Errorf("no path for non-exported %v", obj)
}
return Path(obj.Name()), nil
}
// 3. Not a package-level object.
// Reject obviously non-viable cases.
switch obj := obj.(type) {
case *types.TypeName:
if _, ok := obj.Type().(*typeparams.TypeParam); !ok {
// With the exception of type parameters, only package-level type names
// have a path.
return "", fmt.Errorf("no path for %v", obj)
}
case *types.Const, // Only package-level constants have a path.
*types.Label, // Labels are function-local.
*types.PkgName: // PkgNames are file-local.
return "", fmt.Errorf("no path for %v", obj)
case *types.Var:
// Could be:
// - a field (obj.IsField())
// - a func parameter or result
// - a local var.
// Sadly there is no way to distinguish
// a param/result from a local
// so we must proceed to the find.
case *types.Func:
// A func, if not package-level, must be a method.
if recv := obj.Type().(*types.Signature).Recv(); recv == nil {
return "", fmt.Errorf("func is not a method: %v", obj)
}
if path, ok := enc.concreteMethod(obj); ok {
// Fast path for concrete methods that avoids looping over scope.
return path, nil
}
default:
panic(obj)
}
// 4. Search the API for the path to the var (field/param/result) or method.
// First inspect package-level named types.
// In the presence of path aliases, these give
// the best paths because non-types may
// refer to types, but not the reverse.
empty := make([]byte, 0, 48) // initial space
objs := enc.scopeObjects(scope)
for _, o := range objs {
tname, ok := o.(*types.TypeName)
if !ok {
continue // handle non-types in second pass
}
path := append(empty, o.Name()...)
path = append(path, opType)
T := o.Type()
if tname.IsAlias() {
// type alias
if r := find(obj, T, path, nil); r != nil {
return Path(r), nil
}
} else {
if named, _ := T.(*types.Named); named != nil {
if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil {
// generic named type
return Path(r), nil
}
}
// defined (named) type
if r := find(obj, T.Underlying(), append(path, opUnderlying), nil); r != nil {
return Path(r), nil
}
}
}
// Then inspect everything else:
// non-types, and declared methods of defined types.
for _, o := range objs {
path := append(empty, o.Name()...)
if _, ok := o.(*types.TypeName); !ok {
if o.Exported() {
// exported non-type (const, var, func)
if r := find(obj, o.Type(), append(path, opType), nil); r != nil {
return Path(r), nil
}
}
continue
}
// Inspect declared methods of defined types.
if T, ok := o.Type().(*types.Named); ok {
path = append(path, opType)
if !enc.skipMethodSorting {
// Note that method index here is always with respect
// to canonical ordering of methods, regardless of how
// they appear in the underlying type.
for i, m := range enc.namedMethods(T) {
path2 := appendOpArg(path, opMethod, i)
if m == obj {
return Path(path2), nil // found declared method
}
if r := find(obj, m.Type(), append(path2, opType), nil); r != nil {
return Path(r), nil
}
}
} else {
// This branch must match the logic in the branch above, using go/types
// APIs without sorting.
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
path2 := appendOpArg(path, opMethod, i)
if m == obj {
return Path(path2), nil // found declared method
}
if r := find(obj, m.Type(), append(path2, opType), nil); r != nil {
return Path(r), nil
}
}
}
}
}
return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path())
}
func appendOpArg(path []byte, op byte, arg int) []byte {
path = append(path, op)
path = strconv.AppendInt(path, int64(arg), 10)
return path
}
// concreteMethod returns the path for meth, which must have a non-nil receiver.
// The second return value indicates success and may be false if the method is
// an interface method or if it is an instantiated method.
//
// This function is just an optimization that avoids the general scope walking
// approach. You are expected to fall back to the general approach if this
// function fails.
func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
// Concrete methods can only be declared on package-scoped named types. For
// that reason we can skip the expensive walk over the package scope: the
// path will always be package -> named type -> method. We can trivially get
// the type name from the receiver, and only have to look over the type's
// methods to find the method index.
//
// Methods on generic types require special consideration, however. Consider
// the following package:
//
// L1: type S[T any] struct{}
// L2: func (recv S[A]) Foo() { recv.Bar() }
// L3: func (recv S[B]) Bar() { }
// L4: type Alias = S[int]
// L5: func _[T any]() { var s S[int]; s.Foo() }
//
// The receivers of methods on generic types are instantiations. L2 and L3
// instantiate S with the type-parameters A and B, which are scoped to the
// respective methods. L4 and L5 each instantiate S with int. Each of these
// instantiations has its own method set, full of methods (and thus objects)
// with receivers whose types are the respective instantiations. In other
// words, we have
//
// S[A].Foo, S[A].Bar
// S[B].Foo, S[B].Bar
// S[int].Foo, S[int].Bar
//
// We may thus be trying to produce object paths for any of these objects.
//
// S[A].Foo and S[B].Bar are the origin methods, and their paths are S.Foo
// and S.Bar, which are the paths that this function naturally produces.
//
// S[A].Bar, S[B].Foo, and both methods on S[int] are instantiations that
// don't correspond to the origin methods. For S[int], this is significant.
// The most precise object path for S[int].Foo, for example, is Alias.Foo,
// not S.Foo. Our function, however, would produce S.Foo, which would
// resolve to a different object.
//
// For S[A].Bar and S[B].Foo it could be argued that S.Bar and S.Foo are
// still the correct paths, since only the origin methods have meaningful
// paths. But this is likely only true for trivial cases and has edge cases.
// Since this function is only an optimization, we err on the side of giving
// up, deferring to the slower but definitely correct algorithm. Most users
// of objectpath will only be giving us origin methods, anyway, as referring
// to instantiated methods is usually not useful.
if typeparams.OriginMethod(meth) != meth {
return "", false
}
recvT := meth.Type().(*types.Signature).Recv().Type()
if ptr, ok := recvT.(*types.Pointer); ok {
recvT = ptr.Elem()
}
named, ok := recvT.(*types.Named)
if !ok {
return "", false
}
if types.IsInterface(named) {
// Named interfaces don't have to be package-scoped
//
// TODO(dominikh): opt: if scope.Lookup(name) == named, then we can apply this optimization to interface
// methods, too, I think.
return "", false
}
// Preallocate space for the name, opType, opMethod, and some digits.
name := named.Obj().Name()
path := make([]byte, 0, len(name)+8)
path = append(path, name...)
path = append(path, opType)
if !enc.skipMethodSorting {
for i, m := range enc.namedMethods(named) {
if m == meth {
path = appendOpArg(path, opMethod, i)
return Path(path), true
}
}
} else {
// This branch must match the logic of the branch above, using go/types
// APIs without sorting.
for i := 0; i < named.NumMethods(); i++ {
m := named.Method(i)
if m == meth {
path = appendOpArg(path, opMethod, i)
return Path(path), true
}
}
}
// Due to golang/go#59944, go/types fails to associate the receiver with
// certain methods on cgo types.
//
// TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go
// versions gopls supports.
return "", false
// panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named)))
}
// find finds obj within type T, returning the path to it, or nil if not found.
//
// The seen map is used to short circuit cycles through type parameters. If
// nil, it will be allocated as necessary.
func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]bool) []byte {
switch T := T.(type) {
case *types.Basic, *types.Named:
// Named types belonging to pkg were handled already,
// so T must belong to another package. No path.
return nil
case *types.Pointer:
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Slice:
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Array:
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Chan:
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Map:
if r := find(obj, T.Key(), append(path, opKey), seen); r != nil {
return r
}
return find(obj, T.Elem(), append(path, opElem), seen)
case *types.Signature:
if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil {
return r
}
if r := find(obj, T.Params(), append(path, opParams), seen); r != nil {
return r
}
return find(obj, T.Results(), append(path, opResults), seen)
case *types.Struct:
for i := 0; i < T.NumFields(); i++ {
fld := T.Field(i)
path2 := appendOpArg(path, opField, i)
if fld == obj {
return path2 // found field var
}
if r := find(obj, fld.Type(), append(path2, opType), seen); r != nil {
return r
}
}
return nil
case *types.Tuple:
for i := 0; i < T.Len(); i++ {
v := T.At(i)
path2 := appendOpArg(path, opAt, i)
if v == obj {
return path2 // found param/result var
}
if r := find(obj, v.Type(), append(path2, opType), seen); r != nil {
return r
}
}
return nil
case *types.Interface:
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
path2 := appendOpArg(path, opMethod, i)
if m == obj {
return path2 // found interface method
}
if r := find(obj, m.Type(), append(path2, opType), seen); r != nil {
return r
}
}
return nil
case *typeparams.TypeParam:
name := T.Obj()
if name == obj {
return append(path, opObj)
}
if seen[name] {
return nil
}
if seen == nil {
seen = make(map[*types.TypeName]bool)
}
seen[name] = true
if r := find(obj, T.Constraint(), append(path, opConstraint), seen); r != nil {
return r
}
return nil
}
panic(T)
}
func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte {
for i := 0; i < list.Len(); i++ {
tparam := list.At(i)
path2 := appendOpArg(path, opTypeParam, i)
if r := find(obj, tparam, path2, seen); r != nil {
return r
}
}
return nil
}
// Object returns the object denoted by path p within the package pkg.
func Object(pkg *types.Package, p Path) (types.Object, error) {
return object(pkg, p, false)
}
// Note: the skipMethodSorting parameter must match the value of
// Encoder.skipMethodSorting used during encoding.
func object(pkg *types.Package, p Path, skipMethodSorting bool) (types.Object, error) {
if p == "" {
return nil, fmt.Errorf("empty path")
}
pathstr := string(p)
var pkgobj, suffix string
if dot := strings.IndexByte(pathstr, opType); dot < 0 {
pkgobj = pathstr
} else {
pkgobj = pathstr[:dot]
suffix = pathstr[dot:] // suffix starts with "."
}
obj := pkg.Scope().Lookup(pkgobj)
if obj == nil {
return nil, fmt.Errorf("package %s does not contain %q", pkg.Path(), pkgobj)
}
// abstraction of *types.{Pointer,Slice,Array,Chan,Map}
type hasElem interface {
Elem() types.Type
}
// abstraction of *types.{Named,Signature}
type hasTypeParams interface {
TypeParams() *typeparams.TypeParamList
}
// abstraction of *types.{Named,TypeParam}
type hasObj interface {
Obj() *types.TypeName
}
// The loop state is the pair (t, obj),
// exactly one of which is non-nil, initially obj.
// All suffixes start with '.' (the only object->type operation),
// followed by optional type->type operations,
// then a type->object operation.
// The cycle then repeats.
var t types.Type
for suffix != "" {
code := suffix[0]
suffix = suffix[1:]
// Codes [AFM] have an integer operand.
var index int
switch code {
case opAt, opField, opMethod, opTypeParam:
rest := strings.TrimLeft(suffix, "0123456789")
numerals := suffix[:len(suffix)-len(rest)]
suffix = rest
i, err := strconv.Atoi(numerals)
if err != nil {
return nil, fmt.Errorf("invalid path: bad numeric operand %q for code %q", numerals, code)
}
index = int(i)
case opObj:
// no operand
default:
// The suffix must end with a type->object operation.
if suffix == "" {
return nil, fmt.Errorf("invalid path: ends with %q, want [AFMO]", code)
}
}
if code == opType {
if t != nil {
return nil, fmt.Errorf("invalid path: unexpected %q in type context", opType)
}
t = obj.Type()
obj = nil
continue
}
if t == nil {
return nil, fmt.Errorf("invalid path: code %q in object context", code)
}
// Inv: t != nil, obj == nil
switch code {
case opElem:
hasElem, ok := t.(hasElem) // Pointer, Slice, Array, Chan, Map
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want pointer, slice, array, chan or map)", code, t, t)
}
t = hasElem.Elem()
case opKey:
mapType, ok := t.(*types.Map)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want map)", code, t, t)
}
t = mapType.Key()
case opParams:
sig, ok := t.(*types.Signature)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
}
t = sig.Params()
case opResults:
sig, ok := t.(*types.Signature)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want signature)", code, t, t)
}
t = sig.Results()
case opUnderlying:
named, ok := t.(*types.Named)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named)", code, t, t)
}
t = named.Underlying()
case opTypeParam:
hasTypeParams, ok := t.(hasTypeParams) // Named, Signature
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or signature)", code, t, t)
}
tparams := hasTypeParams.TypeParams()
if n := tparams.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
}
t = tparams.At(index)
case opConstraint:
tparam, ok := t.(*typeparams.TypeParam)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
}
t = tparam.Constraint()
case opAt:
tuple, ok := t.(*types.Tuple)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want tuple)", code, t, t)
}
if n := tuple.Len(); index >= n {
return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n)
}
obj = tuple.At(index)
t = nil
case opField:
structType, ok := t.(*types.Struct)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want struct)", code, t, t)
}
if n := structType.NumFields(); index >= n {
return nil, fmt.Errorf("field index %d out of range [0-%d)", index, n)
}
obj = structType.Field(index)
t = nil
case opMethod:
switch t := t.(type) {
case *types.Interface:
if index >= t.NumMethods() {
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
}
obj = t.Method(index) // Id-ordered
case *types.Named:
if index >= t.NumMethods() {
return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
}
if skipMethodSorting {
obj = t.Method(index)
} else {
methods := namedMethods(t) // (unmemoized)
obj = methods[index] // Id-ordered
}
default:
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
}
t = nil
case opObj:
hasObj, ok := t.(hasObj)
if !ok {
return nil, fmt.Errorf("cannot apply %q to %s (got %T, want named or type param)", code, t, t)
}
obj = hasObj.Obj()
t = nil
default:
return nil, fmt.Errorf("invalid path: unknown code %q", code)
}
}
if obj.Pkg() != pkg {
return nil, fmt.Errorf("path denotes %s, which belongs to a different package", obj)
}
return obj, nil // success
}
// namedMethods returns the methods of a Named type in ascending Id order.
func namedMethods(named *types.Named) []*types.Func {
methods := make([]*types.Func, named.NumMethods())
for i := range methods {
methods[i] = named.Method(i)
}
sort.Slice(methods, func(i, j int) bool {
return methods[i].Id() < methods[j].Id()
})
return methods
}
// namedMethods is a memoization of the namedMethods function. Callers must not modify the result.
func (enc *Encoder) namedMethods(named *types.Named) []*types.Func {
m := enc.namedMethodsMemo
if m == nil {
m = make(map[*types.Named][]*types.Func)
enc.namedMethodsMemo = m
}
methods, ok := m[named]
if !ok {
methods = namedMethods(named) // allocates and sorts
m[named] = methods
}
return methods
}
// scopeObjects is a memoization of scope objects.
// Callers must not modify the result.
func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object {
m := enc.scopeMemo
if m == nil {
m = make(map[*types.Scope][]types.Object)
enc.scopeMemo = m
}
objs, ok := m[scope]
if !ok {
names := scope.Names() // allocates and sorts
objs = make([]types.Object, len(names))
for i, name := range names {
objs[i] = scope.Lookup(name)
}
m[scope] = objs
}
return objs
}

View File

@ -19,7 +19,7 @@ var (
File = keys.NewString("file", "")
Directory = keys.New("directory", "")
URI = keys.New("URI", "")
Package = keys.NewString("package", "") // Package ID
Package = keys.NewString("package", "") // sorted comma-separated list of Package IDs
PackagePath = keys.NewString("package_path", "")
Query = keys.New("query", "")
Snapshot = keys.NewUInt64("snapshot", "")

View File

@ -22,17 +22,23 @@ import (
"strconv"
"strings"
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/internal/tokeninternal"
"golang.org/x/tools/internal/typeparams"
)
// IExportShallow encodes "shallow" export data for the specified package.
//
// No promises are made about the encoding other than that it can be
// decoded by the same version of IIExportShallow. If you plan to save
// export data in the file system, be sure to include a cryptographic
// digest of the executable in the key to avoid version skew.
func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
// No promises are made about the encoding other than that it can be decoded by
// the same version of IIExportShallow. If you plan to save export data in the
// file system, be sure to include a cryptographic digest of the executable in
// the key to avoid version skew.
//
// If the provided reportf func is non-nil, it will be used for reporting bugs
// encountered during export.
// TODO(rfindley): remove reportf when we are confident enough in the new
// objectpath encoding.
func IExportShallow(fset *token.FileSet, pkg *types.Package, reportf ReportFunc) ([]byte, error) {
// In principle this operation can only fail if out.Write fails,
// but that's impossible for bytes.Buffer---and as a matter of
// fact iexportCommon doesn't even check for I/O errors.
@ -47,19 +53,27 @@ func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
// IImportShallow decodes "shallow" types.Package data encoded by
// IExportShallow in the same executable. This function cannot import data from
// cmd/compile or gcexportdata.Write.
func IImportShallow(fset *token.FileSet, getPackage GetPackageFunc, data []byte, path string, insert InsertType) (*types.Package, error) {
//
// The importer calls getPackages to obtain package symbols for all
// packages mentioned in the export data, including the one being
// decoded.
//
// If the provided reportf func is non-nil, it will be used for reporting bugs
// encountered during import.
// TODO(rfindley): remove reportf when we are confident enough in the new
// objectpath encoding.
func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string, reportf ReportFunc) (*types.Package, error) {
const bundle = false
pkgs, err := iimportCommon(fset, getPackage, data, bundle, path, insert)
const shallow = true
pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow, reportf)
if err != nil {
return nil, err
}
return pkgs[0], nil
}
// InsertType is the type of a function that creates a types.TypeName
// object for a named type and inserts it into the scope of the
// specified Package.
type InsertType = func(pkg *types.Package, name string)
// ReportFunc is the type of a function used to report formatted bugs.
type ReportFunc = func(string, ...interface{})
// Current bundled export format version. Increase with each format change.
// 0: initial implementation
@ -314,6 +328,7 @@ type iexporter struct {
version int
shallow bool // don't put types from other packages in the index
objEncoder *objectpath.Encoder // encodes objects from other packages in shallow mode; lazily allocated
localpkg *types.Package // (nil in bundle mode)
// allPkgs tracks all packages that have been referenced by
@ -354,6 +369,17 @@ func (p *iexporter) trace(format string, args ...interface{}) {
fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...)
}
// objectpathEncoder returns the lazily allocated objectpath.Encoder to use
// when encoding objects in other packages during shallow export.
//
// Using a shared Encoder amortizes some of cost of objectpath search.
func (p *iexporter) objectpathEncoder() *objectpath.Encoder {
if p.objEncoder == nil {
p.objEncoder = new(objectpath.Encoder)
}
return p.objEncoder
}
// stringOff returns the offset of s within the string section.
// If not already present, it's added to the end.
func (p *iexporter) stringOff(s string) uint64 {
@ -413,7 +439,6 @@ type exportWriter struct {
p *iexporter
data intWriter
currPkg *types.Package
prevFile string
prevLine int64
prevColumn int64
@ -436,7 +461,6 @@ func (p *iexporter) doDecl(obj types.Object) {
}()
}
w := p.newWriter()
w.setPkg(obj.Pkg(), false)
switch obj := obj.(type) {
case *types.Var:
@ -673,6 +697,9 @@ func (w *exportWriter) qualifiedType(obj *types.TypeName) {
w.pkg(obj.Pkg())
}
// TODO(rfindley): what does 'pkg' even mean here? It would be better to pass
// it in explicitly into signatures and structs that may use it for
// constructing fields.
func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
w.data.uint64(w.p.typOff(t, pkg))
}
@ -764,30 +791,53 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
case *types.Signature:
w.startType(signatureType)
w.setPkg(pkg, true)
w.pkg(pkg)
w.signature(t)
case *types.Struct:
w.startType(structType)
n := t.NumFields()
// Even for struct{} we must emit some qualifying package, because that's
// what the compiler does, and thus that's what the importer expects.
fieldPkg := pkg
if n > 0 {
w.setPkg(t.Field(0).Pkg(), true) // qualifying package for field objects
} else {
w.setPkg(pkg, true)
fieldPkg = t.Field(0).Pkg()
}
if fieldPkg == nil {
// TODO(rfindley): improve this very hacky logic.
//
// The importer expects a package to be set for all struct types, even
// those with no fields. A better encoding might be to set NumFields
// before pkg. setPkg panics with a nil package, which may be possible
// to reach with invalid packages (and perhaps valid packages, too?), so
// (arbitrarily) set the localpkg if available.
//
// Alternatively, we may be able to simply guarantee that pkg != nil, by
// reconsidering the encoding of constant values.
if w.p.shallow {
fieldPkg = w.p.localpkg
} else {
panic(internalErrorf("no package to set for empty struct"))
}
}
w.pkg(fieldPkg)
w.uint64(uint64(n))
for i := 0; i < n; i++ {
f := t.Field(i)
if w.p.shallow {
w.objectPath(f)
}
w.pos(f.Pos())
w.string(f.Name()) // unexported fields implicitly qualified by prior setPkg
w.typ(f.Type(), pkg)
w.typ(f.Type(), fieldPkg)
w.bool(f.Anonymous())
w.string(t.Tag(i)) // note (or tag)
}
case *types.Interface:
w.startType(interfaceType)
w.setPkg(pkg, true)
w.pkg(pkg)
n := t.NumEmbeddeds()
w.uint64(uint64(n))
@ -802,10 +852,16 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
w.typ(ft, tPkg)
}
// See comment for struct fields. In shallow mode we change the encoding
// for interface methods that are promoted from other packages.
n = t.NumExplicitMethods()
w.uint64(uint64(n))
for i := 0; i < n; i++ {
m := t.ExplicitMethod(i)
if w.p.shallow {
w.objectPath(m)
}
w.pos(m.Pos())
w.string(m.Name())
sig, _ := m.Type().(*types.Signature)
@ -827,12 +883,61 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
}
}
func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
if write {
w.pkg(pkg)
// objectPath writes the package and objectPath to use to look up obj in a
// different package, when encoding in "shallow" mode.
//
// When doing a shallow import, the importer creates only the local package,
// and requests package symbols for dependencies from the client.
// However, certain types defined in the local package may hold objects defined
// (perhaps deeply) within another package.
//
// For example, consider the following:
//
// package a
// func F() chan * map[string] struct { X int }
//
// package b
// import "a"
// var B = a.F()
//
// In this example, the type of b.B holds fields defined in package a.
// In order to have the correct canonical objects for the field defined in the
// type of B, they are encoded as objectPaths and later looked up in the
// importer. The same problem applies to interface methods.
func (w *exportWriter) objectPath(obj types.Object) {
if obj.Pkg() == nil || obj.Pkg() == w.p.localpkg {
// obj.Pkg() may be nil for the builtin error.Error.
// In this case, or if obj is declared in the local package, no need to
// encode.
w.string("")
return
}
w.currPkg = pkg
objectPath, err := w.p.objectpathEncoder().For(obj)
if err != nil {
// Fall back to the empty string, which will cause the importer to create a
// new object, which matches earlier behavior. Creating a new object is
// sufficient for many purposes (such as type checking), but causes certain
// references algorithms to fail (golang/go#60819). However, we didn't
// notice this problem during months of gopls@v0.12.0 testing.
//
// TODO(golang/go#61674): this workaround is insufficient, as in the case
// where the field forwarded from an instantiated type that may not appear
// in the export data of the original package:
//
// // package a
// type A[P any] struct{ F P }
//
// // package b
// type B a.A[int]
//
// We need to update references algorithms not to depend on this
// de-duplication, at which point we may want to simply remove the
// workaround here.
w.string("")
return
}
w.string(string(objectPath))
w.pkg(obj.Pkg())
}
func (w *exportWriter) signature(sig *types.Signature) {
@ -913,6 +1018,17 @@ func (w *exportWriter) value(typ types.Type, v constant.Value) {
w.int64(int64(v.Kind()))
}
if v.Kind() == constant.Unknown {
// golang/go#60605: treat unknown constant values as if they have invalid type
//
// This loses some fidelity over the package type-checked from source, but that
// is acceptable.
//
// TODO(rfindley): we should switch on the recorded constant kind rather
// than the constant type
return
}
switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
case types.IsBoolean:
w.bool(constant.BoolVal(v))
@ -1194,6 +1310,13 @@ type internalError string
func (e internalError) Error() string { return "gcimporter: " + string(e) }
// TODO(adonovan): make this call panic, so that it's symmetric with errorf.
// Otherwise it's easy to forget to do anything with the error.
//
// TODO(adonovan): also, consider switching the names "errorf" and
// "internalErrorf" as the former is used for bugs, whose cause is
// internal inconsistency, whereas the latter is used for ordinary
// situations like bad input, whose cause is external.
func internalErrorf(format string, args ...interface{}) error {
return internalError(fmt.Sprintf(format, args...))
}

View File

@ -21,6 +21,7 @@ import (
"sort"
"strings"
"golang.org/x/tools/go/types/objectpath"
"golang.org/x/tools/internal/typeparams"
)
@ -85,7 +86,7 @@ const (
// If the export data version is not recognized or the format is otherwise
// compromised, an error is returned.
func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
pkgs, err := iimportCommon(fset, GetPackageFromMap(imports), data, false, path, nil)
pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false, nil)
if err != nil {
return 0, nil, err
}
@ -94,33 +95,49 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
// IImportBundle imports a set of packages from the serialized package bundle.
func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) {
return iimportCommon(fset, GetPackageFromMap(imports), data, true, "", nil)
return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false, nil)
}
// A GetPackageFunc is a function that gets the package with the given path
// from the importer state, creating it (with the specified name) if necessary.
// It is an abstraction of the map historically used to memoize package creation.
// A GetPackagesFunc function obtains the non-nil symbols for a set of
// packages, creating and recursively importing them as needed. An
// implementation should store each package symbol is in the Pkg
// field of the items array.
//
// Two calls with the same path must return the same package.
//
// If the given getPackage func returns nil, the import will fail.
type GetPackageFunc = func(path, name string) *types.Package
// Any error causes importing to fail. This can be used to quickly read
// the import manifest of an export data file without fully decoding it.
type GetPackagesFunc = func(items []GetPackagesItem) error
// GetPackageFromMap returns a GetPackageFunc that retrieves packages from the
// given map of package path -> package.
//
// The resulting func may mutate m: if a requested package is not found, a new
// package will be inserted into m.
func GetPackageFromMap(m map[string]*types.Package) GetPackageFunc {
return func(path, name string) *types.Package {
if _, ok := m[path]; !ok {
m[path] = types.NewPackage(path, name)
// A GetPackagesItem is a request from the importer for the package
// symbol of the specified name and path.
type GetPackagesItem struct {
Name, Path string
Pkg *types.Package // to be filled in by GetPackagesFunc call
// private importer state
pathOffset uint64
nameIndex map[string]uint64
}
return m[path]
// GetPackagesFromMap returns a GetPackagesFunc that retrieves
// packages from the given map of package path to package.
//
// The returned function may mutate m: each requested package that is not
// found is created with types.NewPackage and inserted into m.
func GetPackagesFromMap(m map[string]*types.Package) GetPackagesFunc {
return func(items []GetPackagesItem) error {
for i, item := range items {
pkg, ok := m[item.Path]
if !ok {
pkg = types.NewPackage(item.Path, item.Name)
m[item.Path] = pkg
}
items[i].Pkg = pkg
}
return nil
}
}
func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte, bundle bool, path string, insert InsertType) (pkgs []*types.Package, err error) {
func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool, reportf ReportFunc) (pkgs []*types.Package, err error) {
const currentVersion = iexportVersionCurrent
version := int64(-1)
if !debug {
@ -159,7 +176,7 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
sLen := int64(r.uint64())
var fLen int64
var fileOffset []uint64
if insert != nil {
if shallow {
// Shallow mode uses a different position encoding.
fLen = int64(r.uint64())
fileOffset = make([]uint64, r.uint64())
@ -178,7 +195,8 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
p := iimporter{
version: int(version),
ipath: path,
insert: insert,
shallow: shallow,
reportf: reportf,
stringData: stringData,
stringCache: make(map[uint64]string),
@ -205,8 +223,9 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
p.typCache[uint64(i)] = pt
}
pkgList := make([]*types.Package, r.uint64())
for i := range pkgList {
// Gather the relevant packages from the manifest.
items := make([]GetPackagesItem, r.uint64())
for i := range items {
pkgPathOff := r.uint64()
pkgPath := p.stringAt(pkgPathOff)
pkgName := p.stringAt(r.uint64())
@ -215,29 +234,42 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
if pkgPath == "" {
pkgPath = path
}
pkg := getPackage(pkgPath, pkgName)
if pkg == nil {
errorf("internal error: getPackage returned nil package for %s", pkgPath)
} else if pkg.Name() != pkgName {
errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
}
if i == 0 && !bundle {
p.localpkg = pkg
}
p.pkgCache[pkgPathOff] = pkg
items[i].Name = pkgName
items[i].Path = pkgPath
items[i].pathOffset = pkgPathOff
// Read index for package.
nameIndex := make(map[string]uint64)
nSyms := r.uint64()
// In shallow mode we don't expect an index for other packages.
assert(nSyms == 0 || p.localpkg == pkg || p.insert == nil)
// In shallow mode, only the current package (i=0) has an index.
assert(!(shallow && i > 0 && nSyms != 0))
for ; nSyms > 0; nSyms-- {
name := p.stringAt(r.uint64())
nameIndex[name] = r.uint64()
}
p.pkgIndex[pkg] = nameIndex
items[i].nameIndex = nameIndex
}
// Request packages all at once from the client,
// enabling a parallel implementation.
if err := getPackages(items); err != nil {
return nil, err // don't wrap this error
}
// Check the results and complete the index.
pkgList := make([]*types.Package, len(items))
for i, item := range items {
pkg := item.Pkg
if pkg == nil {
errorf("internal error: getPackages returned nil package for %q", item.Path)
} else if pkg.Path() != item.Path {
errorf("internal error: getPackages returned wrong path %q, want %q", pkg.Path(), item.Path)
} else if pkg.Name() != item.Name {
errorf("internal error: getPackages returned wrong name %s for package %q, want %s", pkg.Name(), item.Path, item.Name)
}
p.pkgCache[item.pathOffset] = pkg
p.pkgIndex[pkg] = item.nameIndex
pkgList[i] = pkg
}
@ -296,6 +328,13 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
typ.Complete()
}
// Workaround for golang/go#61561. See the doc for instanceList for details.
for _, typ := range p.instanceList {
if iface, _ := typ.Underlying().(*types.Interface); iface != nil {
iface.Complete()
}
}
return pkgs, nil
}
@ -308,8 +347,8 @@ type iimporter struct {
version int
ipath string
localpkg *types.Package
insert func(pkg *types.Package, name string) // "shallow" mode only
shallow bool
reportf ReportFunc // if non-nil, used to report bugs
stringData []byte
stringCache map[uint64]string
@ -326,6 +365,12 @@ type iimporter struct {
fake fakeFileSet
interfaceList []*types.Interface
// Workaround for the go/types bug golang/go#61561: instances produced during
// instantiation may contain incomplete interfaces. Here we only complete the
// underlying type of the instance, which is the most common case but doesn't
// handle parameterized interface literals defined deeper in the type.
instanceList []types.Type // instances for later completion (see golang/go#61561)
// Arguments for calls to SetConstraint that are deferred due to recursive types
later []setConstraintArgs
@ -357,13 +402,9 @@ func (p *iimporter) doDecl(pkg *types.Package, name string) {
off, ok := p.pkgIndex[pkg][name]
if !ok {
// In "shallow" mode, call back to the application to
// find the object and insert it into the package scope.
if p.insert != nil {
assert(pkg != p.localpkg)
p.insert(pkg, name) // "can't fail"
return
}
// In deep mode, the index should be complete. In shallow
// mode, we should have already recursively loaded necessary
// dependencies so the above Lookup succeeds.
errorf("%v.%v not in index", pkg, name)
}
@ -730,7 +771,8 @@ func (r *importReader) qualifiedIdent() (*types.Package, string) {
}
func (r *importReader) pos() token.Pos {
if r.p.insert != nil { // shallow mode
if r.p.shallow {
// precise offsets are encoded only in shallow mode
return r.posv2()
}
if r.p.version >= iexportVersionPosCol {
@ -831,13 +873,28 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
fields := make([]*types.Var, r.uint64())
tags := make([]string, len(fields))
for i := range fields {
var field *types.Var
if r.p.shallow {
field, _ = r.objectPathObject().(*types.Var)
}
fpos := r.pos()
fname := r.ident()
ftyp := r.typ()
emb := r.bool()
tag := r.string()
fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
// Either this is not a shallow import, the field is local, or the
// encoded objectPath failed to produce an object (a bug).
//
// Even in this last, buggy case, fall back on creating a new field. As
// discussed in iexport.go, this is not correct, but mostly works and is
// preferable to failing (for now at least).
if field == nil {
field = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
}
fields[i] = field
tags[i] = tag
}
return types.NewStruct(fields, tags)
@ -853,6 +910,11 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
methods := make([]*types.Func, r.uint64())
for i := range methods {
var method *types.Func
if r.p.shallow {
method, _ = r.objectPathObject().(*types.Func)
}
mpos := r.pos()
mname := r.ident()
@ -862,9 +924,12 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
if base != nil {
recv = types.NewVar(token.NoPos, r.currPkg, "", base)
}
msig := r.signature(recv, nil, nil)
methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig)
if method == nil {
method = types.NewFunc(mpos, r.currPkg, mname, msig)
}
methods[i] = method
}
typ := newInterface(methods, embeddeds)
@ -902,6 +967,9 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
// we must always use the methods of the base (orig) type.
// TODO provide a non-nil *Environment
t, _ := typeparams.Instantiate(nil, baseType, targs, false)
// Workaround for golang/go#61561. See the doc for instanceList for details.
r.p.instanceList = append(r.p.instanceList, t)
return t
case unionType:
@ -920,6 +988,26 @@ func (r *importReader) kind() itag {
return itag(r.uint64())
}
// objectPathObject is the inverse of exportWriter.objectPath.
//
// In shallow mode, certain fields and methods may need to be looked up in an
// imported package. See the doc for exportWriter.objectPath for a full
// explanation.
func (r *importReader) objectPathObject() types.Object {
objPath := objectpath.Path(r.string())
if objPath == "" {
return nil
}
pkg := r.pkg()
obj, err := objectpath.Object(pkg, objPath)
if err != nil {
if r.p.reportf != nil {
r.p.reportf("failed to find object for objectPath %q: %v", objPath, err)
}
}
return obj
}
func (r *importReader) signature(recv *types.Var, rparams []*typeparams.TypeParam, tparams []*typeparams.TypeParam) *types.Signature {
params := r.paramList()
results := r.paramList()

View File

@ -319,7 +319,7 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) {
// Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close
// should cause the Read call in io.Copy to unblock and return
// immediately, but we still need to receive from stdoutErr to confirm
// that that has happened.
// that it has happened.
<-stdoutErr
err2 = ctx.Err()
}
@ -333,7 +333,7 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) {
// one goroutine at a time will call Write.”
//
// Since we're starting a goroutine that writes to cmd.Stdout, we must
// also update cmd.Stderr so that that still holds.
// also update cmd.Stderr so that it still holds.
func() {
defer func() { recover() }()
if cmd.Stderr == prevStdout {

View File

@ -23,6 +23,7 @@
package typeparams
import (
"fmt"
"go/ast"
"go/token"
"go/types"
@ -105,6 +106,31 @@ func OriginMethod(fn *types.Func) *types.Func {
}
orig := NamedTypeOrigin(named)
gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
// This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In:
// package p
// type T *int
// func (*T) f() {}
// LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}.
// Here we make them consistent by force.
// (The go/types bug is general, but this workaround is reached only
// for generic T thanks to the early return above.)
if gfn == nil {
mset := types.NewMethodSet(types.NewPointer(orig))
for i := 0; i < mset.Len(); i++ {
m := mset.At(i)
if m.Obj().Id() == fn.Id() {
gfn = m.Obj()
break
}
}
}
// In golang/go#61196, we observe another crash, this time inexplicable.
if gfn == nil {
panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods()))
}
return gfn.(*types.Func)
}

View File

@ -129,7 +129,7 @@ func NamedTypeArgs(*types.Named) *TypeList {
}
// NamedTypeOrigin is the identity method at this Go version.
func NamedTypeOrigin(named *types.Named) types.Type {
func NamedTypeOrigin(named *types.Named) *types.Named {
return named
}

View File

@ -103,7 +103,7 @@ func NamedTypeArgs(named *types.Named) *TypeList {
}
// NamedTypeOrigin returns named.Orig().
func NamedTypeOrigin(named *types.Named) types.Type {
func NamedTypeOrigin(named *types.Named) *types.Named {
return named.Origin()
}

View File

@ -11,6 +11,8 @@ import (
"go/types"
"reflect"
"unsafe"
"golang.org/x/tools/go/types/objectpath"
)
func SetUsesCgo(conf *types.Config) bool {
@ -50,3 +52,17 @@ func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos,
}
var SetGoVersion = func(conf *types.Config, version string) bool { return false }
// SkipEncoderMethodSorting marks the encoder as not requiring sorted methods,
// as an optimization for gopls (which guarantees the order of parsed source files).
//
// TODO(golang/go#61443): eliminate this parameter one way or the other.
//
//go:linkname SkipEncoderMethodSorting golang.org/x/tools/go/types/objectpath.skipMethodSorting
func SkipEncoderMethodSorting(enc *objectpath.Encoder)
// ObjectpathObject is like objectpath.Object, but allows suppressing method
// sorting (which is not necessary for gopls).
//
//go:linkname ObjectpathObject golang.org/x/tools/go/types/objectpath.object
func ObjectpathObject(pkg *types.Package, p objectpath.Path, skipMethodSorting bool) (types.Object, error)

11
vendor/modules.txt vendored
View File

@ -156,7 +156,7 @@ github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/pkg/sshagent
github.com/containers/buildah/pkg/util
github.com/containers/buildah/util
# github.com/containers/common v0.55.1-0.20230816154734-519ed7fea9bd
# github.com/containers/common v0.55.1-0.20230824140149-b27c2ba2b7e1
## explicit; go 1.18
github.com/containers/common/libimage
github.com/containers/common/libimage/define
@ -653,7 +653,7 @@ github.com/hashicorp/go-retryablehttp
# github.com/inconshreveable/mousetrap v1.1.0
## explicit; go 1.18
github.com/inconshreveable/mousetrap
# github.com/jinzhu/copier v0.3.5
# github.com/jinzhu/copier v0.4.0
## explicit; go 1.13
github.com/jinzhu/copier
# github.com/josharian/intern v1.0.0
@ -755,7 +755,7 @@ github.com/nxadm/tail/winfile
# github.com/oklog/ulid v1.3.1
## explicit
github.com/oklog/ulid
# github.com/onsi/ginkgo/v2 v2.11.0
# github.com/onsi/ginkgo/v2 v2.12.0
## explicit; go 1.18
github.com/onsi/ginkgo/v2
github.com/onsi/ginkgo/v2/config
@ -1064,7 +1064,7 @@ golang.org/x/crypto/ssh/knownhosts
golang.org/x/exp/constraints
golang.org/x/exp/maps
golang.org/x/exp/slices
# golang.org/x/mod v0.11.0
# golang.org/x/mod v0.12.0
## explicit; go 1.17
golang.org/x/mod/semver
golang.org/x/mod/sumdb/note
@ -1126,13 +1126,14 @@ golang.org/x/text/secure/bidirule
golang.org/x/text/transform
golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.9.3
# golang.org/x/tools v0.12.0
## explicit; go 1.18
golang.org/x/tools/cmd/stringer
golang.org/x/tools/go/ast/inspector
golang.org/x/tools/go/gcexportdata
golang.org/x/tools/go/internal/packagesdriver
golang.org/x/tools/go/packages
golang.org/x/tools/go/types/objectpath
golang.org/x/tools/internal/event
golang.org/x/tools/internal/event/core
golang.org/x/tools/internal/event/keys