diff --git a/cmd/gvproxy/main.go b/cmd/gvproxy/main.go index 01ca02a38..8e6977513 100644 --- a/cmd/gvproxy/main.go +++ b/cmd/gvproxy/main.go @@ -22,6 +22,7 @@ import ( "github.com/containers/gvisor-tap-vsock/pkg/transport" "github.com/containers/gvisor-tap-vsock/pkg/types" "github.com/containers/gvisor-tap-vsock/pkg/virtualnetwork" + "github.com/containers/winquit/pkg/winquit" "github.com/dustin/go-humanize" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -84,6 +85,9 @@ func main() { log.SetLevel(log.DebugLevel) } + // Intercept WM_QUIT/WM_CLOSE events if on Windows as SIGTERM (noop on other OSs) + winquit.SimulateSigTermOnQuit(sigChan) + // Make sure the qemu socket provided is valid syntax if len(qemuSocket) > 0 { uri, err := url.Parse(qemuSocket) diff --git a/cmd/win-sshproxy/main.go b/cmd/win-sshproxy/main.go index 7e197932c..b29332eaf 100644 --- a/cmd/win-sshproxy/main.go +++ b/cmd/win-sshproxy/main.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package main @@ -8,15 +9,14 @@ import ( "net/url" "os" "path/filepath" - "runtime" "strings" "syscall" "unsafe" "github.com/containers/gvisor-tap-vsock/pkg/sshclient" + "github.com/containers/winquit/pkg/winquit" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" - "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc/eventlog" ) @@ -25,15 +25,6 @@ const ( WM_QUIT = 0x12 ) -type MSG struct { - hwnd uintptr - message uint32 - wParam uintptr - lParam uintptr - time uint32 - pt struct{ X, Y int32 } -} - var ( stateDir string debug bool @@ -73,11 +64,18 @@ func main() { ctx, cancel := context.WithCancel(context.Background()) group, ctx := errgroup.WithContext(ctx) + quit := make(chan bool, 1) // Wait for a WM_QUIT message to exit - group.Go(func() error { - logrus.Debug("Starting message loop") - return messageLoop(ctx, group, cancel) - }) + winquit.NotifyOnQuit(quit) + go func() { + <-quit + cancel() + }() + + // Save thread for legacy callers which use it to post a quit + if _, err := saveThreadId(); err != nil { + logrus.Errorf("Error saving thread id: " + err.Error()) + } logrus.Debug("Setting up proxies") setupProxies(ctx, group, sources, dests, identities) @@ -108,36 +106,6 @@ func setupLogging(name string) (*eventlog.Log, error) { return log, nil } -func messageLoop(ctx context.Context, group *errgroup.Group, cancel func()) error { - user32 := syscall.NewLazyDLL("user32.dll") - getMessage := user32.NewProc("GetMessageW") - - runtime.LockOSThread() // GetMessageW relies on thread state - defer runtime.UnlockOSThread() - tid, err := saveThreadId() - if err != nil { - return err - } - - // Abort the message loop thread on cancellation - group.Go(func() error { - <-ctx.Done() - terminate(tid) - return nil - }) - - for { - var msg = &MSG{} - ret, _, _ := getMessage.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0, 1) - if ret == 0 || int(ret) == -1 { - logrus.Info("Received QUIT notification") - cancel() - return nil - } - logrus.Infof("Unhandled message: %d", msg.message) - } -} - func setupProxies(ctx context.Context, g *errgroup.Group, sources []string, dests []string, identities []string) error { for i := 0; i < len(sources); i++ { var ( @@ -199,17 +167,11 @@ func saveThreadId() (uint32, error) { return 0, err } defer file.Close() - tid := windows.GetCurrentThreadId() + tid := winquit.GetCurrentMessageLoopThreadId() fmt.Fprintf(file, "%d:%d\n", os.Getpid(), tid) return tid, nil } -func terminate(tid uint32) { - user32 := syscall.NewLazyDLL("user32.dll") - postMessage := user32.NewProc("PostThreadMessageW") - postMessage.Call(uintptr(tid), WM_QUIT, 0, 0) -} - // Creates an "error" style pop-up window func alert(caption string) int { // Error box style @@ -225,4 +187,4 @@ func alert(caption string) int { uintptr(format)) return int(ret) -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index fb492e2e7..a54b76c1b 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/Microsoft/go-winio v0.6.1 github.com/apparentlymart/go-cidr v1.1.0 + github.com/containers/winquit v1.1.0 github.com/coreos/stream-metadata-go v0.4.3 github.com/dustin/go-humanize v1.0.1 github.com/google/gopacket v1.1.19 @@ -38,11 +39,11 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect - golang.org/x/mod v0.10.0 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - golang.org/x/tools v0.9.3 // indirect + golang.org/x/tools v0.12.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7a1dff55e..e5863e685 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5 github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= +github.com/containers/winquit v1.1.0 h1:jArun04BNDQvt2W0Y78kh9TazN2EIEMG5Im6/JY7+pE= +github.com/containers/winquit v1.1.0/go.mod h1:PsPeZlnbkmGGIToMPHF1zhWjBUkd8aHjMOr/vFcPxw8= github.com/coreos/stream-metadata-go v0.4.3 h1:5GykJ8dtZSx1rdlzEAiDVzA73cwmUF3ceTuIP293L6E= github.com/coreos/stream-metadata-go v0.4.3/go.mod h1:fMObQqQm8Ku91G04btKzEH3AsdP1mrAb986z9aaK0tE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -71,7 +73,7 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= @@ -112,8 +114,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -127,8 +129,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -170,8 +172,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -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= diff --git a/vendor/github.com/containers/winquit/LICENSE b/vendor/github.com/containers/winquit/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/containers/winquit/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/containers/winquit/pkg/winquit/channels_windows.go b/vendor/github.com/containers/winquit/pkg/winquit/channels_windows.go new file mode 100644 index 000000000..2d7e48a4a --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/channels_windows.go @@ -0,0 +1,50 @@ +package winquit + +import ( + "os" + "syscall" +) + +type baseChannelType interface { + getKey() any + notifyNonBlocking() + notifyBlocking() +} + +type boolChannelType struct { + channel chan bool +} + +func (b *boolChannelType) getKey() any { + return b.channel +} + +func (b *boolChannelType) notifyNonBlocking() { + select { + case b.channel <- true: + default: + } +} + +func (s *boolChannelType) notifyBlocking() { + s.channel <- true +} + +type sigChannelType struct { + channel chan os.Signal +} + +func (s *sigChannelType) getKey() any { + return s.channel +} + +func (s *sigChannelType) notifyNonBlocking() { + select { + case s.channel <- syscall.SIGTERM: + default: + } +} + +func (s *sigChannelType) notifyBlocking() { + s.channel <- syscall.SIGTERM +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/client.go b/vendor/github.com/containers/winquit/pkg/winquit/client.go new file mode 100644 index 000000000..95b813c0b --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/client.go @@ -0,0 +1,31 @@ +package winquit + +import ( + "time" +) + +// RequestQuit sends a Windows quit notification to the specified process id. +// Since communication is performed over the Win32 GUI messaging facilities, +// console applications may not respond, as they require special handling to do +// so. Additionally incorrectly written or buggy GUI applications may not listen +// or respond appropriately to the event. +// +// All applications, console or GUI, which use the notification mechanisms +// provided by this package (NotifyOnQuit, SimulateSigTermOnQuit) will react +// appropriately to the event sent by RequestQuit. +// +// Callers must have appropriate security permissions, otherwise an error will +// be returned. See the notes in the package documentation for more details. +func RequestQuit(pid int) error { + return requestQuit(pid) +} + +// QuitProcess first sends a Windows quit notification to the specified process id, +// and waits, up the amount of time passed in the waitNicely argument, for it to +// exit. If the process does not exit in time, it is forcefully terminated. +// +// Callers must have appropriate security permissions, otherwise an error will +// be returned. See the notes in the package documentation for more details. +func QuitProcess(pid int, waitNicely time.Duration) error { + return quitProcess(pid, waitNicely) +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/client_unsupported.go b/vendor/github.com/containers/winquit/pkg/winquit/client_unsupported.go new file mode 100644 index 000000000..8aa5ed06e --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/client_unsupported.go @@ -0,0 +1,17 @@ +//go:build !windows +// +build !windows + +package winquit + +import ( + "fmt" + "time" +) + +func requestQuit(pid int) error { + return fmt.Errorf("not implemented on non-Windows") +} + +func quitProcess(pid int, waitNicely time.Duration) error { + return fmt.Errorf("not implemented on non-Windows") +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/client_windows.go b/vendor/github.com/containers/winquit/pkg/winquit/client_windows.go new file mode 100644 index 000000000..e03919b4c --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/client_windows.go @@ -0,0 +1,47 @@ +package winquit + +import ( + "os" + "time" + + "github.com/containers/winquit/pkg/winquit/win32" + "github.com/sirupsen/logrus" +) + +func requestQuit(pid int) error { + threads, err := win32.GetProcThreads(uint32(pid)) + if err != nil { + return err + } + + for _, thread := range threads { + logrus.Debugf("Closing windows on thread %d", thread) + win32.CloseThreadWindows(uint32(thread)) + } + + return nil +} + +func quitProcess(pid int, waitNicely time.Duration) error { + _ = RequestQuit(pid) + + proc, err := os.FindProcess(pid) + if err != nil { + return nil + } + + done := make(chan bool) + + go func() { + proc.Wait() + done <- true + }() + + select { + case <-done: + return nil + case <-time.After(waitNicely): + } + + return proc.Kill() +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/doc.go b/vendor/github.com/containers/winquit/pkg/winquit/doc.go new file mode 100644 index 000000000..079794cb4 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/doc.go @@ -0,0 +1,135 @@ +// Package winquit supports graceful shutdown of Windows applications through +// the sending and receiving of Windows quit events on Win32 message queues. +// This allows golang applications to implement behavior comparable to SIGTERM +// signal handling on UNIX derived systems. Additionally, it supports the +// graceful shutdown mechanism employed by Windows system tools, such as +// taskkill. See the "How it works" section for more details. +// +// To aid application portability, and provide familiarity, the API follows a +// similar convention and approach as the os.signal package. Additionally, the +// SimulateSigTermOnQuit function supports reuse of the same underlying channel, +// supporting the blending of os.signal and winquit together (a subset of +// signals provided by os.signal are still relevant and desirable on Windows, +// for example, break handling in console applications). +// +// # Simple server example +// +// The following example demonstrates usage of NotifyOnQuit() to wait for a +// windows quit event before shutting down: +// +// func server() { +// fmt.Println("Starting server") +// +// // Create a channel, and register it +// done := make(chan bool, 1) +// winquit.NotifyOnQuit(done) +// +// // Wait until we receive a quit event +// <-done +// +// fmt.Println("Shutting down") +// // Perform cleanup tasks +// } +// +// # Blended signal example +// +// The following example demonstrates usage of SimulateSigTermOnQuit() in +// concert with signal.Notify(): +// +// func server() { +// fmt.Println("Starting server") +// +// // Create a channel, and register it +// done := make(chan os.Signal, 1) +// +// // Wait on console interrupt events +// signal.Notify(done, syscall.SIGINT) +// +// // Simulate SIGTERM when a quit occurs +// winquit.SimulateSigTermOnQuit(done) +// +// // Wait until we receive a signal or quit event +// <-done +// +// fmt.Println("Shutting down") +// // Perform cleanup tasks +// } +// +// # Client example +// +// The following example demonstrates how an application can ask or +// force other windows programs to quit: +// +// func client() { +// // Ask nicely for program "one" to quit. This request may not +// // be honored if its a console application, or if the program +// // is hung +// if err := winquit.RequestQuit(pidOne); err != nil { +// fmt.Printf("error sending quit request, %s", err.Error()) +// } +// +// // Force program "two" to quit, but give it 20 seconds to +// // perform any cleanup tasks and quit on it's own +// timeout := time.Second * 20 +// if err := winquit.QuitProcess(pidTwo, timeout); err != nil { +// fmt.Printf("error killing process, %s", err.Error()) +// } +// } +// +// # How it works +// +// Windows GUI applications consist of multiple components (and windows) which +// intercommunicate with events over per-thread message queues and/or direct +// event handoff to window procedures for cross-thread communication. +// Additionally, GUI applications can use the same mechanism to communicate with +// windows and threads owned by other applications, including common desktop +// components. +// +// winquit utilizes this mechanism by creating a standard win32 message loop +// thread and registering a non-visible window to relay a quit message (WM_QUIT) +// in the event of a window close event. WM_CLOSE is sent by Windows in response +// to certain system events, or by other requesting applications. For example, +// the system provided taskkill.exe (similar to the kill command on Unix), works +// by iterating all windows on the system, and sending a WM_CLOSE when the +// process owner matches the specified pid. Note that, unlike UNIX/X11 style +// systems, on Windows the graphical APIs are built-in and accessible to all +// win32 applications, including console based applications. Therefore, the APIs +// provided by winquit *do not* require compilation as a windowsgui app to +// effectively use them. +// +// winquit also provides APIs to trigger a quit of another process using a +// WM_CLOSE event, although in a more efficient manner than taskkill.exe. It +// instead captures a thread snapshot of the target process (effectively a +// memory read on Windows), and enumerates each thread's associated Windows, and +// sending the event to each. In addition to supporting a graceful close of any +// Windows application, which may have multiple message loops, this approach +// also obviates the need for cumbersome approaches to lock code to the main +// thread of the application. The message loop used by winquit does not care +// which thread the golang runtime internally designates. Note that winquit +// purposefully relays through a thread's windows as opposed to posting directly +// to each thread's message queue, since the former is more likely to be +// expected by an application, and it ensures all window procedures have an +// opportunity to perform cleanup work not associated with the thread's message +// loop. +// +// # Limitations +// +// This API is only implemented on Windows platforms. Non-operational stubs +// are provided for compilation purposes. +// +// In addition to requiring appropriate security permissions (typically a user +// can only send events to other applications ran by the same user), Windows +// also restricts inter-app messaging operations to programs running in the same +// user logon session. While logons migrate between RDP and console sessions, +// non-graphical logins (e.g sshd) typically create a logon per connection. For +// this reason, tools like taskkill and winquit are normally disallowed from +// crossing this boundary. Therefore, a user will not be able to gracefully stop +// applications between ssh/winrm sessions, and in between ssh and graphical +// logons. However, the typical user use-case of logging into Windows and +// running multiple applications and terminals will work fine. Additionally, +// multiple back-grounded processes in the same ssh session will be able to +// communicate. Finally, it's possible to bypass this limitation by executing +// code under the system user using the SeTcbPrivilege. The psexec tool does +// exactly this, and can additionally be used as a workaround to this +// limitation. +package winquit diff --git a/vendor/github.com/containers/winquit/pkg/winquit/server.go b/vendor/github.com/containers/winquit/pkg/winquit/server.go new file mode 100644 index 000000000..e50ad1a96 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/server.go @@ -0,0 +1,45 @@ +package winquit + +import ( + "os" +) + +// NotifyOnQuit relays a Windows quit notification to the boolean done channel. +// This is a one-shot operation (will only be delivered once), however multiple +// channels may be registered. Each registered channel is sent one copy of the +// same one-shot value. +// +// This function is a no-op on non-Windows platforms. While the call will +// succeed, no notifications will be delivered to the passed channel. Each +// channel will only ever receive a "true" value. +// +// It is recommended that registered channels establish a buffer of 1, since +// values are sent non-blocking. Blocking redelivery may be attempted to reduce +// the chance of bugs; however, it should not be relied upon. +// +// If this function is called after a Windows quit notification has occurred, it +// will immediately deliver a "true" value. +func NotifyOnQuit(done chan bool) { + notifyOnQuit(done) +} + +// SimulateSigTermOnQuit relays a Windows quit notification following the same +// semantics as NotifyOnQuit; however, instead of a boolean message value, this +// function will send a SIGTERM signal to the passed channel. +// +// This function allows for the reuse of the same underlying channel used with +// in a separate os.signal.Notify method call. +func SimulateSigTermOnQuit(handler chan os.Signal) { + simulateSigTermOnQuit(handler) +} + +// Returns the thread id of the message loop thread created by winquit, or "0" +// if one is not running. The latter indicates a mistake, as this function +// should only be called after a call to one of the _OnQuit functions. +// +// In most cases this method should not be necessary, as RequestQuit and +// QuitProcess do not require it. It is primarily provided to enable legacy +// patterns that serialize the thread id for later direct signaling. +func GetCurrentMessageLoopThreadId() uint32 { + return getCurrentMessageLoopThreadId() +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/server_unsupported.go b/vendor/github.com/containers/winquit/pkg/winquit/server_unsupported.go new file mode 100644 index 000000000..e5013c088 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/server_unsupported.go @@ -0,0 +1,18 @@ +//go:build !windows +// +build !windows + +package winquit + +import ( + "os" +) + +func notifyOnQuit(done chan bool) { +} + +func simulateSigTermOnQuit(handler chan os.Signal) { +} + +func getCurrentMessageLoopThreadId() uint32 { + return 0 +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/server_windows.go b/vendor/github.com/containers/winquit/pkg/winquit/server_windows.go new file mode 100644 index 000000000..4309319bf --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/server_windows.go @@ -0,0 +1,147 @@ +package winquit + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "sync" + "syscall" + + "github.com/containers/winquit/pkg/winquit/win32" + "github.com/sirupsen/logrus" + "golang.org/x/sys/windows" +) + +type receiversType struct { + sync.Mutex + + result bool + channels map[any]baseChannelType +} + +var ( + receivers *receiversType = &receiversType{ + channels: make(map[any]baseChannelType), + } + + loopInit sync.Once + loopTid uint32 +) + +func (r *receiversType) add(channel baseChannelType) { + r.Lock() + defer r.Unlock() + + if _, ok := r.channels[channel.getKey()]; ok { + return + } + + if r.result { + go func() { + channel.notifyBlocking() + }() + return + } + + r.channels[channel.getKey()] = channel +} + +func (r *receiversType) notifyAll() { + r.Lock() + defer r.Unlock() + r.result = true + for _, channel := range r.channels { + channel.notifyNonBlocking() + delete(r.channels, channel.getKey()) + } + for _, channel := range r.channels { + channel.notifyBlocking() + delete(r.channels, channel) + } +} + +func initLoop() { + loopInit.Do(func() { + go messageLoop() + }) +} + +func notifyOnQuit(done chan bool) { + receivers.add(&boolChannelType{done}) + initLoop() +} + +func simulateSigTermOnQuit(handler chan os.Signal) { + receivers.add(&sigChannelType{handler}) + initLoop() +} + +func getCurrentMessageLoopThreadId() uint32 { + return loopTid +} + +func messageLoop() { + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + loopTid = windows.GetCurrentThreadId() + registerDummyWindow() + + logrus.Debug("Entering loop for quit") + for { + ret, msg, err := win32.GetMessage(0, 0, 0) + if err != nil { + logrus.Debugf("Error receiving win32 message, %s", err.Error()) + continue + } + if ret == 0 { + logrus.Debug("Received QUIT notification") + receivers.notifyAll() + + return + } + logrus.Debugf("Unhandled message: %d", msg.Message) + win32.TranslateMessage(msg) + win32.DispatchMessage(msg) + } +} + +func getAppName() (string, error) { + exeName, err := os.Executable() + if err != nil { + return "", err + } + suffix := filepath.Ext(exeName) + return strings.TrimSuffix(filepath.Base(exeName), suffix), nil +} + +func registerDummyWindow() error { + var app syscall.Handle + var err error + + app, err = win32.GetModuleHandle("") + if err != nil { + return err + } + + appName, err := getAppName() + if err != nil { + return err + } + + className := appName + "-rclass" + winName := appName + "-root" + + _, err = win32.RegisterDummyWinClass(className, app) + if err != nil { + return err + } + + _, err = win32.CreateDummyWindow(winName, className, app) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/common.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/common.go new file mode 100644 index 000000000..dbb13657e --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/common.go @@ -0,0 +1,17 @@ +//go:build windows +// +build windows + +package win32 + +import ( + "syscall" +) + +const ( + ERROR_NO_MORE_ITEMS = 259 +) + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + user32 = syscall.NewLazyDLL("user32.dll") +) diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/common_unsupported.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/common_unsupported.go new file mode 100644 index 000000000..705e6b312 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/common_unsupported.go @@ -0,0 +1,4 @@ +//go:build !windows +// +build !windows + +package win32 diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/msg.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/msg.go new file mode 100644 index 000000000..f6063a5e1 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/msg.go @@ -0,0 +1,87 @@ +//go:build windows +// +build windows + +package win32 + +import ( + "syscall" + "unsafe" +) + +type MSG struct { + HWnd uintptr + Message uint32 + WParam uintptr + LParam uintptr + Time uint32 + Pt struct{ X, Y int32 } +} + +const ( + WM_QUIT = 0x12 + WM_DESTROY = 0x02 + WM_CLOSE = 0x10 +) + +var ( + postQuitMessage = user32.NewProc("PostQuitMessage") + procGetMessage = user32.NewProc("GetMessageW") + procTranslateMessage = user32.NewProc("TranslateMessage") + procDispatchMessage = user32.NewProc("DispatchMessageW") + procSendMessage = user32.NewProc("SendMessageW") +) + +func TranslateMessage(msg *MSG) bool { + ret, _, _ := + procTranslateMessage.Call( // BOOL TranslateMessage() + uintptr(unsafe.Pointer(msg)), // [in] const MSG *lpMsg + ) + + return ret != 0 + +} + +func DispatchMessage(msg *MSG) uintptr { + ret, _, _ := + procDispatchMessage.Call( // LRESULT DispatchMessage() + uintptr(unsafe.Pointer(msg)), // [in] const MSG *lpMsg + ) + + return ret +} + +func SendMessage(handle syscall.Handle, message uint, wparm uintptr, lparam uintptr) uintptr { + ret, _, _ := + procSendMessage.Call( // LRESULT SendMessage() + uintptr(handle), // [in] HWND hWnd + uintptr(message), // [in] UINT Msg + wparm, // [in] WPARAM wParam + lparam, // [in] LPARAM lParam + ) + + return ret +} + +func PostQuitMessage(code int) { + _, _, _ = + postQuitMessage.Call( // void PostQuitMessage() + uintptr(code), // [in] int nExitCode + ) +} + +func GetMessage(handle syscall.Handle, int, max int) (int32, *MSG, error) { + var msg MSG + ret, _, err := + procGetMessage.Call( // // BOOL GetMessage() + uintptr(unsafe.Pointer(&msg)), // [out] LPMSG lpMsg, + uintptr(handle), // [in, optional] HWND hWnd, + 0, // [in] UINT wMsgFilterMin, + 0, // [in] UINT wMsgFilterMax + ) + + if int32(ret) == -1 { + return 0, nil, err + } + + return int32(ret), &msg, nil +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/proc.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/proc.go new file mode 100644 index 000000000..6f7ccfc5a --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/proc.go @@ -0,0 +1,59 @@ +//go:build windows +// +build windows + +package win32 + +import ( + "fmt" + "syscall" +) + +const ( + MAXIMUM_ALLOWED = 0x02000000 +) + +var ( + procOpenProcess = kernel32.NewProc("OpenProcess") + procCloseHandle = kernel32.NewProc("CloseHandle") + procGetModuleHandle = kernel32.NewProc("GetModuleHandleW") +) + +func OpenProcess(pid uint32) (syscall.Handle, error) { + ret, _, err := + procOpenProcess.Call( // HANDLE OpenProcess() + MAXIMUM_ALLOWED, // [in] DWORD dwDesiredAccess, + 0, // [in] BOOL bInheritHandle, + uintptr(pid), // [in] DWORD dwProcessId + ) + + if ret == 0 { + return 0, err + } + + return syscall.Handle(ret), nil +} + +func CloseHandle(handle syscall.Handle) error { + ret, _, err := + procCloseHandle.Call( // BOOL CloseHandle() + uintptr(handle), // [in] HANDLE hObject + ) + if ret != 0 { + return fmt.Errorf("error closing handle: %w", err) + } + + return nil +} + +func GetProcThreads(pid uint32) ([]uint, error) { + process, err := OpenProcess(pid) + if err != nil { + return nil, err + } + + defer func() { + _ = CloseHandle(process) + }() + + return GetProcThreadIds(process) +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/pss.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/pss.go new file mode 100644 index 000000000..bd03959a5 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/pss.go @@ -0,0 +1,160 @@ +//go:build windows +// +build windows + +package win32 + +import ( + "fmt" + "syscall" + "unsafe" +) + +type PSS_THREAD_ENTRY struct { + ExitStatus uint32 + TebBaseAddress uintptr + ProcessId uint32 + ThreadId uint32 + AffinityMask uintptr + Priority int32 + BasePriority int32 + LastSyscallFirstArgument uintptr + LastSyscallNumber uint16 + CreateTime uint64 + ExitTime uint64 + KernelTime uint64 + UserTime uint64 + Win32StartAddress uintptr + CaptureTime uint64 + Flags uint32 + SuspendCount uint16 + SizeOfContextRecord uint16 + ContextRecord uintptr +} + +const ( + PSS_CAPTURE_THREADS = 0x00000080 + PSS_WALK_THREADS = 3 + PSS_QUERY_THREAD_INFORMATION = 5 +) + +var ( + procPssCaptureSnapshot = kernel32.NewProc("PssCaptureSnapshot") + procPssFreeSnapshot = kernel32.NewProc("PssFreeSnapshot") + procPssWalkMarkerCreate = kernel32.NewProc("PssWalkMarkerCreate") + procPssWalkMarkerFree = kernel32.NewProc("PssWalkMarkerFree") + procPssWalkSnapshot = kernel32.NewProc("PssWalkSnapshot") +) + +func PssCaptureSnapshot(process syscall.Handle, flags int32, contextFlags int32) (syscall.Handle, error) { + var snapshot syscall.Handle + ret, _, err := + procPssCaptureSnapshot.Call( // DWORD PssCaptureSnapshot() + uintptr(process), // [in] HANDLE ProcessHandle, + uintptr(flags), // [in] PSS_CAPTURE_FLAGS CaptureFlags, + uintptr(contextFlags), // [in, optional] DWORD ThreadContextFlags, + uintptr(unsafe.Pointer(&snapshot)), // [out] HPSS *SnapshotHandle + ) + + if ret != 0 { + return 0, err + } + + return snapshot, nil +} + +func PssFreeSnapshot(process syscall.Handle, snapshot syscall.Handle) error { + ret, _, _ := + procPssFreeSnapshot.Call( // DWORD PssFreeSnapshot() + uintptr(process), // [in] HANDLE ProcessHandle, + uintptr(snapshot), // [in] HPSS SnapshotHandle + ) + if ret != 0 { + return fmt.Errorf("error freeing snapshot: %d", ret) + } + + return nil +} + +func PssWalkMarkerCreate() (syscall.Handle, error) { + var walkptr uintptr + + ret, _, _ := + procPssWalkMarkerCreate.Call( // // DWORD PssWalkMarkerCreate() + 0, // [in, optional] PSS_ALLOCATOR const *Allocator + uintptr(unsafe.Pointer(&walkptr)), // [out] HPSSWALK *WalkMarkerHandle + ) + if ret != 0 { + return 0, fmt.Errorf("error creating process walker mark: %d", ret) + } + + return syscall.Handle(walkptr), nil +} + +func PssWalkMarkerFree(handle syscall.Handle) error { + ret, _, _ := + procPssWalkMarkerFree.Call( // DWORD PssWalkMarkerFree() + uintptr(handle), // [in] HPSSWALK WalkMarkerHandle + ) + if ret != 0 { + return fmt.Errorf("error freeing process walker mark: %d", ret) + } + + return nil +} + +func PssWalkThreadSnapshot(snapshot syscall.Handle, marker syscall.Handle) (*PSS_THREAD_ENTRY, error) { + var thread PSS_THREAD_ENTRY + ret, _, err := + procPssWalkSnapshot.Call( // // DWORD PssWalkSnapshot() + uintptr(snapshot), // [in] HPSS SnapshotHandle, + PSS_WALK_THREADS, // [in] PSS_WALK_INFORMATION_CLASS InformationClass, + uintptr(marker), // [in] HPSSWALK WalkMarkerHandle, + uintptr(unsafe.Pointer(&thread)), // [out] void *Buffer, + unsafe.Sizeof(thread), // [in] DWORD BufferLength + ) + + if ret == ERROR_NO_MORE_ITEMS { + return nil, nil + } + + if ret != 0 { + return nil, fmt.Errorf("error waling thread snapshot: %d (%w)", ret, err) + } + + return &thread, nil +} + +func GetProcThreadIds(process syscall.Handle) ([]uint, error) { + snapshot, err := PssCaptureSnapshot(process, PSS_CAPTURE_THREADS, 0) + if err != nil { + return nil, err + } + defer func() { + _ = PssFreeSnapshot(process, snapshot) + }() + + marker, err := PssWalkMarkerCreate() + if err != nil { + return nil, err + } + + defer func() { + _ = PssWalkMarkerFree(marker) + }() + + var threads []uint + + for { + thread, err := PssWalkThreadSnapshot(snapshot, marker) + if err != nil { + return nil, err + } + if thread == nil { + break + } + + threads = append(threads, uint(thread.ThreadId)) + } + + return threads, nil +} diff --git a/vendor/github.com/containers/winquit/pkg/winquit/win32/win.go b/vendor/github.com/containers/winquit/pkg/winquit/win32/win.go new file mode 100644 index 000000000..b243b0be8 --- /dev/null +++ b/vendor/github.com/containers/winquit/pkg/winquit/win32/win.go @@ -0,0 +1,162 @@ +//go:build windows +// +build windows + +package win32 + +import ( + "fmt" + "syscall" + "unsafe" +) + +type WNDCLASSEX struct { + cbSize uint32 + style uint32 + lpfnWndProc uintptr + cbClsExtra int32 + cbWndExtra int32 + hInstance syscall.Handle + hIcon syscall.Handle + hCursor syscall.Handle + hbrBackground syscall.Handle + menuName *uint16 + className *uint16 + hIconSm syscall.Handle +} + +const ( + COLOR_WINDOW = 0x05 + CW_USEDEFAULT = ^0x7fffffff +) + +var ( + procEnumThreadWindows = user32.NewProc("EnumThreadWindows") + procRegisterClassEx = user32.NewProc("RegisterClassExW") + procCreateWindowEx = user32.NewProc("CreateWindowExW") + procDefWinProc = user32.NewProc("DefWindowProcW") + + callbackEnumThreadWindows = syscall.NewCallback(wndProcCloseWindow) +) + +func DefWindowProc(hWnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) int32 { + + ret, _, _ := + procDefWinProc.Call( // LRESULT DefWindowProcW() + uintptr(hWnd), // [in] HWND hWnd, + uintptr(msg), // [in] UINT Msg, + wParam, // [in] WPARAM wParam, + lParam, // [in] LPARAM lParam + ) + return int32(ret) +} + +func GetModuleHandle(name string) (syscall.Handle, error) { + var name16 *uint16 + var err error + + if len(name) > 0 { + if name16, err = syscall.UTF16PtrFromString(name); err != nil { + return 0, err + } + } + + ret, _, err := + procGetModuleHandle.Call( // HMODULE GetModuleHandleW() + uintptr(unsafe.Pointer(name16)), // [in, optional] LPCWSTR lpModuleName + ) + if ret == 0 { + return 0, fmt.Errorf("could not obtain module handle: %w", err) + } + + return syscall.Handle(ret), nil +} + +func RegisterClassEx(class *WNDCLASSEX) (uint16, error) { + + ret, _, err := + procRegisterClassEx.Call( // ATOM RegisterClassExW() + uintptr(unsafe.Pointer(class)), // [in] const WNDCLASSEXW *unnamedParam1 + ) + if ret == 0 { + return 0, fmt.Errorf("could not register window class: %w", err) + } + + return uint16(ret), nil +} + +func wndProc(hWnd syscall.Handle, msg uint32, wParam uintptr, lParam uintptr) uintptr { + switch msg { + case WM_DESTROY: + PostQuitMessage(0) + return 0 + default: + return uintptr(DefWindowProc(hWnd, msg, wParam, lParam)) + } +} + +func CloseThreadWindows(threadId uint32) { + _, _, _ = + procEnumThreadWindows.Call( // // BOOL EnumThreadWindows() + uintptr(threadId), // [in] DWORD dwThreadId, + callbackEnumThreadWindows, // [in] WNDENUMPROC lpfn, + 0, // [in] LPARAM lParam + ) +} + +func wndProcCloseWindow(hwnd uintptr, lparam uintptr) uintptr { + SendMessage(syscall.Handle(hwnd), WM_CLOSE, 0, 0) + + return 1 +} + +func RegisterDummyWinClass(name string, appInstance syscall.Handle) (uint16, error) { + var class16 *uint16 + var err error + if class16, err = syscall.UTF16PtrFromString(name); err != nil { + return 0, err + } + + class := WNDCLASSEX{ + className: class16, + hInstance: appInstance, + lpfnWndProc: syscall.NewCallback(wndProc), + } + + class.cbSize = uint32(unsafe.Sizeof(class)) + + return RegisterClassEx(&class) +} + +func CreateDummyWindow(name string, className string, appInstance syscall.Handle) (syscall.Handle, error) { + var name16, class16 *uint16 + var err error + + cwDefault := CW_USEDEFAULT + + if name16, err = syscall.UTF16PtrFromString(name); err != nil { + return 0, err + } + if class16, err = syscall.UTF16PtrFromString(className); err != nil { + return 0, err + } + ret, _, err := procCreateWindowEx.Call( //HWND CreateWindowExW( + 0, // [in] DWORD dwExStyle, + uintptr(unsafe.Pointer(class16)), // [in, optional] LPCWSTR lpClassName, + uintptr(unsafe.Pointer(name16)), // [in, optional] LPCWSTR lpWindowName, + 0, // [in] DWORD dwStyle, + uintptr(cwDefault), // [in] int X, + uintptr(cwDefault), // [in] int Y, + 0, // [in] int nWidth, + 0, // [in] int nHeight, + 0, // [in, optional] HWND hWndParent, + 0, // [in, optional] HMENU hMenu, + uintptr(appInstance), // [in, optional] HINSTANCE hInstance, + 0, // [in, optional] LPVOID lpParam + ) + + if ret == 0 { + return 0, fmt.Errorf("could not create window: %w", err) + } + + return syscall.Handle(ret), nil +} diff --git a/vendor/golang.org/x/mod/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go index a30a22bf2..9a2dfd33a 100644 --- a/vendor/golang.org/x/mod/semver/semver.go +++ b/vendor/golang.org/x/mod/semver/semver.go @@ -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)) } diff --git a/vendor/golang.org/x/net/html/render.go b/vendor/golang.org/x/net/html/render.go index 8b2803190..e8c123345 100644 --- a/vendor/golang.org/x/net/html/render.go +++ b/vendor/golang.org/x/net/html/render.go @@ -194,9 +194,8 @@ func render1(w writer, n *Node) error { } } - // Render any child nodes. - switch n.Data { - case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": + // Render any child nodes + if childTextNodesAreLiteral(n) { for c := n.FirstChild; c != nil; c = c.NextSibling { if c.Type == TextNode { if _, err := w.WriteString(c.Data); err != nil { @@ -213,7 +212,7 @@ func render1(w writer, n *Node) error { // last element in the file, with no closing tag. return plaintextAbort } - default: + } else { for c := n.FirstChild; c != nil; c = c.NextSibling { if err := render1(w, c); err != nil { return err @@ -231,6 +230,27 @@ func render1(w writer, n *Node) error { return w.WriteByte('>') } +func childTextNodesAreLiteral(n *Node) bool { + // Per WHATWG HTML 13.3, if the parent of the current node is a style, + // script, xmp, iframe, noembed, noframes, or plaintext element, and the + // current node is a text node, append the value of the node's data + // literally. The specification is not explicit about it, but we only + // enforce this if we are in the HTML namespace (i.e. when the namespace is + // ""). + // NOTE: we also always include noscript elements, although the + // specification states that they should only be rendered as such if + // scripting is enabled for the node (which is not something we track). + if n.Namespace != "" { + return false + } + switch n.Data { + case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": + return true + default: + return false + } +} + // writeQuoted writes s to w surrounded by quotes. Normally it will use double // quotes, but if s contains a double quote, it will use single quotes. // It is used for writing the identifiers in a doctype declaration. diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index e84f19dfa..58230038a 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -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, ":") diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index 632be722a..da1a27eea 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -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{ diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go new file mode 100644 index 000000000..c725d839b --- /dev/null +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -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 +} diff --git a/vendor/golang.org/x/tools/internal/event/tag/tag.go b/vendor/golang.org/x/tools/internal/event/tag/tag.go index ff2f2ecd3..581b26c20 100644 --- a/vendor/golang.org/x/tools/internal/event/tag/tag.go +++ b/vendor/golang.org/x/tools/internal/event/tag/tag.go @@ -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", "") diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 9930d8c36..6103dd710 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -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 @@ -313,8 +327,9 @@ type iexporter struct { out *bytes.Buffer version int - shallow bool // don't put types from other packages in the index - localpkg *types.Package // (nil in bundle mode) + 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 // the export data, so we can ensure to include them in the @@ -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...)) } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 94a5eba33..8e64cf644 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -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 + +// 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 +} -// GetPackageFromMap returns a GetPackageFunc that retrieves packages from the -// given map of package path -> package. +// GetPackagesFromMap returns a GetPackagesFunc that retrieves +// packages from the given map of package path to 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) +// 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 m[path] + 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() diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go index 8d9fc98d8..53cf66da0 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -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 { diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go index cfba8189f..d0d0649fe 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/common.go +++ b/vendor/golang.org/x/tools/internal/typeparams/common.go @@ -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) } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go index b4788978f..7ed86e171 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go @@ -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 } diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go index 114a36b86..cf301af1d 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go +++ b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go @@ -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() } diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index ce7d4351b..66e8b099b 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -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) diff --git a/vendor/modules.txt b/vendor/modules.txt index 5a338377a..ebefbaded 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,6 +8,10 @@ github.com/Microsoft/go-winio/pkg/guid # github.com/apparentlymart/go-cidr v1.1.0 ## explicit github.com/apparentlymart/go-cidr/cidr +# github.com/containers/winquit v1.1.0 +## explicit; go 1.19 +github.com/containers/winquit/pkg/winquit +github.com/containers/winquit/pkg/winquit/win32 # github.com/coreos/stream-metadata-go v0.4.3 ## explicit; go 1.18 github.com/coreos/stream-metadata-go/fedoracoreos @@ -142,10 +146,10 @@ golang.org/x/crypto/internal/poly1305 golang.org/x/crypto/ssh golang.org/x/crypto/ssh/internal/bcrypt_pbkdf golang.org/x/crypto/ssh/knownhosts -# golang.org/x/mod v0.10.0 +# golang.org/x/mod v0.12.0 ## explicit; go 1.17 golang.org/x/mod/semver -# golang.org/x/net v0.12.0 +# golang.org/x/net v0.14.0 ## explicit; go 1.17 golang.org/x/net/bpf golang.org/x/net/html @@ -189,12 +193,13 @@ golang.org/x/text/transform # golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 ## explicit golang.org/x/time/rate -# 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/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