diff --git a/Makefile b/Makefile index ddaeb1f..4a53c85 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,18 @@ CMD ?= raven GO := go GOFMT := go fmt +GOFIX := $(GO) fix GOVET := go vet GOIMPORTS := go tool goimports DEADCODE := go tool deadcode +STATICCHECK := go tool staticcheck GOVULNCHECK := go tool govulncheck BUILDFLAGS := -buildvcs=true PLATFORMS := linux/amd64 -.PHONY: all build build-all buildinfo check clean deadcode deps fmt help \ - imports install lint run test tidy tools uninstall verify vet \ +.PHONY: all build build-all buildinfo check clean deadcode deps fix fmt help \ + imports install lint run staticcheck test tidy tools uninstall verify \ vulncheck $(CMDS) ## Build all binaries (default target) @@ -66,8 +68,12 @@ buildinfo: build test: $(GO) test -cover -race ./... -## Run all linters (fmt, vet, imports, deadcode, vulncheck) -lint: fmt vet imports deadcode vulncheck +## Run all linters (fix, fmt, imports, vet, deadcode, vulncheck, staticcheck) +lint: fix fmt imports vet deadcode vulncheck staticcheck + +## Automatically fix code for newer Go versions +fix: + $(GOFIX) ./... ## Format code using go fmt fmt: @@ -89,6 +95,10 @@ deadcode: vulncheck: $(GOVULNCHECK) ./... +## Run staticcheck +staticcheck: + $(STATICCHECK) ./... + ## Run integration checks (alias for lint) check: lint @@ -108,6 +118,7 @@ verify: tools: go get -tool golang.org/x/tools/cmd/deadcode@latest go get -tool golang.org/x/tools/cmd/goimports@latest + go get -tool honnef.co/go/tools/cmd/staticcheck@latest go get -tool golang.org/x/vuln/cmd/govulncheck@latest ## Run the default binary (use ARGS="..." to pass arguments) diff --git a/go.mod b/go.mod index 41bf322..2124414 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,31 @@ module code.chimeric.al/chimerical/raven -go 1.25.5 +go 1.27 require ( - github.com/emersion/go-imap/v2 v2.0.0-beta.7 + github.com/emersion/go-imap/v2 v2.0.0-beta.8 github.com/emersion/go-message v0.18.2 - github.com/sashabaranov/go-openai v1.41.2 + github.com/sashabaranov/go-openai v1.42.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect - golang.org/x/mod v0.31.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc // indirect - golang.org/x/text v0.32.0 // indirect - golang.org/x/tools v0.40.0 // indirect - golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect - golang.org/x/vuln v1.1.4 // indirect + golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect + golang.org/x/mod v0.39.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/tools v0.49.0 // indirect + golang.org/x/vuln v1.7.0 // indirect + honnef.co/go/tools v0.8.0 // indirect ) tool ( golang.org/x/tools/cmd/deadcode golang.org/x/tools/cmd/goimports golang.org/x/vuln/cmd/govulncheck + honnef.co/go/tools/cmd/staticcheck ) diff --git a/go.sum b/go.sum index 7defae9..170f13a 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,28 @@ -github.com/emersion/go-imap/v2 v2.0.0-beta.7 h1:lNznYWa5uhMrngnSYEklzCeye4DBq9TEJ+pr0K593+8= -github.com/emersion/go-imap/v2 v2.0.0-beta.7/go.mod h1:BZTFHsS1hmgBkFlHqbxGLXk2hnRqTItUgwjSSCsYNAk= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/emersion/go-imap/v2 v2.0.0-beta.8 h1:5IXZK1E33DyeP526320J3RS7eFlCYGFgtbrfapqDPug= +github.com/emersion/go-imap/v2 v2.0.0-beta.8/go.mod h1:dhoFe2Q0PwLrMD7oZw8ODuaD0vLYPe5uj2wcOMnvh48= github.com/emersion/go-message v0.18.2 h1:rl55SQdjd9oJcIoQNhubD2Acs1E6IzlZISRTK7x/Lpg= github.com/emersion/go-message v0.18.2/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM= -github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/sashabaranov/go-openai v1.42.0 h1:fgeZx7/D8dRT//PwXAGe9ylOMtj6vrs999uWF71K+f8= +github.com/sashabaranov/go-openai v1.42.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= +golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= -golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= +golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -26,18 +30,18 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc h1:bH6xUXay0AIFMElXG2rQ4uiE+7ncwtiOdPfYK1NK2XA= -golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc/go.mod h1:hKdjCMrbv9skySur+Nek8Hd0uJ0GuxJIoIX2payrIdQ= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 h1:ZUSxONxc981v7AW7QUg+I9WwZzSTTJ019ENBYr5pV/Q= +golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5/go.mod h1:LVehoXe41cL5SCVQilsV7Gg6BNG+Js6P9PhSbYTIUkQ= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -46,22 +50,24 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= -golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= -golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY= -golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I= -golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s= +golang.org/x/vuln v1.7.0 h1:4MQBuhmXbz2uepNJrf3v+aaZLGDqw1JluwYboegA1qg= +golang.org/x/vuln v1.7.0/go.mod h1:Xw7zvU3e1bsCYYBXu+w4wcn2Kgn27f34WBCTw8LL5Us= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.8.0 h1:UacpzPr7D6i5BAjTkA7sNVcx4kIbhAZcQ4zYtKiXx68= +honnef.co/go/tools v0.8.0/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks= diff --git a/vendor/github.com/BurntSushi/toml/.gitignore b/vendor/github.com/BurntSushi/toml/.gitignore new file mode 100644 index 0000000..fe79e3a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/.gitignore @@ -0,0 +1,2 @@ +/toml.test +/toml-test diff --git a/vendor/github.com/BurntSushi/toml/COPYING b/vendor/github.com/BurntSushi/toml/COPYING new file mode 100644 index 0000000..01b5743 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/COPYING @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 TOML authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/BurntSushi/toml/README.md b/vendor/github.com/BurntSushi/toml/README.md new file mode 100644 index 0000000..639e6c3 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/README.md @@ -0,0 +1,120 @@ +TOML stands for Tom's Obvious, Minimal Language. This Go package provides a +reflection interface similar to Go's standard library `json` and `xml` packages. + +Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). + +Documentation: https://godocs.io/github.com/BurntSushi/toml + +See the [releases page](https://github.com/BurntSushi/toml/releases) for a +changelog; this information is also in the git tag annotations (e.g. `git show +v0.4.0`). + +This library requires Go 1.18 or newer; add it to your go.mod with: + + % go get github.com/BurntSushi/toml@latest + +It also comes with a TOML validator CLI tool: + + % go install github.com/BurntSushi/toml/cmd/tomlv@latest + % tomlv some-toml-file.toml + +### Examples +For the simplest example, consider some TOML file as just a list of keys and +values: + +```toml +Age = 25 +Cats = [ "Cauchy", "Plato" ] +Pi = 3.14 +Perfection = [ 6, 28, 496, 8128 ] +DOB = 1987-07-05T05:45:00Z +``` + +Which can be decoded with: + +```go +type Config struct { + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time +} + +var conf Config +_, err := toml.Decode(tomlData, &conf) +``` + +You can also use struct tags if your struct field name doesn't map to a TOML key +value directly: + +```toml +some_key_NAME = "wat" +``` + +```go +type TOML struct { + ObscureKey string `toml:"some_key_NAME"` +} +``` + +Beware that like other decoders **only exported fields** are considered when +encoding and decoding; private fields are silently ignored. + +### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces +Here's an example that automatically parses values in a `mail.Address`: + +```toml +contacts = [ + "Donald Duck ", + "Scrooge McDuck ", +] +``` + +Can be decoded with: + +```go +// Create address type which satisfies the encoding.TextUnmarshaler interface. +type address struct { + *mail.Address +} + +func (a *address) UnmarshalText(text []byte) error { + var err error + a.Address, err = mail.ParseAddress(string(text)) + return err +} + +// Decode it. +func decode() { + blob := ` + contacts = [ + "Donald Duck ", + "Scrooge McDuck ", + ] + ` + + var contacts struct { + Contacts []address + } + + _, err := toml.Decode(blob, &contacts) + if err != nil { + log.Fatal(err) + } + + for _, c := range contacts.Contacts { + fmt.Printf("%#v\n", c.Address) + } + + // Output: + // &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"} + // &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"} +} +``` + +To target TOML specifically you can implement `UnmarshalTOML` TOML interface in +a similar way. + +### More complex usage +See the [`_example/`](/_example) directory for a more complex example. diff --git a/vendor/github.com/BurntSushi/toml/decode.go b/vendor/github.com/BurntSushi/toml/decode.go new file mode 100644 index 0000000..c05a0b7 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/decode.go @@ -0,0 +1,615 @@ +package toml + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "io" + "io/fs" + "math" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +// Unmarshaler is the interface implemented by objects that can unmarshal a +// TOML description of themselves. +type Unmarshaler interface { + UnmarshalTOML(any) error +} + +// Unmarshal decodes the contents of data in TOML format into a pointer v. +// +// See [Decoder] for a description of the decoding process. +func Unmarshal(data []byte, v any) error { + _, err := NewDecoder(bytes.NewReader(data)).Decode(v) + return err +} + +// Decode the TOML data in to the pointer v. +// +// See [Decoder] for a description of the decoding process. +func Decode(data string, v any) (MetaData, error) { + return NewDecoder(strings.NewReader(data)).Decode(v) +} + +// DecodeFile reads the contents of a file and decodes it with [Decode]. +func DecodeFile(path string, v any) (MetaData, error) { + fp, err := os.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} + +// DecodeFS reads the contents of a file from [fs.FS] and decodes it with +// [Decode]. +func DecodeFS(fsys fs.FS, path string, v any) (MetaData, error) { + fp, err := fsys.Open(path) + if err != nil { + return MetaData{}, err + } + defer fp.Close() + return NewDecoder(fp).Decode(v) +} + +// Primitive is a TOML value that hasn't been decoded into a Go value. +// +// This type can be used for any value, which will cause decoding to be delayed. +// You can use [PrimitiveDecode] to "manually" decode these values. +// +// NOTE: The underlying representation of a `Primitive` value is subject to +// change. Do not rely on it. +// +// NOTE: Primitive values are still parsed, so using them will only avoid the +// overhead of reflection. They can be useful when you don't know the exact type +// of TOML data until runtime. +type Primitive struct { + undecoded any + context Key +} + +// The significand precision for float32 and float64 is 24 and 53 bits; this is +// the range a natural number can be stored in a float without loss of data. +const ( + maxSafeFloat32Int = 16777215 // 2^24-1 + maxSafeFloat64Int = int64(9007199254740991) // 2^53-1 +) + +// Decoder decodes TOML data. +// +// TOML tables correspond to Go structs or maps; they can be used +// interchangeably, but structs offer better type safety. +// +// TOML table arrays correspond to either a slice of structs or a slice of maps. +// +// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the +// local timezone. +// +// [time.Duration] types are treated as nanoseconds if the TOML value is an +// integer, or they're parsed with time.ParseDuration() if they're strings. +// +// All other TOML types (float, string, int, bool and array) correspond to the +// obvious Go types. +// +// An exception to the above rules is if a type implements the TextUnmarshaler +// interface, in which case any primitive TOML value (floats, strings, integers, +// booleans, datetimes) will be converted to a []byte and given to the value's +// UnmarshalText method. See the Unmarshaler example for a demonstration with +// email addresses. +// +// # Key mapping +// +// TOML keys can map to either keys in a Go map or field names in a Go struct. +// The special `toml` struct tag can be used to map TOML keys to struct fields +// that don't match the key name exactly (see the example). A case insensitive +// match to struct names will be tried if an exact match can't be found. +// +// The mapping between TOML values and Go values is loose. That is, there may +// exist TOML values that cannot be placed into your representation, and there +// may be parts of your representation that do not correspond to TOML values. +// This loose mapping can be made stricter by using the IsDefined and/or +// Undecoded methods on the MetaData returned. +// +// This decoder does not handle cyclic types. Decode will not terminate if a +// cyclic type is passed. +type Decoder struct { + r io.Reader +} + +// NewDecoder creates a new Decoder. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +var ( + unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem() + unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() + primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem() +) + +// Decode TOML data in to the pointer `v`. +func (dec *Decoder) Decode(v any) (MetaData, error) { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + s := "%q" + if reflect.TypeOf(v) == nil { + s = "%v" + } + + return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v)) + } + if rv.IsNil() { + return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v)) + } + + // Check if this is a supported type: struct, map, any, or something that + // implements UnmarshalTOML or UnmarshalText. + rv = indirect(rv) + rt := rv.Type() + if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map && + !(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) && + !rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) { + return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt) + } + + // TODO: parser should read from io.Reader? Or at the very least, make it + // read from []byte rather than string + data, err := io.ReadAll(dec.r) + if err != nil { + return MetaData{}, err + } + + p, err := parse(string(data)) + if err != nil { + return MetaData{}, err + } + + md := MetaData{ + mapping: p.mapping, + keyInfo: p.keyInfo, + keys: p.ordered, + decoded: make(map[string]struct{}, len(p.ordered)), + context: nil, + data: data, + } + return md, md.unify(p.mapping, rv) +} + +// PrimitiveDecode is just like the other Decode* functions, except it decodes a +// TOML value that has already been parsed. Valid primitive values can *only* be +// obtained from values filled by the decoder functions, including this method. +// (i.e., v may contain more [Primitive] values.) +// +// Meta data for primitive values is included in the meta data returned by the +// Decode* functions with one exception: keys returned by the Undecoded method +// will only reflect keys that were decoded. Namely, any keys hidden behind a +// Primitive will be considered undecoded. Executing this method will update the +// undecoded keys in the meta data. (See the example.) +func (md *MetaData) PrimitiveDecode(primValue Primitive, v any) error { + md.context = primValue.context + defer func() { md.context = nil }() + return md.unify(primValue.undecoded, rvalue(v)) +} + +// unify performs a sort of type unification based on the structure of `rv`, +// which is the client representation. +// +// Any type mismatch produces an error. Finding a type that we don't know +// how to handle produces an unsupported type error. +func (md *MetaData) unify(data any, rv reflect.Value) error { + // Special case. Look for a `Primitive` value. + // TODO: #76 would make this superfluous after implemented. + if rv.Type() == primitiveType { + // Save the undecoded data and the key context into the primitive + // value. + context := make(Key, len(md.context)) + copy(context, md.context) + rv.Set(reflect.ValueOf(Primitive{ + undecoded: data, + context: context, + })) + return nil + } + + rvi := rv.Interface() + if v, ok := rvi.(Unmarshaler); ok { + err := v.UnmarshalTOML(data) + if err != nil { + return md.parseErr(err) + } + return nil + } + if v, ok := rvi.(encoding.TextUnmarshaler); ok { + return md.unifyText(data, v) + } + + // TODO: + // The behavior here is incorrect whenever a Go type satisfies the + // encoding.TextUnmarshaler interface but also corresponds to a TOML hash or + // array. In particular, the unmarshaler should only be applied to primitive + // TOML values. But at this point, it will be applied to all kinds of values + // and produce an incorrect error whenever those values are hashes or arrays + // (including arrays of tables). + + k := rv.Kind() + + if k >= reflect.Int && k <= reflect.Uint64 { + return md.unifyInt(data, rv) + } + switch k { + case reflect.Struct: + return md.unifyStruct(data, rv) + case reflect.Map: + return md.unifyMap(data, rv) + case reflect.Array: + return md.unifyArray(data, rv) + case reflect.Slice: + return md.unifySlice(data, rv) + case reflect.String: + return md.unifyString(data, rv) + case reflect.Bool: + return md.unifyBool(data, rv) + case reflect.Interface: + if rv.NumMethod() > 0 { /// Only empty interfaces are supported. + return md.e("unsupported type %s", rv.Type()) + } + return md.unifyAnything(data, rv) + case reflect.Float32, reflect.Float64: + return md.unifyFloat64(data, rv) + } + return md.e("unsupported type %s", rv.Kind()) +} + +func (md *MetaData) unifyStruct(mapping any, rv reflect.Value) error { + tmap, ok := mapping.(map[string]any) + if !ok { + if mapping == nil { + return nil + } + return md.e("type mismatch for %s: expected table but found %s", rv.Type().String(), fmtType(mapping)) + } + + for key, datum := range tmap { + var f *field + fields := cachedTypeFields(rv.Type()) + for i := range fields { + ff := &fields[i] + if ff.name == key { + f = ff + break + } + if f == nil && strings.EqualFold(ff.name, key) { + f = ff + } + } + if f != nil { + subv := rv + for _, i := range f.index { + subv = indirect(subv.Field(i)) + } + + if isUnifiable(subv) { + md.decoded[md.context.add(key).String()] = struct{}{} + md.context = append(md.context, key) + + err := md.unify(datum, subv) + if err != nil { + return err + } + md.context = md.context[0 : len(md.context)-1] + } else if f.name != "" { + return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name) + } + } + } + return nil +} + +func (md *MetaData) unifyMap(mapping any, rv reflect.Value) error { + keyType := rv.Type().Key().Kind() + if keyType != reflect.String && keyType != reflect.Interface { + return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)", + keyType, rv.Type()) + } + + tmap, ok := mapping.(map[string]any) + if !ok { + if tmap == nil { + return nil + } + return md.badtype("map", mapping) + } + if rv.IsNil() { + rv.Set(reflect.MakeMap(rv.Type())) + } + for k, v := range tmap { + md.decoded[md.context.add(k).String()] = struct{}{} + md.context = append(md.context, k) + + rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) + + err := md.unify(v, indirect(rvval)) + if err != nil { + return err + } + md.context = md.context[0 : len(md.context)-1] + + rvkey := indirect(reflect.New(rv.Type().Key())) + + switch keyType { + case reflect.Interface: + rvkey.Set(reflect.ValueOf(k)) + case reflect.String: + rvkey.SetString(k) + } + + rv.SetMapIndex(rvkey, rvval) + } + return nil +} + +func (md *MetaData) unifyArray(data any, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + if !datav.IsValid() { + return nil + } + return md.badtype("slice", data) + } + if l := datav.Len(); l != rv.Len() { + return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l) + } + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySlice(data any, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + if !datav.IsValid() { + return nil + } + return md.badtype("slice", data) + } + n := datav.Len() + if rv.IsNil() || rv.Cap() < n { + rv.Set(reflect.MakeSlice(rv.Type(), n, n)) + } + rv.SetLen(n) + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { + l := data.Len() + for i := 0; i < l; i++ { + err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i))) + if err != nil { + return err + } + } + return nil +} + +func (md *MetaData) unifyString(data any, rv reflect.Value) error { + _, ok := rv.Interface().(json.Number) + if ok { + if i, ok := data.(int64); ok { + rv.SetString(strconv.FormatInt(i, 10)) + } else if f, ok := data.(float64); ok { + rv.SetString(strconv.FormatFloat(f, 'f', -1, 64)) + } else { + return md.badtype("string", data) + } + return nil + } + + if s, ok := data.(string); ok { + rv.SetString(s) + return nil + } + return md.badtype("string", data) +} + +func (md *MetaData) unifyFloat64(data any, rv reflect.Value) error { + rvk := rv.Kind() + + if num, ok := data.(float64); ok { + switch rvk { + case reflect.Float32: + if num < -math.MaxFloat32 || num > math.MaxFloat32 { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + fallthrough + case reflect.Float64: + rv.SetFloat(num) + default: + panic("bug") + } + return nil + } + + if num, ok := data.(int64); ok { + if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) || + (rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) { + return md.parseErr(errUnsafeFloat{i: num, size: rvk.String()}) + } + rv.SetFloat(float64(num)) + return nil + } + + return md.badtype("float", data) +} + +func (md *MetaData) unifyInt(data any, rv reflect.Value) error { + _, ok := rv.Interface().(time.Duration) + if ok { + // Parse as string duration, and fall back to regular integer parsing + // (as nanosecond) if this is not a string. + if s, ok := data.(string); ok { + dur, err := time.ParseDuration(s) + if err != nil { + return md.parseErr(errParseDuration{s}) + } + rv.SetInt(int64(dur)) + return nil + } + } + + num, ok := data.(int64) + if !ok { + return md.badtype("integer", data) + } + + rvk := rv.Kind() + switch { + case rvk >= reflect.Int && rvk <= reflect.Int64: + if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) || + (rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) || + (rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetInt(num) + case rvk >= reflect.Uint && rvk <= reflect.Uint64: + unum := uint64(num) + if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) || + rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) || + rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) { + return md.parseErr(errParseRange{i: num, size: rvk.String()}) + } + rv.SetUint(unum) + default: + panic("unreachable") + } + return nil +} + +func (md *MetaData) unifyBool(data any, rv reflect.Value) error { + if b, ok := data.(bool); ok { + rv.SetBool(b) + return nil + } + return md.badtype("boolean", data) +} + +func (md *MetaData) unifyAnything(data any, rv reflect.Value) error { + rv.Set(reflect.ValueOf(data)) + return nil +} + +func (md *MetaData) unifyText(data any, v encoding.TextUnmarshaler) error { + var s string + switch sdata := data.(type) { + case Marshaler: + text, err := sdata.MarshalTOML() + if err != nil { + return err + } + s = string(text) + case encoding.TextMarshaler: + text, err := sdata.MarshalText() + if err != nil { + return err + } + s = string(text) + case fmt.Stringer: + s = sdata.String() + case string: + s = sdata + case bool: + s = fmt.Sprintf("%v", sdata) + case int64: + s = fmt.Sprintf("%d", sdata) + case float64: + s = fmt.Sprintf("%f", sdata) + default: + return md.badtype("primitive (string-like)", data) + } + if err := v.UnmarshalText([]byte(s)); err != nil { + return md.parseErr(err) + } + return nil +} + +func (md *MetaData) badtype(dst string, data any) error { + return md.e("incompatible types: TOML value has type %s; destination has type %s", fmtType(data), dst) +} + +func (md *MetaData) parseErr(err error) error { + k := md.context.String() + d := string(md.data) + return ParseError{ + Message: err.Error(), + err: err, + LastKey: k, + Position: md.keyInfo[k].pos.withCol(d), + Line: md.keyInfo[k].pos.Line, + input: d, + } +} + +func (md *MetaData) e(format string, args ...any) error { + f := "toml: " + if len(md.context) > 0 { + f = fmt.Sprintf("toml: (last key %q): ", md.context) + p := md.keyInfo[md.context.String()].pos + if p.Line > 0 { + f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context) + } + } + return fmt.Errorf(f+format, args...) +} + +// rvalue returns a reflect.Value of `v`. All pointers are resolved. +func rvalue(v any) reflect.Value { + return indirect(reflect.ValueOf(v)) +} + +// indirect returns the value pointed to by a pointer. +// +// Pointers are followed until the value is not a pointer. New values are +// allocated for each nil pointer. +// +// An exception to this rule is if the value satisfies an interface of interest +// to us (like encoding.TextUnmarshaler). +func indirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr { + if v.CanSet() { + pv := v.Addr() + pvi := pv.Interface() + if _, ok := pvi.(encoding.TextUnmarshaler); ok { + return pv + } + if _, ok := pvi.(Unmarshaler); ok { + return pv + } + } + return v + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return indirect(reflect.Indirect(v)) +} + +func isUnifiable(rv reflect.Value) bool { + if rv.CanSet() { + return true + } + rvi := rv.Interface() + if _, ok := rvi.(encoding.TextUnmarshaler); ok { + return true + } + if _, ok := rvi.(Unmarshaler); ok { + return true + } + return false +} + +// fmt %T with "interface {}" replaced with "any", which is far more readable. +func fmtType(t any) string { + return strings.ReplaceAll(fmt.Sprintf("%T", t), "interface {}", "any") +} diff --git a/vendor/github.com/BurntSushi/toml/deprecated.go b/vendor/github.com/BurntSushi/toml/deprecated.go new file mode 100644 index 0000000..155709a --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/deprecated.go @@ -0,0 +1,29 @@ +package toml + +import ( + "encoding" + "io" +) + +// TextMarshaler is an alias for encoding.TextMarshaler. +// +// Deprecated: use encoding.TextMarshaler +type TextMarshaler encoding.TextMarshaler + +// TextUnmarshaler is an alias for encoding.TextUnmarshaler. +// +// Deprecated: use encoding.TextUnmarshaler +type TextUnmarshaler encoding.TextUnmarshaler + +// DecodeReader is an alias for NewDecoder(r).Decode(v). +// +// Deprecated: use NewDecoder(reader).Decode(&value). +func DecodeReader(r io.Reader, v any) (MetaData, error) { return NewDecoder(r).Decode(v) } + +// PrimitiveDecode is an alias for MetaData.PrimitiveDecode(). +// +// Deprecated: use MetaData.PrimitiveDecode. +func PrimitiveDecode(primValue Primitive, v any) error { + md := MetaData{decoded: make(map[string]struct{})} + return md.unify(primValue.undecoded, rvalue(v)) +} diff --git a/vendor/github.com/BurntSushi/toml/doc.go b/vendor/github.com/BurntSushi/toml/doc.go new file mode 100644 index 0000000..82c90a9 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/doc.go @@ -0,0 +1,8 @@ +// Package toml implements decoding and encoding of TOML files. +// +// This package supports TOML v1.0.0, as specified at https://toml.io +// +// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator, +// and can be used to verify if TOML document is valid. It can also be used to +// print the type of each key. +package toml diff --git a/vendor/github.com/BurntSushi/toml/encode.go b/vendor/github.com/BurntSushi/toml/encode.go new file mode 100644 index 0000000..73366c0 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/encode.go @@ -0,0 +1,778 @@ +package toml + +import ( + "bufio" + "bytes" + "encoding" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "reflect" + "sort" + "strconv" + "strings" + "time" + + "github.com/BurntSushi/toml/internal" +) + +type tomlEncodeError struct{ error } + +var ( + errArrayNilElement = errors.New("toml: cannot encode array with nil element") + errNonString = errors.New("toml: cannot encode a map with non-string key type") + errNoKey = errors.New("toml: top-level values must be Go maps or structs") + errAnything = errors.New("") // used in testing +) + +var dblQuotedReplacer = strings.NewReplacer( + "\"", "\\\"", + "\\", "\\\\", + "\x00", `\u0000`, + "\x01", `\u0001`, + "\x02", `\u0002`, + "\x03", `\u0003`, + "\x04", `\u0004`, + "\x05", `\u0005`, + "\x06", `\u0006`, + "\x07", `\u0007`, + "\b", `\b`, + "\t", `\t`, + "\n", `\n`, + "\x0b", `\u000b`, + "\f", `\f`, + "\r", `\r`, + "\x0e", `\u000e`, + "\x0f", `\u000f`, + "\x10", `\u0010`, + "\x11", `\u0011`, + "\x12", `\u0012`, + "\x13", `\u0013`, + "\x14", `\u0014`, + "\x15", `\u0015`, + "\x16", `\u0016`, + "\x17", `\u0017`, + "\x18", `\u0018`, + "\x19", `\u0019`, + "\x1a", `\u001a`, + "\x1b", `\u001b`, + "\x1c", `\u001c`, + "\x1d", `\u001d`, + "\x1e", `\u001e`, + "\x1f", `\u001f`, + "\x7f", `\u007f`, +) + +var ( + marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem() + marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() + timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +) + +// Marshaler is the interface implemented by types that can marshal themselves +// into valid TOML. +type Marshaler interface { + MarshalTOML() ([]byte, error) +} + +// Marshal returns a TOML representation of the Go value. +// +// See [Encoder] for a description of the encoding process. +func Marshal(v any) ([]byte, error) { + buff := new(bytes.Buffer) + if err := NewEncoder(buff).Encode(v); err != nil { + return nil, err + } + return buff.Bytes(), nil +} + +// Encoder encodes a Go to a TOML document. +// +// The mapping between Go values and TOML values should be precisely the same as +// for [Decode]. +// +// time.Time is encoded as a RFC 3339 string, and time.Duration as its string +// representation. +// +// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to +// encoding the value as custom TOML. +// +// If you want to write arbitrary binary data then you will need to use +// something like base64 since TOML does not have any binary types. +// +// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes +// are encoded first. +// +// Go maps will be sorted alphabetically by key for deterministic output. +// +// The toml struct tag can be used to provide the key name; if omitted the +// struct field name will be used. If the "omitempty" option is present the +// following value will be skipped: +// +// - arrays, slices, maps, and string with len of 0 +// - struct with all zero values +// - bool false +// +// If omitzero is given all int and float types with a value of 0 will be +// skipped. +// +// Encoding Go values without a corresponding TOML representation will return an +// error. Examples of this includes maps with non-string keys, slices with nil +// elements, embedded non-struct types, and nested slices containing maps or +// structs. (e.g. [][]map[string]string is not allowed but []map[string]string +// is okay, as is []map[string][]string). +// +// NOTE: only exported keys are encoded due to the use of reflection. Unexported +// keys are silently discarded. +type Encoder struct { + Indent string // string for a single indentation level; default is two spaces. + hasWritten bool // written any output to w yet? + w *bufio.Writer +} + +// NewEncoder create a new Encoder. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{w: bufio.NewWriter(w), Indent: " "} +} + +// Encode writes a TOML representation of the Go value to the [Encoder]'s writer. +// +// An error is returned if the value given cannot be encoded to a valid TOML +// document. +func (enc *Encoder) Encode(v any) error { + rv := eindirect(reflect.ValueOf(v)) + err := enc.safeEncode(Key([]string{}), rv) + if err != nil { + return err + } + return enc.w.Flush() +} + +func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { + defer func() { + if r := recover(); r != nil { + if terr, ok := r.(tomlEncodeError); ok { + err = terr.error + return + } + panic(r) + } + }() + enc.encode(key, rv) + return nil +} + +func (enc *Encoder) encode(key Key, rv reflect.Value) { + // If we can marshal the type to text, then we use that. This prevents the + // encoder for handling these types as generic structs (or whatever the + // underlying type of a TextMarshaler is). + switch { + case isMarshaler(rv): + enc.writeKeyValue(key, rv, false) + return + case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented. + enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded)) + return + } + + k := rv.Kind() + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, + reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: + enc.writeKeyValue(key, rv, false) + case reflect.Array, reflect.Slice: + if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { + enc.eArrayOfTables(key, rv) + } else { + enc.writeKeyValue(key, rv, false) + } + case reflect.Interface: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Map: + if rv.IsNil() { + return + } + enc.eTable(key, rv) + case reflect.Ptr: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Struct: + enc.eTable(key, rv) + default: + encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k)) + } +} + +// eElement encodes any value that can be an array element. +func (enc *Encoder) eElement(rv reflect.Value) { + switch v := rv.Interface().(type) { + case time.Time: // Using TextMarshaler adds extra quotes, which we don't want. + format := time.RFC3339Nano + switch v.Location() { + case internal.LocalDatetime: + format = "2006-01-02T15:04:05.999999999" + case internal.LocalDate: + format = "2006-01-02" + case internal.LocalTime: + format = "15:04:05.999999999" + } + switch v.Location() { + default: + enc.wf(v.Format(format)) + case internal.LocalDatetime, internal.LocalDate, internal.LocalTime: + enc.wf(v.In(time.UTC).Format(format)) + } + return + case Marshaler: + s, err := v.MarshalTOML() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalTOML returned nil and no error")) + } + enc.w.Write(s) + return + case encoding.TextMarshaler: + s, err := v.MarshalText() + if err != nil { + encPanic(err) + } + if s == nil { + encPanic(errors.New("MarshalText returned nil and no error")) + } + enc.writeQuoted(string(s)) + return + case time.Duration: + enc.writeQuoted(v.String()) + return + case json.Number: + n, _ := rv.Interface().(json.Number) + + if n == "" { /// Useful zero value. + enc.w.WriteByte('0') + return + } else if v, err := n.Int64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } else if v, err := n.Float64(); err == nil { + enc.eElement(reflect.ValueOf(v)) + return + } + encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n)) + } + + switch rv.Kind() { + case reflect.Ptr: + enc.eElement(rv.Elem()) + return + case reflect.String: + enc.writeQuoted(rv.String()) + case reflect.Bool: + enc.wf(strconv.FormatBool(rv.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + enc.wf(strconv.FormatInt(rv.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + enc.wf(strconv.FormatUint(rv.Uint(), 10)) + case reflect.Float32: + f := rv.Float() + if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("nan") + } else if math.IsInf(f, 0) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32))) + } + case reflect.Float64: + f := rv.Float() + if math.IsNaN(f) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("nan") + } else if math.IsInf(f, 0) { + if math.Signbit(f) { + enc.wf("-") + } + enc.wf("inf") + } else { + enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64))) + } + case reflect.Array, reflect.Slice: + enc.eArrayOrSliceElement(rv) + case reflect.Struct: + enc.eStruct(nil, rv, true) + case reflect.Map: + enc.eMap(nil, rv, true) + case reflect.Interface: + enc.eElement(rv.Elem()) + default: + encPanic(fmt.Errorf("unexpected type: %s", fmtType(rv.Interface()))) + } +} + +// By the TOML spec, all floats must have a decimal with at least one number on +// either side. +func floatAddDecimal(fstr string) string { + if !strings.Contains(fstr, ".") { + return fstr + ".0" + } + return fstr +} + +func (enc *Encoder) writeQuoted(s string) { + enc.wf("\"%s\"", dblQuotedReplacer.Replace(s)) +} + +func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { + length := rv.Len() + enc.wf("[") + for i := 0; i < length; i++ { + elem := eindirect(rv.Index(i)) + enc.eElement(elem) + if i != length-1 { + enc.wf(", ") + } + } + enc.wf("]") +} + +func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { + if len(key) == 0 { + encPanic(errNoKey) + } + for i := 0; i < rv.Len(); i++ { + trv := eindirect(rv.Index(i)) + if isNil(trv) { + continue + } + enc.newline() + enc.wf("%s[[%s]]", enc.indentStr(key), key) + enc.newline() + enc.eMapOrStruct(key, trv, false) + } +} + +func (enc *Encoder) eTable(key Key, rv reflect.Value) { + if len(key) == 1 { + // Output an extra newline between top-level tables. + // (The newline isn't written if nothing else has been written though.) + enc.newline() + } + if len(key) > 0 { + enc.wf("%s[%s]", enc.indentStr(key), key) + enc.newline() + } + enc.eMapOrStruct(key, rv, false) +} + +func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) { + switch rv.Kind() { + case reflect.Map: + enc.eMap(key, rv, inline) + case reflect.Struct: + enc.eStruct(key, rv, inline) + default: + // Should never happen? + panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) + } +} + +func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) { + rt := rv.Type() + if rt.Key().Kind() != reflect.String { + encPanic(errNonString) + } + + // Sort keys so that we have deterministic output. And write keys directly + // underneath this key first, before writing sub-structs or sub-maps. + var mapKeysDirect, mapKeysSub []string + for _, mapKey := range rv.MapKeys() { + k := mapKey.String() + if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) { + mapKeysSub = append(mapKeysSub, k) + } else { + mapKeysDirect = append(mapKeysDirect, k) + } + } + + var writeMapKeys = func(mapKeys []string, trailC bool) { + sort.Strings(mapKeys) + for i, mapKey := range mapKeys { + val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey))) + if isNil(val) { + continue + } + + if inline { + enc.writeKeyValue(Key{mapKey}, val, true) + if trailC || i != len(mapKeys)-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(mapKey), val) + } + } + } + + if inline { + enc.wf("{") + } + writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0) + writeMapKeys(mapKeysSub, false) + if inline { + enc.wf("}") + } +} + +const is32Bit = (32 << (^uint(0) >> 63)) == 32 + +func pointerTo(t reflect.Type) reflect.Type { + if t.Kind() == reflect.Ptr { + return pointerTo(t.Elem()) + } + return t +} + +func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { + // Write keys for fields directly under this key first, because if we write + // a field that creates a new table then all keys under it will be in that + // table (not the one we're writing here). + // + // Fields is a [][]int: for fieldsDirect this always has one entry (the + // struct index). For fieldsSub it contains two entries: the parent field + // index from tv, and the field indexes for the fields of the sub. + var ( + rt = rv.Type() + fieldsDirect, fieldsSub [][]int + addFields func(rt reflect.Type, rv reflect.Value, start []int) + ) + addFields = func(rt reflect.Type, rv reflect.Value, start []int) { + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct + if f.PkgPath != "" && !isEmbed { /// Skip unexported fields. + continue + } + opts := getOptions(f.Tag) + if opts.skip { + continue + } + + frv := eindirect(rv.Field(i)) + + if is32Bit { + // Copy so it works correct on 32bit archs; not clear why this + // is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4 + // This also works fine on 64bit, but 32bit archs are somewhat + // rare and this is a wee bit faster. + copyStart := make([]int, len(start)) + copy(copyStart, start) + start = copyStart + } + + // Treat anonymous struct fields with tag names as though they are + // not anonymous, like encoding/json does. + // + // Non-struct anonymous fields use the normal encoding logic. + if isEmbed { + if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct { + addFields(frv.Type(), frv, append(start, f.Index...)) + continue + } + } + + if typeIsTable(tomlTypeOfGo(frv)) { + fieldsSub = append(fieldsSub, append(start, f.Index...)) + } else { + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + } + } + } + addFields(rt, rv, nil) + + writeFields := func(fields [][]int) { + for _, fieldIndex := range fields { + fieldType := rt.FieldByIndex(fieldIndex) + fieldVal := rv.FieldByIndex(fieldIndex) + + opts := getOptions(fieldType.Tag) + if opts.skip { + continue + } + if opts.omitempty && isEmpty(fieldVal) { + continue + } + + fieldVal = eindirect(fieldVal) + + if isNil(fieldVal) { /// Don't write anything for nil fields. + continue + } + + keyName := fieldType.Name + if opts.name != "" { + keyName = opts.name + } + + if opts.omitzero && isZero(fieldVal) { + continue + } + + if inline { + enc.writeKeyValue(Key{keyName}, fieldVal, true) + if fieldIndex[0] != len(fields)-1 { + enc.wf(", ") + } + } else { + enc.encode(key.add(keyName), fieldVal) + } + } + } + + if inline { + enc.wf("{") + } + writeFields(fieldsDirect) + writeFields(fieldsSub) + if inline { + enc.wf("}") + } +} + +// tomlTypeOfGo returns the TOML type name of the Go value's type. +// +// It is used to determine whether the types of array elements are mixed (which +// is forbidden). If the Go value is nil, then it is illegal for it to be an +// array element, and valueIsNil is returned as true. +// +// The type may be `nil`, which means no concrete TOML type could be found. +func tomlTypeOfGo(rv reflect.Value) tomlType { + if isNil(rv) || !rv.IsValid() { + return nil + } + + if rv.Kind() == reflect.Struct { + if rv.Type() == timeType { + return tomlDatetime + } + if isMarshaler(rv) { + return tomlString + } + return tomlHash + } + + if isMarshaler(rv) { + return tomlString + } + + switch rv.Kind() { + case reflect.Bool: + return tomlBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + return tomlInteger + case reflect.Float32, reflect.Float64: + return tomlFloat + case reflect.Array, reflect.Slice: + if isTableArray(rv) { + return tomlArrayHash + } + return tomlArray + case reflect.Ptr, reflect.Interface: + return tomlTypeOfGo(rv.Elem()) + case reflect.String: + return tomlString + case reflect.Map: + return tomlHash + default: + encPanic(errors.New("unsupported type: " + rv.Kind().String())) + panic("unreachable") + } +} + +func isMarshaler(rv reflect.Value) bool { + return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml) +} + +// isTableArray reports if all entries in the array or slice are a table. +func isTableArray(arr reflect.Value) bool { + if isNil(arr) || !arr.IsValid() || arr.Len() == 0 { + return false + } + + ret := true + for i := 0; i < arr.Len(); i++ { + tt := tomlTypeOfGo(eindirect(arr.Index(i))) + // Don't allow nil. + if tt == nil { + encPanic(errArrayNilElement) + } + + if ret && !typeEqual(tomlHash, tt) { + ret = false + } + } + return ret +} + +type tagOptions struct { + skip bool // "-" + name string + omitempty bool + omitzero bool +} + +func getOptions(tag reflect.StructTag) tagOptions { + t := tag.Get("toml") + if t == "-" { + return tagOptions{skip: true} + } + var opts tagOptions + parts := strings.Split(t, ",") + opts.name = parts[0] + for _, s := range parts[1:] { + switch s { + case "omitempty": + opts.omitempty = true + case "omitzero": + opts.omitzero = true + } + } + return opts +} + +func isZero(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return rv.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return rv.Uint() == 0 + case reflect.Float32, reflect.Float64: + return rv.Float() == 0.0 + } + return false +} + +func isEmpty(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return rv.Len() == 0 + case reflect.Struct: + if rv.Type().Comparable() { + return reflect.Zero(rv.Type()).Interface() == rv.Interface() + } + // Need to also check if all the fields are empty, otherwise something + // like this with uncomparable types will always return true: + // + // type a struct{ field b } + // type b struct{ s []string } + // s := a{field: b{s: []string{"AAA"}}} + for i := 0; i < rv.NumField(); i++ { + if !isEmpty(rv.Field(i)) { + return false + } + } + return true + case reflect.Bool: + return !rv.Bool() + case reflect.Ptr: + return rv.IsNil() + } + return false +} + +func (enc *Encoder) newline() { + if enc.hasWritten { + enc.wf("\n") + } +} + +// Write a key/value pair: +// +// key = +// +// This is also used for "k = v" in inline tables; so something like this will +// be written in three calls: +// +// ┌───────────────────┐ +// │ ┌───┐ ┌────┐│ +// v v v v vv +// key = {k = 1, k2 = 2} +func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) { + /// Marshaler used on top-level document; call eElement() to just call + /// Marshal{TOML,Text}. + if len(key) == 0 { + enc.eElement(val) + return + } + enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) + enc.eElement(val) + if !inline { + enc.newline() + } +} + +func (enc *Encoder) wf(format string, v ...any) { + _, err := fmt.Fprintf(enc.w, format, v...) + if err != nil { + encPanic(err) + } + enc.hasWritten = true +} + +func (enc *Encoder) indentStr(key Key) string { + return strings.Repeat(enc.Indent, len(key)-1) +} + +func encPanic(err error) { + panic(tomlEncodeError{err}) +} + +// Resolve any level of pointers to the actual value (e.g. **string → string). +func eindirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface { + if isMarshaler(v) { + return v + } + if v.CanAddr() { /// Special case for marshalers; see #358. + if pv := v.Addr(); isMarshaler(pv) { + return pv + } + } + return v + } + + if v.IsNil() { + return v + } + + return eindirect(v.Elem()) +} + +func isNil(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return rv.IsNil() + default: + return false + } +} diff --git a/vendor/github.com/BurntSushi/toml/error.go b/vendor/github.com/BurntSushi/toml/error.go new file mode 100644 index 0000000..1dd5232 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/error.go @@ -0,0 +1,347 @@ +package toml + +import ( + "fmt" + "strings" +) + +// ParseError is returned when there is an error parsing the TOML syntax such as +// invalid syntax, duplicate keys, etc. +// +// In addition to the error message itself, you can also print detailed location +// information with context by using [ErrorWithPosition]: +// +// toml: error: Key 'fruit' was already created and cannot be used as an array. +// +// At line 4, column 2-7: +// +// 2 | fruit = [] +// 3 | +// 4 | [[fruit]] # Not allowed +// ^^^^^ +// +// [ErrorWithUsage] can be used to print the above with some more detailed usage +// guidance: +// +// toml: error: newlines not allowed within inline tables +// +// At line 1, column 18: +// +// 1 | x = [{ key = 42 # +// ^ +// +// Error help: +// +// Inline tables must always be on a single line: +// +// table = {key = 42, second = 43} +// +// It is invalid to split them over multiple lines like so: +// +// # INVALID +// table = { +// key = 42, +// second = 43 +// } +// +// Use regular for this: +// +// [table] +// key = 42 +// second = 43 +type ParseError struct { + Message string // Short technical message. + Usage string // Longer message with usage guidance; may be blank. + Position Position // Position of the error + LastKey string // Last parsed key, may be blank. + + // Line the error occurred. + // + // Deprecated: use [Position]. + Line int + + err error + input string +} + +// Position of an error. +type Position struct { + Line int // Line number, starting at 1. + Col int // Error column, starting at 1. + Start int // Start of error, as byte offset starting at 0. + Len int // Lenght of the error in bytes. +} + +func (p Position) withCol(tomlFile string) Position { + var ( + pos int + lines = strings.Split(tomlFile, "\n") + ) + for i := range lines { + ll := len(lines[i]) + 1 // +1 for the removed newline + if pos+ll >= p.Start { + p.Col = p.Start - pos + 1 + if p.Col < 1 { // Should never happen, but just in case. + p.Col = 1 + } + break + } + pos += ll + } + return p +} + +func (pe ParseError) Error() string { + if pe.LastKey == "" { + return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, pe.Message) + } + return fmt.Sprintf("toml: line %d (last key %q): %s", + pe.Position.Line, pe.LastKey, pe.Message) +} + +// ErrorWithPosition returns the error with detailed location context. +// +// See the documentation on [ParseError]. +func (pe ParseError) ErrorWithPosition() string { + if pe.input == "" { // Should never happen, but just in case. + return pe.Error() + } + + // TODO: don't show control characters as literals? This may not show up + // well everywhere. + + var ( + lines = strings.Split(pe.input, "\n") + b = new(strings.Builder) + ) + if pe.Position.Len == 1 { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n", + pe.Message, pe.Position.Line, pe.Position.Col) + } else { + fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n", + pe.Message, pe.Position.Line, pe.Position.Col, pe.Position.Col+pe.Position.Len-1) + } + if pe.Position.Line > 2 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, expandTab(lines[pe.Position.Line-3])) + } + if pe.Position.Line > 1 { + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, expandTab(lines[pe.Position.Line-2])) + } + + /// Expand tabs, so that the ^^^s are at the correct position, but leave + /// "column 10-13" intact. Adjusting this to the visual column would be + /// better, but we don't know the tabsize of the user in their editor, which + /// can be 8, 4, 2, or something else. We can't know. So leaving it as the + /// character index is probably the "most correct". + expanded := expandTab(lines[pe.Position.Line-1]) + diff := len(expanded) - len(lines[pe.Position.Line-1]) + + fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, expanded) + fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", pe.Position.Col-1+diff), strings.Repeat("^", pe.Position.Len)) + return b.String() +} + +// ErrorWithUsage returns the error with detailed location context and usage +// guidance. +// +// See the documentation on [ParseError]. +func (pe ParseError) ErrorWithUsage() string { + m := pe.ErrorWithPosition() + if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" { + lines := strings.Split(strings.TrimSpace(u.Usage()), "\n") + for i := range lines { + if lines[i] != "" { + lines[i] = " " + lines[i] + } + } + return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n" + } + return m +} + +func expandTab(s string) string { + var ( + b strings.Builder + l int + fill = func(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = ' ' + } + return string(b) + } + ) + b.Grow(len(s)) + for _, r := range s { + switch r { + case '\t': + tw := 8 - l%8 + b.WriteString(fill(tw)) + l += tw + default: + b.WriteRune(r) + l += 1 + } + } + return b.String() +} + +type ( + errLexControl struct{ r rune } + errLexEscape struct{ r rune } + errLexUTF8 struct{ b byte } + errParseDate struct{ v string } + errLexInlineTableNL struct{} + errLexStringNL struct{} + errParseRange struct { + i any // int or float + size string // "int64", "uint16", etc. + } + errUnsafeFloat struct { + i interface{} // float32 or float64 + size string // "float32" or "float64" + } + errParseDuration struct{ d string } +) + +func (e errLexControl) Error() string { + return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r) +} +func (e errLexControl) Usage() string { return "" } + +func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) } +func (e errLexEscape) Usage() string { return usageEscape } +func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) } +func (e errLexUTF8) Usage() string { return "" } +func (e errParseDate) Error() string { return fmt.Sprintf("invalid datetime: %q", e.v) } +func (e errParseDate) Usage() string { return usageDate } +func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" } +func (e errLexInlineTableNL) Usage() string { return usageInlineNewline } +func (e errLexStringNL) Error() string { return "strings cannot contain newlines" } +func (e errLexStringNL) Usage() string { return usageStringNewline } +func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) } +func (e errParseRange) Usage() string { return usageIntOverflow } +func (e errUnsafeFloat) Error() string { + return fmt.Sprintf("%v is out of the safe %s range", e.i, e.size) +} +func (e errUnsafeFloat) Usage() string { return usageUnsafeFloat } +func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) } +func (e errParseDuration) Usage() string { return usageDuration } + +const usageEscape = ` +A '\' inside a "-delimited string is interpreted as an escape character. + +The following escape sequences are supported: +\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX + +To prevent a '\' from being recognized as an escape character, use either: + +- a ' or '''-delimited string; escape characters aren't processed in them; or +- write two backslashes to get a single backslash: '\\'. + +If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/' +instead of '\' will usually also work: "C:/Users/martin". +` + +const usageInlineNewline = ` +Inline tables must always be on a single line: + + table = {key = 42, second = 43} + +It is invalid to split them over multiple lines like so: + + # INVALID + table = { + key = 42, + second = 43 + } + +Use regular for this: + + [table] + key = 42 + second = 43 +` + +const usageStringNewline = ` +Strings must always be on a single line, and cannot span more than one line: + + # INVALID + string = "Hello, + world!" + +Instead use """ or ''' to split strings over multiple lines: + + string = """Hello, + world!""" +` + +const usageIntOverflow = ` +This number is too large; this may be an error in the TOML, but it can also be a +bug in the program that uses too small of an integer. + +The maximum and minimum values are: + + size │ lowest │ highest + ───────┼────────────────┼────────────── + int8 │ -128 │ 127 + int16 │ -32,768 │ 32,767 + int32 │ -2,147,483,648 │ 2,147,483,647 + int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷ + uint8 │ 0 │ 255 + uint16 │ 0 │ 65,535 + uint32 │ 0 │ 4,294,967,295 + uint64 │ 0 │ 1.8 × 10¹⁸ + +int refers to int32 on 32-bit systems and int64 on 64-bit systems. +` + +const usageUnsafeFloat = ` +This number is outside of the "safe" range for floating point numbers; whole +(non-fractional) numbers outside the below range can not always be represented +accurately in a float, leading to some loss of accuracy. + +Explicitly mark a number as a fractional unit by adding ".0", which will incur +some loss of accuracy; for example: + + f = 2_000_000_000.0 + +Accuracy ranges: + + float32 = 16,777,215 + float64 = 9,007,199,254,740,991 +` + +const usageDuration = ` +A duration must be as "number", without any spaces. Valid units are: + + ns nanoseconds (billionth of a second) + us, µs microseconds (millionth of a second) + ms milliseconds (thousands of a second) + s seconds + m minutes + h hours + +You can combine multiple units; for example "5m10s" for 5 minutes and 10 +seconds. +` + +const usageDate = ` +A TOML datetime must be in one of the following formats: + + 2006-01-02T15:04:05Z07:00 Date and time, with timezone. + 2006-01-02T15:04:05 Date and time, but without timezone. + 2006-01-02 Date without a time or timezone. + 15:04:05 Just a time, without any timezone. + +Seconds may optionally have a fraction, up to nanosecond precision: + + 15:04:05.123 + 15:04:05.856018510 +` + +// TOML 1.1: +// The seconds part in times is optional, and may be omitted: +// 2006-01-02T15:04Z07:00 +// 2006-01-02T15:04 +// 15:04 diff --git a/vendor/github.com/BurntSushi/toml/internal/tz.go b/vendor/github.com/BurntSushi/toml/internal/tz.go new file mode 100644 index 0000000..022f15b --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/internal/tz.go @@ -0,0 +1,36 @@ +package internal + +import "time" + +// Timezones used for local datetime, date, and time TOML types. +// +// The exact way times and dates without a timezone should be interpreted is not +// well-defined in the TOML specification and left to the implementation. These +// defaults to current local timezone offset of the computer, but this can be +// changed by changing these variables before decoding. +// +// TODO: +// Ideally we'd like to offer people the ability to configure the used timezone +// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit +// tricky: the reason we use three different variables for this is to support +// round-tripping – without these specific TZ names we wouldn't know which +// format to use. +// +// There isn't a good way to encode this right now though, and passing this sort +// of information also ties in to various related issues such as string format +// encoding, encoding of comments, etc. +// +// So, for the time being, just put this in internal until we can write a good +// comprehensive API for doing all of this. +// +// The reason they're exported is because they're referred from in e.g. +// internal/tag. +// +// Note that this behaviour is valid according to the TOML spec as the exact +// behaviour is left up to implementations. +var ( + localOffset = func() int { _, o := time.Now().Zone(); return o }() + LocalDatetime = time.FixedZone("datetime-local", localOffset) + LocalDate = time.FixedZone("date-local", localOffset) + LocalTime = time.FixedZone("time-local", localOffset) +) diff --git a/vendor/github.com/BurntSushi/toml/lex.go b/vendor/github.com/BurntSushi/toml/lex.go new file mode 100644 index 0000000..6878d9d --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/lex.go @@ -0,0 +1,1287 @@ +package toml + +import ( + "fmt" + "reflect" + "runtime" + "strings" + "unicode" + "unicode/utf8" +) + +type itemType int + +const ( + itemError itemType = iota + itemNIL // used in the parser to indicate no type + itemEOF + itemText + itemString + itemStringEsc + itemRawString + itemMultilineString + itemRawMultilineString + itemBool + itemInteger + itemFloat + itemDatetime + itemArray // the start of an array + itemArrayEnd + itemTableStart + itemTableEnd + itemArrayTableStart + itemArrayTableEnd + itemKeyStart + itemKeyEnd + itemCommentStart + itemInlineTableStart + itemInlineTableEnd +) + +const eof = 0 + +type stateFn func(lx *lexer) stateFn + +func (p Position) String() string { + return fmt.Sprintf("at line %d; start %d; length %d", p.Line, p.Start, p.Len) +} + +type lexer struct { + input string + start int + pos int + line int + state stateFn + items chan item + tomlNext bool + esc bool + + // Allow for backing up up to 4 runes. This is necessary because TOML + // contains 3-rune tokens (""" and '''). + prevWidths [4]int + nprev int // how many of prevWidths are in use + atEOF bool // If we emit an eof, we can still back up, but it is not OK to call next again. + + // A stack of state functions used to maintain context. + // + // The idea is to reuse parts of the state machine in various places. For + // example, values can appear at the top level or within arbitrarily nested + // arrays. The last state on the stack is used after a value has been lexed. + // Similarly for comments. + stack []stateFn +} + +type item struct { + typ itemType + val string + err error + pos Position +} + +func (lx *lexer) nextItem() item { + for { + select { + case item := <-lx.items: + return item + default: + lx.state = lx.state(lx) + //fmt.Printf(" STATE %-24s current: %-10s stack: %s\n", lx.state, lx.current(), lx.stack) + } + } +} + +func lex(input string, tomlNext bool) *lexer { + lx := &lexer{ + input: input, + state: lexTop, + items: make(chan item, 10), + stack: make([]stateFn, 0, 10), + line: 1, + tomlNext: tomlNext, + } + return lx +} + +func (lx *lexer) push(state stateFn) { + lx.stack = append(lx.stack, state) +} + +func (lx *lexer) pop() stateFn { + if len(lx.stack) == 0 { + return lx.errorf("BUG in lexer: no states to pop") + } + last := lx.stack[len(lx.stack)-1] + lx.stack = lx.stack[0 : len(lx.stack)-1] + return last +} + +func (lx *lexer) current() string { + return lx.input[lx.start:lx.pos] +} + +func (lx lexer) getPos() Position { + p := Position{ + Line: lx.line, + Start: lx.start, + Len: lx.pos - lx.start, + } + if p.Len <= 0 { + p.Len = 1 + } + return p +} + +func (lx *lexer) emit(typ itemType) { + // Needed for multiline strings ending with an incomplete UTF-8 sequence. + if lx.start > lx.pos { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return + } + lx.items <- item{typ: typ, pos: lx.getPos(), val: lx.current()} + lx.start = lx.pos +} + +func (lx *lexer) emitTrim(typ itemType) { + lx.items <- item{typ: typ, pos: lx.getPos(), val: strings.TrimSpace(lx.current())} + lx.start = lx.pos +} + +func (lx *lexer) next() (r rune) { + if lx.atEOF { + panic("BUG in lexer: next called after EOF") + } + if lx.pos >= len(lx.input) { + lx.atEOF = true + return eof + } + + if lx.input[lx.pos] == '\n' { + lx.line++ + } + lx.prevWidths[3] = lx.prevWidths[2] + lx.prevWidths[2] = lx.prevWidths[1] + lx.prevWidths[1] = lx.prevWidths[0] + if lx.nprev < 4 { + lx.nprev++ + } + + r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) + if r == utf8.RuneError && w == 1 { + lx.error(errLexUTF8{lx.input[lx.pos]}) + return utf8.RuneError + } + + // Note: don't use peek() here, as this calls next(). + if isControl(r) || (r == '\r' && (len(lx.input)-1 == lx.pos || lx.input[lx.pos+1] != '\n')) { + lx.errorControlChar(r) + return utf8.RuneError + } + + lx.prevWidths[0] = w + lx.pos += w + return r +} + +// ignore skips over the pending input before this point. +func (lx *lexer) ignore() { + lx.start = lx.pos +} + +// backup steps back one rune. Can be called 4 times between calls to next. +func (lx *lexer) backup() { + if lx.atEOF { + lx.atEOF = false + return + } + if lx.nprev < 1 { + panic("BUG in lexer: backed up too far") + } + w := lx.prevWidths[0] + lx.prevWidths[0] = lx.prevWidths[1] + lx.prevWidths[1] = lx.prevWidths[2] + lx.prevWidths[2] = lx.prevWidths[3] + lx.nprev-- + + lx.pos -= w + if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { + lx.line-- + } +} + +// accept consumes the next rune if it's equal to `valid`. +func (lx *lexer) accept(valid rune) bool { + if lx.next() == valid { + return true + } + lx.backup() + return false +} + +// peek returns but does not consume the next rune in the input. +func (lx *lexer) peek() rune { + r := lx.next() + lx.backup() + return r +} + +// skip ignores all input that matches the given predicate. +func (lx *lexer) skip(pred func(rune) bool) { + for { + r := lx.next() + if pred(r) { + continue + } + lx.backup() + lx.ignore() + return + } +} + +// error stops all lexing by emitting an error and returning `nil`. +// +// Note that any value that is a character is escaped if it's a special +// character (newlines, tabs, etc.). +func (lx *lexer) error(err error) stateFn { + if lx.atEOF { + return lx.errorPrevLine(err) + } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: err} + return nil +} + +// errorfPrevline is like error(), but sets the position to the last column of +// the previous line. +// +// This is so that unexpected EOF or NL errors don't show on a new blank line. +func (lx *lexer) errorPrevLine(err error) stateFn { + pos := lx.getPos() + pos.Line-- + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorPos is like error(), but allows explicitly setting the position. +func (lx *lexer) errorPos(start, length int, err error) stateFn { + pos := lx.getPos() + pos.Start = start + pos.Len = length + lx.items <- item{typ: itemError, pos: pos, err: err} + return nil +} + +// errorf is like error, and creates a new error. +func (lx *lexer) errorf(format string, values ...any) stateFn { + if lx.atEOF { + pos := lx.getPos() + pos.Line-- + pos.Len = 1 + pos.Start = lx.pos - 1 + lx.items <- item{typ: itemError, pos: pos, err: fmt.Errorf(format, values...)} + return nil + } + lx.items <- item{typ: itemError, pos: lx.getPos(), err: fmt.Errorf(format, values...)} + return nil +} + +func (lx *lexer) errorControlChar(cc rune) stateFn { + return lx.errorPos(lx.pos-1, 1, errLexControl{cc}) +} + +// lexTop consumes elements at the top level of TOML data. +func lexTop(lx *lexer) stateFn { + r := lx.next() + if isWhitespace(r) || isNL(r) { + return lexSkip(lx, lexTop) + } + switch r { + case '#': + lx.push(lexTop) + return lexCommentStart + case '[': + return lexTableStart + case eof: + if lx.pos > lx.start { + return lx.errorf("unexpected EOF") + } + lx.emit(itemEOF) + return nil + } + + // At this point, the only valid item can be a key, so we back up + // and let the key lexer do the rest. + lx.backup() + lx.push(lexTopEnd) + return lexKeyStart +} + +// lexTopEnd is entered whenever a top-level item has been consumed. (A value +// or a table.) It must see only whitespace, and will turn back to lexTop +// upon a newline. If it sees EOF, it will quit the lexer successfully. +func lexTopEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == '#': + // a comment will read to a newline for us. + lx.push(lexTop) + return lexCommentStart + case isWhitespace(r): + return lexTopEnd + case isNL(r): + lx.ignore() + return lexTop + case r == eof: + lx.emit(itemEOF) + return nil + } + return lx.errorf("expected a top-level item to end with a newline, comment, or EOF, but got %q instead", r) +} + +// lexTable lexes the beginning of a table. Namely, it makes sure that +// it starts with a character other than '.' and ']'. +// It assumes that '[' has already been consumed. +// It also handles the case that this is an item in an array of tables. +// e.g., '[[name]]'. +func lexTableStart(lx *lexer) stateFn { + if lx.peek() == '[' { + lx.next() + lx.emit(itemArrayTableStart) + lx.push(lexArrayTableEnd) + } else { + lx.emit(itemTableStart) + lx.push(lexTableEnd) + } + return lexTableNameStart +} + +func lexTableEnd(lx *lexer) stateFn { + lx.emit(itemTableEnd) + return lexTopEnd +} + +func lexArrayTableEnd(lx *lexer) stateFn { + if r := lx.next(); r != ']' { + return lx.errorf("expected end of table array name delimiter ']', but got %q instead", r) + } + lx.emit(itemArrayTableEnd) + return lexTopEnd +} + +func lexTableNameStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == ']' || r == eof: + return lx.errorf("unexpected end of table name (table names cannot be empty)") + case r == '.': + return lx.errorf("unexpected table separator (table names cannot be empty)") + case r == '"' || r == '\'': + lx.ignore() + lx.push(lexTableNameEnd) + return lexQuotedName + default: + lx.push(lexTableNameEnd) + return lexBareName + } +} + +// lexTableNameEnd reads the end of a piece of a table name, optionally +// consuming whitespace. +func lexTableNameEnd(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.next(); { + case isWhitespace(r): + return lexTableNameEnd + case r == '.': + lx.ignore() + return lexTableNameStart + case r == ']': + return lx.pop() + default: + return lx.errorf("expected '.' or ']' to end table name, but got %q instead", r) + } +} + +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only 'a' inside 'a.b'. +func lexBareName(lx *lexer) stateFn { + r := lx.next() + if isBareKeyChar(r, lx.tomlNext) { + return lexBareName + } + lx.backup() + lx.emit(itemText) + return lx.pop() +} + +// lexBareName lexes one part of a key or table. +// +// It assumes that at least one valid character for the table has already been +// read. +// +// Lexes only one part, e.g. only '"a"' inside '"a".b'. +func lexQuotedName(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexValue) + case r == '"': + lx.ignore() // ignore the '"' + return lexString + case r == '\'': + lx.ignore() // ignore the "'" + return lexRawString + case r == eof: + return lx.errorf("unexpected EOF; expected value") + default: + return lx.errorf("expected value but found %q instead", r) + } +} + +// lexKeyStart consumes all key parts until a '='. +func lexKeyStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '=': key name appears blank") + case r == '.': + return lx.errorf("unexpected '.': keys cannot start with a '.'") + case r == '"' || r == '\'': + lx.ignore() + fallthrough + default: // Bare key + lx.emit(itemKeyStart) + return lexKeyNameStart + } +} + +func lexKeyNameStart(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.peek(); { + case r == '=' || r == eof: + return lx.errorf("unexpected '='") + case r == '.': + return lx.errorf("unexpected '.'") + case r == '"' || r == '\'': + lx.ignore() + lx.push(lexKeyEnd) + return lexQuotedName + default: + lx.push(lexKeyEnd) + return lexBareName + } +} + +// lexKeyEnd consumes the end of a key and trims whitespace (up to the key +// separator). +func lexKeyEnd(lx *lexer) stateFn { + lx.skip(isWhitespace) + switch r := lx.next(); { + case isWhitespace(r): + return lexSkip(lx, lexKeyEnd) + case r == eof: + return lx.errorf("unexpected EOF; expected key separator '='") + case r == '.': + lx.ignore() + return lexKeyNameStart + case r == '=': + lx.emit(itemKeyEnd) + return lexSkip(lx, lexValue) + default: + if r == '\n' { + return lx.errorPrevLine(fmt.Errorf("expected '.' or '=', but got %q instead", r)) + } + return lx.errorf("expected '.' or '=', but got %q instead", r) + } +} + +// lexValue starts the consumption of a value anywhere a value is expected. +// lexValue will ignore whitespace. +// After a value is lexed, the last state on the next is popped and returned. +func lexValue(lx *lexer) stateFn { + // We allow whitespace to precede a value, but NOT newlines. + // In array syntax, the array states are responsible for ignoring newlines. + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexValue) + case isDigit(r): + lx.backup() // avoid an extra state and use the same as above + return lexNumberOrDateStart + } + switch r { + case '[': + lx.ignore() + lx.emit(itemArray) + return lexArrayValue + case '{': + lx.ignore() + lx.emit(itemInlineTableStart) + return lexInlineTableValue + case '"': + if lx.accept('"') { + if lx.accept('"') { + lx.ignore() // Ignore """ + return lexMultilineString + } + lx.backup() + } + lx.ignore() // ignore the '"' + return lexString + case '\'': + if lx.accept('\'') { + if lx.accept('\'') { + lx.ignore() // Ignore """ + return lexMultilineRawString + } + lx.backup() + } + lx.ignore() // ignore the "'" + return lexRawString + case '.': // special error case, be kind to users + return lx.errorf("floats must start with a digit, not '.'") + case 'i', 'n': + if (lx.accept('n') && lx.accept('f')) || (lx.accept('a') && lx.accept('n')) { + lx.emit(itemFloat) + return lx.pop() + } + case '-', '+': + return lexDecimalNumberStart + } + if unicode.IsLetter(r) { + // Be permissive here; lexBool will give a nice error if the + // user wrote something like + // x = foo + // (i.e. not 'true' or 'false' but is something else word-like.) + lx.backup() + return lexBool + } + if r == eof { + return lx.errorf("unexpected EOF; expected value") + } + if r == '\n' { + return lx.errorPrevLine(fmt.Errorf("expected value but found %q instead", r)) + } + return lx.errorf("expected value but found %q instead", r) +} + +// lexArrayValue consumes one value in an array. It assumes that '[' or ',' +// have already been consumed. All whitespace and newlines are ignored. +func lexArrayValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValue) + case r == '#': + lx.push(lexArrayValue) + return lexCommentStart + case r == ',': + return lx.errorf("unexpected comma") + case r == ']': + return lexArrayEnd + } + + lx.backup() + lx.push(lexArrayValueEnd) + return lexValue +} + +// lexArrayValueEnd consumes everything between the end of an array value and +// the next value (or the end of the array): it ignores whitespace and newlines +// and expects either a ',' or a ']'. +func lexArrayValueEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValueEnd) + case r == '#': + lx.push(lexArrayValueEnd) + return lexCommentStart + case r == ',': + lx.ignore() + return lexArrayValue // move on to the next value + case r == ']': + return lexArrayEnd + default: + return lx.errorf("expected a comma (',') or array terminator (']'), but got %s", runeOrEOF(r)) + } +} + +// lexArrayEnd finishes the lexing of an array. +// It assumes that a ']' has just been consumed. +func lexArrayEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemArrayEnd) + return lx.pop() +} + +// lexInlineTableValue consumes one key/value pair in an inline table. +// It assumes that '{' or ',' have already been consumed. Whitespace is ignored. +func lexInlineTableValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r): + return lexSkip(lx, lexInlineTableValue) + case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValue) + } + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': + lx.push(lexInlineTableValue) + return lexCommentStart + case r == ',': + return lx.errorf("unexpected comma") + case r == '}': + return lexInlineTableEnd + } + lx.backup() + lx.push(lexInlineTableValueEnd) + return lexKeyStart +} + +// lexInlineTableValueEnd consumes everything between the end of an inline table +// key/value pair and the next pair (or the end of the table): +// it ignores whitespace and expects either a ',' or a '}'. +func lexInlineTableValueEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case isWhitespace(r): + return lexSkip(lx, lexInlineTableValueEnd) + case isNL(r): + if lx.tomlNext { + return lexSkip(lx, lexInlineTableValueEnd) + } + return lx.errorPrevLine(errLexInlineTableNL{}) + case r == '#': + lx.push(lexInlineTableValueEnd) + return lexCommentStart + case r == ',': + lx.ignore() + lx.skip(isWhitespace) + if lx.peek() == '}' { + if lx.tomlNext { + return lexInlineTableValueEnd + } + return lx.errorf("trailing comma not allowed in inline tables") + } + return lexInlineTableValue + case r == '}': + return lexInlineTableEnd + default: + return lx.errorf("expected a comma or an inline table terminator '}', but got %s instead", runeOrEOF(r)) + } +} + +func runeOrEOF(r rune) string { + if r == eof { + return "end of file" + } + return "'" + string(r) + "'" +} + +// lexInlineTableEnd finishes the lexing of an inline table. +// It assumes that a '}' has just been consumed. +func lexInlineTableEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemInlineTableEnd) + return lx.pop() +} + +// lexString consumes the inner contents of a string. It assumes that the +// beginning '"' has already been consumed and ignored. +func lexString(lx *lexer) stateFn { + r := lx.next() + switch { + case r == eof: + return lx.errorf(`unexpected EOF; expected '"'`) + case isNL(r): + return lx.errorPrevLine(errLexStringNL{}) + case r == '\\': + lx.push(lexString) + return lexStringEscape + case r == '"': + lx.backup() + if lx.esc { + lx.esc = false + lx.emit(itemStringEsc) + } else { + lx.emit(itemString) + } + lx.next() + lx.ignore() + return lx.pop() + } + return lexString +} + +// lexMultilineString consumes the inner contents of a string. It assumes that +// the beginning '"""' has already been consumed and ignored. +func lexMultilineString(lx *lexer) stateFn { + r := lx.next() + switch r { + default: + return lexMultilineString + case eof: + return lx.errorf(`unexpected EOF; expected '"""'`) + case '\\': + return lexMultilineStringEscape + case '"': + /// Found " → try to read two more "". + if lx.accept('"') { + if lx.accept('"') { + /// Peek ahead: the string can contain " and "", including at the + /// end: """str""""" + /// 6 or more at the end, however, is an error. + if lx.peek() == '"' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + /// + /// Second check is for the edge case: + /// + /// two quotes allowed. + /// vv + /// """lol \"""""" + /// ^^ ^^^---- closing three + /// escaped + /// + /// But ugly, but it works + if strings.HasSuffix(lx.current(), `"""""`) && !strings.HasSuffix(lx.current(), `\"""""`) { + return lx.errorf(`unexpected '""""""'`) + } + lx.backup() + lx.backup() + return lexMultilineString + } + + lx.backup() /// backup: don't include the """ in the item. + lx.backup() + lx.backup() + lx.esc = false + lx.emit(itemMultilineString) + lx.next() /// Read over ''' again and discard it. + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + return lexMultilineString + } +} + +// lexRawString consumes a raw string. Nothing can be escaped in such a string. +// It assumes that the beginning "'" has already been consumed and ignored. +func lexRawString(lx *lexer) stateFn { + r := lx.next() + switch { + default: + return lexRawString + case r == eof: + return lx.errorf(`unexpected EOF; expected "'"`) + case isNL(r): + return lx.errorPrevLine(errLexStringNL{}) + case r == '\'': + lx.backup() + lx.emit(itemRawString) + lx.next() + lx.ignore() + return lx.pop() + } +} + +// lexMultilineRawString consumes a raw string. Nothing can be escaped in such a +// string. It assumes that the beginning triple-' has already been consumed and +// ignored. +func lexMultilineRawString(lx *lexer) stateFn { + r := lx.next() + switch r { + default: + return lexMultilineRawString + case eof: + return lx.errorf(`unexpected EOF; expected "'''"`) + case '\'': + /// Found ' → try to read two more ''. + if lx.accept('\'') { + if lx.accept('\'') { + /// Peek ahead: the string can contain ' and '', including at the + /// end: '''str''''' + /// 6 or more at the end, however, is an error. + if lx.peek() == '\'' { + /// Check if we already lexed 5 's; if so we have 6 now, and + /// that's just too many man! + if strings.HasSuffix(lx.current(), "'''''") { + return lx.errorf(`unexpected "''''''"`) + } + lx.backup() + lx.backup() + return lexMultilineRawString + } + + lx.backup() /// backup: don't include the ''' in the item. + lx.backup() + lx.backup() + lx.emit(itemRawMultilineString) + lx.next() /// Read over ''' again and discard it. + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + return lexMultilineRawString + } +} + +// lexMultilineStringEscape consumes an escaped character. It assumes that the +// preceding '\\' has already been consumed. +func lexMultilineStringEscape(lx *lexer) stateFn { + if isNL(lx.next()) { /// \ escaping newline. + return lexMultilineString + } + lx.backup() + lx.push(lexMultilineString) + return lexStringEscape(lx) +} + +func lexStringEscape(lx *lexer) stateFn { + lx.esc = true + r := lx.next() + switch r { + case 'e': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + fallthrough + case 'b': + fallthrough + case 't': + fallthrough + case 'n': + fallthrough + case 'f': + fallthrough + case 'r': + fallthrough + case '"': + fallthrough + case ' ', '\t': + // Inside """ .. """ strings you can use \ to escape newlines, and any + // amount of whitespace can be between the \ and \n. + fallthrough + case '\\': + return lx.pop() + case 'x': + if !lx.tomlNext { + return lx.error(errLexEscape{r}) + } + return lexHexEscape + case 'u': + return lexShortUnicodeEscape + case 'U': + return lexLongUnicodeEscape + } + return lx.error(errLexEscape{r}) +} + +func lexHexEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 2; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected two hexadecimal digits after '\x', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +func lexShortUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 4; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected four hexadecimal digits after '\u', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +func lexLongUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 8; i++ { + r = lx.next() + if !isHex(r) { + return lx.errorf(`expected eight hexadecimal digits after '\U', but got %q instead`, lx.current()) + } + } + return lx.pop() +} + +// lexNumberOrDateStart processes the first character of a value which begins +// with a digit. It exists to catch values starting with '0', so that +// lexBaseNumberOrDate can differentiate base prefixed integers from other +// types. +func lexNumberOrDateStart(lx *lexer) stateFn { + r := lx.next() + switch r { + case '0': + return lexBaseNumberOrDate + } + + if !isDigit(r) { + // The only way to reach this state is if the value starts + // with a digit, so specifically treat anything else as an + // error. + return lx.errorf("expected a digit but got %q", r) + } + + return lexNumberOrDate +} + +// lexNumberOrDate consumes either an integer, float or datetime. +func lexNumberOrDate(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexNumberOrDate + } + switch r { + case '-', ':': + return lexDatetime + case '_': + return lexDecimalNumber + case '.', 'e', 'E': + return lexFloat + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDatetime consumes a Datetime, to a first approximation. +// The parser validates that it matches one of the accepted formats. +func lexDatetime(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexDatetime + } + switch r { + case '-', ':', 'T', 't', ' ', '.', 'Z', 'z', '+': + return lexDatetime + } + + lx.backup() + lx.emitTrim(itemDatetime) + return lx.pop() +} + +// lexHexInteger consumes a hexadecimal integer after seeing the '0x' prefix. +func lexHexInteger(lx *lexer) stateFn { + r := lx.next() + if isHex(r) { + return lexHexInteger + } + switch r { + case '_': + return lexHexInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexOctalInteger consumes an octal integer after seeing the '0o' prefix. +func lexOctalInteger(lx *lexer) stateFn { + r := lx.next() + if isOctal(r) { + return lexOctalInteger + } + switch r { + case '_': + return lexOctalInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexBinaryInteger consumes a binary integer after seeing the '0b' prefix. +func lexBinaryInteger(lx *lexer) stateFn { + r := lx.next() + if isBinary(r) { + return lexBinaryInteger + } + switch r { + case '_': + return lexBinaryInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes a decimal float or integer. +func lexDecimalNumber(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexDecimalNumber + } + switch r { + case '.', 'e', 'E': + return lexFloat + case '_': + return lexDecimalNumber + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDecimalNumber consumes the first digit of a number beginning with a sign. +// It assumes the sign has already been consumed. Values which start with a sign +// are only allowed to be decimal integers or floats. +// +// The special "nan" and "inf" values are also recognized. +func lexDecimalNumberStart(lx *lexer) stateFn { + r := lx.next() + + // Special error cases to give users better error messages + switch r { + case 'i': + if !lx.accept('n') || !lx.accept('f') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case 'n': + if !lx.accept('a') || !lx.accept('n') { + return lx.errorf("invalid float: '%s'", lx.current()) + } + lx.emit(itemFloat) + return lx.pop() + case '0': + p := lx.peek() + switch p { + case 'b', 'o', 'x': + return lx.errorf("cannot use sign with non-decimal numbers: '%s%c'", lx.current(), p) + } + case '.': + return lx.errorf("floats must start with a digit, not '.'") + } + + if isDigit(r) { + return lexDecimalNumber + } + + return lx.errorf("expected a digit but got %q", r) +} + +// lexBaseNumberOrDate differentiates between the possible values which +// start with '0'. It assumes that before reaching this state, the initial '0' +// has been consumed. +func lexBaseNumberOrDate(lx *lexer) stateFn { + r := lx.next() + // Note: All datetimes start with at least two digits, so we don't + // handle date characters (':', '-', etc.) here. + if isDigit(r) { + return lexNumberOrDate + } + switch r { + case '_': + // Can only be decimal, because there can't be an underscore + // between the '0' and the base designator, and dates can't + // contain underscores. + return lexDecimalNumber + case '.', 'e', 'E': + return lexFloat + case 'b': + r = lx.peek() + if !isBinary(r) { + lx.errorf("not a binary number: '%s%c'", lx.current(), r) + } + return lexBinaryInteger + case 'o': + r = lx.peek() + if !isOctal(r) { + lx.errorf("not an octal number: '%s%c'", lx.current(), r) + } + return lexOctalInteger + case 'x': + r = lx.peek() + if !isHex(r) { + lx.errorf("not a hexidecimal number: '%s%c'", lx.current(), r) + } + return lexHexInteger + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexFloat consumes the elements of a float. It allows any sequence of +// float-like characters, so floats emitted by the lexer are only a first +// approximation and must be validated by the parser. +func lexFloat(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexFloat + } + switch r { + case '_', '.', '-', '+', 'e', 'E': + return lexFloat + } + + lx.backup() + lx.emit(itemFloat) + return lx.pop() +} + +// lexBool consumes a bool string: 'true' or 'false. +func lexBool(lx *lexer) stateFn { + var rs []rune + for { + r := lx.next() + if !unicode.IsLetter(r) { + lx.backup() + break + } + rs = append(rs, r) + } + s := string(rs) + switch s { + case "true", "false": + lx.emit(itemBool) + return lx.pop() + } + return lx.errorf("expected value but found %q instead", s) +} + +// lexCommentStart begins the lexing of a comment. It will emit +// itemCommentStart and consume no characters, passing control to lexComment. +func lexCommentStart(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemCommentStart) + return lexComment +} + +// lexComment lexes an entire comment. It assumes that '#' has been consumed. +// It will consume *up to* the first newline character, and pass control +// back to the last state on the stack. +func lexComment(lx *lexer) stateFn { + switch r := lx.next(); { + case isNL(r) || r == eof: + lx.backup() + lx.emit(itemText) + return lx.pop() + default: + return lexComment + } +} + +// lexSkip ignores all slurped input and moves on to the next state. +func lexSkip(lx *lexer, nextState stateFn) stateFn { + lx.ignore() + return nextState +} + +func (s stateFn) String() string { + name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name() + if i := strings.LastIndexByte(name, '.'); i > -1 { + name = name[i+1:] + } + if s == nil { + name = "" + } + return name + "()" +} + +func (itype itemType) String() string { + switch itype { + case itemError: + return "Error" + case itemNIL: + return "NIL" + case itemEOF: + return "EOF" + case itemText: + return "Text" + case itemString, itemStringEsc, itemRawString, itemMultilineString, itemRawMultilineString: + return "String" + case itemBool: + return "Bool" + case itemInteger: + return "Integer" + case itemFloat: + return "Float" + case itemDatetime: + return "DateTime" + case itemTableStart: + return "TableStart" + case itemTableEnd: + return "TableEnd" + case itemKeyStart: + return "KeyStart" + case itemKeyEnd: + return "KeyEnd" + case itemArray: + return "Array" + case itemArrayEnd: + return "ArrayEnd" + case itemCommentStart: + return "CommentStart" + case itemInlineTableStart: + return "InlineTableStart" + case itemInlineTableEnd: + return "InlineTableEnd" + } + panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) +} + +func (item item) String() string { + return fmt.Sprintf("(%s, %s)", item.typ, item.val) +} + +func isWhitespace(r rune) bool { return r == '\t' || r == ' ' } +func isNL(r rune) bool { return r == '\n' || r == '\r' } +func isControl(r rune) bool { // Control characters except \t, \r, \n + switch r { + case '\t', '\r', '\n': + return false + default: + return (r >= 0x00 && r <= 0x1f) || r == 0x7f + } +} +func isDigit(r rune) bool { return r >= '0' && r <= '9' } +func isBinary(r rune) bool { return r == '0' || r == '1' } +func isOctal(r rune) bool { return r >= '0' && r <= '7' } +func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') } +func isBareKeyChar(r rune, tomlNext bool) bool { + if tomlNext { + return (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || + r == '_' || r == '-' || + r == 0xb2 || r == 0xb3 || r == 0xb9 || (r >= 0xbc && r <= 0xbe) || + (r >= 0xc0 && r <= 0xd6) || (r >= 0xd8 && r <= 0xf6) || (r >= 0xf8 && r <= 0x037d) || + (r >= 0x037f && r <= 0x1fff) || + (r >= 0x200c && r <= 0x200d) || (r >= 0x203f && r <= 0x2040) || + (r >= 0x2070 && r <= 0x218f) || (r >= 0x2460 && r <= 0x24ff) || + (r >= 0x2c00 && r <= 0x2fef) || (r >= 0x3001 && r <= 0xd7ff) || + (r >= 0xf900 && r <= 0xfdcf) || (r >= 0xfdf0 && r <= 0xfffd) || + (r >= 0x10000 && r <= 0xeffff) + } + + return (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || + r == '_' || r == '-' +} diff --git a/vendor/github.com/BurntSushi/toml/meta.go b/vendor/github.com/BurntSushi/toml/meta.go new file mode 100644 index 0000000..e614537 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/meta.go @@ -0,0 +1,148 @@ +package toml + +import ( + "strings" +) + +// MetaData allows access to meta information about TOML data that's not +// accessible otherwise. +// +// It allows checking if a key is defined in the TOML data, whether any keys +// were undecoded, and the TOML type of a key. +type MetaData struct { + context Key // Used only during decoding. + + keyInfo map[string]keyInfo + mapping map[string]any + keys []Key + decoded map[string]struct{} + data []byte // Input file; for errors. +} + +// IsDefined reports if the key exists in the TOML data. +// +// The key should be specified hierarchically, for example to access the TOML +// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive. +// +// Returns false for an empty key. +func (md *MetaData) IsDefined(key ...string) bool { + if len(key) == 0 { + return false + } + + var ( + hash map[string]any + ok bool + hashOrVal any = md.mapping + ) + for _, k := range key { + if hash, ok = hashOrVal.(map[string]any); !ok { + return false + } + if hashOrVal, ok = hash[k]; !ok { + return false + } + } + return true +} + +// Type returns a string representation of the type of the key specified. +// +// Type will return the empty string if given an empty key or a key that does +// not exist. Keys are case sensitive. +func (md *MetaData) Type(key ...string) string { + if ki, ok := md.keyInfo[Key(key).String()]; ok { + return ki.tomlType.typeString() + } + return "" +} + +// Keys returns a slice of every key in the TOML data, including key groups. +// +// Each key is itself a slice, where the first element is the top of the +// hierarchy and the last is the most specific. The list will have the same +// order as the keys appeared in the TOML data. +// +// All keys returned are non-empty. +func (md *MetaData) Keys() []Key { + return md.keys +} + +// Undecoded returns all keys that have not been decoded in the order in which +// they appear in the original TOML document. +// +// This includes keys that haven't been decoded because of a [Primitive] value. +// Once the Primitive value is decoded, the keys will be considered decoded. +// +// Also note that decoding into an empty interface will result in no decoding, +// and so no keys will be considered decoded. +// +// In this sense, the Undecoded keys correspond to keys in the TOML document +// that do not have a concrete type in your representation. +func (md *MetaData) Undecoded() []Key { + undecoded := make([]Key, 0, len(md.keys)) + for _, key := range md.keys { + if _, ok := md.decoded[key.String()]; !ok { + undecoded = append(undecoded, key) + } + } + return undecoded +} + +// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get +// values of this type. +type Key []string + +func (k Key) String() string { + // This is called quite often, so it's a bit funky to make it faster. + var b strings.Builder + b.Grow(len(k) * 25) +outer: + for i, kk := range k { + if i > 0 { + b.WriteByte('.') + } + if kk == "" { + b.WriteString(`""`) + } else { + for _, r := range kk { + // "Inline" isBareKeyChar + if !((r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-') { + b.WriteByte('"') + b.WriteString(dblQuotedReplacer.Replace(kk)) + b.WriteByte('"') + continue outer + } + } + b.WriteString(kk) + } + } + return b.String() +} + +func (k Key) maybeQuoted(i int) string { + if k[i] == "" { + return `""` + } + for _, r := range k[i] { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '_' || r == '-' { + continue + } + return `"` + dblQuotedReplacer.Replace(k[i]) + `"` + } + return k[i] +} + +// Like append(), but only increase the cap by 1. +func (k Key) add(piece string) Key { + if cap(k) > len(k) { + return append(k, piece) + } + newKey := make(Key, len(k)+1) + copy(newKey, k) + newKey[len(k)] = piece + return newKey +} + +func (k Key) parent() Key { return k[:len(k)-1] } // all except the last piece. +func (k Key) last() string { return k[len(k)-1] } // last piece of this key. diff --git a/vendor/github.com/BurntSushi/toml/parse.go b/vendor/github.com/BurntSushi/toml/parse.go new file mode 100644 index 0000000..3f2c090 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/parse.go @@ -0,0 +1,846 @@ +package toml + +import ( + "fmt" + "math" + "os" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/BurntSushi/toml/internal" +) + +type parser struct { + lx *lexer + context Key // Full key for the current hash in scope. + currentKey string // Base key name for everything except hashes. + pos Position // Current position in the TOML file. + tomlNext bool + + ordered []Key // List of keys in the order that they appear in the TOML data. + + keyInfo map[string]keyInfo // Map keyname → info about the TOML key. + mapping map[string]any // Map keyname → key value. + implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names"). +} + +type keyInfo struct { + pos Position + tomlType tomlType +} + +func parse(data string) (p *parser, err error) { + _, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110") + + defer func() { + if r := recover(); r != nil { + if pErr, ok := r.(ParseError); ok { + pErr.input = data + err = pErr + return + } + panic(r) + } + }() + + // Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString() + // which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add + // it anyway. + if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16 + data = data[2:] + //lint:ignore S1017 https://github.com/dominikh/go-tools/issues/1447 + } else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8 + data = data[3:] + } + + // Examine first few bytes for NULL bytes; this probably means it's a UTF-16 + // file (second byte in surrogate pair being NULL). Again, do this here to + // avoid having to deal with UTF-8/16 stuff in the lexer. + ex := 6 + if len(data) < 6 { + ex = len(data) + } + if i := strings.IndexRune(data[:ex], 0); i > -1 { + return nil, ParseError{ + Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8", + Position: Position{Line: 1, Col: 1, Start: i, Len: 1}, + Line: 1, + input: data, + } + } + + p = &parser{ + keyInfo: make(map[string]keyInfo), + mapping: make(map[string]any), + lx: lex(data, tomlNext), + ordered: make([]Key, 0), + implicits: make(map[string]struct{}), + tomlNext: tomlNext, + } + for { + item := p.next() + if item.typ == itemEOF { + break + } + p.topLevel(item) + } + + return p, nil +} + +func (p *parser) panicErr(it item, err error) { + panic(ParseError{ + Message: err.Error(), + err: err, + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Len, + LastKey: p.current(), + }) +} + +func (p *parser) panicItemf(it item, format string, v ...any) { + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Len, + LastKey: p.current(), + }) +} + +func (p *parser) panicf(format string, v ...any) { + panic(ParseError{ + Message: fmt.Sprintf(format, v...), + Position: p.pos.withCol(p.lx.input), + Line: p.pos.Line, + LastKey: p.current(), + }) +} + +func (p *parser) next() item { + it := p.lx.nextItem() + //fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val) + if it.typ == itemError { + if it.err != nil { + panic(ParseError{ + Message: it.err.Error(), + err: it.err, + Position: it.pos.withCol(p.lx.input), + Line: it.pos.Line, + LastKey: p.current(), + }) + } + + p.panicItemf(it, "%s", it.val) + } + return it +} + +func (p *parser) nextPos() item { + it := p.next() + p.pos = it.pos + return it +} + +func (p *parser) bug(format string, v ...any) { + panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) +} + +func (p *parser) expect(typ itemType) item { + it := p.next() + p.assertEqual(typ, it.typ) + return it +} + +func (p *parser) assertEqual(expected, got itemType) { + if expected != got { + p.bug("Expected '%s' but got '%s'.", expected, got) + } +} + +func (p *parser) topLevel(item item) { + switch item.typ { + case itemCommentStart: // # .. + p.expect(itemText) + case itemTableStart: // [ .. ] + name := p.nextPos() + + var key Key + for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) + } + p.assertEqual(itemTableEnd, name.typ) + + p.addContext(key, false) + p.setType("", tomlHash, item.pos) + p.ordered = append(p.ordered, key) + case itemArrayTableStart: // [[ .. ]] + name := p.nextPos() + + var key Key + for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() { + key = append(key, p.keyString(name)) + } + p.assertEqual(itemArrayTableEnd, name.typ) + + p.addContext(key, true) + p.setType("", tomlArrayHash, item.pos) + p.ordered = append(p.ordered, key) + case itemKeyStart: // key = .. + outerContext := p.context + /// Read all the key parts (e.g. 'a' and 'b' in 'a.b') + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) + + /// The current key is the last part. + p.currentKey = key.last() + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key.parent() + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + /// Set value. + vItem := p.next() + val, typ := p.value(vItem, false) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ, vItem.pos) + + /// Remove the context we added (preserving any context from [tbl] lines). + p.context = outerContext + p.currentKey = "" + default: + p.bug("Unexpected type at top level: %s", item.typ) + } +} + +// Gets a string for a key (or part of a key in a table name). +func (p *parser) keyString(it item) string { + switch it.typ { + case itemText: + return it.val + case itemString, itemStringEsc, itemMultilineString, + itemRawString, itemRawMultilineString: + s, _ := p.value(it, false) + return s.(string) + default: + p.bug("Unexpected key type: %s", it.typ) + } + panic("unreachable") +} + +var datetimeRepl = strings.NewReplacer( + "z", "Z", + "t", "T", + " ", "T") + +// value translates an expected value from the lexer into a Go value wrapped +// as an empty interface. +func (p *parser) value(it item, parentIsArray bool) (any, tomlType) { + switch it.typ { + case itemString: + return it.val, p.typeOfPrimitive(it) + case itemStringEsc: + return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it) + case itemMultilineString: + return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it) + case itemRawString: + return it.val, p.typeOfPrimitive(it) + case itemRawMultilineString: + return stripFirstNewline(it.val), p.typeOfPrimitive(it) + case itemInteger: + return p.valueInteger(it) + case itemFloat: + return p.valueFloat(it) + case itemBool: + switch it.val { + case "true": + return true, p.typeOfPrimitive(it) + case "false": + return false, p.typeOfPrimitive(it) + default: + p.bug("Expected boolean value, but got '%s'.", it.val) + } + case itemDatetime: + return p.valueDatetime(it) + case itemArray: + return p.valueArray(it) + case itemInlineTableStart: + return p.valueInlineTable(it, parentIsArray) + default: + p.bug("Unexpected value type: %s", it.typ) + } + panic("unreachable") +} + +func (p *parser) valueInteger(it item) (any, tomlType) { + if !numUnderscoresOK(it.val) { + p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val) + } + if numHasLeadingZero(it.val) { + p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val) + } + + num, err := strconv.ParseInt(it.val, 0, 64) + if err != nil { + // Distinguish integer values. Normally, it'd be a bug if the lexer + // provides an invalid integer, but it's possible that the number is + // out of range of valid values (which the lexer cannot determine). + // So mark the former as a bug but the latter as a legitimate user + // error. + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "int64"}) + } else { + p.bug("Expected integer value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) +} + +func (p *parser) valueFloat(it item) (any, tomlType) { + parts := strings.FieldsFunc(it.val, func(r rune) bool { + switch r { + case '.', 'e', 'E': + return true + } + return false + }) + for _, part := range parts { + if !numUnderscoresOK(part) { + p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val) + } + } + if len(parts) > 0 && numHasLeadingZero(parts[0]) { + p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val) + } + if !numPeriodsOK(it.val) { + // As a special case, numbers like '123.' or '1.e2', + // which are valid as far as Go/strconv are concerned, + // must be rejected because TOML says that a fractional + // part consists of '.' followed by 1+ digits. + p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val) + } + val := strings.Replace(it.val, "_", "", -1) + signbit := false + if val == "+nan" || val == "-nan" { + signbit = val == "-nan" + val = "nan" + } + num, err := strconv.ParseFloat(val, 64) + if err != nil { + if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { + p.panicErr(it, errParseRange{i: it.val, size: "float64"}) + } else { + p.panicItemf(it, "Invalid float value: %q", it.val) + } + } + if signbit { + num = math.Copysign(num, -1) + } + return num, p.typeOfPrimitive(it) +} + +var dtTypes = []struct { + fmt string + zone *time.Location + next bool +}{ + {time.RFC3339Nano, time.Local, false}, + {"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false}, + {"2006-01-02", internal.LocalDate, false}, + {"15:04:05.999999999", internal.LocalTime, false}, + + // tomlNext + {"2006-01-02T15:04Z07:00", time.Local, true}, + {"2006-01-02T15:04", internal.LocalDatetime, true}, + {"15:04", internal.LocalTime, true}, +} + +func (p *parser) valueDatetime(it item) (any, tomlType) { + it.val = datetimeRepl.Replace(it.val) + var ( + t time.Time + ok bool + err error + ) + for _, dt := range dtTypes { + if dt.next && !p.tomlNext { + continue + } + t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone) + if err == nil { + if missingLeadingZero(it.val, dt.fmt) { + p.panicErr(it, errParseDate{it.val}) + } + ok = true + break + } + } + if !ok { + p.panicErr(it, errParseDate{it.val}) + } + return t, p.typeOfPrimitive(it) +} + +// Go's time.Parse() will accept numbers without a leading zero; there isn't any +// way to require it. https://github.com/golang/go/issues/29911 +// +// Depend on the fact that the separators (- and :) should always be at the same +// location. +func missingLeadingZero(d, l string) bool { + for i, c := range []byte(l) { + if c == '.' || c == 'Z' { + return false + } + if (c < '0' || c > '9') && d[i] != c { + return true + } + } + return false +} + +func (p *parser) valueArray(it item) (any, tomlType) { + p.setType(p.currentKey, tomlArray, it.pos) + + var ( + // Initialize to a non-nil slice to make it consistent with how S = [] + // decodes into a non-nil slice inside something like struct { S + // []string }. See #338 + array = make([]any, 0, 2) + ) + for it = p.next(); it.typ != itemArrayEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + val, typ := p.value(it, true) + array = append(array, val) + + // XXX: type isn't used here, we need it to record the accurate type + // information. + // + // Not entirely sure how to best store this; could use "key[0]", + // "key[1]" notation, or maybe store it on the Array type? + _ = typ + } + return array, tomlArray +} + +func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) { + var ( + topHash = make(map[string]any) + outerContext = p.context + outerKey = p.currentKey + ) + + p.context = append(p.context, p.currentKey) + prevContext := p.context + p.currentKey = "" + + p.addImplicit(p.context) + p.addContext(p.context, parentIsArray) + + /// Loop over all table key/value pairs. + for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + /// Read all key parts. + k := p.nextPos() + var key Key + for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() { + key = append(key, p.keyString(k)) + } + p.assertEqual(itemKeyEnd, k.typ) + + /// The current key is the last part. + p.currentKey = key.last() + + /// All the other parts (if any) are the context; need to set each part + /// as implicit. + context := key.parent() + for i := range context { + p.addImplicitContext(append(p.context, context[i:i+1]...)) + } + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + + /// Set the value. + val, typ := p.value(p.next(), false) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ, it.pos) + + hash := topHash + for _, c := range context { + h, ok := hash[c] + if !ok { + h = make(map[string]any) + hash[c] = h + } + hash, ok = h.(map[string]any) + if !ok { + p.panicf("%q is not a table", p.context) + } + } + hash[p.currentKey] = val + + /// Restore context. + p.context = prevContext + } + p.context = outerContext + p.currentKey = outerKey + return topHash, tomlHash +} + +// numHasLeadingZero checks if this number has leading zeroes, allowing for '0', +// +/- signs, and base prefixes. +func numHasLeadingZero(s string) bool { + if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x + return true + } + if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' { + return true + } + return false +} + +// numUnderscoresOK checks whether each underscore in s is surrounded by +// characters that are not underscores. +func numUnderscoresOK(s string) bool { + switch s { + case "nan", "+nan", "-nan", "inf", "-inf", "+inf": + return true + } + accept := false + for _, r := range s { + if r == '_' { + if !accept { + return false + } + } + + // isHexis a superset of all the permissable characters surrounding an + // underscore. + accept = isHex(r) + } + return accept +} + +// numPeriodsOK checks whether every period in s is followed by a digit. +func numPeriodsOK(s string) bool { + period := false + for _, r := range s { + if period && !isDigit(r) { + return false + } + period = r == '.' + } + return !period +} + +// Set the current context of the parser, where the context is either a hash or +// an array of hashes, depending on the value of the `array` parameter. +// +// Establishing the context also makes sure that the key isn't a duplicate, and +// will create implicit hashes automatically. +func (p *parser) addContext(key Key, array bool) { + /// Always start at the top level and drill down for our context. + hashContext := p.mapping + keyContext := make(Key, 0, len(key)-1) + + /// We only need implicit hashes for the parents. + for _, k := range key.parent() { + _, ok := hashContext[k] + keyContext = append(keyContext, k) + + // No key? Make an implicit hash and move on. + if !ok { + p.addImplicit(keyContext) + hashContext[k] = make(map[string]any) + } + + // If the hash context is actually an array of tables, then set + // the hash context to the last element in that array. + // + // Otherwise, it better be a table, since this MUST be a key group (by + // virtue of it not being the last element in a key). + switch t := hashContext[k].(type) { + case []map[string]any: + hashContext = t[len(t)-1] + case map[string]any: + hashContext = t + default: + p.panicf("Key '%s' was already created as a hash.", keyContext) + } + } + + p.context = keyContext + if array { + // If this is the first element for this array, then allocate a new + // list of tables for it. + k := key.last() + if _, ok := hashContext[k]; !ok { + hashContext[k] = make([]map[string]any, 0, 4) + } + + // Add a new table. But make sure the key hasn't already been used + // for something else. + if hash, ok := hashContext[k].([]map[string]any); ok { + hashContext[k] = append(hash, make(map[string]any)) + } else { + p.panicf("Key '%s' was already created and cannot be used as an array.", key) + } + } else { + p.setValue(key.last(), make(map[string]any)) + } + p.context = append(p.context, key.last()) +} + +// setValue sets the given key to the given value in the current context. +// It will make sure that the key hasn't already been defined, account for +// implicit key groups. +func (p *parser) setValue(key string, value any) { + var ( + tmpHash any + ok bool + hash = p.mapping + keyContext = make(Key, 0, len(p.context)+1) + ) + for _, k := range p.context { + keyContext = append(keyContext, k) + if tmpHash, ok = hash[k]; !ok { + p.bug("Context for key '%s' has not been established.", keyContext) + } + switch t := tmpHash.(type) { + case []map[string]any: + // The context is a table of hashes. Pick the most recent table + // defined as the current hash. + hash = t[len(t)-1] + case map[string]any: + hash = t + default: + p.panicf("Key '%s' has already been defined.", keyContext) + } + } + keyContext = append(keyContext, key) + + if _, ok := hash[key]; ok { + // Normally redefining keys isn't allowed, but the key could have been + // defined implicitly and it's allowed to be redefined concretely. (See + // the `valid/implicit-and-explicit-after.toml` in toml-test) + // + // But we have to make sure to stop marking it as an implicit. (So that + // another redefinition provokes an error.) + // + // Note that since it has already been defined (as a hash), we don't + // want to overwrite it. So our business is done. + if p.isArray(keyContext) { + p.removeImplicit(keyContext) + hash[key] = value + return + } + if p.isImplicit(keyContext) { + p.removeImplicit(keyContext) + return + } + // Otherwise, we have a concrete key trying to override a previous key, + // which is *always* wrong. + p.panicf("Key '%s' has already been defined.", keyContext) + } + + hash[key] = value +} + +// setType sets the type of a particular value at a given key. It should be +// called immediately AFTER setValue. +// +// Note that if `key` is empty, then the type given will be applied to the +// current context (which is either a table or an array of tables). +func (p *parser) setType(key string, typ tomlType, pos Position) { + keyContext := make(Key, 0, len(p.context)+1) + keyContext = append(keyContext, p.context...) + if len(key) > 0 { // allow type setting for hashes + keyContext = append(keyContext, key) + } + // Special case to make empty keys ("" = 1) work. + // Without it it will set "" rather than `""`. + // TODO: why is this needed? And why is this only needed here? + if len(keyContext) == 0 { + keyContext = Key{""} + } + p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos} +} + +// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and +// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly). +func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } +func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } +func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } +func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } +func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) } + +// current returns the full key name of the current context. +func (p *parser) current() string { + if len(p.currentKey) == 0 { + return p.context.String() + } + if len(p.context) == 0 { + return p.currentKey + } + return fmt.Sprintf("%s.%s", p.context, p.currentKey) +} + +func stripFirstNewline(s string) string { + if len(s) > 0 && s[0] == '\n' { + return s[1:] + } + if len(s) > 1 && s[0] == '\r' && s[1] == '\n' { + return s[2:] + } + return s +} + +// stripEscapedNewlines removes whitespace after line-ending backslashes in +// multiline strings. +// +// A line-ending backslash is an unescaped \ followed only by whitespace until +// the next newline. After a line-ending backslash, all whitespace is removed +// until the next non-whitespace character. +func (p *parser) stripEscapedNewlines(s string) string { + var ( + b strings.Builder + i int + ) + b.Grow(len(s)) + for { + ix := strings.Index(s[i:], `\`) + if ix < 0 { + b.WriteString(s) + return b.String() + } + i += ix + + if len(s) > i+1 && s[i+1] == '\\' { + // Escaped backslash. + i += 2 + continue + } + // Scan until the next non-whitespace. + j := i + 1 + whitespaceLoop: + for ; j < len(s); j++ { + switch s[j] { + case ' ', '\t', '\r', '\n': + default: + break whitespaceLoop + } + } + if j == i+1 { + // Not a whitespace escape. + i++ + continue + } + if !strings.Contains(s[i:j], "\n") { + // This is not a line-ending backslash. (It's a bad escape sequence, + // but we can let replaceEscapes catch it.) + i++ + continue + } + b.WriteString(s[:i]) + s = s[j:] + i = 0 + } +} + +func (p *parser) replaceEscapes(it item, str string) string { + var ( + b strings.Builder + skip = 0 + ) + b.Grow(len(str)) + for i, c := range str { + if skip > 0 { + skip-- + continue + } + if c != '\\' { + b.WriteRune(c) + continue + } + + if i >= len(str) { + p.bug("Escape sequence at end of string.") + return "" + } + switch str[i+1] { + default: + p.bug("Expected valid escape code after \\, but got %q.", str[i+1]) + case ' ', '\t': + p.panicItemf(it, "invalid escape: '\\%c'", str[i+1]) + case 'b': + b.WriteByte(0x08) + skip = 1 + case 't': + b.WriteByte(0x09) + skip = 1 + case 'n': + b.WriteByte(0x0a) + skip = 1 + case 'f': + b.WriteByte(0x0c) + skip = 1 + case 'r': + b.WriteByte(0x0d) + skip = 1 + case 'e': + if p.tomlNext { + b.WriteByte(0x1b) + skip = 1 + } + case '"': + b.WriteByte(0x22) + skip = 1 + case '\\': + b.WriteByte(0x5c) + skip = 1 + // The lexer guarantees the correct number of characters are present; + // don't need to check here. + case 'x': + if p.tomlNext { + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4]) + b.WriteRune(escaped) + skip = 3 + } + case 'u': + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6]) + b.WriteRune(escaped) + skip = 5 + case 'U': + escaped := p.asciiEscapeToUnicode(it, str[i+2:i+10]) + b.WriteRune(escaped) + skip = 9 + } + } + return b.String() +} + +func (p *parser) asciiEscapeToUnicode(it item, s string) rune { + hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) + if err != nil { + p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err) + } + if !utf8.ValidRune(rune(hex)) { + p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s) + } + return rune(hex) +} diff --git a/vendor/github.com/BurntSushi/toml/type_fields.go b/vendor/github.com/BurntSushi/toml/type_fields.go new file mode 100644 index 0000000..10c51f7 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_fields.go @@ -0,0 +1,238 @@ +package toml + +// Struct field handling is adapted from code in encoding/json: +// +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the Go distribution. + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string // the name of the field (`toml` tag included) + tag bool // whether field has a `toml` tag + index []int // represents the depth of an anonymous field + typ reflect.Type // the type of the field +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from toml tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that TOML should recognize for the given +// type. The algorithm is breadth-first search over the set of structs to +// include - the top struct and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + var count map[reflect.Type]int + var nextCount map[reflect.Type]int + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + opts := getOptions(sf.Tag) + if opts.skip { + continue + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := opts.name != "" + name := opts.name + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + f := field{name: ft.Name(), index: index, typ: ft} + next = append(next, f) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with TOML tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// TOML tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/vendor/github.com/BurntSushi/toml/type_toml.go b/vendor/github.com/BurntSushi/toml/type_toml.go new file mode 100644 index 0000000..1c090d3 --- /dev/null +++ b/vendor/github.com/BurntSushi/toml/type_toml.go @@ -0,0 +1,65 @@ +package toml + +// tomlType represents any Go type that corresponds to a TOML type. +// While the first draft of the TOML spec has a simplistic type system that +// probably doesn't need this level of sophistication, we seem to be militating +// toward adding real composite types. +type tomlType interface { + typeString() string +} + +// typeEqual accepts any two types and returns true if they are equal. +func typeEqual(t1, t2 tomlType) bool { + if t1 == nil || t2 == nil { + return false + } + return t1.typeString() == t2.typeString() +} + +func typeIsTable(t tomlType) bool { + return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) +} + +type tomlBaseType string + +func (btype tomlBaseType) typeString() string { return string(btype) } +func (btype tomlBaseType) String() string { return btype.typeString() } + +var ( + tomlInteger tomlBaseType = "Integer" + tomlFloat tomlBaseType = "Float" + tomlDatetime tomlBaseType = "Datetime" + tomlString tomlBaseType = "String" + tomlBool tomlBaseType = "Bool" + tomlArray tomlBaseType = "Array" + tomlHash tomlBaseType = "Hash" + tomlArrayHash tomlBaseType = "ArrayHash" +) + +// typeOfPrimitive returns a tomlType of any primitive value in TOML. +// Primitive values are: Integer, Float, Datetime, String and Bool. +// +// Passing a lexer item other than the following will cause a BUG message +// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. +func (p *parser) typeOfPrimitive(lexItem item) tomlType { + switch lexItem.typ { + case itemInteger: + return tomlInteger + case itemFloat: + return tomlFloat + case itemDatetime: + return tomlDatetime + case itemString, itemStringEsc: + return tomlString + case itemMultilineString: + return tomlString + case itemRawString: + return tomlString + case itemRawMultilineString: + return tomlString + case itemBool: + return tomlBool + } + p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) + panic("unreachable") +} diff --git a/vendor/github.com/emersion/go-imap/v2/imapclient/client.go b/vendor/github.com/emersion/go-imap/v2/imapclient/client.go index 620bce3..4933d2f 100644 --- a/vendor/github.com/emersion/go-imap/v2/imapclient/client.go +++ b/vendor/github.com/emersion/go-imap/v2/imapclient/client.go @@ -385,6 +385,15 @@ func (c *Client) Mailbox() *SelectedMailbox { return c.mailbox } +// Closed returns a channel that is closed when the connection is closed. +// +// This channel cannot be used to reliably determine whether a connection is healthy. If +// the underlying connection times out, the channel will be closed eventually, but not +// immediately. To check whether the connection is healthy, send a command (such as Noop). +func (c *Client) Closed() <-chan struct{} { + return c.decCh +} + // Close immediately closes the connection. func (c *Client) Close() error { c.mutex.Lock() @@ -1080,7 +1089,7 @@ func (c *Client) Subscribe(mailbox string) *Command { return cmd } -// Subscribe sends an UNSUBSCRIBE command. +// Unsubscribe sends an UNSUBSCRIBE command. func (c *Client) Unsubscribe(mailbox string) *Command { cmd := &Command{} enc := c.beginCommand("UNSUBSCRIBE", cmd) diff --git a/vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go b/vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go index f60256f..309825c 100644 --- a/vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go +++ b/vendor/github.com/emersion/go-imap/v2/imapclient/fetch.go @@ -975,9 +975,9 @@ func readBodyType1part(dec *imapwire.Decoder, typ string, options *Options) (*im } // Content-Transfer-Encoding should always be set, but some non-standard - // servers leave it NIL. Default to 7BIT. + // servers leave it NIL. Default to 7bit. if bs.Encoding == "" { - bs.Encoding = "7BIT" + bs.Encoding = "7bit" } // TODO: handle errors diff --git a/vendor/github.com/emersion/go-imap/v2/imapclient/select.go b/vendor/github.com/emersion/go-imap/v2/imapclient/select.go index 90cd115..c325ff0 100644 --- a/vendor/github.com/emersion/go-imap/v2/imapclient/select.go +++ b/vendor/github.com/emersion/go-imap/v2/imapclient/select.go @@ -51,7 +51,7 @@ func (c *Client) handleFlags() error { c.mutex.Lock() if c.state == imap.ConnStateSelected { c.mailbox = c.mailbox.copy() - c.mailbox.PermanentFlags = flags + c.mailbox.Flags = flags } c.mutex.Unlock() diff --git a/vendor/github.com/sashabaranov/go-openai/.codecov.yml b/vendor/github.com/sashabaranov/go-openai/.codecov.yml index 8177366..bcb1e66 100644 --- a/vendor/github.com/sashabaranov/go-openai/.codecov.yml +++ b/vendor/github.com/sashabaranov/go-openai/.codecov.yml @@ -2,3 +2,11 @@ coverage: ignore: - "examples/**" - "internal/test/**" + status: + project: + default: + target: auto + threshold: 2% + patch: + default: + target: 80% diff --git a/vendor/github.com/sashabaranov/go-openai/.gitignore b/vendor/github.com/sashabaranov/go-openai/.gitignore index b0ac160..aa5f341 100644 --- a/vendor/github.com/sashabaranov/go-openai/.gitignore +++ b/vendor/github.com/sashabaranov/go-openai/.gitignore @@ -19,4 +19,6 @@ .idea # Generated by tests -test.mp3 \ No newline at end of file +test.mp3 + +.env diff --git a/vendor/github.com/sashabaranov/go-openai/.golangci.yml b/vendor/github.com/sashabaranov/go-openai/.golangci.yml index 6391ad7..cd9036f 100644 --- a/vendor/github.com/sashabaranov/go-openai/.golangci.yml +++ b/vendor/github.com/sashabaranov/go-openai/.golangci.yml @@ -22,7 +22,7 @@ linters: - gocyclo - godot - gomoddirectives - - gomodguard + - gomodguard_v2 - goprintffuncname - gosec - govet @@ -66,21 +66,20 @@ linters: paramsOnly: false underef: skipRecvDeref: false - gomodguard: + gomodguard_v2: blocked: - modules: - - github.com/golang/protobuf: - recommendations: - - google.golang.org/protobuf - reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules - - github.com/satori/go.uuid: - recommendations: - - github.com/google/uuid - reason: satori's package is not maintained - - github.com/gofrs/uuid: - recommendations: - - github.com/google/uuid - reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw' + - module: github.com/golang/protobuf + recommendations: + - google.golang.org/protobuf + reason: see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules + - module: github.com/satori/go.uuid + recommendations: + - github.com/google/uuid + reason: satori's package is not maintained + - module: github.com/gofrs/uuid + recommendations: + - github.com/google/uuid + reason: 'see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw' govet: disable: - fieldalignment diff --git a/vendor/github.com/sashabaranov/go-openai/README.md b/vendor/github.com/sashabaranov/go-openai/README.md index 77b85e5..52af0a0 100644 --- a/vendor/github.com/sashabaranov/go-openai/README.md +++ b/vendor/github.com/sashabaranov/go-openai/README.md @@ -1,243 +1,35 @@ # Go OpenAI + [![Go Reference](https://pkg.go.dev/badge/github.com/sashabaranov/go-openai.svg)](https://pkg.go.dev/github.com/sashabaranov/go-openai) [![Go Report Card](https://goreportcard.com/badge/github.com/sashabaranov/go-openai)](https://goreportcard.com/report/github.com/sashabaranov/go-openai) [![codecov](https://codecov.io/gh/sashabaranov/go-openai/branch/master/graph/badge.svg?token=bCbIfHLIsW)](https://codecov.io/gh/sashabaranov/go-openai) -This library provides unofficial Go clients for [OpenAI API](https://platform.openai.com/). We support: +An unofficial Go client for the [OpenAI API](https://developers.openai.com/api/docs/overview). -* ChatGPT 4o, o1 -* GPT-3, GPT-4 -* DALL·E 2, DALL·E 3, GPT Image 1 -* Whisper +For new text-generation, reasoning, tool-calling, and multi-turn integrations, +start with the [Responses API](https://developers.openai.com/api/docs/guides/migrate-to-responses). +Chat Completions remains available for existing integrations. + +The client also covers embeddings, images, audio, moderation, files, fine-tuning, +batches, vector stores, and legacy Assistants API surfaces. ## Installation -``` +```sh go get github.com/sashabaranov/go-openai ``` -Currently, go-openai requires Go version 1.18 or greater. +Go OpenAI requires Go 1.18 or later. -## Usage +## Quick start: Responses API -### ChatGPT example usage: - -```go -package main - -import ( - "context" - "fmt" - openai "github.com/sashabaranov/go-openai" -) - -func main() { - client := openai.NewClient("your token") - resp, err := client.CreateChatCompletion( - context.Background(), - openai.ChatCompletionRequest{ - Model: openai.GPT3Dot5Turbo, - Messages: []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleUser, - Content: "Hello!", - }, - }, - }, - ) - - if err != nil { - fmt.Printf("ChatCompletion error: %v\n", err) - return - } - - fmt.Println(resp.Choices[0].Message.Content) -} +Set an [OpenAI API key](https://platform.openai.com/api-keys) in your environment: +```sh +export OPENAI_API_KEY="" ``` -### Getting an OpenAI API Key: - -1. Visit the OpenAI website at [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys). -2. If you don't have an account, click on "Sign Up" to create one. If you do, click "Log In". -3. Once logged in, navigate to your API key management page. -4. Click on "Create new secret key". -5. Enter a name for your new key, then click "Create secret key". -6. Your new API key will be displayed. Use this key to interact with the OpenAI API. - -**Note:** Your API key is sensitive information. Do not share it with anyone. - -### Other examples: - -
-ChatGPT streaming completion - -```go -package main - -import ( - "context" - "errors" - "fmt" - "io" - openai "github.com/sashabaranov/go-openai" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - req := openai.ChatCompletionRequest{ - Model: openai.GPT3Dot5Turbo, - MaxTokens: 20, - Messages: []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleUser, - Content: "Lorem ipsum", - }, - }, - Stream: true, - } - stream, err := c.CreateChatCompletionStream(ctx, req) - if err != nil { - fmt.Printf("ChatCompletionStream error: %v\n", err) - return - } - defer stream.Close() - - fmt.Printf("Stream response: ") - for { - response, err := stream.Recv() - if errors.Is(err, io.EOF) { - fmt.Println("\nStream finished") - return - } - - if err != nil { - fmt.Printf("\nStream error: %v\n", err) - return - } - - fmt.Printf(response.Choices[0].Delta.Content) - } -} -``` -
- -
-GPT-3 completion - -```go -package main - -import ( - "context" - "fmt" - openai "github.com/sashabaranov/go-openai" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - req := openai.CompletionRequest{ - Model: openai.GPT3Babbage002, - MaxTokens: 5, - Prompt: "Lorem ipsum", - } - resp, err := c.CreateCompletion(ctx, req) - if err != nil { - fmt.Printf("Completion error: %v\n", err) - return - } - fmt.Println(resp.Choices[0].Text) -} -``` -
- -
-GPT-3 streaming completion - -```go -package main - -import ( - "errors" - "context" - "fmt" - "io" - openai "github.com/sashabaranov/go-openai" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - req := openai.CompletionRequest{ - Model: openai.GPT3Babbage002, - MaxTokens: 5, - Prompt: "Lorem ipsum", - Stream: true, - } - stream, err := c.CreateCompletionStream(ctx, req) - if err != nil { - fmt.Printf("CompletionStream error: %v\n", err) - return - } - defer stream.Close() - - for { - response, err := stream.Recv() - if errors.Is(err, io.EOF) { - fmt.Println("Stream finished") - return - } - - if err != nil { - fmt.Printf("Stream error: %v\n", err) - return - } - - - fmt.Printf("Stream response: %v\n", response) - } -} -``` -
- -
-Audio Speech-To-Text - -```go -package main - -import ( - "context" - "fmt" - - openai "github.com/sashabaranov/go-openai" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - req := openai.AudioRequest{ - Model: openai.Whisper1, - FilePath: "recording.mp3", - } - resp, err := c.CreateTranscription(ctx, req) - if err != nil { - fmt.Printf("Transcription error: %v\n", err) - return - } - fmt.Println(resp.Text) -} -``` -
- -
-Audio Captions +Then create a response and read its generated text: ```go package main @@ -245,669 +37,180 @@ package main import ( "context" "fmt" + "log" "os" openai "github.com/sashabaranov/go-openai" ) func main() { - c := openai.NewClient(os.Getenv("OPENAI_KEY")) + client := openai.NewClient(os.Getenv("OPENAI_API_KEY")) - req := openai.AudioRequest{ - Model: openai.Whisper1, - FilePath: os.Args[1], - Format: openai.AudioResponseFormatSRT, - } - resp, err := c.CreateTranscription(context.Background(), req) + response, err := client.CreateResponse(context.Background(), openai.CreateResponseRequest{ + Model: openai.GPT5Dot6Sol, + Instructions: "You are a concise technical explainer.", + Input: "Why is the sky blue?", + }) if err != nil { - fmt.Printf("Transcription error: %v\n", err) - return - } - f, err := os.Create(os.Args[1] + ".srt") - if err != nil { - fmt.Printf("Could not open file: %v\n", err) - return - } - defer f.Close() - if _, err := f.WriteString(resp.Text); err != nil { - fmt.Printf("Error writing to file: %v\n", err) - return + log.Fatal(err) } + + fmt.Println(response.GetOutputText()) } ``` -
-
-DALL-E 2 image generation +`Input` can be a string or a slice of typed input items. For reasoning, tools, +multimodal output, or custom processing, inspect `response.Output` instead of +using the `GetOutputText` convenience method. + +### Continue a conversation + +Use `PreviousResponseID` when OpenAI should carry the earlier response context. +Resend `Instructions` on each call when they should continue to apply. ```go -package main +store := true -import ( - "bytes" - "context" - "encoding/base64" - "fmt" - openai "github.com/sashabaranov/go-openai" - "image/png" - "os" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - // Sample image by link - reqUrl := openai.ImageRequest{ - Prompt: "Parrot on a skateboard performs a trick, cartoon style, natural light, high detail", - Size: openai.CreateImageSize256x256, - ResponseFormat: openai.CreateImageResponseFormatURL, - N: 1, - } - - respUrl, err := c.CreateImage(ctx, reqUrl) - if err != nil { - fmt.Printf("Image creation error: %v\n", err) - return - } - fmt.Println(respUrl.Data[0].URL) - - // Example image as base64 - reqBase64 := openai.ImageRequest{ - Prompt: "Portrait of a humanoid parrot in a classic costume, high detail, realistic light, unreal engine", - Size: openai.CreateImageSize256x256, - ResponseFormat: openai.CreateImageResponseFormatB64JSON, - N: 1, - } - - respBase64, err := c.CreateImage(ctx, reqBase64) - if err != nil { - fmt.Printf("Image creation error: %v\n", err) - return - } - - imgBytes, err := base64.StdEncoding.DecodeString(respBase64.Data[0].B64JSON) - if err != nil { - fmt.Printf("Base64 decode error: %v\n", err) - return - } - - r := bytes.NewReader(imgBytes) - imgData, err := png.Decode(r) - if err != nil { - fmt.Printf("PNG decode error: %v\n", err) - return - } - - file, err := os.Create("example.png") - if err != nil { - fmt.Printf("File creation error: %v\n", err) - return - } - defer file.Close() - - if err := png.Encode(file, imgData); err != nil { - fmt.Printf("PNG encode error: %v\n", err) - return - } - - fmt.Println("The image was saved as example.png") -} - -``` -
- -
-GPT Image 1 image generation - -```go -package main - -import ( - "context" - "encoding/base64" - "fmt" - "os" - - openai "github.com/sashabaranov/go-openai" -) - -func main() { - c := openai.NewClient("your token") - ctx := context.Background() - - req := openai.ImageRequest{ - Prompt: "Parrot on a skateboard performing a trick. Large bold text \"SKATE MASTER\" banner at the bottom of the image. Cartoon style, natural light, high detail, 1:1 aspect ratio.", - Background: openai.CreateImageBackgroundOpaque, - Model: openai.CreateImageModelGptImage1, - Size: openai.CreateImageSize1024x1024, - N: 1, - Quality: openai.CreateImageQualityLow, - OutputCompression: 100, - OutputFormat: openai.CreateImageOutputFormatJPEG, - // Moderation: openai.CreateImageModerationLow, - // User: "", - } - - resp, err := c.CreateImage(ctx, req) - if err != nil { - fmt.Printf("Image creation Image generation with GPT Image 1error: %v\n", err) - return - } - - fmt.Println("Image Base64:", resp.Data[0].B64JSON) - - // Decode the base64 data - imgBytes, err := base64.StdEncoding.DecodeString(resp.Data[0].B64JSON) - if err != nil { - fmt.Printf("Base64 decode error: %v\n", err) - return - } - - // Write image to file - outputPath := "generated_image.jpg" - err = os.WriteFile(outputPath, imgBytes, 0644) - if err != nil { - fmt.Printf("Failed to write image file: %v\n", err) - return - } - - fmt.Printf("The image was saved as %s\n", outputPath) -} -``` -
- -
-Configuring proxy - -```go -config := openai.DefaultConfig("token") -proxyUrl, err := url.Parse("http://localhost:{port}") +first, err := client.CreateResponse(ctx, openai.CreateResponseRequest{ + Model: openai.GPT5Dot6Sol, + Instructions: "Answer as a travel guide.", + Input: "What should I see in Lisbon?", + Store: &store, +}) if err != nil { - panic(err) -} -transport := &http.Transport{ - Proxy: http.ProxyURL(proxyUrl), -} -config.HTTPClient = &http.Client{ - Transport: transport, + return err } -c := openai.NewClientWithConfig(config) +second, err := client.CreateResponse(ctx, openai.CreateResponseRequest{ + Model: openai.GPT5Dot6Sol, + Instructions: "Answer as a travel guide.", + Input: "Which one is best on a rainy day?", + PreviousResponseID: first.ID, + Store: &store, +}) +if err != nil { + return err +} + +fmt.Println(second.GetOutputText()) ``` -See also: https://pkg.go.dev/github.com/sashabaranov/go-openai#ClientConfig -
- -
-ChatGPT support context +### Stream output ```go -package main +stream, err := client.CreateResponseStream(ctx, openai.CreateResponseRequest{ + Model: openai.GPT5Dot6Sol, + Input: "Write a short story about a curious gopher.", +}) +if err != nil { + return err +} +defer stream.Close() -import ( - "bufio" - "context" - "fmt" - "os" - "strings" +for { + event, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return err + } + if event.Type == openai.ResponseStreamEventOutputTextDelta { + fmt.Print(event.Delta) + } +} +``` - "github.com/sashabaranov/go-openai" -) +## Choosing a model -func main() { - client := openai.NewClient("your token") - messages := make([]openai.ChatCompletionMessage, 0) - reader := bufio.NewReader(os.Stdin) - fmt.Println("Conversation") - fmt.Println("---------------------") +The current GPT-5.6 family exposes separate capability, balance, and efficiency +tiers. Pick the tier that matches the workload instead of using the flagship for +every request. - for { - fmt.Print("-> ") - text, _ := reader.ReadString('\n') - // convert CRLF to LF - text = strings.Replace(text, "\n", "", -1) - messages = append(messages, openai.ChatCompletionMessage{ +| Constant | Model ID | Typical use | +| --- | --- | --- | +| `GPT5Dot6Sol` | `gpt-5.6-sol` | Complex reasoning and coding | +| `GPT5Dot6Terra` | `gpt-5.6-terra` | Balance of intelligence and cost | +| `GPT5Dot6Luna` | `gpt-5.6-luna` | Cost-sensitive, high-volume work | +| `GPT5Dot6` | `gpt-5.6` | Family alias that currently routes to Sol | + +See the [OpenAI model catalog](https://developers.openai.com/api/docs/models) for +capabilities and availability. Model IDs are accepted as strings, so you can use +a model before a named constant is added to this package. + +## Chat Completions + +Chat Completions remains supported for existing integrations: + +```go +response, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ + Model: openai.GPT4oMini, + Messages: []openai.ChatCompletionMessage{ + { Role: openai.ChatMessageRoleUser, - Content: text, - }) - - resp, err := client.CreateChatCompletion( - context.Background(), - openai.ChatCompletionRequest{ - Model: openai.GPT3Dot5Turbo, - Messages: messages, - }, - ) - - if err != nil { - fmt.Printf("ChatCompletion error: %v\n", err) - continue - } - - content := resp.Choices[0].Message.Content - messages = append(messages, openai.ChatCompletionMessage{ - Role: openai.ChatMessageRoleAssistant, - Content: content, - }) - fmt.Println(content) - } -} -``` -
- -
-Azure OpenAI ChatGPT - -```go -package main - -import ( - "context" - "fmt" - - openai "github.com/sashabaranov/go-openai" -) - -func main() { - config := openai.DefaultAzureConfig("your Azure OpenAI Key", "https://your Azure OpenAI Endpoint") - // If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function - // config.AzureModelMapperFunc = func(model string) string { - // azureModelMapping := map[string]string{ - // "gpt-3.5-turbo": "your gpt-3.5-turbo deployment name", - // } - // return azureModelMapping[model] - // } - - client := openai.NewClientWithConfig(config) - resp, err := client.CreateChatCompletion( - context.Background(), - openai.ChatCompletionRequest{ - Model: openai.GPT3Dot5Turbo, - Messages: []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleUser, - Content: "Hello Azure OpenAI!", - }, - }, + Content: "Hello!", }, - ) - if err != nil { - fmt.Printf("ChatCompletion error: %v\n", err) - return - } - - fmt.Println(resp.Choices[0].Message.Content) + }, +}) +if err != nil { + return err } +fmt.Println(response.Choices[0].Message.Content) ``` -
-
-Embedding Semantic Similarity +For a new integration, prefer Responses unless you specifically need the Chat +Completions request or response shape. + +## Configuration + +Use `DefaultConfig` to customize the HTTP client, base URL, organization, or +headers before constructing a client: ```go -package main - -import ( - "context" - "log" - openai "github.com/sashabaranov/go-openai" - -) - -func main() { - client := openai.NewClient("your-token") - - // Create an EmbeddingRequest for the user query - queryReq := openai.EmbeddingRequest{ - Input: []string{"How many chucks would a woodchuck chuck"}, - Model: openai.AdaEmbeddingV2, - } - - // Create an embedding for the user query - queryResponse, err := client.CreateEmbeddings(context.Background(), queryReq) - if err != nil { - log.Fatal("Error creating query embedding:", err) - } - - // Create an EmbeddingRequest for the target text - targetReq := openai.EmbeddingRequest{ - Input: []string{"How many chucks would a woodchuck chuck if the woodchuck could chuck wood"}, - Model: openai.AdaEmbeddingV2, - } - - // Create an embedding for the target text - targetResponse, err := client.CreateEmbeddings(context.Background(), targetReq) - if err != nil { - log.Fatal("Error creating target embedding:", err) - } - - // Now that we have the embeddings for the user query and the target text, we - // can calculate their similarity. - queryEmbedding := queryResponse.Data[0] - targetEmbedding := targetResponse.Data[0] - - similarity, err := queryEmbedding.DotProduct(&targetEmbedding) - if err != nil { - log.Fatal("Error calculating dot product:", err) - } - - log.Printf("The similarity score between the query and the target is %f", similarity) -} - +config := openai.DefaultConfig(os.Getenv("OPENAI_API_KEY")) +config.BaseURL = "https://your-compatible-endpoint.example/v1" +client := openai.NewClientWithConfig(config) ``` -
-
-Azure OpenAI Embeddings +For Azure OpenAI, start with `DefaultAzureConfig` and configure the deployment +mapping or API version required by your Azure resource. + +## Error handling + +API failures can be inspected with `errors.As`: ```go -package main - -import ( - "context" - "fmt" - - openai "github.com/sashabaranov/go-openai" -) - -func main() { - - config := openai.DefaultAzureConfig("your Azure OpenAI Key", "https://your Azure OpenAI Endpoint") - config.APIVersion = "2023-05-15" // optional update to latest API version - - //If you use a deployment name different from the model name, you can customize the AzureModelMapperFunc function - //config.AzureModelMapperFunc = func(model string) string { - // azureModelMapping := map[string]string{ - // "gpt-3.5-turbo":"your gpt-3.5-turbo deployment name", - // } - // return azureModelMapping[model] - //} - - input := "Text to vectorize" - - client := openai.NewClientWithConfig(config) - resp, err := client.CreateEmbeddings( - context.Background(), - openai.EmbeddingRequest{ - Input: []string{input}, - Model: openai.AdaEmbeddingV2, - }) - - if err != nil { - fmt.Printf("CreateEmbeddings error: %v\n", err) - return - } - - vectors := resp.Data[0].Embedding // []float32 with 1536 dimensions - - fmt.Println(vectors[:10], "...", vectors[len(vectors)-10:]) -} -``` -
- -
-JSON Schema for function calling - -It is now possible for chat completion to choose to call a function for more information ([see developer docs here](https://platform.openai.com/docs/guides/gpt/function-calling)). - -In order to describe the type of functions that can be called, a JSON schema must be provided. Many JSON schema libraries exist and are more advanced than what we can offer in this library, however we have included a simple `jsonschema` package for those who want to use this feature without formatting their own JSON schema payload. - -The developer documents give this JSON schema definition as an example: - -```json -{ - "name":"get_current_weather", - "description":"Get the current weather in a given location", - "parameters":{ - "type":"object", - "properties":{ - "location":{ - "type":"string", - "description":"The city and state, e.g. San Francisco, CA" - }, - "unit":{ - "type":"string", - "enum":[ - "celsius", - "fahrenheit" - ] - } - }, - "required":[ - "location" - ] - } +var apiError *openai.APIError +if errors.As(err, &apiError) { + fmt.Printf("OpenAI error: status=%d code=%v message=%s\n", + apiError.HTTPStatusCode, apiError.Code, apiError.Message) } ``` -Using the `jsonschema` package, this schema could be created using structs as such: +## Examples -```go -FunctionDefinition{ - Name: "get_current_weather", - Parameters: jsonschema.Definition{ - Type: jsonschema.Object, - Properties: map[string]jsonschema.Definition{ - "location": { - Type: jsonschema.String, - Description: "The city and state, e.g. San Francisco, CA", - }, - "unit": { - Type: jsonschema.String, - Enum: []string{"celsius", "fahrenheit"}, - }, - }, - Required: []string{"location"}, - }, -} +Runnable examples live in [`examples/`](examples): + +- [Responses API with multi-turn state](examples/responses) +- [Chat Completions](examples/completion) +- [Chat Completions with a function tool](examples/completion-with-tool) +- [Image generation](examples/images) +- [Speech to text](examples/voice-to-text) + +To run one: + +```sh +go run ./examples/responses ``` -The `Parameters` field of a `FunctionDefinition` can accept either of the above styles, or even a nested struct from another library (as long as it can be marshalled into JSON). -
- -
-Error handling - -Open-AI maintains clear documentation on how to [handle API errors](https://platform.openai.com/docs/guides/error-codes/api-errors) - -example: -``` -e := &openai.APIError{} -if errors.As(err, &e) { - switch e.HTTPStatusCode { - case 401: - // invalid auth or key (do not retry) - case 429: - // rate limiting or engine overload (wait and retry) - case 500: - // openai server error (retry) - default: - // unhandled - } -} - -``` -
- -
-Fine Tune Model - -```go -package main - -import ( - "context" - "fmt" - "github.com/sashabaranov/go-openai" -) - -func main() { - client := openai.NewClient("your token") - ctx := context.Background() - - // create a .jsonl file with your training data for conversational model - // {"prompt": "", "completion": ""} - // {"prompt": "", "completion": ""} - // {"prompt": "", "completion": ""} - - // chat models are trained using the following file format: - // {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]} - // {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]} - // {"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]} - - // you can use openai cli tool to validate the data - // For more info - https://platform.openai.com/docs/guides/fine-tuning - - file, err := client.CreateFile(ctx, openai.FileRequest{ - FilePath: "training_prepared.jsonl", - Purpose: "fine-tune", - }) - if err != nil { - fmt.Printf("Upload JSONL file error: %v\n", err) - return - } - - // create a fine tuning job - // Streams events until the job is done (this often takes minutes, but can take hours if there are many jobs in the queue or your dataset is large) - // use below get method to know the status of your model - fineTuningJob, err := client.CreateFineTuningJob(ctx, openai.FineTuningJobRequest{ - TrainingFile: file.ID, - Model: "davinci-002", // gpt-3.5-turbo-0613, babbage-002. - }) - if err != nil { - fmt.Printf("Creating new fine tune model error: %v\n", err) - return - } - - fineTuningJob, err = client.RetrieveFineTuningJob(ctx, fineTuningJob.ID) - if err != nil { - fmt.Printf("Getting fine tune model error: %v\n", err) - return - } - fmt.Println(fineTuningJob.FineTunedModel) - - // once the status of fineTuningJob is `succeeded`, you can use your fine tune model in Completion Request or Chat Completion Request - - // resp, err := client.CreateCompletion(ctx, openai.CompletionRequest{ - // Model: fineTuningJob.FineTunedModel, - // Prompt: "your prompt", - // }) - // if err != nil { - // fmt.Printf("Create completion error %v\n", err) - // return - // } - // - // fmt.Println(resp.Choices[0].Text) -} -``` -
- -
-Structured Outputs - -```go -package main - -import ( - "context" - "fmt" - "log" - - "github.com/sashabaranov/go-openai" - "github.com/sashabaranov/go-openai/jsonschema" -) - -func main() { - client := openai.NewClient("your token") - ctx := context.Background() - - type Result struct { - Steps []struct { - Explanation string `json:"explanation"` - Output string `json:"output"` - } `json:"steps"` - FinalAnswer string `json:"final_answer"` - } - var result Result - schema, err := jsonschema.GenerateSchemaForType(result) - if err != nil { - log.Fatalf("GenerateSchemaForType error: %v", err) - } - resp, err := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ - Model: openai.GPT4oMini, - Messages: []openai.ChatCompletionMessage{ - { - Role: openai.ChatMessageRoleSystem, - Content: "You are a helpful math tutor. Guide the user through the solution step by step.", - }, - { - Role: openai.ChatMessageRoleUser, - Content: "how can I solve 8x + 7 = -23", - }, - }, - ResponseFormat: &openai.ChatCompletionResponseFormat{ - Type: openai.ChatCompletionResponseFormatTypeJSONSchema, - JSONSchema: &openai.ChatCompletionResponseFormatJSONSchema{ - Name: "math_reasoning", - Schema: schema, - Strict: true, - }, - }, - }) - if err != nil { - log.Fatalf("CreateChatCompletion error: %v", err) - } - err = schema.Unmarshal(resp.Choices[0].Message.Content, &result) - if err != nil { - log.Fatalf("Unmarshal schema error: %v", err) - } - fmt.Println(result) -} -``` -
-See the `examples/` folder for more. - -## Frequently Asked Questions - -### Why don't we get the same answer when specifying a temperature field of 0 and asking the same question? - -Even when specifying a temperature field of 0, it doesn't guarantee that you'll always get the same response. Several factors come into play. - -1. Go OpenAI Behavior: When you specify a temperature field of 0 in Go OpenAI, the omitempty tag causes that field to be removed from the request. Consequently, the OpenAI API applies the default value of 1. -2. Token Count for Input/Output: If there's a large number of tokens in the input and output, setting the temperature to 0 can still result in non-deterministic behavior. In particular, when using around 32k tokens, the likelihood of non-deterministic behavior becomes highest even with a temperature of 0. - -Due to the factors mentioned above, different answers may be returned even for the same question. - -**Workarounds:** -1. As of November 2023, use [the new `seed` parameter](https://platform.openai.com/docs/guides/text-generation/reproducible-outputs) in conjunction with the `system_fingerprint` response field, alongside Temperature management. -2. Try using `math.SmallestNonzeroFloat32`: By specifying `math.SmallestNonzeroFloat32` in the temperature field instead of 0, you can mimic the behavior of setting it to 0. -3. Limiting Token Count: By limiting the number of tokens in the input and output and especially avoiding large requests close to 32k tokens, you can reduce the risk of non-deterministic behavior. - -By adopting these strategies, you can expect more consistent results. - -**Related Issues:** -[omitempty option of request struct will generate incorrect request when parameter is 0.](https://github.com/sashabaranov/go-openai/issues/9) - -### Does Go OpenAI provide a method to count tokens? - -No, Go OpenAI does not offer a feature to count tokens, and there are no plans to provide such a feature in the future. However, if there's a way to implement a token counting feature with zero dependencies, it might be possible to merge that feature into Go OpenAI. Otherwise, it would be more appropriate to implement it in a dedicated library or repository. - -For counting tokens, you might find the following links helpful: -- [Counting Tokens For Chat API Calls](https://github.com/pkoukk/tiktoken-go#counting-tokens-for-chat-api-calls) -- [How to count tokens with tiktoken](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb) - -**Related Issues:** -[Is it possible to join the implementation of GPT3 Tokenizer](https://github.com/sashabaranov/go-openai/issues/62) - ## Contributing -By following [Contributing Guidelines](https://github.com/sashabaranov/go-openai/blob/master/CONTRIBUTING.md), we hope to ensure that your contributions are made smoothly and efficiently. +See the [contributing guidelines](CONTRIBUTING.md) before opening a pull request. ## Thank you -We want to take a moment to express our deepest gratitude to the [contributors](https://github.com/sashabaranov/go-openai/graphs/contributors) and sponsors of this project: -- [Carson Kahn](https://carsonkahn.com) of [Spindle AI](https://spindleai.com) - -To all of you: thank you. You've helped us achieve more than we ever imagined possible. Can't wait to see where we go next, together! +Thank you to all of the project's +[contributors](https://github.com/sashabaranov/go-openai/graphs/contributors) +and sponsors, including [Carson Kahn](https://carsonkahn.com) of +[Spindle AI](https://spindleai.com). diff --git a/vendor/github.com/sashabaranov/go-openai/audio.go b/vendor/github.com/sashabaranov/go-openai/audio.go index f321f93..35fdb61 100644 --- a/vendor/github.com/sashabaranov/go-openai/audio.go +++ b/vendor/github.com/sashabaranov/go-openai/audio.go @@ -11,9 +11,13 @@ import ( utils "github.com/sashabaranov/go-openai/internal" ) -// Whisper Defines the models provided by OpenAI to use when processing audio with OpenAI. +// Audio transcription models provided by OpenAI. const ( - Whisper1 = "whisper-1" + Whisper1 = "whisper-1" + GPT4oTranscribe = "gpt-4o-transcribe" + GPT4oMiniTranscribe = "gpt-4o-mini-transcribe" + GPT4oTranscribeDiarize = "gpt-4o-transcribe-diarize" + GPTTranscribe = "gpt-transcribe" ) // Response formats; Whisper uses AudioResponseFormatJSON by default. diff --git a/vendor/github.com/sashabaranov/go-openai/batch.go b/vendor/github.com/sashabaranov/go-openai/batch.go index 3c1a9d0..53e66d4 100644 --- a/vendor/github.com/sashabaranov/go-openai/batch.go +++ b/vendor/github.com/sashabaranov/go-openai/batch.go @@ -17,6 +17,7 @@ const ( BatchEndpointChatCompletions BatchEndpoint = "/v1/chat/completions" BatchEndpointCompletions BatchEndpoint = "/v1/completions" BatchEndpointEmbeddings BatchEndpoint = "/v1/embeddings" + BatchEndpointResponses BatchEndpoint = "/v1/responses" ) type BatchLineItem interface { @@ -54,6 +55,18 @@ type BatchEmbeddingRequest struct { URL BatchEndpoint `json:"url"` } +type BatchResponseRequest struct { + CustomID string `json:"custom_id"` + Body CreateResponseRequest `json:"body"` + Method string `json:"method"` + URL BatchEndpoint `json:"url"` +} + +func (r BatchResponseRequest) MarshalBatchLineItem() []byte { + marshal, _ := json.Marshal(r) + return marshal +} + func (r BatchEmbeddingRequest) MarshalBatchLineItem() []byte { marshal, _ := json.Marshal(r) return marshal @@ -146,7 +159,7 @@ func (r *UploadBatchFileRequest) AddChatCompletion(customerID string, body ChatC r.Lines = append(r.Lines, BatchChatCompletionRequest{ CustomID: customerID, Body: body, - Method: "POST", + Method: http.MethodPost, URL: BatchEndpointChatCompletions, }) } @@ -155,7 +168,7 @@ func (r *UploadBatchFileRequest) AddCompletion(customerID string, body Completio r.Lines = append(r.Lines, BatchCompletionRequest{ CustomID: customerID, Body: body, - Method: "POST", + Method: http.MethodPost, URL: BatchEndpointCompletions, }) } @@ -164,11 +177,20 @@ func (r *UploadBatchFileRequest) AddEmbedding(customerID string, body EmbeddingR r.Lines = append(r.Lines, BatchEmbeddingRequest{ CustomID: customerID, Body: body, - Method: "POST", + Method: http.MethodPost, URL: BatchEndpointEmbeddings, }) } +func (r *UploadBatchFileRequest) AddResponse(customerID string, body CreateResponseRequest) { + r.Lines = append(r.Lines, BatchResponseRequest{ + CustomID: customerID, + Body: body, + Method: http.MethodPost, + URL: BatchEndpointResponses, + }) +} + // UploadBatchFile — upload batch file. func (c *Client) UploadBatchFile(ctx context.Context, request UploadBatchFileRequest) (File, error) { if request.FileName == "" { diff --git a/vendor/github.com/sashabaranov/go-openai/chat.go b/vendor/github.com/sashabaranov/go-openai/chat.go index 0aa0187..d085cc9 100644 --- a/vendor/github.com/sashabaranov/go-openai/chat.go +++ b/vendor/github.com/sashabaranov/go-openai/chat.go @@ -265,6 +265,7 @@ type ChatCompletionRequest struct { Messages []ChatCompletionMessage `json:"messages"` // MaxTokens The maximum number of tokens that can be generated in the chat completion. // This value can be used to control costs for text generated via API. + // // Deprecated: use MaxCompletionTokens. Not compatible with o1-series models. // refs: https://platform.openai.com/docs/api-reference/chat/create#chat-create-max_tokens MaxTokens int `json:"max_tokens,omitempty"` @@ -274,7 +275,7 @@ type ChatCompletionRequest struct { Temperature float32 `json:"temperature,omitempty"` TopP float32 `json:"top_p,omitempty"` N int `json:"n,omitempty"` - Stream bool `json:"stream,omitempty"` + Stream bool `json:"stream"` Stop []string `json:"stop,omitempty"` PresencePenalty float32 `json:"presence_penalty,omitempty"` ResponseFormat *ChatCompletionResponseFormat `json:"response_format,omitempty"` @@ -346,12 +347,45 @@ type StreamOptions struct { type ToolType string const ( - ToolTypeFunction ToolType = "function" + ToolTypeFunction ToolType = "function" + ToolTypeWebSearch ToolType = "web_search" + ToolTypeWebSearchPreview ToolType = "web_search_preview" + ToolTypeFileSearch ToolType = "file_search" + ToolTypeComputer ToolType = "computer" + ToolTypeComputerUsePreview ToolType = "computer_use_preview" + ToolTypeComputerUse ToolType = "computer_use" + ToolTypeCodeInterpreter ToolType = "code_interpreter" + ToolTypeImageGeneration ToolType = "image_generation" + ToolTypeMCP ToolType = "mcp" + ToolTypeCustom ToolType = "custom" + ToolTypeLocalShell ToolType = "local_shell" + ToolTypeShell ToolType = "shell" + ToolTypeApplyPatch ToolType = "apply_patch" + ToolTypeToolSearch ToolType = "tool_search" ) type Tool struct { Type ToolType `json:"type"` Function *FunctionDefinition `json:"function,omitempty"` + // Parameters contains Responses API tool properties that are serialized next to type. + // For example: {"search_context_size": "low"} for a web search tool. + Parameters map[string]any `json:"-"` +} + +// MarshalJSON preserves the nested Chat Completions function format while also +// supporting the inline properties used by built-in and function tools in the Responses API. +func (t Tool) MarshalJSON() ([]byte, error) { + tool := map[string]any{"type": t.Type} + if t.Function != nil { + tool["function"] = t.Function + } + for key, value := range t.Parameters { + if key == "type" || key == "function" { + return nil, errors.New("tool parameters cannot override type or function") + } + tool[key] = value + } + return json.Marshal(tool) } type ToolChoice struct { diff --git a/vendor/github.com/sashabaranov/go-openai/client.go b/vendor/github.com/sashabaranov/go-openai/client.go index 413b8db..dc0f73c 100644 --- a/vendor/github.com/sashabaranov/go-openai/client.go +++ b/vendor/github.com/sashabaranov/go-openai/client.go @@ -257,9 +257,9 @@ func withModel(model string) fullURLOption { } var azureDeploymentsEndpoints = []string{ - "/completions", + completionsSuffix, "/embeddings", - "/chat/completions", + chatCompletionsSuffix, "/audio/transcriptions", "/audio/translations", "/audio/speech", diff --git a/vendor/github.com/sashabaranov/go-openai/completion.go b/vendor/github.com/sashabaranov/go-openai/completion.go index 27d69f5..e322d1b 100644 --- a/vendor/github.com/sashabaranov/go-openai/completion.go +++ b/vendor/github.com/sashabaranov/go-openai/completion.go @@ -5,10 +5,9 @@ import ( "net/http" ) -// GPT3 Defines the models provided by OpenAI to use when generating -// completions from OpenAI. -// GPT3 Models are designed for text-based tasks. For code-specific -// tasks, please refer to the Codex series of models. +const completionsSuffix = "/completions" + +// Text generation and reasoning models provided by OpenAI. const ( O1Mini = "o1-mini" O1Mini20240912 = "o1-mini-2024-09-12" @@ -20,8 +19,11 @@ const ( O320250416 = "o3-2025-04-16" O3Mini = "o3-mini" O3Mini20250131 = "o3-mini-2025-01-31" + O3Pro = "o3-pro" + O3DeepResearch = "o3-deep-research" O4Mini = "o4-mini" O4Mini20250416 = "o4-mini-2025-04-16" + O4MiniDeepResearch = "o4-mini-deep-research" GPT432K0613 = "gpt-4-32k-0613" GPT432K0314 = "gpt-4-32k-0314" GPT432K = "gpt-4-32k" @@ -52,7 +54,30 @@ const ( GPT5 = "gpt-5" GPT5Mini = "gpt-5-mini" GPT5Nano = "gpt-5-nano" + GPT5Pro = "gpt-5-pro" GPT5ChatLatest = "gpt-5-chat-latest" + GPT5Codex = "gpt-5-codex" + GPT5Dot1 = "gpt-5.1" + GPT5Dot1ChatLatest = "gpt-5.1-chat-latest" + GPT5Dot1Codex = "gpt-5.1-codex" + GPT5Dot1CodexMini = "gpt-5.1-codex-mini" + GPT5Dot1CodexMax = "gpt-5.1-codex-max" + GPT5Dot2 = "gpt-5.2" + GPT5Dot2ChatLatest = "gpt-5.2-chat-latest" + GPT5Dot2Pro = "gpt-5.2-pro" + GPT5Dot2Codex = "gpt-5.2-codex" + GPT5Dot3ChatLatest = "gpt-5.3-chat-latest" + GPT5Dot3Codex = "gpt-5.3-codex" + GPT5Dot4 = "gpt-5.4" + GPT5Dot4Mini = "gpt-5.4-mini" + GPT5Dot4Nano = "gpt-5.4-nano" + GPT5Dot4Pro = "gpt-5.4-pro" + GPT5Dot5 = "gpt-5.5" + GPT5Dot5Pro = "gpt-5.5-pro" + GPT5Dot6 = "gpt-5.6" + GPT5Dot6Sol = "gpt-5.6-sol" + GPT5Dot6Terra = "gpt-5.6-terra" + GPT5Dot6Luna = "gpt-5.6-luna" GPT3Dot5Turbo0125 = "gpt-3.5-turbo-0125" GPT3Dot5Turbo1106 = "gpt-3.5-turbo-1106" GPT3Dot5Turbo0613 = "gpt-3.5-turbo-0613" @@ -100,15 +125,18 @@ const ( ) var disabledModelsForEndpoints = map[string]map[string]bool{ - "/completions": { + completionsSuffix: { O1Mini: true, O1Mini20240912: true, O1Preview: true, O1Preview20240912: true, O3Mini: true, O3Mini20250131: true, + O3Pro: true, + O3DeepResearch: true, O4Mini: true, O4Mini20250416: true, + O4MiniDeepResearch: true, O3: true, O320250416: true, GPT3Dot5Turbo: true, @@ -149,7 +177,30 @@ var disabledModelsForEndpoints = map[string]map[string]bool{ GPT5: true, GPT5Mini: true, GPT5Nano: true, + GPT5Pro: true, GPT5ChatLatest: true, + GPT5Codex: true, + GPT5Dot1: true, + GPT5Dot1ChatLatest: true, + GPT5Dot1Codex: true, + GPT5Dot1CodexMini: true, + GPT5Dot1CodexMax: true, + GPT5Dot2: true, + GPT5Dot2ChatLatest: true, + GPT5Dot2Pro: true, + GPT5Dot2Codex: true, + GPT5Dot3ChatLatest: true, + GPT5Dot3Codex: true, + GPT5Dot4: true, + GPT5Dot4Mini: true, + GPT5Dot4Nano: true, + GPT5Dot4Pro: true, + GPT5Dot5: true, + GPT5Dot5Pro: true, + GPT5Dot6: true, + GPT5Dot6Sol: true, + GPT5Dot6Terra: true, + GPT5Dot6Luna: true, }, chatCompletionsSuffix: { CodexCodeDavinci002: true, @@ -218,7 +269,7 @@ type CompletionRequest struct { PresencePenalty float32 `json:"presence_penalty,omitempty"` Seed *int `json:"seed,omitempty"` Stop []string `json:"stop,omitempty"` - Stream bool `json:"stream,omitempty"` + Stream bool `json:"stream"` Suffix string `json:"suffix,omitempty"` Temperature float32 `json:"temperature,omitempty"` TopP float32 `json:"top_p,omitempty"` @@ -269,7 +320,7 @@ func (c *Client) CreateCompletion( return } - urlSuffix := "/completions" + urlSuffix := completionsSuffix if !checkEndpointSupportsModel(urlSuffix, request.Model) { err = ErrCompletionUnsupportedModel return diff --git a/vendor/github.com/sashabaranov/go-openai/config.go b/vendor/github.com/sashabaranov/go-openai/config.go index 4788ba6..4b8cfb6 100644 --- a/vendor/github.com/sashabaranov/go-openai/config.go +++ b/vendor/github.com/sashabaranov/go-openai/config.go @@ -3,6 +3,7 @@ package openai import ( "net/http" "regexp" + "strings" ) const ( @@ -70,7 +71,11 @@ func DefaultAzureConfig(apiKey, baseURL string) ClientConfig { APIType: APITypeAzure, APIVersion: "2023-05-15", AzureModelMapperFunc: func(model string) string { - return regexp.MustCompile(`[.:]`).ReplaceAllString(model, "") + // only 3.5 models have the "." stripped in their names + if strings.Contains(model, "3.5") { + return regexp.MustCompile(`[.:]`).ReplaceAllString(model, "") + } + return strings.ReplaceAll(model, ":", "") }, HTTPClient: &http.Client{}, diff --git a/vendor/github.com/sashabaranov/go-openai/fine_tunes.go b/vendor/github.com/sashabaranov/go-openai/fine_tunes.go index 74b47bf..b3d76fa 100644 --- a/vendor/github.com/sashabaranov/go-openai/fine_tunes.go +++ b/vendor/github.com/sashabaranov/go-openai/fine_tunes.go @@ -111,6 +111,7 @@ func (c *Client) CreateFineTune(ctx context.Context, request FineTuneRequest) (r } // CancelFineTune cancel a fine-tune job. +// // Deprecated: On August 22nd, 2023, OpenAI announced the deprecation of the /v1/fine-tunes API. // This API will be officially deprecated on January 4th, 2024. // OpenAI recommends to migrate to the new fine tuning API implemented in fine_tuning_job.go. diff --git a/vendor/github.com/sashabaranov/go-openai/image.go b/vendor/github.com/sashabaranov/go-openai/image.go index 84b9daf..76a8430 100644 --- a/vendor/github.com/sashabaranov/go-openai/image.go +++ b/vendor/github.com/sashabaranov/go-openai/image.go @@ -18,7 +18,7 @@ const ( CreateImageSize1792x1024 = "1792x1024" CreateImageSize1024x1792 = "1024x1792" - // gpt-image-1 supported only. + // GPT Image models only. CreateImageSize1536x1024 = "1536x1024" // Landscape CreateImageSize1024x1536 = "1024x1536" // Portrait ) @@ -30,16 +30,20 @@ const ( ) const ( - CreateImageModelDallE2 = "dall-e-2" - CreateImageModelDallE3 = "dall-e-3" - CreateImageModelGptImage1 = "gpt-image-1" + CreateImageModelDallE2 = "dall-e-2" + CreateImageModelDallE3 = "dall-e-3" + CreateImageModelGptImage1 = "gpt-image-1" + CreateImageModelGptImage1Mini = "gpt-image-1-mini" + CreateImageModelGptImage1Dot5 = "gpt-image-1.5" + CreateImageModelGptImage2 = "gpt-image-2" + CreateImageModelChatGPTImageLatest = "chatgpt-image-latest" ) const ( CreateImageQualityHD = "hd" CreateImageQualityStandard = "standard" - // gpt-image-1 only. + // GPT Image models only. CreateImageQualityHigh = "high" CreateImageQualityMedium = "medium" CreateImageQualityLow = "low" @@ -52,18 +56,18 @@ const ( ) const ( - // gpt-image-1 only. + // GPT Image models only. CreateImageBackgroundTransparent = "transparent" CreateImageBackgroundOpaque = "opaque" ) const ( - // gpt-image-1 only. + // GPT Image models only. CreateImageModerationLow = "low" ) const ( - // gpt-image-1 only. + // GPT Image models only. CreateImageOutputFormatPNG = "png" CreateImageOutputFormatJPEG = "jpeg" CreateImageOutputFormatWEBP = "webp" @@ -195,6 +199,27 @@ func (c *Client) CreateEditImage(ctx context.Context, request ImageEditRequest) return } + if request.Model != "" { + err = builder.WriteField("model", request.Model) + if err != nil { + return + } + } + + if request.Quality != "" { + err = builder.WriteField("quality", request.Quality) + if err != nil { + return + } + } + + if request.User != "" { + err = builder.WriteField("user", request.User) + if err != nil { + return + } + } + err = builder.WriteField("n", strconv.Itoa(request.N)) if err != nil { return diff --git a/vendor/github.com/sashabaranov/go-openai/internal/form_builder.go b/vendor/github.com/sashabaranov/go-openai/internal/form_builder.go index a17e820..2ad5374 100644 --- a/vendor/github.com/sashabaranov/go-openai/internal/form_builder.go +++ b/vendor/github.com/sashabaranov/go-openai/internal/form_builder.go @@ -3,6 +3,7 @@ package openai import ( "fmt" "io" + "mime" "mime/multipart" "net/textproto" "os" @@ -50,6 +51,19 @@ func (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r io.Reader if f, ok := r.(interface{ ContentType() string }); ok { contentType = f.ContentType() } + // The OpenAI API rejects the file part when Content-Type is + // missing, so when the reader doesn't supply one and we can't + // derive it from the filename extension, fall back to the same + // default the stdlib's multipart.Writer.CreateFormFile uses. + // See #1010. + if contentType == "" { + if ext := filepath.Ext(filename); ext != "" { + contentType = mime.TypeByExtension(ext) + } + if contentType == "" { + contentType = "application/octet-stream" + } + } h := make(textproto.MIMEHeader) h.Set( @@ -60,10 +74,7 @@ func (fb *DefaultFormBuilder) CreateFormFileReader(fieldname string, r io.Reader escapeQuotes(filepath.Base(filename)), ), ) - // content type is optional, but it can be set - if contentType != "" { - h.Set("Content-Type", contentType) - } + h.Set("Content-Type", contentType) fieldWriter, err := fb.writer.CreatePart(h) if err != nil { diff --git a/vendor/github.com/sashabaranov/go-openai/jsonschema/json.go b/vendor/github.com/sashabaranov/go-openai/jsonschema/json.go index 75e3b51..dbbb494 100644 --- a/vendor/github.com/sashabaranov/go-openai/jsonschema/json.go +++ b/vendor/github.com/sashabaranov/go-openai/jsonschema/json.go @@ -140,7 +140,7 @@ func reflectSchema(t reflect.Type, defs map[string]Definition) (*Definition, err return nil, err } d = *object - case reflect.Ptr: + case reflect.Pointer: definition, err := reflectSchema(t.Elem(), defs) if err != nil { return nil, err diff --git a/vendor/github.com/sashabaranov/go-openai/response.go b/vendor/github.com/sashabaranov/go-openai/response.go new file mode 100644 index 0000000..54d3216 --- /dev/null +++ b/vendor/github.com/sashabaranov/go-openai/response.go @@ -0,0 +1,597 @@ +package openai + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" +) + +const responsesSuffix = "/responses" + +var ErrResponseStreamNotSupported = errors.New( + "streaming is not supported with this method, please use CreateResponseStream", +) + +// ResponseInclude identifies optional data to include in a response. +type ResponseInclude string + +const ( + ResponseIncludeFileSearchCallResults ResponseInclude = "file_search_call.results" + ResponseIncludeWebSearchCallResults ResponseInclude = "web_search_call.results" + ResponseIncludeWebSearchCallActionSources ResponseInclude = "web_search_call.action.sources" + ResponseIncludeInputImageURL ResponseInclude = "message.input_image.image_url" + ResponseIncludeComputerCallOutputImageURL ResponseInclude = "computer_call_output.output.image_url" + ResponseIncludeCodeInterpreterCallOutputs ResponseInclude = "code_interpreter_call.outputs" + ResponseIncludeReasoningEncryptedContent ResponseInclude = "reasoning.encrypted_content" + ResponseIncludeMessageOutputTextLogprobs ResponseInclude = "message.output_text.logprobs" +) + +// ResponseStatus is the lifecycle status of a response. +type ResponseStatus string + +const ( + ResponseStatusQueued ResponseStatus = "queued" + ResponseStatusInProgress ResponseStatus = "in_progress" + ResponseStatusCompleted ResponseStatus = "completed" + ResponseStatusFailed ResponseStatus = "failed" + ResponseStatusIncomplete ResponseStatus = "incomplete" + ResponseStatusCancelling ResponseStatus = "cancelling" + ResponseStatusCancelled ResponseStatus = "cancelled" +) + +// ResponseTruncation controls how input that exceeds the context window is handled. +type ResponseTruncation string + +const ( + ResponseTruncationAuto ResponseTruncation = "auto" + ResponseTruncationDisabled ResponseTruncation = "disabled" +) + +// ResponseTool is an alias for Tool. Responses API tool-specific properties can +// be supplied through Tool.Parameters. +type ResponseTool = Tool + +// NewResponseFunctionTool converts a function definition to the inline function +// tool representation expected by the Responses API. +func NewResponseFunctionTool(function FunctionDefinition) ResponseTool { + parameters := map[string]any{ + "name": function.Name, + "parameters": function.Parameters, + } + if function.Description != "" { + parameters["description"] = function.Description + } + if function.Strict { + parameters["strict"] = true + } + return ResponseTool{Type: ToolTypeFunction, Parameters: parameters} +} + +// ResponseReasoning represents reasoning configuration for the Responses API. +type ResponseReasoning struct { + Effort string `json:"effort,omitempty"` + GenerateSummary string `json:"generate_summary,omitempty"` + Summary string `json:"summary,omitempty"` + Context string `json:"context,omitempty"` + Mode string `json:"mode,omitempty"` +} + +// ResponseStreamOptions controls Responses API streaming behavior. +type ResponseStreamOptions struct { + IncludeObfuscation *bool `json:"include_obfuscation,omitempty"` +} + +// ResponseTextConfig controls plain-text or structured response output. +type ResponseTextConfig struct { + Format *ResponseTextFormat `json:"format,omitempty"` + Verbosity string `json:"verbosity,omitempty"` +} + +// ResponseTextFormat describes the requested output format. +type ResponseTextFormat struct { + Type string `json:"type"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Schema any `json:"schema,omitempty"` + Strict bool `json:"strict,omitempty"` +} + +// ResponsePrompt references a reusable prompt template. +type ResponsePrompt struct { + ID string `json:"id"` + Variables map[string]any `json:"variables,omitempty"` + Version string `json:"version,omitempty"` +} + +// ResponsePromptCacheOptions controls prompt cache creation. +type ResponsePromptCacheOptions struct { + Mode string `json:"mode,omitempty"` + TTL string `json:"ttl,omitempty"` +} + +// ResponsePromptCacheBreakpoint marks the end of a reusable prompt prefix. +type ResponsePromptCacheBreakpoint struct { + Mode string `json:"mode"` +} + +// CreateResponseRequest represents a request to the Responses API. Input may be +// a string or a slice of response input items. +type CreateResponseRequest struct { + Background bool `json:"background,omitempty"` + ContextManagement []any `json:"context_management,omitempty"` + Conversation any `json:"conversation,omitempty"` + Include []ResponseInclude `json:"include,omitempty"` + Input any `json:"input"` + Instructions string `json:"instructions,omitempty"` + MaxOutputTokens int `json:"max_output_tokens,omitempty"` + MaxToolCalls int `json:"max_tool_calls,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + Model string `json:"model,omitempty"` + Moderation any `json:"moderation,omitempty"` + ParallelToolCalls *bool `json:"parallel_tool_calls,omitempty"` + PreviousResponseID string `json:"previous_response_id,omitempty"` + Prompt *ResponsePrompt `json:"prompt,omitempty"` + PromptCacheKey string `json:"prompt_cache_key,omitempty"` + PromptCacheOptions *ResponsePromptCacheOptions `json:"prompt_cache_options,omitempty"` + PromptCacheRetention string `json:"prompt_cache_retention,omitempty"` + Reasoning *ResponseReasoning `json:"reasoning,omitempty"` + SafetyIdentifier string `json:"safety_identifier,omitempty"` + ServiceTier string `json:"service_tier,omitempty"` + Store *bool `json:"store,omitempty"` + Stream bool `json:"stream,omitempty"` + StreamOptions *ResponseStreamOptions `json:"stream_options,omitempty"` + Temperature *float32 `json:"temperature,omitempty"` + Text *ResponseTextConfig `json:"text,omitempty"` + ToolChoice any `json:"tool_choice,omitempty"` + Tools []ResponseTool `json:"tools,omitempty"` + TopLogprobs int `json:"top_logprobs,omitempty"` + TopP *float32 `json:"top_p,omitempty"` + Truncation ResponseTruncation `json:"truncation,omitempty"` + User string `json:"user,omitempty"` + ExtraBody map[string]any `json:"-"` +} + +// MarshalJSON merges ExtraBody into the request payload. ExtraBody values take +// precedence over fields represented directly by CreateResponseRequest. +func (r CreateResponseRequest) MarshalJSON() ([]byte, error) { + type requestAlias CreateResponseRequest + base, err := json.Marshal(requestAlias(r)) + if err != nil || len(r.ExtraBody) == 0 { + return base, err + } + + var body map[string]any + if err = json.Unmarshal(base, &body); err != nil { + return nil, err + } + for key, value := range r.ExtraBody { + body[key] = value + } + return json.Marshal(body) +} + +// ResponseInputMessage is a message supplied as structured input. +type ResponseInputMessage struct { + Type string `json:"type,omitempty"` + Role string `json:"role"` + Content any `json:"content"` + Status string `json:"status,omitempty"` + Phase string `json:"phase,omitempty"` +} + +// ResponseInputText is a text content part in a structured input message. +type ResponseInputText struct { + Type string `json:"type"` + Text string `json:"text"` + PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"` +} + +// ResponseInputImage is an image content part in a structured input message. +type ResponseInputImage struct { + Type string `json:"type"` + Detail string `json:"detail,omitempty"` + FileID string `json:"file_id,omitempty"` + ImageURL string `json:"image_url,omitempty"` + PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"` +} + +// ResponseInputFile is a file content part in a structured input message. +type ResponseInputFile struct { + Type string `json:"type"` + FileData string `json:"file_data,omitempty"` + FileID string `json:"file_id,omitempty"` + FileURL string `json:"file_url,omitempty"` + Filename string `json:"filename,omitempty"` + Detail string `json:"detail,omitempty"` + PromptCacheBreakpoint *ResponsePromptCacheBreakpoint `json:"prompt_cache_breakpoint,omitempty"` +} + +// ResponseFunctionCallOutput supplies the result of a prior function call. +type ResponseFunctionCallOutput struct { + Type string `json:"type"` + CallID string `json:"call_id"` + Output any `json:"output"` + Status string `json:"status,omitempty"` +} + +// ResponseError is an error embedded in an otherwise successful Responses API request. +type ResponseError struct { + Code string `json:"code"` + Message string `json:"message"` +} + +// ResponseIncompleteDetails explains why a response did not complete. +type ResponseIncompleteDetails struct { + Reason string `json:"reason,omitempty"` +} + +// ResponseConversation identifies the conversation associated with a response. +type ResponseConversation struct { + ID string `json:"id"` +} + +// ResponseUsage reports token use for a response. +type ResponseUsage struct { + InputTokens int `json:"input_tokens"` + InputTokensDetails *ResponseInputTokensDetails `json:"input_tokens_details,omitempty"` + OutputTokens int `json:"output_tokens"` + OutputTokensDetails *ResponseOutputTokensDetails `json:"output_tokens_details,omitempty"` + TotalTokens int `json:"total_tokens"` +} + +// ResponseInputTokensDetails is the input-token usage breakdown. +type ResponseInputTokensDetails struct { + CachedTokens int `json:"cached_tokens"` + CacheWriteTokens int `json:"cache_write_tokens"` +} + +// ResponseOutputTokensDetails is the output-token usage breakdown. +type ResponseOutputTokensDetails struct { + ReasoningTokens int `json:"reasoning_tokens"` +} + +// ResponseAnnotation describes a citation or file annotation in output text. +type ResponseAnnotation struct { + Type string `json:"type"` + FileID string `json:"file_id,omitempty"` + Filename string `json:"filename,omitempty"` + Index int `json:"index,omitempty"` + StartIndex int `json:"start_index,omitempty"` + EndIndex int `json:"end_index,omitempty"` + URL string `json:"url,omitempty"` + Title string `json:"title,omitempty"` +} + +// ResponseLogprob contains token log-probability information. +type ResponseLogprob struct { + Token string `json:"token"` + Bytes []int64 `json:"bytes,omitempty"` + Logprob float64 `json:"logprob"` + TopLogprobs []ResponseLogprob `json:"top_logprobs,omitempty"` +} + +// ResponseOutputContent is a text or refusal content part in an output message. +type ResponseOutputContent struct { + Type string `json:"type"` + Text string `json:"text,omitempty"` + Refusal string `json:"refusal,omitempty"` + Annotations []ResponseAnnotation `json:"annotations,omitempty"` + Logprobs []ResponseLogprob `json:"logprobs,omitempty"` +} + +// ResponseSummaryPart is a reasoning summary content part. +type ResponseSummaryPart struct { + Type string `json:"type"` + Text string `json:"text"` +} + +// ResponseOutputItem contains the common fields shared by response output item variants. +// The top-level Output field remains []any so new variants can be consumed without a library release. +type ResponseOutputItem struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Status string `json:"status,omitempty"` + Role string `json:"role,omitempty"` + Content []ResponseOutputContent `json:"content,omitempty"` + CallID string `json:"call_id,omitempty"` + Name string `json:"name,omitempty"` + Arguments string `json:"arguments,omitempty"` + Summary []ResponseSummaryPart `json:"summary,omitempty"` + Action any `json:"action,omitempty"` + Results any `json:"results,omitempty"` + Output any `json:"output,omitempty"` +} + +// CreateResponseResponse represents a response returned by the Responses API. +type CreateResponseResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created_at"` + CompletedAt *int64 `json:"completed_at,omitempty"` + Status ResponseStatus `json:"status,omitempty"` + Error *ResponseError `json:"error,omitempty"` + IncompleteDetails *ResponseIncompleteDetails `json:"incomplete_details,omitempty"` + Instructions any `json:"instructions,omitempty"` + MaxOutputTokens *int `json:"max_output_tokens,omitempty"` + MaxToolCalls *int `json:"max_tool_calls,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` + Model string `json:"model"` + Moderation any `json:"moderation,omitempty"` + Output []any `json:"output"` + OutputText string `json:"output_text,omitempty"` + ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"` + PreviousResponseID string `json:"previous_response_id,omitempty"` + Reasoning *ResponseReasoning `json:"reasoning,omitempty"` + ServiceTier string `json:"service_tier,omitempty"` + Store bool `json:"store,omitempty"` + Temperature *float32 `json:"temperature,omitempty"` + Text *ResponseTextConfig `json:"text,omitempty"` + ToolChoice any `json:"tool_choice,omitempty"` + Tools []any `json:"tools,omitempty"` + TopLogprobs int `json:"top_logprobs,omitempty"` + TopP *float32 `json:"top_p,omitempty"` + Truncation ResponseTruncation `json:"truncation,omitempty"` + Usage *ResponseUsage `json:"usage,omitempty"` + Background *bool `json:"background,omitempty"` + Conversation *ResponseConversation `json:"conversation,omitempty"` + Prompt *ResponsePrompt `json:"prompt,omitempty"` + PromptCacheKey string `json:"prompt_cache_key,omitempty"` + PromptCacheOptions *ResponsePromptCacheOptions `json:"prompt_cache_options,omitempty"` + PromptCacheRetention string `json:"prompt_cache_retention,omitempty"` + SafetyIdentifier string `json:"safety_identifier,omitempty"` + User string `json:"user,omitempty"` + + httpHeader +} + +// GetOutputText returns the aggregated text output. It uses the API's output_text +// convenience field when present and otherwise extracts output_text content parts. +func (r CreateResponseResponse) GetOutputText() string { + if r.OutputText != "" { + return r.OutputText + } + + var output strings.Builder + for _, rawItem := range r.Output { + data, err := json.Marshal(rawItem) + if err != nil { + continue + } + var item ResponseOutputItem + if err = json.Unmarshal(data, &item); err != nil { + continue + } + for _, content := range item.Content { + if content.Type == "output_text" { + output.WriteString(content.Text) + } + } + } + return output.String() +} + +// RetrieveResponseOptions controls optional data returned by RetrieveResponse. +type RetrieveResponseOptions struct { + Include []ResponseInclude + IncludeObfuscation *bool + StartingAfter *int +} + +// ResponseInputItemsListOptions controls pagination for ListResponseInputItems. +type ResponseInputItemsListOptions struct { + After string + Include []ResponseInclude + Limit int + Order string +} + +// ResponseInputItemsList contains the input items for a response. +type ResponseInputItemsList struct { + Object string `json:"object"` + Data []any `json:"data"` + FirstID string `json:"first_id"` + LastID string `json:"last_id"` + HasMore bool `json:"has_more"` + + httpHeader +} + +// ResponseDeleteResponse is returned after deleting a stored response. +type ResponseDeleteResponse struct { + ID string `json:"id"` + Object string `json:"object"` + Deleted bool `json:"deleted"` + + httpHeader +} + +// DeleteResponseResponse is kept as a descriptive alias for ResponseDeleteResponse. +type DeleteResponseResponse = ResponseDeleteResponse + +// ResponseInputTokensResponse reports the token count for response input. +type ResponseInputTokensResponse struct { + Object string `json:"object"` + InputTokens int `json:"input_tokens"` + + httpHeader +} + +// ResponseInputTokensRequest contains the response input whose tokens should be counted. +type ResponseInputTokensRequest = CreateResponseRequest + +// ResponseCompaction is a compacted response context. +type ResponseCompaction struct { + ID string `json:"id"` + Object string `json:"object"` + CreatedAt int64 `json:"created_at"` + Output []any `json:"output"` + Usage *ResponseUsage `json:"usage,omitempty"` + + httpHeader +} + +// CompactResponseRequest contains the response context to compact. +type CompactResponseRequest = CreateResponseRequest + +// CreateResponse creates a non-streaming model response. +func (c *Client) CreateResponse( + ctx context.Context, + request CreateResponseRequest, +) (response CreateResponseResponse, err error) { + if request.Stream { + return response, ErrResponseStreamNotSupported + } + + req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(responsesSuffix), withBody(request)) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// RetrieveResponse gets a stored response by ID. +func (c *Client) RetrieveResponse( + ctx context.Context, + responseID string, + options ...RetrieveResponseOptions, +) (response CreateResponseResponse, err error) { + values := url.Values{} + if len(options) > 0 { + for _, include := range options[0].Include { + values.Add("include", string(include)) + } + if options[0].IncludeObfuscation != nil { + values.Set("include_obfuscation", strconv.FormatBool(*options[0].IncludeObfuscation)) + } + if options[0].StartingAfter != nil { + values.Set("starting_after", strconv.Itoa(*options[0].StartingAfter)) + } + } + + urlSuffix := responseResourceSuffix(responseID, "", values) + req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix)) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// GetResponse is an alias for RetrieveResponse. +func (c *Client) GetResponse( + ctx context.Context, + responseID string, + options ...RetrieveResponseOptions, +) (CreateResponseResponse, error) { + return c.RetrieveResponse(ctx, responseID, options...) +} + +// DeleteResponse deletes a stored response. +func (c *Client) DeleteResponse(ctx context.Context, responseID string) (response ResponseDeleteResponse, err error) { + urlSuffix := responseResourceSuffix(responseID, "", nil) + req, err := c.newRequest(ctx, http.MethodDelete, c.fullURL(urlSuffix)) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// CancelResponse cancels a background response. +func (c *Client) CancelResponse(ctx context.Context, responseID string) (response CreateResponseResponse, err error) { + urlSuffix := responseResourceSuffix(responseID, "cancel", nil) + req, err := c.newRequest(ctx, http.MethodPost, c.fullURL(urlSuffix)) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// ListResponseInputItems lists the input items for a response. +func (c *Client) ListResponseInputItems( + ctx context.Context, + responseID string, + options ...ResponseInputItemsListOptions, +) (response ResponseInputItemsList, err error) { + values := url.Values{} + if len(options) > 0 { + if options[0].After != "" { + values.Set("after", options[0].After) + } + for _, include := range options[0].Include { + values.Add("include", string(include)) + } + if options[0].Limit != 0 { + values.Set("limit", strconv.Itoa(options[0].Limit)) + } + if options[0].Order != "" { + values.Set("order", options[0].Order) + } + } + + urlSuffix := responseResourceSuffix(responseID, "input_items", values) + req, err := c.newRequest(ctx, http.MethodGet, c.fullURL(urlSuffix)) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// CountResponseInputTokens returns the number of input tokens a request would use. +func (c *Client) CountResponseInputTokens( + ctx context.Context, + request ResponseInputTokensRequest, +) (response ResponseInputTokensResponse, err error) { + request.Stream = false + request.StreamOptions = nil + req, err := c.newRequest( + ctx, + http.MethodPost, + c.fullURL(responsesSuffix+"/input_tokens"), + withBody(request), + ) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +// CompactResponse compacts a response context for use in a later request. +func (c *Client) CompactResponse( + ctx context.Context, + request CompactResponseRequest, +) (response ResponseCompaction, err error) { + request.Stream = false + request.StreamOptions = nil + req, err := c.newRequest( + ctx, + http.MethodPost, + c.fullURL(responsesSuffix+"/compact"), + withBody(request), + ) + if err != nil { + return response, err + } + err = c.sendRequest(req, &response) + return response, err +} + +func responseResourceSuffix(responseID, action string, values url.Values) string { + suffix := fmt.Sprintf("%s/%s", responsesSuffix, url.PathEscape(responseID)) + if action != "" { + suffix += "/" + action + } + if len(values) != 0 { + suffix += "?" + values.Encode() + } + return suffix +} diff --git a/vendor/github.com/sashabaranov/go-openai/response_stream.go b/vendor/github.com/sashabaranov/go-openai/response_stream.go new file mode 100644 index 0000000..c06558b --- /dev/null +++ b/vendor/github.com/sashabaranov/go-openai/response_stream.go @@ -0,0 +1,131 @@ +package openai + +import ( + "context" + "encoding/json" + "net/http" +) + +// ResponseStreamEventType identifies an event emitted while a response is generated. +type ResponseStreamEventType string + +const ( + ResponseStreamEventCreated ResponseStreamEventType = "response.created" + ResponseStreamEventQueued ResponseStreamEventType = "response.queued" + ResponseStreamEventInProgress ResponseStreamEventType = "response.in_progress" + ResponseStreamEventCompleted ResponseStreamEventType = "response.completed" + ResponseStreamEventFailed ResponseStreamEventType = "response.failed" + ResponseStreamEventIncomplete ResponseStreamEventType = "response.incomplete" + ResponseStreamEventOutputItemAdded ResponseStreamEventType = "response.output_item.added" + ResponseStreamEventOutputItemDone ResponseStreamEventType = "response.output_item.done" + ResponseStreamEventContentPartAdded ResponseStreamEventType = "response.content_part.added" + ResponseStreamEventContentPartDone ResponseStreamEventType = "response.content_part.done" + ResponseStreamEventOutputTextDelta ResponseStreamEventType = "response.output_text.delta" + ResponseStreamEventOutputTextDone ResponseStreamEventType = "response.output_text.done" + ResponseStreamEventOutputTextAnnotationAdded ResponseStreamEventType = "response.output_text.annotation.added" + ResponseStreamEventRefusalDelta ResponseStreamEventType = "response.refusal.delta" + ResponseStreamEventRefusalDone ResponseStreamEventType = "response.refusal.done" + ResponseStreamEventFunctionArgumentsDelta ResponseStreamEventType = "response.function_call_arguments.delta" + ResponseStreamEventFunctionArgumentsDone ResponseStreamEventType = "response.function_call_arguments.done" + ResponseStreamEventReasoningSummaryTextDelta ResponseStreamEventType = "response.reasoning_summary_text.delta" + ResponseStreamEventReasoningSummaryTextDone ResponseStreamEventType = "response.reasoning_summary_text.done" + ResponseStreamEventReasoningSummaryPartAdded ResponseStreamEventType = "response.reasoning_summary_part.added" + ResponseStreamEventReasoningSummaryPartDone ResponseStreamEventType = "response.reasoning_summary_part.done" + ResponseStreamEventReasoningTextDelta ResponseStreamEventType = "response.reasoning_text.delta" + ResponseStreamEventReasoningTextDone ResponseStreamEventType = "response.reasoning_text.done" + ResponseStreamEventAudioDelta ResponseStreamEventType = "response.audio.delta" + ResponseStreamEventAudioDone ResponseStreamEventType = "response.audio.done" + ResponseStreamEventAudioTranscriptDelta ResponseStreamEventType = "response.audio.transcript.delta" + ResponseStreamEventAudioTranscriptDone ResponseStreamEventType = "response.audio.transcript.done" + ResponseStreamEventWebSearchInProgress ResponseStreamEventType = "response.web_search_call.in_progress" + ResponseStreamEventWebSearchSearching ResponseStreamEventType = "response.web_search_call.searching" + ResponseStreamEventWebSearchCompleted ResponseStreamEventType = "response.web_search_call.completed" + ResponseStreamEventFileSearchInProgress ResponseStreamEventType = "response.file_search_call.in_progress" + ResponseStreamEventFileSearchSearching ResponseStreamEventType = "response.file_search_call.searching" + ResponseStreamEventFileSearchCompleted ResponseStreamEventType = "response.file_search_call.completed" + ResponseStreamEventCodeInterpreterInProgress ResponseStreamEventType = "response.code_interpreter_call.in_progress" + ResponseStreamEventCodeInterpreterInterpreting ResponseStreamEventType = "response.code_interpreter_call.interpreting" + ResponseStreamEventCodeInterpreterCompleted ResponseStreamEventType = "response.code_interpreter_call.completed" + ResponseStreamEventCodeInterpreterCodeDelta ResponseStreamEventType = "response.code_interpreter_call_code.delta" + ResponseStreamEventCodeInterpreterCodeDone ResponseStreamEventType = "response.code_interpreter_call_code.done" + ResponseStreamEventCustomToolInputDelta ResponseStreamEventType = "response.custom_tool_call_input.delta" + ResponseStreamEventCustomToolInputDone ResponseStreamEventType = "response.custom_tool_call_input.done" + ResponseStreamEventImageGenerationInProgress ResponseStreamEventType = "response.image_generation_call.in_progress" + ResponseStreamEventImageGenerationGenerating ResponseStreamEventType = "response.image_generation_call.generating" + ResponseStreamEventImageGenerationCompleted ResponseStreamEventType = "response.image_generation_call.completed" + ResponseStreamEventImageGenerationPartialImage ResponseStreamEventType = "response.image_generation_call.partial_image" + ResponseStreamEventMCPCallInProgress ResponseStreamEventType = "response.mcp_call.in_progress" + ResponseStreamEventMCPCallCompleted ResponseStreamEventType = "response.mcp_call.completed" + ResponseStreamEventMCPCallFailed ResponseStreamEventType = "response.mcp_call.failed" + ResponseStreamEventMCPCallArgumentsDelta ResponseStreamEventType = "response.mcp_call_arguments.delta" + ResponseStreamEventMCPCallArgumentsDone ResponseStreamEventType = "response.mcp_call_arguments.done" + ResponseStreamEventMCPListToolsInProgress ResponseStreamEventType = "response.mcp_list_tools.in_progress" + ResponseStreamEventMCPListToolsCompleted ResponseStreamEventType = "response.mcp_list_tools.completed" + ResponseStreamEventMCPListToolsFailed ResponseStreamEventType = "response.mcp_list_tools.failed" + ResponseStreamEventError ResponseStreamEventType = "error" +) + +// ResponseStreamEvent contains the common fields across Responses API SSE event variants. +type ResponseStreamEvent struct { + Type ResponseStreamEventType `json:"type"` + SequenceNumber int `json:"sequence_number,omitempty"` + Response *CreateResponseResponse `json:"response,omitempty"` + Item *ResponseOutputItem `json:"item,omitempty"` + Part *ResponseOutputContent `json:"part,omitempty"` + Annotation *ResponseAnnotation `json:"annotation,omitempty"` + ItemID string `json:"item_id,omitempty"` + OutputIndex int `json:"output_index,omitempty"` + ContentIndex int `json:"content_index,omitempty"` + SummaryIndex int `json:"summary_index,omitempty"` + Delta string `json:"delta,omitempty"` + Text string `json:"text,omitempty"` + Arguments string `json:"arguments,omitempty"` + PartialImageB64 string `json:"partial_image_b64,omitempty"` + PartialImageIndex int `json:"partial_image_index,omitempty"` + Logprobs []ResponseLogprob `json:"logprobs,omitempty"` + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + Param any `json:"param,omitempty"` + Error *ResponseError `json:"error,omitempty"` + Obfuscation string `json:"obfuscation,omitempty"` + Raw json.RawMessage `json:"-"` +} + +// UnmarshalJSON decodes known event fields and retains the complete event for +// forward compatibility with event variants added by the API. +func (e *ResponseStreamEvent) UnmarshalJSON(data []byte) error { + type eventAlias ResponseStreamEvent + if err := json.Unmarshal(data, (*eventAlias)(e)); err != nil { + return err + } + e.Raw = append(e.Raw[:0], data...) + return nil +} + +// ResponseStream reads server-sent events from a streaming Responses API request. +type ResponseStream struct { + *streamReader[ResponseStreamEvent] +} + +// CreateResponseStream creates a response and streams its generation events. +func (c *Client) CreateResponseStream( + ctx context.Context, + request CreateResponseRequest, +) (stream *ResponseStream, err error) { + request.Stream = true + req, err := c.newRequest( + ctx, + http.MethodPost, + c.fullURL(responsesSuffix), + withBody(request), + ) + if err != nil { + return nil, err + } + + reader, err := sendRequestStream[ResponseStreamEvent](c, req) + if err != nil { + return nil, err + } + return &ResponseStream{streamReader: reader}, nil +} diff --git a/vendor/github.com/sashabaranov/go-openai/stream.go b/vendor/github.com/sashabaranov/go-openai/stream.go index a61c7c9..84a8373 100644 --- a/vendor/github.com/sashabaranov/go-openai/stream.go +++ b/vendor/github.com/sashabaranov/go-openai/stream.go @@ -22,7 +22,7 @@ func (c *Client) CreateCompletionStream( ctx context.Context, request CompletionRequest, ) (stream *CompletionStream, err error) { - urlSuffix := "/completions" + urlSuffix := completionsSuffix if !checkEndpointSupportsModel(urlSuffix, request.Model) { err = ErrCompletionUnsupportedModel return diff --git a/vendor/github.com/sashabaranov/go-openai/stream_reader.go b/vendor/github.com/sashabaranov/go-openai/stream_reader.go index 6faefe0..bab2510 100644 --- a/vendor/github.com/sashabaranov/go-openai/stream_reader.go +++ b/vendor/github.com/sashabaranov/go-openai/stream_reader.go @@ -17,7 +17,7 @@ var ( ) type streamable interface { - ChatCompletionStreamResponse | CompletionResponse + ChatCompletionStreamResponse | CompletionResponse | ResponseStreamEvent } type streamReader[T streamable] struct { @@ -64,7 +64,13 @@ func (stream *streamReader[T]) processLines() ([]byte, error) { rawLine, readErr := stream.reader.ReadBytes('\n') if readErr != nil || hasErrorPrefix { respErr := stream.unmarshalError() - if respErr != nil { + // respErr.Error can be nil even when respErr itself isn't: + // any payload that round-trips through unmarshaler.Unmarshal + // (an empty object, an aborted partial frame after context + // cancellation, etc.) ends up as a non-nil *ErrorResponse with + // a nil Error field. Wrapping that produced the famously + // useless "error, " message in #1060. + if respErr != nil && respErr.Error != nil { return nil, fmt.Errorf("error, %w", respErr.Error) } return nil, readErr diff --git a/vendor/golang.org/x/exp/typeparams/LICENSE b/vendor/golang.org/x/exp/typeparams/LICENSE new file mode 100644 index 0000000..6a66aea --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/exp/typeparams/common.go b/vendor/golang.org/x/exp/typeparams/common.go new file mode 100644 index 0000000..7f867cf --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/common.go @@ -0,0 +1,182 @@ +// Copyright 2021 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 typeparams contains common utilities for writing tools that interact +// with generic Go code, as introduced with Go 1.18. +// +// Many of the types and functions in this package are proxies for the new APIs +// introduced in the standard library with Go 1.18. For example, the +// typeparams.Union type is an alias for go/types.Union, and the ForTypeSpec +// function returns the value of the go/ast.TypeSpec.TypeParams field. At Go +// versions older than 1.18 these helpers are implemented as stubs, allowing +// users of this package to write code that handles generic constructs inline, +// even if the Go version being used to compile does not support generics. +// +// Additionally, this package contains common utilities for working with the +// new generic constructs, to supplement the standard library APIs. Notably, +// the NormalTerms API computes a minimal representation of the structural +// restrictions on a type parameter. In the future, these supplemental APIs may +// be available in the standard library.. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +// Enabled reports whether type parameters are enabled in the current build +// environment. +func Enabled() bool { + return enabled +} + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter. +func IsTypeParam(t types.Type) bool { + _, ok := t.(*TypeParam) + return ok +} + +// OriginMethod returns the origin method associated with the method fn. For +// methods on a non-generic receiver base type, this is just fn. However, for +// methods with a generic receiver, OriginMethod returns the corresponding +// method in the method set of the origin type. +// +// As a special case, if fn is not a method (has no receiver), OriginMethod +// returns fn. +func OriginMethod(fn *types.Func) *types.Func { + recv := fn.Type().(*types.Signature).Recv() + if recv == nil { + return fn + } + base := recv.Type() + p, isPtr := base.(*types.Pointer) + if isPtr { + base = p.Elem() + } + named, isNamed := base.(*types.Named) + if !isNamed { + // Receiver is a *types.Interface. + return fn + } + if ForNamed(named).Len() == 0 { + // Receiver base has no type parameters, so we can avoid the lookup below. + return fn + } + orig := NamedTypeOrigin(named) + gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name()) + return gfn.(*types.Func) +} + +// GenericAssignableTo is a generalization of types.AssignableTo that +// implements the following rule for uninstantiated generic types: +// +// If V and T are generic named types, then V is considered assignable to T if, +// for every possible instantation of V[A_1, ..., A_N], the instantiation +// T[A_1, ..., A_N] is valid and V[A_1, ..., A_N] implements T[A_1, ..., A_N]. +// +// If T has structural constraints, they must be satisfied by V. +// +// For example, consider the following type declarations: +// +// type Interface[T any] interface { +// Accept(T) +// } +// +// type Container[T any] struct { +// Element T +// } +// +// func (c Container[T]) Accept(t T) { c.Element = t } +// +// In this case, GenericAssignableTo reports that instantiations of Container +// are assignable to the corresponding instantiation of Interface. +func GenericAssignableTo(ctxt *Context, V, T types.Type) bool { + // If V and T are not both named, or do not have matching non-empty type + // parameter lists, fall back on types.AssignableTo. + + VN, Vnamed := V.(*types.Named) + TN, Tnamed := T.(*types.Named) + if !Vnamed || !Tnamed { + return types.AssignableTo(V, T) + } + + vtparams := ForNamed(VN) + ttparams := ForNamed(TN) + if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 { + return types.AssignableTo(V, T) + } + + // V and T have the same (non-zero) number of type params. Instantiate both + // with the type parameters of V. This must always succeed for V, and will + // succeed for T if and only if the type set of each type parameter of V is a + // subset of the type set of the corresponding type parameter of T, meaning + // that every instantiation of V corresponds to a valid instantiation of T. + + // Minor optimization: ensure we share a context across the two + // instantiations below. + if ctxt == nil { + ctxt = NewContext() + } + + var targs []types.Type + for i := 0; i < vtparams.Len(); i++ { + targs = append(targs, vtparams.At(i)) + } + + vinst, err := Instantiate(ctxt, V, targs, true) + if err != nil { + panic("type parameters should satisfy their own constraints") + } + + tinst, err := Instantiate(ctxt, T, targs, true) + if err != nil { + return false + } + + return types.AssignableTo(vinst, tinst) +} diff --git a/vendor/golang.org/x/exp/typeparams/normalize.go b/vendor/golang.org/x/exp/typeparams/normalize.go new file mode 100644 index 0000000..6cf71f0 --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/normalize.go @@ -0,0 +1,200 @@ +// Copyright 2021 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 typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +const debug = false + +// ErrEmptyTypeSet is returned if a type set computation results in a type set +// with no types. +var ErrEmptyTypeSet = errors.New("empty type set") + +// NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types whose underlying type is not *types.TypeParam, +// *types.Interface, or *types.Union, this is just a single term with Tilde() +// == false and Type() == typ. For types whose underlying type is +// *types.TypeParam, *types.Interface, and *types.Union, see below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type T[P +// interface{~int; m()}] int is the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, NormalTerms returns ErrEmptyTypeSet. +// +// NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func NormalTerms(typ types.Type) ([]*Term, error) { + if tparam, ok := typ.(*TypeParam); ok { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + typ = iface + } + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*Term + for _, term := range tset.terms { + terms = append(terms, NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for i := 0; i < u.NumEmbeddeds(); i++ { + embedded := u.EmbeddedType(i) + if _, ok := embedded.Underlying().(*TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for i := 0; i < u.Len(); i++ { + t := u.Term(i) + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *TypeParam, *Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/vendor/golang.org/x/exp/typeparams/termlist.go b/vendor/golang.org/x/exp/typeparams/termlist.go new file mode 100644 index 0000000..6f88bfa --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/termlist.go @@ -0,0 +1,172 @@ +// Copyright 2021 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "bytes" + "go/types" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf bytes.Buffer + for i, x := range xl { + if i > 0 { + buf.WriteString(" ∪ ") + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// If the type set represented by xl is specified by a single (non-𝓤) term, +// singleType returns that type. Otherwise it returns nil. +func (xl termlist) singleType() types.Type { + if nl := xl.norm(); len(nl) == 1 { + return nl[0].typ // if nl.isAll() then typ is nil, which is ok + } + return nil +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/vendor/golang.org/x/exp/typeparams/typeparams_go117.go b/vendor/golang.org/x/exp/typeparams/typeparams_go117.go new file mode 100644 index 0000000..c1da793 --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/typeparams_go117.go @@ -0,0 +1,201 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.18 + +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +const enabled = false + +func unsupported() { + panic("type parameters are unsupported at this go version") +} + +// IndexListExpr is a placeholder type, as type parameters are not supported at +// this Go version. Its methods panic on use. +type IndexListExpr struct { + ast.Expr + X ast.Expr // expression + Lbrack token.Pos // position of "[" + Indices []ast.Expr // index expressions + Rbrack token.Pos // position of "]" +} + +func (*IndexListExpr) Pos() token.Pos { unsupported(); return token.NoPos } +func (*IndexListExpr) End() token.Pos { unsupported(); return token.NoPos } + +// ForTypeSpec returns an empty field list, as type parameters on not supported +// at this Go version. +func ForTypeSpec(*ast.TypeSpec) *ast.FieldList { + return nil +} + +// ForFuncType returns an empty field list, as type parameters are not +// supported at this Go version. +func ForFuncType(*ast.FuncType) *ast.FieldList { + return nil +} + +// TypeParam is a placeholder type, as type parameters are not supported at +// this Go version. Its methods panic on use. +type TypeParam struct{ types.Type } + +func (*TypeParam) String() string { unsupported(); return "" } +func (*TypeParam) Underlying() types.Type { unsupported(); return nil } +func (*TypeParam) Index() int { unsupported(); return 0 } +func (*TypeParam) Constraint() types.Type { unsupported(); return nil } +func (*TypeParam) SetConstraint(types.Type) { unsupported() } +func (*TypeParam) Obj() *types.TypeName { unsupported(); return nil } + +// TypeParamList is a placeholder for an empty type parameter list. +type TypeParamList struct{} + +func (*TypeParamList) Len() int { return 0 } +func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil } + +// TypeList is a placeholder for an empty type list. +type TypeList struct{} + +func (*TypeList) Len() int { return 0 } +func (*TypeList) At(int) types.Type { unsupported(); return nil } + +// NewTypeParam is unsupported at this Go version, and panics. +func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { + unsupported() + return nil +} + +// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or +// typeParams is non-empty. +func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { + if len(recvTypeParams) != 0 || len(typeParams) != 0 { + unsupported() + } + return types.NewSignature(recv, params, results, variadic) +} + +// ForSignature returns an empty slice. +func ForSignature(*types.Signature) *TypeParamList { + return nil +} + +// RecvTypeParams returns a nil slice. +func RecvTypeParams(sig *types.Signature) *TypeParamList { + return nil +} + +// IsComparable returns false, as no interfaces are type-restricted at this Go +// version. +func IsComparable(*types.Interface) bool { + return false +} + +// IsMethodSet returns true, as no interfaces are type-restricted at this Go +// version. +func IsMethodSet(*types.Interface) bool { + return true +} + +// IsImplicit returns false, as no interfaces are implicit at this Go version. +func IsImplicit(*types.Interface) bool { + return false +} + +// MarkImplicit does nothing, because this Go version does not have implicit +// interfaces. +func MarkImplicit(*types.Interface) {} + +// ForNamed returns an empty type parameter list, as type parameters are not +// supported at this Go version. +func ForNamed(*types.Named) *TypeParamList { + return nil +} + +// SetForNamed panics if tparams is non-empty. +func SetForNamed(_ *types.Named, tparams []*TypeParam) { + if len(tparams) > 0 { + unsupported() + } +} + +// NamedTypeArgs returns nil. +func NamedTypeArgs(*types.Named) *TypeList { + return nil +} + +// NamedTypeOrigin is the identity method at this Go version. +func NamedTypeOrigin(named *types.Named) types.Type { + return named +} + +// Term holds information about a structural type restriction. +type Term struct { + tilde bool + typ types.Type +} + +func (m *Term) Tilde() bool { return m.tilde } +func (m *Term) Type() types.Type { return m.typ } +func (m *Term) String() string { + pre := "" + if m.tilde { + pre = "~" + } + return pre + m.typ.String() +} + +// NewTerm creates a new placeholder term type. +func NewTerm(tilde bool, typ types.Type) *Term { + return &Term{tilde, typ} +} + +// Union is a placeholder type, as type parameters are not supported at this Go +// version. Its methods panic on use. +type Union struct{ types.Type } + +func (*Union) String() string { unsupported(); return "" } +func (*Union) Underlying() types.Type { unsupported(); return nil } +func (*Union) Len() int { return 0 } +func (*Union) Term(i int) *Term { unsupported(); return nil } + +// NewUnion is unsupported at this Go version, and panics. +func NewUnion(terms []*Term) *Union { + unsupported() + return nil +} + +// InitInstances is a noop at this Go version. +func InitInstances(*types.Info) {} + +// Instance is a placeholder type, as type parameters are not supported at this +// Go version. +type Instance struct { + TypeArgs *TypeList + Type types.Type +} + +// GetInstances returns a nil map, as type parameters are not supported at this +// Go version. +func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil } + +// Context is a placeholder type, as type parameters are not supported at +// this Go version. +type Context struct{} + +// NewContext returns a placeholder Context instance. +func NewContext() *Context { + return &Context{} +} + +// Instantiate is unsupported on this Go version, and panics. +func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { + unsupported() + return nil, nil +} diff --git a/vendor/golang.org/x/exp/typeparams/typeparams_go118.go b/vendor/golang.org/x/exp/typeparams/typeparams_go118.go new file mode 100644 index 0000000..0b35449 --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/typeparams_go118.go @@ -0,0 +1,147 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.18 + +package typeparams + +import ( + "go/ast" + "go/types" +) + +const enabled = true + +// IndexListExpr is an alias for ast.IndexListExpr. +type IndexListExpr = ast.IndexListExpr + +// ForTypeSpec returns n.TypeParams. +func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// ForFuncType returns n.TypeParams. +func ForFuncType(n *ast.FuncType) *ast.FieldList { + if n == nil { + return nil + } + return n.TypeParams +} + +// TypeParam is an alias for types.TypeParam +type TypeParam = types.TypeParam + +// TypeParamList is an alias for types.TypeParamList +type TypeParamList = types.TypeParamList + +// TypeList is an alias for types.TypeList +type TypeList = types.TypeList + +// NewTypeParam calls types.NewTypeParam. +func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam { + return types.NewTypeParam(name, constraint) +} + +// NewSignatureType calls types.NewSignatureType. +func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature { + return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic) +} + +// ForSignature returns sig.TypeParams() +func ForSignature(sig *types.Signature) *TypeParamList { + return sig.TypeParams() +} + +// RecvTypeParams returns sig.RecvTypeParams(). +func RecvTypeParams(sig *types.Signature) *TypeParamList { + return sig.RecvTypeParams() +} + +// IsComparable calls iface.IsComparable(). +func IsComparable(iface *types.Interface) bool { + return iface.IsComparable() +} + +// IsMethodSet calls iface.IsMethodSet(). +func IsMethodSet(iface *types.Interface) bool { + return iface.IsMethodSet() +} + +// IsImplicit calls iface.IsImplicit(). +func IsImplicit(iface *types.Interface) bool { + return iface.IsImplicit() +} + +// MarkImplicit calls iface.MarkImplicit(). +func MarkImplicit(iface *types.Interface) { + iface.MarkImplicit() +} + +// ForNamed extracts the (possibly empty) type parameter object list from +// named. +func ForNamed(named *types.Named) *TypeParamList { + return named.TypeParams() +} + +// SetForNamed sets the type params tparams on n. Each tparam must be of +// dynamic type *types.TypeParam. +func SetForNamed(n *types.Named, tparams []*TypeParam) { + n.SetTypeParams(tparams) +} + +// NamedTypeArgs returns named.TypeArgs(). +func NamedTypeArgs(named *types.Named) *TypeList { + return named.TypeArgs() +} + +// NamedTypeOrigin returns named.Orig(). +func NamedTypeOrigin(named *types.Named) types.Type { + return named.Origin() +} + +// Term is an alias for types.Term. +type Term = types.Term + +// NewTerm calls types.NewTerm. +func NewTerm(tilde bool, typ types.Type) *Term { + return types.NewTerm(tilde, typ) +} + +// Union is an alias for types.Union +type Union = types.Union + +// NewUnion calls types.NewUnion. +func NewUnion(terms []*Term) *Union { + return types.NewUnion(terms) +} + +// InitInstances initializes info to record information about type and function +// instances. +func InitInstances(info *types.Info) { + info.Instances = make(map[*ast.Ident]types.Instance) +} + +// Instance is an alias for types.Instance. +type Instance = types.Instance + +// GetInstances returns info.Instances. +func GetInstances(info *types.Info) map[*ast.Ident]Instance { + return info.Instances +} + +// Context is an alias for types.Context. +type Context = types.Context + +// NewContext calls types.NewContext. +func NewContext() *Context { + return types.NewContext() +} + +// Instantiate calls types.Instantiate. +func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) { + return types.Instantiate(ctxt, typ, targs, validate) +} diff --git a/vendor/golang.org/x/exp/typeparams/typeterm.go b/vendor/golang.org/x/exp/typeparams/typeterm.go new file mode 100644 index 0000000..7350bb7 --- /dev/null +++ b/vendor/golang.org/x/exp/typeparams/typeterm.go @@ -0,0 +1,169 @@ +// Copyright 2021 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go index f69fd75..c261a8e 100644 --- a/vendor/golang.org/x/sync/errgroup/errgroup.go +++ b/vendor/golang.org/x/sync/errgroup/errgroup.go @@ -109,7 +109,7 @@ func (g *Group) TryGo(f func() error) bool { if g.sem != nil { select { case g.sem <- token{}: - // Note: this allows barging iff channels in general allow barging. + // Note: this allows barging if and only if channels in general allow barging. default: return false } diff --git a/vendor/golang.org/x/sys/windows/aliases.go b/vendor/golang.org/x/sys/windows/aliases.go index 16f9056..9631796 100644 --- a/vendor/golang.org/x/sys/windows/aliases.go +++ b/vendor/golang.org/x/sys/windows/aliases.go @@ -8,5 +8,6 @@ package windows import "syscall" +type Signal = syscall.Signal type Errno = syscall.Errno type SysProcAttr = syscall.SysProcAttr diff --git a/vendor/golang.org/x/sys/windows/dll_windows.go b/vendor/golang.org/x/sys/windows/dll_windows.go index 3ca814f..1157b06 100644 --- a/vendor/golang.org/x/sys/windows/dll_windows.go +++ b/vendor/golang.org/x/sys/windows/dll_windows.go @@ -163,42 +163,7 @@ func (p *Proc) Addr() uintptr { // (according to the semantics of the specific function being called) before consulting // the error. The error will be guaranteed to contain windows.Errno. func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) { - switch len(a) { - case 0: - return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0) - case 1: - return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0) - case 2: - return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0) - case 3: - return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2]) - case 4: - return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0) - case 5: - return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0) - case 6: - return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5]) - case 7: - return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0) - case 8: - return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0) - case 9: - return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]) - case 10: - return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0) - case 11: - return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0) - case 12: - return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11]) - case 13: - return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0) - case 14: - return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0) - case 15: - return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14]) - default: - panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".") - } + return syscall.SyscallN(p.Addr(), a...) } // A LazyDLL implements access to a single DLL. diff --git a/vendor/golang.org/x/sys/windows/security_windows.go b/vendor/golang.org/x/sys/windows/security_windows.go index a8b0364..7836215 100644 --- a/vendor/golang.org/x/sys/windows/security_windows.go +++ b/vendor/golang.org/x/sys/windows/security_windows.go @@ -1109,17 +1109,53 @@ const ( ) // This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions. +// +// Go pointers stored in a TrusteeValue must be pinned using [runtime.Pinner] +// for the lifetime of the TrusteeValue. type TrusteeValue uintptr +// TrusteeValueFromString is unsafe and should not be used. +// +// It returns a uintptr containing a reference to newly-allocated memory +// which will be freed by the garbage collector. +// There is no way for the caller to safely reference this memory. +// +// To create a [TrusteeValue] from a string, use: +// +// p, err := windows.UTF16PtrFromString(s) +// if err != nil { +// // handle error +// } +// +// // Pin the string for as long as it is used. +// var pinner runtime.Pinner +// pinner.Pin(p) +// defer pinner.Unpin() +// +// tv := TrusteeValue(unsafe.Pointer(p)) +// +// Deprecated: TrusteeValueFromString is unsafe and should not be used. func TrusteeValueFromString(str string) TrusteeValue { return TrusteeValue(unsafe.Pointer(StringToUTF16Ptr(str))) } + +// TrusteeValueFromSID returns a [TrusteeValue] referencing sid. +// +// The caller must pin sid using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromSID(sid *SID) TrusteeValue { return TrusteeValue(unsafe.Pointer(sid)) } + +// TrusteeValueFromObjectsAndSid returns a [TrusteeValue] referencing objectsAndSid. +// +// The caller must pin objectsAndSid using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromObjectsAndSid(objectsAndSid *OBJECTS_AND_SID) TrusteeValue { return TrusteeValue(unsafe.Pointer(objectsAndSid)) } + +// TrusteeValueFromObjectsAndName returns a [TrusteeValue] referencing objectsAndName. +// +// The caller must pin objectsAndName using a [runtime.Pinner] for the lifetime of the TrusteeValue. func TrusteeValueFromObjectsAndName(objectsAndName *OBJECTS_AND_NAME) TrusteeValue { return TrusteeValue(unsafe.Pointer(objectsAndName)) } @@ -1438,13 +1474,17 @@ func GetSecurityInfo(handle Handle, objectType SE_OBJECT_TYPE, securityInformati } // GetNamedSecurityInfo queries the security information for a given named object and returns the self-relative security -// descriptor result on the Go heap. +// descriptor result on the Go heap. The security descriptor might be nil, even when err is nil, if the object exists +// but has no security descriptor. func GetNamedSecurityInfo(objectName string, objectType SE_OBJECT_TYPE, securityInformation SECURITY_INFORMATION) (sd *SECURITY_DESCRIPTOR, err error) { var winHeapSD *SECURITY_DESCRIPTOR err = getNamedSecurityInfo(objectName, objectType, securityInformation, nil, nil, nil, nil, &winHeapSD) if err != nil { return } + if winHeapSD == nil { + return nil, nil + } defer LocalFree(Handle(unsafe.Pointer(winHeapSD))) return winHeapSD.copySelfRelativeSecurityDescriptor(), nil } diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go index 69439df..e6966b4 100644 --- a/vendor/golang.org/x/sys/windows/syscall_windows.go +++ b/vendor/golang.org/x/sys/windows/syscall_windows.go @@ -452,6 +452,7 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys RtlInitString(destinationString *NTString, sourceString *byte) = ntdll.RtlInitString //sys NtCreateFile(handle *Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, allocationSize *int64, attributes uint32, share uint32, disposition uint32, options uint32, eabuffer uintptr, ealength uint32) (ntstatus error) = ntdll.NtCreateFile //sys NtCreateNamedPipeFile(pipe *Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (ntstatus error) = ntdll.NtCreateNamedPipeFile +//sys NtQueryInformationFile(handle Handle, iosb *IO_STATUS_BLOCK, outBuffer *byte, outBufferLen uint32, class uint32) (ntstatus error) = ntdll.NtQueryInformationFile //sys NtSetInformationFile(handle Handle, iosb *IO_STATUS_BLOCK, inBuffer *byte, inBufferLen uint32, class uint32) (ntstatus error) = ntdll.NtSetInformationFile //sys RtlDosPathNameToNtPathName(dosName *uint16, ntName *NTUnicodeString, ntFileNamePart *uint16, relativeName *RTL_RELATIVE_NAME) (ntstatus error) = ntdll.RtlDosPathNameToNtPathName_U_WithStatus //sys RtlDosPathNameToRelativeNtPathName(dosName *uint16, ntName *NTUnicodeString, ntFileNamePart *uint16, relativeName *RTL_RELATIVE_NAME) (ntstatus error) = ntdll.RtlDosPathNameToRelativeNtPathName_U_WithStatus @@ -460,6 +461,8 @@ func NewCallbackCDecl(fn interface{}) uintptr { //sys NtSetInformationProcess(proc Handle, procInfoClass int32, procInfo unsafe.Pointer, procInfoLen uint32) (ntstatus error) = ntdll.NtSetInformationProcess //sys NtQuerySystemInformation(sysInfoClass int32, sysInfo unsafe.Pointer, sysInfoLen uint32, retLen *uint32) (ntstatus error) = ntdll.NtQuerySystemInformation //sys NtSetSystemInformation(sysInfoClass int32, sysInfo unsafe.Pointer, sysInfoLen uint32) (ntstatus error) = ntdll.NtSetSystemInformation +//sys NtQueryEaFile(handle Handle, iosb *IO_STATUS_BLOCK, outBuffer *byte, outBufferLen uint32, returnSingleEntry bool, eaList *byte, eaListLen uint32, eaIndex *uint32, restartScan bool) (ntstatus error) = ntdll.NtQueryEaFile +//sys NtSetEaFile(handle Handle, iosb *IO_STATUS_BLOCK, inBuffer *byte, inBufferLen uint32) (ntstatus error) = ntdll.NtSetEaFile //sys RtlAddFunctionTable(functionTable *RUNTIME_FUNCTION, entryCount uint32, baseAddress uintptr) (ret bool) = ntdll.RtlAddFunctionTable //sys RtlDeleteFunctionTable(functionTable *RUNTIME_FUNCTION) (ret bool) = ntdll.RtlDeleteFunctionTable @@ -892,14 +895,19 @@ const socket_error = uintptr(^uint32(0)) //sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar //sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx //sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex +//sys GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) = iphlpapi.GetIfTable2Ex //sys GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) = iphlpapi.GetIpForwardEntry2 //sys GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode error) = iphlpapi.GetIpForwardTable2 +//sys GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) = iphlpapi.GetIpInterfaceEntry +//sys GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) = iphlpapi.GetIpInterfaceTable //sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry +//sys GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) = iphlpapi.GetUnicastIpAddressTable //sys FreeMibTable(memory unsafe.Pointer) = iphlpapi.FreeMibTable //sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange //sys NotifyRouteChange2(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyRouteChange2 //sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange //sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2 +//sys IsProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) = kernel32.IsProcessorFeaturePresent // For testing: clients can set this flag to force // creation of IPv6 sockets to return EAFNOSUPPORT. @@ -1489,20 +1497,6 @@ func Getgid() (gid int) { return -1 } func Getegid() (egid int) { return -1 } func Getgroups() (gids []int, err error) { return nil, syscall.EWINDOWS } -type Signal int - -func (s Signal) Signal() {} - -func (s Signal) String() string { - if 0 <= s && int(s) < len(signals) { - str := signals[s] - if str != "" { - return str - } - } - return "signal " + itoa(int(s)) -} - func LoadCreateSymbolicLink() error { return procCreateSymbolicLinkW.Find() } @@ -1706,10 +1700,13 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) { if err != nil { return nil, err } - n := uint16(len(s16) * 2) + n := len(s16) * 2 + if n > (1<<16)-1 { + return nil, syscall.EINVAL + } return &NTUnicodeString{ - Length: n - 2, // subtract 2 bytes for the NULL terminator - MaximumLength: n, + Length: uint16(n) - 2, // subtract 2 bytes for the NULL terminator + MaximumLength: uint16(n), Buffer: &s16[0], }, nil } @@ -1731,11 +1728,15 @@ func (s *NTUnicodeString) String() string { // the more common *uint16 string type. func NewNTString(s string) (*NTString, error) { var nts NTString - s8, err := BytePtrFromString(s) + s8, err := ByteSliceFromString(s) if err != nil { return nil, err } - RtlInitString(&nts, s8) + // The source string plus its terminating NUL must fit within MAX_USHORT. + if len(s8) > MAX_USHORT { + return nil, syscall.EINVAL + } + RtlInitString(&nts, &s8[0]) return &nts, nil } diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go index 6e4f50e..75a50b3 100644 --- a/vendor/golang.org/x/sys/windows/types_windows.go +++ b/vendor/golang.org/x/sys/windows/types_windows.go @@ -169,6 +169,7 @@ const ( FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192 FORMAT_MESSAGE_MAX_WIDTH_MASK = 255 + MAX_USHORT = 0xffff MAX_PATH = 260 MAX_LONG_PATH = 32768 @@ -2320,6 +2321,21 @@ type MibIfRow2 struct { OutQLen uint64 } +// MIB_IF_TABLE_LEVEL enumeration from netioapi.h or +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ne-netioapi-mib_if_table_level. +const ( + MibIfTableNormal = 0 + MibIfTableRaw = 1 + MibIfTableNormalWithoutStatistics = 2 +) + +// MibIfTable2 contains a table of logical and physical interface entries. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_table2. +type MibIfTable2 struct { + NumEntries uint32 + Table [1]MibIfRow2 +} + // IP_ADDRESS_PREFIX stores an IP address prefix. See // https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-ip_address_prefix. type IpAddressPrefix struct { @@ -2413,6 +2429,13 @@ type MibUnicastIpAddressRow struct { CreationTimeStamp Filetime } +// MibUnicastIpAddressTable contains a table of unicast IP address entries. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_table. +type MibUnicastIpAddressTable struct { + NumEntries uint32 + Table [1]MibUnicastIpAddressRow +} + const ScopeLevelCount = 16 // MIB_IPINTERFACE_ROW stores interface management information for a particular IP address family on a network interface. @@ -2455,6 +2478,13 @@ type MibIpInterfaceRow struct { DisableDefaultRoutes uint8 } +// MibIpInterfaceTable contains a table of IP interface entries. See +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_table. +type MibIpInterfaceTable struct { + NumEntries uint32 + Table [1]MibIpInterfaceRow +} + // Console related constants used for the mode parameter to SetConsoleMode. See // https://docs.microsoft.com/en-us/windows/console/setconsolemode for details. @@ -3014,8 +3044,10 @@ const ( ) const ( - // FileInformationClass for NtSetInformationFile + // FileInformationClass for NtSetInformationFile/NtQueryInformationFile, see + // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ne-wdm-_file_information_class FileBasicInformation = 4 + FileEaInformation = 7 FileRenameInformation = 10 FileDispositionInformation = 13 FilePositionInformation = 14 @@ -3938,3 +3970,88 @@ const ( MOUSE_EVENT = 0x0002 WINDOW_BUFFER_SIZE_EVENT = 0x0004 ) + +// The processor features to be tested for IsProcessorFeaturePresent, see +// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent +const ( + PF_ARM_64BIT_LOADSTORE_ATOMIC = 25 + PF_ARM_DIVIDE_INSTRUCTION_AVAILABLE = 24 + PF_ARM_EXTERNAL_CACHE_AVAILABLE = 26 + PF_ARM_FMAC_INSTRUCTIONS_AVAILABLE = 27 + PF_ARM_VFP_32_REGISTERS_AVAILABLE = 18 + PF_3DNOW_INSTRUCTIONS_AVAILABLE = 7 + PF_CHANNELS_ENABLED = 16 + PF_COMPARE_EXCHANGE_DOUBLE = 2 + PF_COMPARE_EXCHANGE128 = 14 + PF_COMPARE64_EXCHANGE128 = 15 + PF_FASTFAIL_AVAILABLE = 23 + PF_FLOATING_POINT_EMULATED = 1 + PF_FLOATING_POINT_PRECISION_ERRATA = 0 + PF_MMX_INSTRUCTIONS_AVAILABLE = 3 + PF_NX_ENABLED = 12 + PF_PAE_ENABLED = 9 + PF_RDTSC_INSTRUCTION_AVAILABLE = 8 + PF_RDWRFSGSBASE_AVAILABLE = 22 + PF_SECOND_LEVEL_ADDRESS_TRANSLATION = 20 + PF_SSE3_INSTRUCTIONS_AVAILABLE = 13 + PF_SSSE3_INSTRUCTIONS_AVAILABLE = 36 + PF_SSE4_1_INSTRUCTIONS_AVAILABLE = 37 + PF_SSE4_2_INSTRUCTIONS_AVAILABLE = 38 + PF_AVX_INSTRUCTIONS_AVAILABLE = 39 + PF_AVX2_INSTRUCTIONS_AVAILABLE = 40 + PF_AVX512F_INSTRUCTIONS_AVAILABLE = 41 + PF_VIRT_FIRMWARE_ENABLED = 21 + PF_XMMI_INSTRUCTIONS_AVAILABLE = 6 + PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10 + PF_XSAVE_ENABLED = 17 + PF_ARM_V8_INSTRUCTIONS_AVAILABLE = 29 + PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE = 30 + PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE = 31 + PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE = 34 + PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE = 43 + PF_ARM_V83_JSCVT_INSTRUCTIONS_AVAILABLE = 44 + PF_ARM_V83_LRCPC_INSTRUCTIONS_AVAILABLE = 45 + PF_ARM_SVE_INSTRUCTIONS_AVAILABLE = 46 + PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE = 47 + PF_ARM_SVE2_1_INSTRUCTIONS_AVAILABLE = 48 + PF_ARM_SVE_AES_INSTRUCTIONS_AVAILABLE = 49 + PF_ARM_SVE_PMULL128_INSTRUCTIONS_AVAILABLE = 50 + PF_ARM_SVE_BITPERM_INSTRUCTIONS_AVAILABLE = 51 + PF_ARM_SVE_BF16_INSTRUCTIONS_AVAILABLE = 52 + PF_ARM_SVE_EBF16_INSTRUCTIONS_AVAILABLE = 53 + PF_ARM_SVE_B16B16_INSTRUCTIONS_AVAILABLE = 54 + PF_ARM_SVE_SHA3_INSTRUCTIONS_AVAILABLE = 55 + PF_ARM_SVE_SM4_INSTRUCTIONS_AVAILABLE = 56 + PF_ARM_SVE_I8MM_INSTRUCTIONS_AVAILABLE = 57 + PF_ARM_SVE_F32MM_INSTRUCTIONS_AVAILABLE = 58 + PF_ARM_SVE_F64MM_INSTRUCTIONS_AVAILABLE = 59 + PF_BMI2_INSTRUCTIONS_AVAILABLE = 60 + PF_MOVDIR64B_INSTRUCTION_AVAILABLE = 61 + PF_ARM_LSE2_AVAILABLE = 62 + PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE = 64 + PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE = 65 + PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE = 66 + PF_ARM_V82_FP16_INSTRUCTIONS_AVAILABLE = 67 + PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE = 68 + PF_ARM_V86_EBF16_INSTRUCTIONS_AVAILABLE = 69 + PF_ARM_SME_INSTRUCTIONS_AVAILABLE = 70 + PF_ARM_SME2_INSTRUCTIONS_AVAILABLE = 71 + PF_ARM_SME2_1_INSTRUCTIONS_AVAILABLE = 72 + PF_ARM_SME2_2_INSTRUCTIONS_AVAILABLE = 73 + PF_ARM_SME_AES_INSTRUCTIONS_AVAILABLE = 74 + PF_ARM_SME_SBITPERM_INSTRUCTIONS_AVAILABLE = 75 + PF_ARM_SME_SF8MM4_INSTRUCTIONS_AVAILABLE = 76 + PF_ARM_SME_SF8MM8_INSTRUCTIONS_AVAILABLE = 77 + PF_ARM_SME_SF8DP2_INSTRUCTIONS_AVAILABLE = 78 + PF_ARM_SME_SF8DP4_INSTRUCTIONS_AVAILABLE = 79 + PF_ARM_SME_SF8FMA_INSTRUCTIONS_AVAILABLE = 80 + PF_ARM_SME_F8F32_INSTRUCTIONS_AVAILABLE = 81 + PF_ARM_SME_F8F16_INSTRUCTIONS_AVAILABLE = 82 + PF_ARM_SME_F16F16_INSTRUCTIONS_AVAILABLE = 83 + PF_ARM_SME_B16B16_INSTRUCTIONS_AVAILABLE = 84 + PF_ARM_SME_F64F64_INSTRUCTIONS_AVAILABLE = 85 + PF_ARM_SME_I16I64_INSTRUCTIONS_AVAILABLE = 86 + PF_ARM_SME_LUTv2_INSTRUCTIONS_AVAILABLE = 87 + PF_ARM_SME_FA64_INSTRUCTIONS_AVAILABLE = 88 + PF_UMONITOR_INSTRUCTION_AVAILABLE = 89 +) diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go index f25b730..192d193 100644 --- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go +++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go @@ -188,9 +188,13 @@ var ( procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx") procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") + procGetIfTable2Ex = modiphlpapi.NewProc("GetIfTable2Ex") procGetIpForwardEntry2 = modiphlpapi.NewProc("GetIpForwardEntry2") procGetIpForwardTable2 = modiphlpapi.NewProc("GetIpForwardTable2") + procGetIpInterfaceEntry = modiphlpapi.NewProc("GetIpInterfaceEntry") + procGetIpInterfaceTable = modiphlpapi.NewProc("GetIpInterfaceTable") procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry") + procGetUnicastIpAddressTable = modiphlpapi.NewProc("GetUnicastIpAddressTable") procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange") procNotifyRouteChange2 = modiphlpapi.NewProc("NotifyRouteChange2") procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange") @@ -320,6 +324,7 @@ var ( procGetVolumePathNamesForVolumeNameW = modkernel32.NewProc("GetVolumePathNamesForVolumeNameW") procGetWindowsDirectoryW = modkernel32.NewProc("GetWindowsDirectoryW") procInitializeProcThreadAttributeList = modkernel32.NewProc("InitializeProcThreadAttributeList") + procIsProcessorFeaturePresent = modkernel32.NewProc("IsProcessorFeaturePresent") procIsWow64Process = modkernel32.NewProc("IsWow64Process") procIsWow64Process2 = modkernel32.NewProc("IsWow64Process2") procLoadLibraryExW = modkernel32.NewProc("LoadLibraryExW") @@ -423,8 +428,11 @@ var ( procNetUserGetInfo = modnetapi32.NewProc("NetUserGetInfo") procNtCreateFile = modntdll.NewProc("NtCreateFile") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") + procNtQueryEaFile = modntdll.NewProc("NtQueryEaFile") + procNtQueryInformationFile = modntdll.NewProc("NtQueryInformationFile") procNtQueryInformationProcess = modntdll.NewProc("NtQueryInformationProcess") procNtQuerySystemInformation = modntdll.NewProc("NtQuerySystemInformation") + procNtSetEaFile = modntdll.NewProc("NtSetEaFile") procNtSetInformationFile = modntdll.NewProc("NtSetInformationFile") procNtSetInformationProcess = modntdll.NewProc("NtSetInformationProcess") procNtSetSystemInformation = modntdll.NewProc("NtSetSystemInformation") @@ -1673,6 +1681,14 @@ func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) { return } +func GetIfTable2Ex(level uint32, table **MibIfTable2) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetIfTable2Ex.Addr(), uintptr(level), uintptr(unsafe.Pointer(table))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func GetIpForwardEntry2(row *MibIpForwardRow2) (errcode error) { r0, _, _ := syscall.SyscallN(procGetIpForwardEntry2.Addr(), uintptr(unsafe.Pointer(row))) if r0 != 0 { @@ -1689,6 +1705,22 @@ func GetIpForwardTable2(family uint16, table **MibIpForwardTable2) (errcode erro return } +func GetIpInterfaceEntry(row *MibIpInterfaceRow) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetIpInterfaceEntry.Addr(), uintptr(unsafe.Pointer(row))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + +func GetIpInterfaceTable(family uint16, table **MibIpInterfaceTable) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetIpInterfaceTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) { r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressEntry.Addr(), uintptr(unsafe.Pointer(row))) if r0 != 0 { @@ -1697,6 +1729,14 @@ func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) { return } +func GetUnicastIpAddressTable(family uint16, table **MibUnicastIpAddressTable) (errcode error) { + r0, _, _ := syscall.SyscallN(procGetUnicastIpAddressTable.Addr(), uintptr(family), uintptr(unsafe.Pointer(table))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) { var _p0 uint32 if initialNotification { @@ -2786,6 +2826,12 @@ func initializeProcThreadAttributeList(attrlist *ProcThreadAttributeList, attrco return } +func IsProcessorFeaturePresent(ProcessorFeature uint32) (ret bool) { + r0, _, _ := syscall.SyscallN(procIsProcessorFeaturePresent.Addr(), uintptr(ProcessorFeature)) + ret = r0 != 0 + return +} + func IsWow64Process(handle Handle, isWow64 *bool) (err error) { var _p0 uint32 if *isWow64 { @@ -3697,6 +3743,30 @@ func NtCreateNamedPipeFile(pipe *Handle, access uint32, oa *OBJECT_ATTRIBUTES, i return } +func NtQueryEaFile(handle Handle, iosb *IO_STATUS_BLOCK, outBuffer *byte, outBufferLen uint32, returnSingleEntry bool, eaList *byte, eaListLen uint32, eaIndex *uint32, restartScan bool) (ntstatus error) { + var _p0 uint32 + if returnSingleEntry { + _p0 = 1 + } + var _p1 uint32 + if restartScan { + _p1 = 1 + } + r0, _, _ := syscall.SyscallN(procNtQueryEaFile.Addr(), uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(outBuffer)), uintptr(outBufferLen), uintptr(_p0), uintptr(unsafe.Pointer(eaList)), uintptr(eaListLen), uintptr(unsafe.Pointer(eaIndex)), uintptr(_p1)) + if r0 != 0 { + ntstatus = NTStatus(r0) + } + return +} + +func NtQueryInformationFile(handle Handle, iosb *IO_STATUS_BLOCK, outBuffer *byte, outBufferLen uint32, class uint32) (ntstatus error) { + r0, _, _ := syscall.SyscallN(procNtQueryInformationFile.Addr(), uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(outBuffer)), uintptr(outBufferLen), uintptr(class)) + if r0 != 0 { + ntstatus = NTStatus(r0) + } + return +} + func NtQueryInformationProcess(proc Handle, procInfoClass int32, procInfo unsafe.Pointer, procInfoLen uint32, retLen *uint32) (ntstatus error) { r0, _, _ := syscall.SyscallN(procNtQueryInformationProcess.Addr(), uintptr(proc), uintptr(procInfoClass), uintptr(procInfo), uintptr(procInfoLen), uintptr(unsafe.Pointer(retLen))) if r0 != 0 { @@ -3713,6 +3783,14 @@ func NtQuerySystemInformation(sysInfoClass int32, sysInfo unsafe.Pointer, sysInf return } +func NtSetEaFile(handle Handle, iosb *IO_STATUS_BLOCK, inBuffer *byte, inBufferLen uint32) (ntstatus error) { + r0, _, _ := syscall.SyscallN(procNtSetEaFile.Addr(), uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(inBuffer)), uintptr(inBufferLen)) + if r0 != 0 { + ntstatus = NTStatus(r0) + } + return +} + func NtSetInformationFile(handle Handle, iosb *IO_STATUS_BLOCK, inBuffer *byte, inBufferLen uint32, class uint32) (ntstatus error) { r0, _, _ := syscall.SyscallN(procNtSetInformationFile.Addr(), uintptr(handle), uintptr(unsafe.Pointer(iosb)), uintptr(unsafe.Pointer(inBuffer)), uintptr(inBufferLen), uintptr(class)) if r0 != 0 { diff --git a/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go b/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go index ac22f68..f6e6c70 100644 --- a/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go +++ b/vendor/golang.org/x/telemetry/internal/crashmonitor/monitor.go @@ -202,19 +202,26 @@ func parseStackPCs(crash string) ([]uintptr, error) { return strconv.ParseUint(pcstr, 0, 64) // 0 => allow 0x prefix } + type goroutine struct { + id uint64 + pcs []uintptr + system bool // stack starts with runtime.systemstack_switch + } + var ( - pcs []uintptr + glist []*goroutine parentSentinel uint64 childSentinel = sentinel() - on = false // are we in the first running goroutine? lines = strings.Split(crash, "\n") - symLine = true // within a goroutine, every other line is a symbol or file/line/pc location, starting with symbol. - currSymbol string - prevSymbol string // symbol of the most recent previous frame with a PC. - ) - for i := 0; i < len(lines); i++ { - line := lines[i] + // Parsing state for the current goroutine. + g *goroutine + symLine = true // a symbol (not a filename) is expected on this line + currSymbol string + prevSymbol string + ) + + for _, line := range lines { // Read sentinel value. if parentSentinel == 0 && strings.HasPrefix(line, "sentinel ") { _, err := fmt.Sscanf(line, "sentinel %x", &parentSentinel) @@ -224,27 +231,37 @@ func parseStackPCs(crash string) ([]uintptr, error) { continue } - // Search for "goroutine GID [STATUS]" - if !on { - if strings.HasPrefix(line, "goroutine ") && - strings.Contains(line, " [running]:") { - on = true - - if parentSentinel == 0 { - return nil, fmt.Errorf("no sentinel value in crash report") + // Check for a "goroutine GID [STATUS]" header. + if strings.HasPrefix(line, "goroutine ") { + if parentSentinel == 0 { + return nil, fmt.Errorf("no sentinel value in crash report") + } + isG0 := strings.HasPrefix(line, "goroutine 0 ") + isRunning := strings.Contains(line, " [running]:") + if isG0 || isRunning { + var id uint64 + if parts := strings.Fields(line); len(parts) > 1 { + id, _ = strconv.ParseUint(parts[1], 10, 64) } + g = &goroutine{id: id} + glist = append(glist, g) + symLine = true + currSymbol = "" + prevSymbol = "" + } else { + g = nil } continue } - // A blank line marks end of a goroutine stack. - if line == "" { - break + if g == nil { + continue } - // Skip the final "created by SYMBOL in goroutine GID" part. - if strings.HasPrefix(line, "created by ") { - break + // A blank line or "created by " marks the end of the goroutine stack. + if line == "" || strings.HasPrefix(line, "created by ") { + g = nil + continue } // Expect a pair of lines: @@ -316,7 +333,10 @@ func parseStackPCs(crash string) ([]uintptr, error) { pc++ } - pcs = append(pcs, uintptr(pc)) + if len(g.pcs) == 0 && currSymbol == "runtime.systemstack_switch" { + g.system = true + } + g.pcs = append(g.pcs, uintptr(pc)) // Done with this frame. Next line is a new frame. prevSymbol = currSymbol @@ -324,5 +344,24 @@ func parseStackPCs(crash string) ([]uintptr, error) { symLine = true } } - return pcs, nil + + if len(glist) == 0 { + return nil, nil + } + + // The first goroutine in the dump is the one that crashed. + firstG := glist[0] + + // If the first goroutine is g0 (the system stack), we want to find the user + // goroutine that called it, which will start with systemstack_switch. + if firstG.id == 0 { + for _, g := range glist[1:] { + if g.system && len(g.pcs) > 0 { + // Stitch the g0 stack and user stack (skipping systemstack_switch). + return append(firstG.pcs, g.pcs[1:]...), nil + } + } + } + + return firstG.pcs, nil } diff --git a/vendor/golang.org/x/tools/cmd/goimports/goimports.go b/vendor/golang.org/x/tools/cmd/goimports/goimports.go index f7dec9b..e796905 100644 --- a/vendor/golang.org/x/tools/cmd/goimports/goimports.go +++ b/vendor/golang.org/x/tools/cmd/goimports/goimports.go @@ -19,6 +19,7 @@ import ( "runtime" "runtime/pprof" "strings" + "testing" "golang.org/x/telemetry/counter" "golang.org/x/tools/internal/gocommand" @@ -199,7 +200,8 @@ func walkDir(path string) { } func main() { - // is anyone using this command? + // Measure how many people still use goimports. + // (See https://go.dev/issue/78671 for one.) counter.Open() counter.Inc("tools/cmd:goimports") runtime.GOMAXPROCS(runtime.NumCPU()) @@ -208,7 +210,9 @@ func main() { // so that it can use defer and have them // run before the exit. gofmtMain() - os.Exit(exitCode) + if !testing.Testing() { + os.Exit(exitCode) + } } // parseFlags parses command line flags and returns the paths to process. diff --git a/vendor/golang.org/x/tools/go/analysis/analysis.go b/vendor/golang.org/x/tools/go/analysis/analysis.go new file mode 100644 index 0000000..786c29d --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/analysis.go @@ -0,0 +1,269 @@ +// 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 analysis + +import ( + "flag" + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + "time" +) + +// An Analyzer describes an analysis function and its options. +type Analyzer struct { + // The Name of the analyzer must be a valid Go identifier + // as it may appear in command-line flags, URLs, and so on. + Name string + + // Doc is the documentation for the analyzer. + // The part before the first "\n\n" is the title + // (no capital or period, max ~60 letters). + Doc string + + // URL holds an optional link to a web page with additional + // documentation for this analyzer. + URL string + + // Flags defines any flags accepted by the analyzer. + // The manner in which these flags are exposed to the user + // depends on the driver which runs the analyzer. + Flags flag.FlagSet + + // Run applies the analyzer to a package. + // It returns an error if the analyzer failed. + // + // On success, the Run function may return a result + // computed by the Analyzer; its type must match ResultType. + // The driver makes this result available as an input to + // another Analyzer that depends directly on this one (see + // Requires) when it analyzes the same package. + // + // To pass analysis results between packages (and thus + // potentially between address spaces), use Facts, which are + // serializable. + Run func(*Pass) (any, error) + + // RunDespiteErrors allows the driver to invoke + // the Run method of this analyzer even on a + // package that contains parse or type errors. + // The [Pass.TypeErrors] field may consequently be non-empty. + RunDespiteErrors bool + + // Requires is a set of analyzers that must run successfully + // before this one on a given package. This analyzer may inspect + // the outputs produced by each analyzer in Requires. + // The graph over analyzers implied by Requires edges must be acyclic. + // + // Requires establishes a "horizontal" dependency between + // analysis passes (different analyzers, same package). + Requires []*Analyzer + + // ResultType is the type of the optional result of the Run function. + ResultType reflect.Type + + // FactTypes indicates that this analyzer imports and exports + // Facts of the specified concrete types. + // An analyzer that uses facts may assume that its import + // dependencies have been similarly analyzed before it runs. + // Facts must be pointers. + // + // FactTypes establishes a "vertical" dependency between + // analysis passes (same analyzer, different packages). + FactTypes []Fact +} + +func (a *Analyzer) String() string { return a.Name } + +// A Pass provides information to the Run function that +// applies a specific analyzer to a single Go package. +// +// It forms the interface between the analysis logic and the driver +// program, and has both input and an output components. +// +// As in a compiler, one pass may depend on the result computed by another. +// +// The Run function should not call any of the Pass functions concurrently. +type Pass struct { + Analyzer *Analyzer // the identity of the current analyzer + + // syntax and type information + Fset *token.FileSet // file position information; Run may add new files + Files []*ast.File // the abstract syntax tree of each file + OtherFiles []string // names of non-Go files of this package + IgnoredFiles []string // names of ignored source files in this package + Pkg *types.Package // type information about the package + TypesInfo *types.Info // type information about the syntax trees + TypesSizes types.Sizes // function for computing sizes of types + TypeErrors []types.Error // type errors (only if Analyzer.RunDespiteErrors) + + Module *Module // the package's enclosing module (possibly nil in some drivers) + + // Report reports a Diagnostic, a finding about a specific location + // in the analyzed source code such as a potential mistake. + // It may be called by the Run function. + Report func(Diagnostic) + + // ResultOf provides the inputs to this analysis pass, which are + // the corresponding results of its prerequisite analyzers. + // The map keys are the elements of Analysis.Required, + // and the type of each corresponding value is the required + // analysis's ResultType. + ResultOf map[*Analyzer]any + + // ReadFile returns the contents of the named file. + // + // The only valid file names are the elements of OtherFiles + // and IgnoredFiles, and names returned by + // Fset.File(f.FileStart).Name() for each f in Files. + // + // Analyzers must use this function (if provided) instead of + // accessing the file system directly. This allows a driver to + // provide a virtualized file tree (including, for example, + // unsaved editor buffers) and to track dependencies precisely + // to avoid unnecessary recomputation. + ReadFile func(filename string) ([]byte, error) + + // -- facts -- + + // ImportObjectFact retrieves a fact associated with obj. + // Given a value ptr of type *T, where *T satisfies Fact, + // ImportObjectFact copies the value to *ptr. + // + // ImportObjectFact panics if called after the pass is complete. + // ImportObjectFact is not concurrency-safe. + ImportObjectFact func(obj types.Object, fact Fact) bool + + // ImportPackageFact retrieves a fact associated with package pkg, + // which must be this package or one of its dependencies. + // See comments for ImportObjectFact. + ImportPackageFact func(pkg *types.Package, fact Fact) bool + + // ExportObjectFact associates a fact of type *T with the obj, + // replacing any previous fact of that type. + // + // ExportObjectFact panics if it is called after the pass is + // complete, or if obj does not belong to the package being analyzed. + // ExportObjectFact is not concurrency-safe. + ExportObjectFact func(obj types.Object, fact Fact) + + // ExportPackageFact associates a fact with the current package. + // See comments for ExportObjectFact. + ExportPackageFact func(fact Fact) + + // AllPackageFacts returns a new slice containing all package + // facts of the analysis's FactTypes in unspecified order. + // See comments for AllObjectFacts. + AllPackageFacts func() []PackageFact + + // AllObjectFacts returns a new slice containing all object + // facts of the analysis's FactTypes in unspecified order. + // + // The result includes all facts exported by packages + // whose symbols are referenced by the current package + // (by qualified identifiers or field/method selections). + // And it includes all facts exported from the current + // package by the current analysis pass. + AllObjectFacts func() []ObjectFact + + /* Further fields may be added in future. */ +} + +// PackageFact is a package together with an associated fact. +type PackageFact struct { + Package *types.Package + Fact Fact +} + +// ObjectFact is an object together with an associated fact. +type ObjectFact struct { + Object types.Object + Fact Fact +} + +// Reportf is a helper function that reports a Diagnostic using the +// specified position and formatted error message. +func (pass *Pass) Reportf(pos token.Pos, format string, args ...any) { + msg := fmt.Sprintf(format, args...) + pass.Report(Diagnostic{Pos: pos, Message: msg}) +} + +// The Range interface provides a range. It's equivalent to and satisfied by +// ast.Node. +type Range interface { + Pos() token.Pos // position of first character belonging to the node + End() token.Pos // position of first character immediately after the node +} + +// ReportRangef is a helper function that reports a Diagnostic using the +// range provided. ast.Node values can be passed in as the range because +// they satisfy the Range interface. +func (pass *Pass) ReportRangef(rng Range, format string, args ...any) { + msg := fmt.Sprintf(format, args...) + pass.Report(Diagnostic{Pos: rng.Pos(), End: rng.End(), Message: msg}) +} + +func (pass *Pass) String() string { + return fmt.Sprintf("%s@%s", pass.Analyzer.Name, pass.Pkg.Path()) +} + +// A Fact is an intermediate fact produced during analysis. +// +// Each fact is associated with a named declaration (a types.Object) or +// with a package as a whole. A single object or package may have +// multiple associated facts, but only one of any particular fact type. +// +// A Fact represents a predicate such as "never returns", but does not +// represent the subject of the predicate such as "function F" or "package P". +// +// Facts may be produced in one analysis pass and consumed by another +// analysis pass even if these are in different address spaces. +// If package P imports Q, all facts about Q produced during +// analysis of that package will be available during later analysis of P. +// Facts are analogous to type export data in a build system: +// just as export data enables separate compilation of several passes, +// facts enable "separate analysis". +// +// Each pass (a, p) starts with the set of facts produced by the +// same analyzer a applied to the packages directly imported by p. +// The analysis may add facts to the set, and they may be exported in turn. +// An analysis's Run function may retrieve facts by calling +// Pass.Import{Object,Package}Fact and update them using +// Pass.Export{Object,Package}Fact. +// +// A fact is logically private to its Analysis. To pass values +// between different analyzers, use the results mechanism; +// see Analyzer.Requires, Analyzer.ResultType, and Pass.ResultOf. +// +// A Fact type must be a pointer. +// Facts are encoded and decoded using encoding/gob. +// A Fact may implement the GobEncoder/GobDecoder interfaces +// to customize its encoding. Fact encoding should not fail. +// +// A Fact should not be modified once exported. +type Fact interface { + AFact() // dummy method to avoid type errors +} + +// A Module describes the module to which a package belongs. +type Module struct { + Path string // module path + Version string // module version ("" if unknown, such as for workspace modules) + Replace *Module // replaced by this module + Time *time.Time // time version was created + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file used when loading this module, if any + GoVersion string // go version used in module (e.g. "go1.22.0") + Error *ModuleError // error loading module +} + +// ModuleError holds errors loading a module. +type ModuleError struct { + Err string // the error itself +} diff --git a/vendor/golang.org/x/tools/go/analysis/diagnostic.go b/vendor/golang.org/x/tools/go/analysis/diagnostic.go new file mode 100644 index 0000000..527540c --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/diagnostic.go @@ -0,0 +1,88 @@ +// Copyright 2019 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 analysis + +import "go/token" + +// A Diagnostic is a message associated with a source location or range. +// +// An Analyzer may return a variety of diagnostics; the optional Category, +// which should be a constant, may be used to classify them. +// It is primarily intended to make it easy to look up documentation. +// +// All Pos values are interpreted relative to Pass.Fset. If End is +// provided, the diagnostic is specified to apply to the range between +// Pos and End. +type Diagnostic struct { + Pos token.Pos + End token.Pos // optional + Category string // optional + Message string + + // URL is the optional location of a web page that provides + // additional documentation for this diagnostic. + // + // If URL is empty but a Category is specified, then the + // Analysis driver should treat the URL as "#"+Category. + // + // The URL may be relative. If so, the base URL is that of the + // Analyzer that produced the diagnostic; + // see https://pkg.go.dev/net/url#URL.ResolveReference. + URL string + + // SuggestedFixes is an optional list of fixes to address the + // problem described by the diagnostic. Each one represents an + // alternative strategy, and should have a distinct and + // descriptive message; at most one may be applied. + // + // Fixes for different diagnostics should be treated as + // independent changes to the same baseline file state, + // analogous to a set of git commits all with the same parent. + // Combining fixes requires resolving any conflicts that + // arise, analogous to a git merge. + // Any conflicts that remain may be dealt with, depending on + // the tool, by discarding fixes, consulting the user, or + // aborting the operation. + SuggestedFixes []SuggestedFix + + // Related contains optional secondary positions and messages + // related to the primary diagnostic. + Related []RelatedInformation +} + +// RelatedInformation contains information related to a diagnostic. +// For example, a diagnostic that flags duplicated declarations of a +// variable may include one RelatedInformation per existing +// declaration. +type RelatedInformation struct { + Pos token.Pos + End token.Pos // optional + Message string +} + +// A SuggestedFix is a code change associated with a Diagnostic that a +// user can choose to apply to their code. Usually the SuggestedFix is +// meant to fix the issue flagged by the diagnostic. +// +// The TextEdits must not overlap, nor contain edits for other +// packages. Edits need not be totally ordered, but the order +// determines how insertions at the same point will be applied. +type SuggestedFix struct { + // A verb phrase describing the fix, to be shown to + // a user trying to decide whether to accept it. + // + // Example: "Remove the surplus argument" + Message string + TextEdits []TextEdit +} + +// A TextEdit represents the replacement of the code between Pos and End with the new text. +// Each TextEdit should apply to a single file. End should not be earlier in the file than Pos. +type TextEdit struct { + // For a pure insertion, End can either be set to Pos or token.NoPos. + Pos token.Pos + End token.Pos + NewText []byte +} diff --git a/vendor/golang.org/x/tools/go/analysis/doc.go b/vendor/golang.org/x/tools/go/analysis/doc.go new file mode 100644 index 0000000..2a0aa57 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/doc.go @@ -0,0 +1,317 @@ +// 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 analysis defines the interface between a modular static +analysis and an analysis driver program. + +# Background + +A static analysis is a function that inspects a package of Go code and +reports a set of diagnostics (typically mistakes in the code), and +perhaps produces other results as well, such as suggested refactorings +or other facts. An analysis that reports mistakes is informally called a +"checker". For example, the printf checker reports mistakes in +fmt.Printf format strings. + +A "modular" analysis is one that inspects one package at a time but can +save information from a lower-level package and use it when inspecting a +higher-level package, analogous to separate compilation in a toolchain. +The printf checker is modular: when it discovers that a function such as +log.Fatalf delegates to fmt.Printf, it records this fact, and checks +calls to that function too, including calls made from another package. + +By implementing a common interface, checkers from a variety of sources +can be easily selected, incorporated, and reused in a wide range of +driver programs including command-line tools (such as vet), text editors and +IDEs, build and test systems (such as go build, Bazel, or Buck), test +frameworks, code review tools, code-base indexers (such as SourceGraph), +documentation viewers (such as godoc), batch pipelines for large code +bases, and so on. + +# Analyzer + +The primary type in the API is [Analyzer]. An Analyzer statically +describes an analysis function: its name, documentation, flags, +relationship to other analyzers, and of course, its logic. + +To define an analysis, a user declares a (logically constant) variable +of type Analyzer. Here is a typical example from one of the analyzers in +the go/analysis/passes/ subdirectory: + + package unusedresult + + var Analyzer = &analysis.Analyzer{ + Name: "unusedresult", + Doc: "check for unused results of calls to some functions", + Run: run, + ... + } + + func run(pass *analysis.Pass) (interface{}, error) { + ... + } + +An analysis driver is a program such as vet that runs a set of +analyses and prints the diagnostics that they report. +The driver program must import the list of Analyzers it needs. +Typically each Analyzer resides in a separate package. +To add a new Analyzer to an existing driver, add another item to the list: + + import ( "unusedresult"; "nilness"; "printf" ) + + var analyses = []*analysis.Analyzer{ + unusedresult.Analyzer, + nilness.Analyzer, + printf.Analyzer, + } + +A driver may use the name, flags, and documentation to provide on-line +help that describes the analyses it performs. +The doc comment contains a brief one-line summary, +optionally followed by paragraphs of explanation. + +The [Analyzer] type has more fields besides those shown above: + + type Analyzer struct { + Name string + Doc string + Flags flag.FlagSet + Run func(*Pass) (interface{}, error) + RunDespiteErrors bool + ResultType reflect.Type + Requires []*Analyzer + FactTypes []Fact + } + +The Flags field declares a set of named (global) flag variables that +control analysis behavior. Unlike vet, analysis flags are not declared +directly in the command line FlagSet; it is up to the driver to set the +flag variables. A driver for a single analysis, a, might expose its flag +f directly on the command line as -f, whereas a driver for multiple +analyses might prefix the flag name by the analysis name (-a.f) to avoid +ambiguity. An IDE might expose the flags through a graphical interface, +and a batch pipeline might configure them from a config file. +See the "findcall" analyzer for an example of flags in action. + +The RunDespiteErrors flag indicates whether the analysis is equipped to +handle ill-typed code. If not, the driver will skip the analysis if +there were parse or type errors. +The optional ResultType field specifies the type of the result value +computed by this analysis and made available to other analyses. +The Requires field specifies a list of analyses upon which +this one depends and whose results it may access, and it constrains the +order in which a driver may run analyses. +The FactTypes field is discussed in the section on Modularity. +The analysis package provides a Validate function to perform basic +sanity checks on an Analyzer, such as that its Requires graph is +acyclic, its fact and result types are unique, and so on. + +Finally, the Run field contains a function to be called by the driver to +execute the analysis on a single package. The driver passes it an +instance of the Pass type. + +# Pass + +A [Pass] describes a single unit of work: the application of a particular +Analyzer to a particular package of Go code. +The Pass provides information to the Analyzer's Run function about the +package being analyzed, and provides operations to the Run function for +reporting diagnostics and other information back to the driver. + + type Pass struct { + Fset *token.FileSet + Files []*ast.File + OtherFiles []string + IgnoredFiles []string + Pkg *types.Package + TypesInfo *types.Info + ResultOf map[*Analyzer]interface{} + Report func(Diagnostic) + ... + } + +The Fset, Files, Pkg, and TypesInfo fields provide the syntax trees, +type information, and source positions for a single package of Go code. + +The OtherFiles field provides the names of non-Go +files such as assembly that are part of this package. +Similarly, the IgnoredFiles field provides the names of Go and non-Go +source files that are not part of this package with the current build +configuration but may be part of other build configurations. +The contents of these files may be read using Pass.ReadFile; +see the "asmdecl" or "buildtags" analyzers for examples of loading +non-Go files and reporting diagnostics against them. + +The ResultOf field provides the results computed by the analyzers +required by this one, as expressed in its Analyzer.Requires field. The +driver runs the required analyzers first and makes their results +available in this map. Each Analyzer must return a value of the type +described in its Analyzer.ResultType field. +For example, the "ctrlflow" analyzer returns a *ctrlflow.CFGs, which +provides a control-flow graph for each function in the package (see +golang.org/x/tools/go/cfg); the "inspect" analyzer returns a value that +enables other Analyzers to traverse the syntax trees of the package more +efficiently; and the "buildssa" analyzer constructs an SSA-form +intermediate representation. +Each of these Analyzers extends the capabilities of later Analyzers +without adding a dependency to the core API, so an analysis tool pays +only for the extensions it needs. + +The Report function emits a diagnostic, a message associated with a +source position. For most analyses, diagnostics are their primary +result. +For convenience, Pass provides a helper method, Reportf, to report a new +diagnostic by formatting a string. +Diagnostic is defined as: + + type Diagnostic struct { + Pos token.Pos + Category string // optional + Message string + } + +The optional Category field is a short identifier that classifies the +kind of message when an analysis produces several kinds of diagnostic. + +The [Diagnostic] struct does not have a field to indicate its severity +because opinions about the relative importance of Analyzers and their +diagnostics vary widely among users. The design of this framework does +not hold each Analyzer responsible for identifying the severity of its +diagnostics. Instead, we expect that drivers will allow the user to +customize the filtering and prioritization of diagnostics based on the +producing Analyzer and optional Category, according to the user's +preferences. + +Most Analyzers inspect typed Go syntax trees, but a few, such as asmdecl +and buildtag, inspect the raw text of Go source files or even non-Go +files such as assembly. To report a diagnostic against a line of a +raw text file, use the following sequence: + + content, err := pass.ReadFile(filename) + if err != nil { ... } + tf := fset.AddFile(filename, -1, len(content)) + tf.SetLinesForContent(content) + ... + pass.Reportf(tf.LineStart(line), "oops") + +# Modular analysis with Facts + +To improve efficiency and scalability, large programs are routinely +built using separate compilation: units of the program are compiled +separately, and recompiled only when one of their dependencies changes; +independent modules may be compiled in parallel. The same technique may +be applied to static analyses, for the same benefits. Such analyses are +described as "modular". + +A compiler’s type checker is an example of a modular static analysis. +Many other checkers we would like to apply to Go programs can be +understood as alternative or non-standard type systems. For example, +vet's printf checker infers whether a function has the "printf wrapper" +type, and it applies stricter checks to calls of such functions. In +addition, it records which functions are printf wrappers for use by +later analysis passes to identify other printf wrappers by induction. +A result such as “f is a printf wrapper” that is not interesting by +itself but serves as a stepping stone to an interesting result (such as +a diagnostic) is called a [Fact]. + +The analysis API allows an analysis to define new types of facts, to +associate facts of these types with objects (named entities) declared +within the current package, or with the package as a whole, and to query +for an existing fact of a given type associated with an object or +package. + +An Analyzer that uses facts must declare their types: + + var Analyzer = &analysis.Analyzer{ + Name: "printf", + FactTypes: []analysis.Fact{new(isWrapper)}, + ... + } + + type isWrapper struct{} // => *types.Func f “is a printf wrapper” + +The driver program ensures that facts for a pass’s dependencies are +generated before analyzing the package and is responsible for propagating +facts from one package to another, possibly across address spaces. +Consequently, Facts must be serializable. The API requires that drivers +use the gob encoding, an efficient, robust, self-describing binary +protocol. A fact type may implement the GobEncoder/GobDecoder interfaces +if the default encoding is unsuitable. Facts should be stateless. +Because serialized facts may appear within build outputs, the gob encoding +of a fact must be deterministic, to avoid spurious cache misses in +build systems that use content-addressable caches. +The driver makes a single call to the gob encoder for all facts +exported by a given analysis pass, so that the topology of +shared data structures referenced by multiple facts is preserved. + +The Pass type has functions to import and export facts, +associated either with an object or with a package: + + type Pass struct { + ... + ExportObjectFact func(types.Object, Fact) + ImportObjectFact func(types.Object, Fact) bool + + ExportPackageFact func(fact Fact) + ImportPackageFact func(*types.Package, Fact) bool + } + +An Analyzer may only export facts associated with the current package or +its objects, though it may import facts from any package or object that +is an import dependency of the current package. + +Conceptually, ExportObjectFact(obj, fact) inserts fact into a hidden map keyed by +the pair (obj, TypeOf(fact)), and the ImportObjectFact function +retrieves the entry from this map and copies its value into the variable +pointed to by fact. This scheme assumes that the concrete type of fact +is a pointer; this assumption is checked by the Validate function. +See the "printf" analyzer for an example of object facts in action. + +Some driver implementations (such as those based on Bazel and Blaze) do +not currently apply analyzers to packages of the standard library. +Therefore, for best results, analyzer authors should not rely on +analysis facts being available for standard packages. +For example, although the printf checker is capable of deducing during +analysis of the log package that log.Printf is a printf wrapper, +this fact is built in to the analyzer so that it correctly checks +calls to log.Printf even when run in a driver that does not apply +it to standard packages. We would like to remove this limitation in future. + +# Testing an Analyzer + +The analysistest subpackage provides utilities for testing an Analyzer. +In a few lines of code, it is possible to run an analyzer on a package +of testdata files and check that it reported all the expected +diagnostics and facts (and no more). Expectations are expressed using +"// want ..." comments in the input code. + +# Standalone commands + +Analyzers are provided in the form of packages that a driver program is +expected to import. The vet command imports a set of several analyzers, +but users may wish to define their own analysis commands that perform +additional checks. To simplify the task of creating an analysis command, +either for a single analyzer or for a whole suite, we provide the +singlechecker and multichecker subpackages. + +The singlechecker package provides the main function for a command that +runs one analyzer. By convention, each analyzer such as +go/analysis/passes/findcall should be accompanied by a singlechecker-based +command such as go/analysis/passes/findcall/cmd/findcall, defined in its +entirety as: + + package main + + import ( + "golang.org/x/tools/go/analysis/passes/findcall" + "golang.org/x/tools/go/analysis/singlechecker" + ) + + func main() { singlechecker.Main(findcall.Analyzer) } + +A tool that provides multiple analyzers can use multichecker in a +similar way, giving it the list of Analyzers. +*/ +package analysis diff --git a/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go b/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go new file mode 100644 index 0000000..4e6ea9d --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/passes/ctrlflow/ctrlflow.go @@ -0,0 +1,278 @@ +// 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 ctrlflow is an analysis that provides a syntactic +// control-flow graph (CFG) for the body of a function. +// It records whether a function cannot return. +// By itself, it does not report any diagnostics. +package ctrlflow + +import ( + "go/ast" + "go/types" + "log" + "reflect" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/cfg" + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/typesinternal" +) + +var Analyzer = &analysis.Analyzer{ + Name: "ctrlflow", + Doc: "build a control-flow graph", + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow", + Run: run, + ResultType: reflect.TypeFor[*CFGs](), + FactTypes: []analysis.Fact{new(noReturn)}, + Requires: []*analysis.Analyzer{inspect.Analyzer}, +} + +// noReturn is a fact indicating that a function does not return. +type noReturn struct{} + +func (*noReturn) AFact() {} + +func (*noReturn) String() string { return "noReturn" } + +// A CFGs holds the control-flow graphs +// for all the functions of the current package. +type CFGs struct { + defs map[*ast.Ident]types.Object // from Pass.TypesInfo.Defs + funcDecls map[*types.Func]*declInfo + funcLits map[*ast.FuncLit]*litInfo + noReturn map[*types.Func]bool // functions lacking a reachable return statement + pass *analysis.Pass // transient; nil after construction +} + +// NoReturn reports whether the specified control-flow graph cannot return normally. +// +// It is defined for at least all function symbols that appear as the static callee of a +// CallExpr in the current package, even if the callee was imported from a dependency. +// +// The result may incorporate interprocedural information based on induction of +// the "no return" property over the static call graph within the package. +// For example, if f simply calls g and g always calls os.Exit, then both f and g may +// be deemed never to return. +func (c *CFGs) NoReturn(fn *types.Func) bool { + return c.noReturn[fn] +} + +// CFGs has two maps: funcDecls for named functions and funcLits for +// unnamed ones. Unlike funcLits, the funcDecls map is not keyed by its +// syntax node, *ast.FuncDecl, because callMayReturn needs to do a +// look-up by *types.Func, and you can get from an *ast.FuncDecl to a +// *types.Func but not the other way. + +type declInfo struct { + decl *ast.FuncDecl + cfg *cfg.CFG // iff decl.Body != nil + started bool // to break cycles +} + +type litInfo struct { + cfg *cfg.CFG + noReturn bool // (currently unused) +} + +// FuncDecl returns the control-flow graph for a named function. +// It returns nil if decl.Body==nil. +func (c *CFGs) FuncDecl(decl *ast.FuncDecl) *cfg.CFG { + if decl.Body == nil { + return nil + } + fn := c.defs[decl.Name].(*types.Func) + return c.funcDecls[fn].cfg +} + +// FuncLit returns the control-flow graph for a literal function. +func (c *CFGs) FuncLit(lit *ast.FuncLit) *cfg.CFG { + return c.funcLits[lit].cfg +} + +func run(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + + // Because CFG construction consumes and produces noReturn + // facts, CFGs for exported FuncDecls must be built before 'run' + // returns; we cannot construct them lazily. + // (We could build CFGs for FuncLits lazily, + // but the benefit is marginal.) + + // Pass 1. Map types.Funcs to ast.FuncDecls in this package. + funcDecls := make(map[*types.Func]*declInfo) // functions and methods + funcLits := make(map[*ast.FuncLit]*litInfo) + + var decls []*types.Func // keys(funcDecls), in order + var lits []*ast.FuncLit // keys(funcLits), in order + + nodeFilter := []ast.Node{ + (*ast.FuncDecl)(nil), + (*ast.FuncLit)(nil), + } + inspect.Preorder(nodeFilter, func(n ast.Node) { + switch n := n.(type) { + case *ast.FuncDecl: + // Type information may be incomplete. + if fn, ok := pass.TypesInfo.Defs[n.Name].(*types.Func); ok { + funcDecls[fn] = &declInfo{decl: n} + decls = append(decls, fn) + } + case *ast.FuncLit: + funcLits[n] = new(litInfo) + lits = append(lits, n) + } + }) + + c := &CFGs{ + defs: pass.TypesInfo.Defs, + funcDecls: funcDecls, + funcLits: funcLits, + noReturn: make(map[*types.Func]bool), + pass: pass, + } + + // Pass 2. Build CFGs. + + // Build CFGs for named functions. + // Cycles in the static call graph are broken + // arbitrarily but deterministically. + // We create noReturn facts as discovered. + for _, fn := range decls { + c.buildDecl(fn, funcDecls[fn]) + } + + // Build CFGs for literal functions. + // These aren't relevant to facts (since they aren't named) + // but are required for the CFGs.FuncLit API. + for _, lit := range lits { + li := funcLits[lit] + if li.cfg == nil { + li.cfg = cfg.New(lit.Body, c.callMayReturn) + if li.cfg.NoReturn() { + li.noReturn = true + } + } + } + + // All CFGs are now built. + c.pass = nil + + return c, nil +} + +// di.cfg may be nil on return. +func (c *CFGs) buildDecl(fn *types.Func, di *declInfo) { + // buildDecl may call itself recursively for the same function, + // because cfg.New is passed the callMayReturn method, which + // builds the CFG of the callee, leading to recursion. + // The buildDecl call tree thus resembles the static call graph. + // We mark each node when we start working on it to break cycles. + + if di.started { + return // break cycle + } + di.started = true + + noreturn, known := knownIntrinsic(fn) + if !known { + if di.decl.Body != nil { + di.cfg = cfg.New(di.decl.Body, c.callMayReturn) + if di.cfg.NoReturn() { + noreturn = true + } + } + } + if noreturn { + c.pass.ExportObjectFact(fn, new(noReturn)) + c.noReturn[fn] = true + } + + // debugging + if false { + log.Printf("CFG for %s:\n%s (noreturn=%t)\n", fn, di.cfg.Format(c.pass.Fset), noreturn) + } +} + +// callMayReturn reports whether the called function may return. +// It is passed to the CFG builder. +func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) { + if id, ok := call.Fun.(*ast.Ident); ok && c.pass.TypesInfo.Uses[id] == panicBuiltin { + return false // panic never returns + } + + // Is this a static call? Also includes static functions + // parameterized by a type. Such functions may or may not + // return depending on the parameter type, but in some + // cases the answer is definite. We let ctrlflow figure + // that out. + fn := typeutil.StaticCallee(c.pass.TypesInfo, call) + if fn == nil { + return true // callee not statically known; be conservative + } + + // Function or method declared in this package? + if di, ok := c.funcDecls[fn]; ok { + c.buildDecl(fn, di) + return !c.noReturn[fn] + } + + // Not declared in this package. + // Is there a fact from another package? + if c.pass.ImportObjectFact(fn, new(noReturn)) { + c.noReturn[fn] = true + return false + } + + return true +} + +var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin) + +// knownIntrinsic reports whether a function intrinsically never +// returns because it stops execution of the calling thread, or does +// in fact return, contrary to its apparent body, because it is +// handled specially by the compiler. +// +// It is the base case in the recursion. +func knownIntrinsic(fn *types.Func) (noreturn, known bool) { + // Add functions here as the need arises, but don't allocate memory. + + // Functions known intrinsically never to return. + if typesinternal.IsFunctionNamed(fn, "syscall", "Exit", "ExitProcess", "ExitThread") || + typesinternal.IsFunctionNamed(fn, "runtime", "Goexit", "fatalthrow", "fatalpanic", "exit") || + // Following staticcheck (see go/ir/exits.go) we include functions + // in several popular logging packages whose no-return status is + // beyond the analysis to infer. + // TODO(adonovan): make this list extensible. + typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "Logger", "Fatal", "Panic") || + typesinternal.IsMethodNamed(fn, "go.uber.org/zap", "SugaredLogger", "Fatal", "Fatalw", "Fatalf", "Panic", "Panicw", "Panicf") || + typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Logger", "Exit", "Panic", "Panicf", "Panicln") || + typesinternal.IsMethodNamed(fn, "github.com/sirupsen/logrus", "Entry", "Panicf", "Panicln") || + typesinternal.IsFunctionNamed(fn, "k8s.io/klog", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") || + typesinternal.IsFunctionNamed(fn, "k8s.io/klog/v2", "Exit", "ExitDepth", "Exitf", "Exitln", "Fatal", "FatalDepth", "Fatalf", "Fatalln") { + return true, true + } + + // Compiler intrinsics known to return, contrary to + // what analysis of the function body would conclude. + // + // Not all such intrinsics must be listed here: ctrlflow + // considers any function called for its value--such as + // crypto/internal/constanttime.bool2Uint8--to potentially + // return; only functions called as a statement, for effects, + // are no-return candidates. + // + // Unfortunately this does sometimes mean peering into internals. + // Where possible, use the nearest enclosing public API function. + if typesinternal.IsFunctionNamed(fn, "internal/abi", "EscapeNonString") || + typesinternal.IsFunctionNamed(fn, "hash/maphash", "Comparable") { + return false, true + } + + return // unknown +} diff --git a/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go b/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go new file mode 100644 index 0000000..aae5d25 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/passes/inspect/inspect.go @@ -0,0 +1,49 @@ +// 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 inspect defines an Analyzer that provides an AST inspector +// (golang.org/x/tools/go/ast/inspector.Inspector) for the syntax trees +// of a package. It is only a building block for other analyzers. +// +// Example of use in another analysis: +// +// import ( +// "golang.org/x/tools/go/analysis" +// "golang.org/x/tools/go/analysis/passes/inspect" +// "golang.org/x/tools/go/ast/inspector" +// ) +// +// var Analyzer = &analysis.Analyzer{ +// ... +// Requires: []*analysis.Analyzer{inspect.Analyzer}, +// } +// +// func run(pass *analysis.Pass) (interface{}, error) { +// inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) +// inspect.Preorder(nil, func(n ast.Node) { +// ... +// }) +// return nil, nil +// } +package inspect + +import ( + "reflect" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ast/inspector" +) + +var Analyzer = &analysis.Analyzer{ + Name: "inspect", + Doc: "optimize AST traversal for later passes", + URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/inspect", + Run: run, + RunDespiteErrors: true, + ResultType: reflect.TypeFor[*inspector.Inspector](), +} + +func run(pass *analysis.Pass) (any, error) { + return inspector.New(pass.Files), nil +} diff --git a/vendor/golang.org/x/tools/go/analysis/validate.go b/vendor/golang.org/x/tools/go/analysis/validate.go new file mode 100644 index 0000000..1453939 --- /dev/null +++ b/vendor/golang.org/x/tools/go/analysis/validate.go @@ -0,0 +1,137 @@ +// 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 analysis + +import ( + "fmt" + "reflect" + "strings" + "unicode" +) + +// Validate reports an error if any of the analyzers are misconfigured. +// Checks include: +// that the name is a valid identifier; +// that the Doc is not empty; +// that the Run is non-nil; +// that the Requires graph is acyclic; +// that analyzer fact types are unique; +// that each fact type is a pointer. +// +// Analyzer names need not be unique, though this may be confusing. +func Validate(analyzers []*Analyzer) error { + // Map each fact type to its sole generating analyzer. + factTypes := make(map[reflect.Type]*Analyzer) + + // Traverse the Requires graph, depth first. + const ( + white = iota + grey + black + finished + ) + color := make(map[*Analyzer]uint8) + var visit func(a *Analyzer) error + visit = func(a *Analyzer) error { + if a == nil { + return fmt.Errorf("nil *Analyzer") + } + if color[a] == white { + color[a] = grey + + // names + if !validIdent(a.Name) { + return fmt.Errorf("invalid analyzer name %q", a) + } + + if a.Doc == "" { + return fmt.Errorf("analyzer %q is undocumented", a) + } + + if a.Run == nil { + return fmt.Errorf("analyzer %q has nil Run", a) + } + // fact types + for _, f := range a.FactTypes { + if f == nil { + return fmt.Errorf("analyzer %s has nil FactType", a) + } + t := reflect.TypeOf(f) + if prev := factTypes[t]; prev != nil { + return fmt.Errorf("fact type %s registered by two analyzers: %v, %v", + t, a, prev) + } + if t.Kind() != reflect.Pointer { + return fmt.Errorf("%s: fact type %s is not a pointer", a, t) + } + factTypes[t] = a + } + + // recursion + for _, req := range a.Requires { + if err := visit(req); err != nil { + return err + } + } + color[a] = black + } + + if color[a] == grey { + stack := []*Analyzer{a} + inCycle := map[string]bool{} + for len(stack) > 0 { + current := stack[len(stack)-1] + stack = stack[:len(stack)-1] + if color[current] == grey && !inCycle[current.Name] { + inCycle[current.Name] = true + stack = append(stack, current.Requires...) + } + } + return &CycleInRequiresGraphError{AnalyzerNames: inCycle} + } + + return nil + } + for _, a := range analyzers { + if err := visit(a); err != nil { + return err + } + } + + // Reject duplicates among analyzers. + // Precondition: color[a] == black. + // Postcondition: color[a] == finished. + for _, a := range analyzers { + if color[a] == finished { + return fmt.Errorf("duplicate analyzer: %s", a.Name) + } + color[a] = finished + } + + return nil +} + +func validIdent(name string) bool { + for i, r := range name { + if !(r == '_' || unicode.IsLetter(r) || i > 0 && unicode.IsDigit(r)) { + return false + } + } + return name != "" +} + +type CycleInRequiresGraphError struct { + AnalyzerNames map[string]bool +} + +func (e *CycleInRequiresGraphError) Error() string { + var b strings.Builder + b.WriteString("cycle detected involving the following analyzers:") + for n := range e.AnalyzerNames { + b.WriteByte(' ') + b.WriteString(n) + } + return b.String() +} diff --git a/vendor/golang.org/x/tools/go/ast/edge/edge.go b/vendor/golang.org/x/tools/go/ast/edge/edge.go index 4f6ccfd..8dc4dd1 100644 --- a/vendor/golang.org/x/tools/go/ast/edge/edge.go +++ b/vendor/golang.org/x/tools/go/ast/edge/edge.go @@ -12,7 +12,7 @@ import ( "reflect" ) -// A Kind describes a field of an ast.Node struct. +// A Kind describes a field of an [ast.Node] struct. type Kind uint8 // String returns a description of the edge kind. @@ -41,21 +41,25 @@ func (k Kind) Get(n ast.Node, idx int) ast.Node { panic(fmt.Sprintf("%v.Get(%T): invalid node type", k, n)) } v := reflect.ValueOf(n).Elem().Field(fieldInfos[k].index) - if idx != -1 { - v = v.Index(idx) // asserts valid index - } else { - // (The type assertion below asserts that v is not a slice.) + + if v.Kind() == reflect.Slice { + v = v.Index(idx) // asserts valid idx + } else if idx != -1 { + panic(fmt.Sprintf("%v, Get(%T, %d): cannot index non-slice", v, n, idx)) } - return v.Interface().(ast.Node) // may be nil + + out, _ := v.Interface().(ast.Node) // may be nil + return out } +// Each [Kind] is named Type_Field, where Type is the +// [ast.Node] struct type and Field is the name of the field const ( Invalid Kind = iota // for nodes at the root of the traversal - // Kinds are sorted alphabetically. - // Numbering is not stable. - // Each is named Type_Field, where Type is the - // ast.Node struct type and Field is the name of the field + // As of Go1.26 these kinds are sorted alphabetically, but + // numbering must be stable, so any new addition of const should + // use a new value (be added at the end of the list). ArrayType_Elt ArrayType_Len diff --git a/vendor/golang.org/x/tools/go/ast/inspector/cursor.go b/vendor/golang.org/x/tools/go/ast/inspector/cursor.go index fc9bbc7..1c48225 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/cursor.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/cursor.go @@ -10,6 +10,7 @@ import ( "go/token" "iter" "reflect" + "strings" "golang.org/x/tools/go/ast/edge" ) @@ -18,8 +19,11 @@ import ( // // Two Cursors compare equal if they represent the same node. // -// Call [Inspector.Root] to obtain a valid cursor for the virtual root -// node of the traversal. +// The zero value of Cursor is not valid. +// +// Call [Inspector.Root] to obtain a cursor for the virtual root node +// of the traversal. This is the sole valid cursor for which [Cursor.Node] +// returns nil. // // Use the following methods to navigate efficiently around the tree: // - for ancestors, use [Cursor.Parent] and [Cursor.Enclosing]; @@ -37,7 +41,7 @@ type Cursor struct { index int32 // index of push node; -1 for virtual root node } -// Root returns a cursor for the virtual root node, +// Root returns a valid cursor for the virtual root node, // whose children are the files provided to [New]. // // Its [Cursor.Node] method return nil. @@ -61,14 +65,23 @@ func (in *Inspector) At(index int32) Cursor { return Cursor{in, index} } +// Valid reports whether the cursor is valid. +// The zero value of cursor is invalid. +// Unless otherwise documented, it is not safe to call +// any other method on an invalid cursor. +func (c Cursor) Valid() bool { + return c.in != nil +} + // Inspector returns the cursor's Inspector. +// It returns nil if the Cursor is not valid. func (c Cursor) Inspector() *Inspector { return c.in } // Index returns the index of this cursor position within the package. // // Clients should not assume anything about the numeric Index value // except that it increases monotonically throughout the traversal. -// It is provided for use with [At]. +// It is provided for use with [Inspector.At]. // // Index must not be called on the Root node. func (c Cursor) Index() int32 { @@ -89,7 +102,7 @@ func (c Cursor) Node() ast.Node { // String returns information about the cursor's node, if any. func (c Cursor) String() string { - if c.in == nil { + if !c.Valid() { return "(invalid)" } if c.index < 0 { @@ -98,6 +111,46 @@ func (c Cursor) String() string { return reflect.TypeOf(c.Node()).String() } +// GoString returns a string describing the cursor's path from the +// root, if any. +func (c Cursor) GoString() string { + if !c.Valid() { + return "(invalid)" + } + if c.index < 0 { + return "(root)" + } + // e.g "File.Decls[1].(*ast.GenDecl).Specs[0].(*ast.TypeSpec)" + // + // In hindsight even the File node should have reported a + // virtual ParentEdge of (Root_Files, i) where i is the index + // among the files passed to NewInspector. Then the path would + // read "(root).Files[i]", etc; but we missed the boat. + var buf strings.Builder + buf.WriteString("File") + var visit func(Cursor) + visit = func(c Cursor) { + ek, idx := c.ParentEdge() + if ek == edge.Invalid { + return // File + } + visit(c.Parent()) + fmt.Fprintf(&buf, ".%s", ek.FieldName()) + if idx >= 0 { + fmt.Fprintf(&buf, "[%d]", idx) + } + ftype := ek.FieldType() + if idx >= 0 { + ftype = ftype.Elem() // []T -> T + } + if ftype.Kind() == reflect.Interface { + fmt.Fprintf(&buf, ".(%T)", c.Node()) + } + } + visit(c) + return buf.String() +} + // indices return the [start, end) half-open interval of event indices. func (c Cursor) indices() (int32, int32) { if c.index < 0 { @@ -233,6 +286,18 @@ func (c Cursor) ParentEdge() (edge.Kind, int) { return unpackEdgeKindAndIndex(events[pop].parent) } +// ParentEdgeKind returns the kind component of the result of [Cursor.ParentEdge]. +func (c Cursor) ParentEdgeKind() edge.Kind { + ek, _ := c.ParentEdge() + return ek +} + +// ParentEdgeIndex returns the index component of the result of [Cursor.ParentEdge]. +func (c Cursor) ParentEdgeIndex() int { + _, index := c.ParentEdge() + return index +} + // ChildAt returns the cursor for the child of the // current node identified by its edge and index. // The index must be -1 if the edge.Kind is not a slice. @@ -453,6 +518,9 @@ func (c Cursor) FindNode(n ast.Node) (Cursor, bool) { // rooted at c such that n.Pos() <= start && end <= n.End(). // (For an *ast.File, it uses the bounds n.FileStart-n.FileEnd.) // +// An empty range (start == end) between two adjacent nodes is +// considered to belong to the first node. +// // It returns zero if none is found. // Precondition: start <= end. // @@ -501,10 +569,17 @@ func (c Cursor) FindByPos(start, end token.Pos) (Cursor, bool) { break // disjoint, after; stop } } + // Inv: node.{Pos,FileStart} <= start if end <= nodeEnd { // node fully contains target range best = i + + // Don't search beyond end of the first match. + // This is important only for an empty range (start=end) + // between two adjoining nodes, which would otherwise + // match both nodes; we want to match only the first. + limit = ev.index } else if nodeEnd < start { i = ev.index // disjoint, before; skip forward } diff --git a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go index a703cdf..b414d17 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/inspector.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/inspector.go @@ -87,7 +87,7 @@ type event struct { // Type can be recovered from the sole bit in typ. // [Tried this, wasn't faster. --adonovan] -// Preorder visits all the nodes of the files supplied to New in +// Preorder visits all the nodes of the files supplied to [New] in // depth-first order. It calls f(n) for each node n before it visits // n's children. // @@ -133,7 +133,7 @@ func (in *Inspector) Preorder(types []ast.Node, f func(ast.Node)) { } } -// Nodes visits the nodes of the files supplied to New in depth-first +// Nodes visits the nodes of the files supplied to [New] in depth-first // order. It calls f(n, true) for each node n before it visits n's // children. If f returns true, Nodes invokes f recursively for each // of the non-nil children of the node, followed by a call of diff --git a/vendor/golang.org/x/tools/go/ast/inspector/iter.go b/vendor/golang.org/x/tools/go/ast/inspector/iter.go index c576dc7..b68c553 100644 --- a/vendor/golang.org/x/tools/go/ast/inspector/iter.go +++ b/vendor/golang.org/x/tools/go/ast/inspector/iter.go @@ -12,13 +12,31 @@ import ( ) // PreorderSeq returns an iterator that visits all the -// nodes of the files supplied to New in depth-first order. +// nodes of the files supplied to [New] in depth-first order. // It visits each node n before n's children. // The complete traversal sequence is determined by ast.Inspect. // -// The types argument, if non-empty, enables type-based -// filtering of events: only nodes whose type matches an -// element of the types slice are included in the sequence. +// The types argument, if non-empty, enables type-based filtering: +// only nodes whose type matches an element of the types slice are +// included in the sequence. +// +// Example: +// +// for call := range in.PreorderSeq((*ast.CallExpr)(nil)) { ... } +// +// The [All] function is more convenient if there is exactly one node type: +// +// for call := range All[*ast.CallExpr](in) { ... } +// +// See also the newer and more flexible [Cursor] API, which lets you +// start the traversal at an arbitrary node, and reports each matching +// node by its Cursor, enabling easier navigation. +// The above example would be written thus: +// +// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) { +// call := curCall.Node().(*ast.CallExpr) +// ... +// } func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] { // This implementation is identical to Preorder, @@ -53,6 +71,16 @@ func (in *Inspector) PreorderSeq(types ...ast.Node) iter.Seq[ast.Node] { // Example: // // for call := range All[*ast.CallExpr](in) { ... } +// +// See also the newer and more flexible [Cursor] API, which lets you +// start the traversal at an arbitrary node, and reports each matching +// node by its Cursor, enabling easier navigation. +// The above example would be written thus: +// +// for curCall := range in.Root().Preorder((*ast.CallExpr)(nil)) { +// call := curCall.Node().(*ast.CallExpr) +// ... +// } func All[N interface { *S ast.Node diff --git a/vendor/golang.org/x/tools/go/buildutil/allpackages.go b/vendor/golang.org/x/tools/go/buildutil/allpackages.go index 8a7f0fc..1a77435 100644 --- a/vendor/golang.org/x/tools/go/buildutil/allpackages.go +++ b/vendor/golang.org/x/tools/go/buildutil/allpackages.go @@ -52,11 +52,9 @@ func ForEachPackage(ctxt *build.Context, found func(importPath string, err error var wg sync.WaitGroup for _, root := range ctxt.SrcDirs() { - wg.Add(1) - go func() { + wg.Go(func() { allPackages(ctxt, root, ch) - wg.Done() - }() + }) } go func() { wg.Wait() @@ -107,11 +105,9 @@ func allPackages(ctxt *build.Context, root string, ch chan<- item) { } for _, fi := range files { if fi.IsDir() { - wg.Add(1) - go func() { + wg.Go(func() { walkDir(filepath.Join(dir, fi.Name())) - wg.Done() - }() + }) } } } diff --git a/vendor/golang.org/x/tools/go/callgraph/rta/rta.go b/vendor/golang.org/x/tools/go/callgraph/rta/rta.go index 442a226..fdb64e0 100644 --- a/vendor/golang.org/x/tools/go/callgraph/rta/rta.go +++ b/vendor/golang.org/x/tools/go/callgraph/rta/rta.go @@ -45,6 +45,7 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/internal/typesinternal" ) // A Result holds the results of Rapid Type Analysis, which includes the @@ -72,6 +73,10 @@ type Result struct { // fmt.Println(new(A)) // Types *A, A and B are accessible to reflection, but the unnamed // type struct{B} is not. + // + // TODO(adonovan): populating this field is expensive yet it + // is never used in x/tools. Add a revised [Analyze] API that + // provides the option not to set it. RuntimeTypes typeutil.Map } @@ -114,15 +119,13 @@ type rta struct { type concreteTypeInfo struct { C types.Type - mset *types.MethodSet fprint uint64 // fingerprint of method set implements []*types.Interface // unordered set of implemented interfaces } type interfaceTypeInfo struct { I *types.Interface - mset *types.MethodSet - fprint uint64 + fprint uint64 // fingerprint of method set implementations []types.Type // unordered set of concrete implementations } @@ -281,7 +284,7 @@ func (r *rta) visitFunc(f *ssa.Function) { // interface materializes its runtime // type, allowing any of its exported // methods to be called though reflection. - r.addRuntimeType(instr.X.Type(), false) + r.addRuntimeType(instr.X.Type()) } // Process all address-taken functions. @@ -361,11 +364,9 @@ func (r *rta) interfaces(C types.Type) []*types.Interface { if v := r.concreteTypes.At(C); v != nil { cinfo = v.(*concreteTypeInfo) } else { - mset := r.prog.MethodSets.MethodSet(C) cinfo = &concreteTypeInfo{ C: C, - mset: mset, - fprint: fingerprint(mset), + fprint: fingerprint(r.prog.MethodSets.MethodSet(C)), } r.concreteTypes.Set(C, cinfo) @@ -390,11 +391,9 @@ func (r *rta) implementations(I *types.Interface) []types.Type { if v := r.interfaceTypes.At(I); v != nil { iinfo = v.(*interfaceTypeInfo) } else { - mset := r.prog.MethodSets.MethodSet(I) iinfo = &interfaceTypeInfo{ I: I, - mset: mset, - fprint: fingerprint(mset), + fprint: fingerprint(r.prog.MethodSets.MethodSet(I)), } r.interfaceTypes.Set(I, iinfo) @@ -413,127 +412,43 @@ func (r *rta) implementations(I *types.Interface) []types.Type { // addRuntimeType is called for each concrete type that can be the // dynamic type of some interface or reflect.Value. -// Adapted from needMethods in go/ssa/builder.go -func (r *rta) addRuntimeType(T types.Type, skip bool) { - // Never record aliases. - T = types.Unalias(T) - - if prev, ok := r.result.RuntimeTypes.At(T).(bool); ok { - if skip && !prev { - r.result.RuntimeTypes.Set(T, skip) +func (r *rta) addRuntimeType(T types.Type) { + methodSetOf := r.prog.MethodSets.MethodSet + typesinternal.ForEachElement(methodSetOf, T, func(T types.Type, access bool) bool { + if prevInaccess, ok := r.result.RuntimeTypes.At(T).(bool); ok { + if prevInaccess && access { + // A type previously marked inaccessible (ok && prevInaccess) + // is now found to be accessible (access): + // record that it is no longer inaccessible (false). + // (The inverted sense of the map is regrettable.) + r.result.RuntimeTypes.Set(T, false) + } + return true // seen; prune traversal } - return - } - r.result.RuntimeTypes.Set(T, skip) + r.result.RuntimeTypes.Set(T, !access) // record inaccessibility - mset := r.prog.MethodSets.MethodSet(T) + if !types.IsInterface(T) { + // T is a new concrete type. - if _, ok := T.Underlying().(*types.Interface); !ok { - // T is a new concrete type. - for i, n := 0, mset.Len(); i < n; i++ { - sel := mset.At(i) - m := sel.Obj() + // Exported methods are always potentially callable via reflection. + for sel := range methodSetOf(T).Methods() { + if sel.Obj().Exported() { + r.addReachable(r.prog.MethodValue(sel), true) + } + } - if m.Exported() { - // Exported methods are always potentially callable via reflection. - r.addReachable(r.prog.MethodValue(sel), true) + // Add callgraph edge for each existing dynamic + // "invoke"-mode call via that interface. + for _, I := range r.interfaces(T) { + sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction) + for _, site := range sites { + r.addInvokeEdge(site, T) + } } } - // Add callgraph edge for each existing dynamic - // "invoke"-mode call via that interface. - for _, I := range r.interfaces(T) { - sites, _ := r.invokeSites.At(I).([]ssa.CallInstruction) - for _, site := range sites { - r.addInvokeEdge(site, T) - } - } - } - - // Precondition: T is not a method signature (*Signature with Recv()!=nil). - // Recursive case: skip => don't call makeMethods(T). - // Each package maintains its own set of types it has visited. - - var n *types.Named - switch T := types.Unalias(T).(type) { - case *types.Named: - n = T - case *types.Pointer: - n, _ = types.Unalias(T.Elem()).(*types.Named) - } - if n != nil { - owner := n.Obj().Pkg() - if owner == nil { - return // built-in error type - } - } - - // Recursion over signatures of each exported method. - for method := range mset.Methods() { - if method.Obj().Exported() { - sig := method.Type().(*types.Signature) - r.addRuntimeType(sig.Params(), true) // skip the Tuple itself - r.addRuntimeType(sig.Results(), true) // skip the Tuple itself - } - } - - switch t := T.(type) { - case *types.Alias: - panic("unreachable") - - case *types.Basic: - // nop - - case *types.Interface: - // nop---handled by recursion over method set. - - case *types.Pointer: - r.addRuntimeType(t.Elem(), false) - - case *types.Slice: - r.addRuntimeType(t.Elem(), false) - - case *types.Chan: - r.addRuntimeType(t.Elem(), false) - - case *types.Map: - r.addRuntimeType(t.Key(), false) - r.addRuntimeType(t.Elem(), false) - - case *types.Signature: - if t.Recv() != nil { - panic(fmt.Sprintf("Signature %s has Recv %s", t, t.Recv())) - } - r.addRuntimeType(t.Params(), true) // skip the Tuple itself - r.addRuntimeType(t.Results(), true) // skip the Tuple itself - - case *types.Named: - // A pointer-to-named type can be derived from a named - // type via reflection. It may have methods too. - r.addRuntimeType(types.NewPointer(T), false) - - // Consider 'type T struct{S}' where S has methods. - // Reflection provides no way to get from T to struct{S}, - // only to S, so the method set of struct{S} is unwanted, - // so set 'skip' flag during recursion. - r.addRuntimeType(t.Underlying(), true) - - case *types.Array: - r.addRuntimeType(t.Elem(), false) - - case *types.Struct: - for i, n := 0, t.NumFields(); i < n; i++ { - r.addRuntimeType(t.Field(i).Type(), false) - } - - case *types.Tuple: - for i, n := 0, t.Len(); i < n; i++ { - r.addRuntimeType(t.At(i).Type(), false) - } - - default: - panic(T) - } + return false + }) } // fingerprint returns a bitmask with one bit set per method id, @@ -544,6 +459,9 @@ func fingerprint(mset *types.MethodSet) uint64 { for method := range mset.Methods() { method := method.Obj() sig := method.Type().(*types.Signature) + if sig.TypeParams() != nil { + continue // skip generic methods since interfaces don't have them + } sum := crc32.ChecksumIEEE(fmt.Appendf(space[:], "%s/%d/%d", method.Id(), sig.Params().Len(), diff --git a/vendor/golang.org/x/tools/go/callgraph/util.go b/vendor/golang.org/x/tools/go/callgraph/util.go index 5499320..d8562a9 100644 --- a/vendor/golang.org/x/tools/go/callgraph/util.go +++ b/vendor/golang.org/x/tools/go/callgraph/util.go @@ -101,6 +101,7 @@ func (g *Graph) DeleteSyntheticNodes() { edges[*e] = true } } + // Note: these cross-products can exceed 100 x 100. for fn, cgn := range g.Nodes { if cgn == g.Root || isInit(cgn.Func) || fn.Syntax() != nil { continue // keep diff --git a/vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go b/vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go index 4a6d0bb..1f43742 100644 --- a/vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go +++ b/vendor/golang.org/x/tools/go/callgraph/vta/internal/trie/scope.go @@ -16,10 +16,10 @@ type Scope struct { id int32 } -var nextScopeId int32 +var nextScopeId atomic.Int32 func newScope() Scope { - id := atomic.AddInt32(&nextScopeId, 1) + id := nextScopeId.Add(1) return Scope{id: id} } diff --git a/vendor/golang.org/x/tools/go/callgraph/vta/propagation.go b/vendor/golang.org/x/tools/go/callgraph/vta/propagation.go index a96f82d..33ec246 100644 --- a/vendor/golang.org/x/tools/go/callgraph/vta/propagation.go +++ b/vendor/golang.org/x/tools/go/callgraph/vta/propagation.go @@ -155,9 +155,9 @@ func propagate(graph *vtaGraph, canon *typeutil.Map) propTypeMap { sccToTypes[sccID] = &typeSet } - for i := len(sccs) - 1; i >= 0; i-- { + for i, scc := range slices.Backward(sccs) { nextSccs := make(map[int]empty) - for _, n := range sccs[i] { + for _, n := range scc { for succ := range graph.successors(n) { nextSccs[idxToSccID[succ]] = empty{} } diff --git a/vendor/golang.org/x/tools/go/callgraph/vta/vta.go b/vendor/golang.org/x/tools/go/callgraph/vta/vta.go index ed12001..26b33f0 100644 --- a/vendor/golang.org/x/tools/go/callgraph/vta/vta.go +++ b/vendor/golang.org/x/tools/go/callgraph/vta/vta.go @@ -73,6 +73,9 @@ import ( // CallGraph does not make any assumptions on initial types global variables // and function/method inputs can have. CallGraph is then sound, modulo use of // reflection and unsafe, if the initial call graph is sound. +// +// The supplied SSA functions must have been constructed with the +// [ssa.InstantiateGenerics] mode flag. func CallGraph(funcs map[*ssa.Function]bool, initial *callgraph.Graph) *callgraph.Graph { callees := makeCalleesFunc(funcs, initial) vtaG, canon := typePropGraph(funcs, callees) diff --git a/vendor/golang.org/x/tools/go/cfg/builder.go b/vendor/golang.org/x/tools/go/cfg/builder.go new file mode 100644 index 0000000..f16cd42 --- /dev/null +++ b/vendor/golang.org/x/tools/go/cfg/builder.go @@ -0,0 +1,519 @@ +// Copyright 2016 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 cfg + +// This file implements the CFG construction pass. + +import ( + "fmt" + "go/ast" + "go/token" +) + +type builder struct { + blocks []*Block + mayReturn func(*ast.CallExpr) bool + current *Block + lblocks map[string]*lblock // labeled blocks + targets *targets // linked stack of branch targets +} + +func (b *builder) stmt(_s ast.Stmt) { + // The label of the current statement. If non-nil, its _goto + // target is always set; its _break and _continue are set only + // within the body of switch/typeswitch/select/for/range. + // It is effectively an additional default-nil parameter of stmt(). + var label *lblock +start: + switch s := _s.(type) { + case *ast.BadStmt, + *ast.SendStmt, + *ast.IncDecStmt, + *ast.GoStmt, + *ast.EmptyStmt, + *ast.AssignStmt: + // No effect on control flow. + b.add(s) + + case *ast.DeferStmt: + b.add(s) + // Assume conservatively that this behaves like: + // defer func() { recover() } + // so any subsequent panic may act like a return. + b.current.returns = true + + case *ast.ExprStmt: + b.add(s) + if call, ok := s.X.(*ast.CallExpr); ok && !b.mayReturn(call) { + // Calls to panic, os.Exit, etc, never return. + b.current = b.newBlock(KindUnreachable, s) + } + + case *ast.DeclStmt: + // Treat each var ValueSpec as a separate statement. + d := s.Decl.(*ast.GenDecl) + if d.Tok == token.VAR { + for _, spec := range d.Specs { + if spec, ok := spec.(*ast.ValueSpec); ok { + b.add(spec) + } + } + } + + case *ast.LabeledStmt: + label = b.labeledBlock(s.Label, s) + b.jump(label._goto) + b.current = label._goto + _s = s.Stmt + goto start // effectively: tailcall stmt(g, s.Stmt, label) + + case *ast.ReturnStmt: + b.current.returns = true + b.add(s) + b.current = b.newBlock(KindUnreachable, s) + + case *ast.BranchStmt: + b.branchStmt(s) + + case *ast.BlockStmt: + b.stmtList(s.List) + + case *ast.IfStmt: + if s.Init != nil { + b.stmt(s.Init) + } + then := b.newBlock(KindIfThen, s) + done := b.newBlock(KindIfDone, s) + _else := done + if s.Else != nil { + _else = b.newBlock(KindIfElse, s) + } + b.add(s.Cond) + b.ifelse(then, _else) + b.current = then + b.stmt(s.Body) + b.jump(done) + + if s.Else != nil { + b.current = _else + b.stmt(s.Else) + b.jump(done) + } + + b.current = done + + case *ast.SwitchStmt: + b.switchStmt(s, label) + + case *ast.TypeSwitchStmt: + b.typeSwitchStmt(s, label) + + case *ast.SelectStmt: + b.selectStmt(s, label) + + case *ast.ForStmt: + b.forStmt(s, label) + + case *ast.RangeStmt: + b.rangeStmt(s, label) + + default: + panic(fmt.Sprintf("unexpected statement kind: %T", s)) + } +} + +func (b *builder) stmtList(list []ast.Stmt) { + for _, s := range list { + b.stmt(s) + } +} + +func (b *builder) branchStmt(s *ast.BranchStmt) { + var block *Block + switch s.Tok { + case token.BREAK: + if s.Label != nil { + if lb := b.labeledBlock(s.Label, nil); lb != nil { + block = lb._break + } + } else { + for t := b.targets; t != nil && block == nil; t = t.tail { + block = t._break + } + } + + case token.CONTINUE: + if s.Label != nil { + if lb := b.labeledBlock(s.Label, nil); lb != nil { + block = lb._continue + } + } else { + for t := b.targets; t != nil && block == nil; t = t.tail { + block = t._continue + } + } + + case token.FALLTHROUGH: + for t := b.targets; t != nil && block == nil; t = t.tail { + block = t._fallthrough + } + + case token.GOTO: + if s.Label != nil { + block = b.labeledBlock(s.Label, nil)._goto + } + } + if block == nil { // ill-typed (e.g. undefined label) + block = b.newBlock(KindUnreachable, s) + } + b.jump(block) + b.current = b.newBlock(KindUnreachable, s) +} + +func (b *builder) switchStmt(s *ast.SwitchStmt, label *lblock) { + if s.Init != nil { + b.stmt(s.Init) + } + if s.Tag != nil { + b.add(s.Tag) + } + done := b.newBlock(KindSwitchDone, s) + if label != nil { + label._break = done + } + // We pull the default case (if present) down to the end. + // But each fallthrough label must point to the next + // body block in source order, so we preallocate a + // body block (fallthru) for the next case. + // Unfortunately this makes for a confusing block order. + var defaultBody *[]ast.Stmt + var defaultFallthrough *Block + var fallthru, defaultBlock *Block + ncases := len(s.Body.List) + for i, clause := range s.Body.List { + body := fallthru + if body == nil { + body = b.newBlock(KindSwitchCaseBody, clause) // first case only + } + + // Preallocate body block for the next case. + fallthru = done + if i+1 < ncases { + fallthru = b.newBlock(KindSwitchCaseBody, s.Body.List[i+1]) + } + + cc := clause.(*ast.CaseClause) + if cc.List == nil { + // Default case. + defaultBody = &cc.Body + defaultFallthrough = fallthru + defaultBlock = body + continue + } + + var nextCond *Block + for _, cond := range cc.List { + nextCond = b.newBlock(KindSwitchNextCase, cc) + b.add(cond) // one half of the tag==cond condition + b.ifelse(body, nextCond) + b.current = nextCond + } + b.current = body + b.targets = &targets{ + tail: b.targets, + _break: done, + _fallthrough: fallthru, + } + b.stmtList(cc.Body) + b.targets = b.targets.tail + b.jump(done) + b.current = nextCond + } + if defaultBlock != nil { + b.jump(defaultBlock) + b.current = defaultBlock + b.targets = &targets{ + tail: b.targets, + _break: done, + _fallthrough: defaultFallthrough, + } + b.stmtList(*defaultBody) + b.targets = b.targets.tail + } + b.jump(done) + b.current = done +} + +func (b *builder) typeSwitchStmt(s *ast.TypeSwitchStmt, label *lblock) { + if s.Init != nil { + b.stmt(s.Init) + } + if s.Assign != nil { + b.add(s.Assign) + } + + done := b.newBlock(KindSwitchDone, s) + if label != nil { + label._break = done + } + var default_ *ast.CaseClause + for _, clause := range s.Body.List { + cc := clause.(*ast.CaseClause) + if cc.List == nil { + default_ = cc + continue + } + body := b.newBlock(KindSwitchCaseBody, cc) + var next *Block + for _, casetype := range cc.List { + next = b.newBlock(KindSwitchNextCase, cc) + // casetype is a type, so don't call b.add(casetype). + // This block logically contains a type assertion, + // x.(casetype), but it's unclear how to represent x. + _ = casetype + b.ifelse(body, next) + b.current = next + } + b.current = body + b.typeCaseBody(cc, done) + b.current = next + } + if default_ != nil { + b.typeCaseBody(default_, done) + } else { + b.jump(done) + } + b.current = done +} + +func (b *builder) typeCaseBody(cc *ast.CaseClause, done *Block) { + b.targets = &targets{ + tail: b.targets, + _break: done, + } + b.stmtList(cc.Body) + b.targets = b.targets.tail + b.jump(done) +} + +func (b *builder) selectStmt(s *ast.SelectStmt, label *lblock) { + // First evaluate channel expressions. + // TODO(adonovan): fix: evaluate only channel exprs here. + for _, clause := range s.Body.List { + if comm := clause.(*ast.CommClause).Comm; comm != nil { + b.stmt(comm) + } + } + + done := b.newBlock(KindSelectDone, s) + if label != nil { + label._break = done + } + + var defaultBody *[]ast.Stmt + for _, cc := range s.Body.List { + clause := cc.(*ast.CommClause) + if clause.Comm == nil { + defaultBody = &clause.Body + continue + } + body := b.newBlock(KindSelectCaseBody, clause) + next := b.newBlock(KindSelectAfterCase, clause) + b.ifelse(body, next) + b.current = body + b.targets = &targets{ + tail: b.targets, + _break: done, + } + switch comm := clause.Comm.(type) { + case *ast.ExprStmt: // <-ch + // nop + case *ast.AssignStmt: // x := <-states[state].Chan + b.add(comm.Lhs[0]) + } + b.stmtList(clause.Body) + b.targets = b.targets.tail + b.jump(done) + b.current = next + } + if defaultBody != nil { + b.targets = &targets{ + tail: b.targets, + _break: done, + } + b.stmtList(*defaultBody) + b.targets = b.targets.tail + b.jump(done) + } + b.current = done +} + +func (b *builder) forStmt(s *ast.ForStmt, label *lblock) { + // ...init... + // jump loop + // loop: + // if cond goto body else done + // body: + // ...body... + // jump post + // post: (target of continue) + // ...post... + // jump loop + // done: (target of break) + if s.Init != nil { + b.stmt(s.Init) + } + body := b.newBlock(KindForBody, s) + done := b.newBlock(KindForDone, s) // target of 'break' + loop := body // target of back-edge + if s.Cond != nil { + loop = b.newBlock(KindForLoop, s) + } + cont := loop // target of 'continue' + if s.Post != nil { + cont = b.newBlock(KindForPost, s) + } + if label != nil { + label._break = done + label._continue = cont + } + b.jump(loop) + b.current = loop + if loop != body { + b.add(s.Cond) + b.ifelse(body, done) + b.current = body + } + b.targets = &targets{ + tail: b.targets, + _break: done, + _continue: cont, + } + b.stmt(s.Body) + b.targets = b.targets.tail + b.jump(cont) + + if s.Post != nil { + b.current = cont + b.stmt(s.Post) + b.jump(loop) // back-edge + } + b.current = done +} + +func (b *builder) rangeStmt(s *ast.RangeStmt, label *lblock) { + b.add(s.X) + + if s.Key != nil { + b.add(s.Key) + } + if s.Value != nil { + b.add(s.Value) + } + + // ... + // loop: (target of continue) + // if ... goto body else done + // body: + // ... + // jump loop + // done: (target of break) + + loop := b.newBlock(KindRangeLoop, s) + b.jump(loop) + b.current = loop + + body := b.newBlock(KindRangeBody, s) + done := b.newBlock(KindRangeDone, s) + b.ifelse(body, done) + b.current = body + + if label != nil { + label._break = done + label._continue = loop + } + b.targets = &targets{ + tail: b.targets, + _break: done, + _continue: loop, + } + b.stmt(s.Body) + b.targets = b.targets.tail + b.jump(loop) // back-edge + b.current = done +} + +// -------- helpers -------- + +// Destinations associated with unlabeled for/switch/select stmts. +// We push/pop one of these as we enter/leave each construct and for +// each BranchStmt we scan for the innermost target of the right type. +type targets struct { + tail *targets // rest of stack + _break *Block + _continue *Block + _fallthrough *Block +} + +// Destinations associated with a labeled block. +// We populate these as labels are encountered in forward gotos or +// labeled statements. +type lblock struct { + _goto *Block + _break *Block + _continue *Block +} + +// labeledBlock returns the branch target associated with the +// specified label, creating it if needed. +func (b *builder) labeledBlock(label *ast.Ident, stmt *ast.LabeledStmt) *lblock { + lb := b.lblocks[label.Name] + if lb == nil { + lb = &lblock{_goto: b.newBlock(KindLabel, nil)} + if b.lblocks == nil { + b.lblocks = make(map[string]*lblock) + } + b.lblocks[label.Name] = lb + } + // Fill in the label later (in case of forward goto). + // Stmt may be set already if labels are duplicated (ill-typed). + if stmt != nil && lb._goto.Stmt == nil { + lb._goto.Stmt = stmt + } + return lb +} + +// newBlock appends a new unconnected basic block to b.cfg's block +// slice and returns it. +// It does not automatically become the current block. +// comment is an optional string for more readable debugging output. +func (b *builder) newBlock(kind BlockKind, stmt ast.Stmt) *Block { + block := &Block{ + Index: int32(len(b.blocks)), + Kind: kind, + Stmt: stmt, + } + block.Succs = block.succs2[:0] + b.blocks = append(b.blocks, block) + return block +} + +func (b *builder) add(n ast.Node) { + b.current.Nodes = append(b.current.Nodes, n) +} + +// jump adds an edge from the current block to the target block, +// and sets b.current to nil. +func (b *builder) jump(target *Block) { + b.current.Succs = append(b.current.Succs, target) + b.current = nil +} + +// ifelse emits edges from the current block to the t and f blocks, +// and sets b.current to nil. +func (b *builder) ifelse(t, f *Block) { + b.current.Succs = append(b.current.Succs, t, f) + b.current = nil +} diff --git a/vendor/golang.org/x/tools/go/cfg/cfg.go b/vendor/golang.org/x/tools/go/cfg/cfg.go new file mode 100644 index 0000000..f69912c --- /dev/null +++ b/vendor/golang.org/x/tools/go/cfg/cfg.go @@ -0,0 +1,273 @@ +// Copyright 2016 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 cfg constructs a simple control-flow graph (CFG) of the +// statements and expressions within a single function. +// +// Use cfg.New to construct the CFG for a function body. +// +// The blocks of the CFG contain all the function's non-control +// statements. The CFG does not contain control statements such as If, +// Switch, Select, and Branch, but does contain their subexpressions; +// also, each block records the control statement (Block.Stmt) that +// gave rise to it and its relationship (Block.Kind) to that statement. +// +// For example, this source code: +// +// if x := f(); x != nil { +// T() +// } else { +// F() +// } +// +// produces this CFG: +// +// 1: x := f() Body +// x != nil +// succs: 2, 3 +// 2: T() IfThen +// succs: 4 +// 3: F() IfElse +// succs: 4 +// 4: IfDone +// +// The CFG does contain Return statements; even implicit returns are +// materialized (at the position of the function's closing brace). +// +// The CFG does not record conditions associated with conditional branch +// edges, nor the short-circuit semantics of the && and || operators, +// nor abnormal control flow caused by panic. If you need this +// information, use golang.org/x/tools/go/ssa instead. +package cfg + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" +) + +// A CFG represents the control-flow graph of a single function. +// +// The entry point is Blocks[0]; there may be multiple return blocks. +type CFG struct { + Blocks []*Block // block[0] is entry; order otherwise undefined + noreturn bool // function body lacks a reachable return statement +} + +// NoReturn reports whether the function has no reachable return. +func (cfg *CFG) NoReturn() bool { return cfg.noreturn } + +// A Block represents a basic block: a list of statements and +// expressions that are always evaluated sequentially. +// +// A block may have 0-2 successors: zero for a return block or a block +// that calls a function such as panic that never returns; one for a +// normal (jump) block; and two for a conditional (if) block. +// +// In a conditional block, the last entry in Nodes is the condition and always +// an [ast.Expr], Succs[0] is the successor if the condition is true, and +// Succs[1] is the successor if the condition is false. +type Block struct { + Nodes []ast.Node // statements, expressions, and ValueSpecs + Succs []*Block // successor nodes in the graph + Index int32 // index within CFG.Blocks + Live bool // block is reachable from entry + returns bool // block contains return or defer (which may recover and return) + Kind BlockKind // block kind + Stmt ast.Stmt // statement that gave rise to this block (see BlockKind for details) + + succs2 [2]*Block // underlying array for Succs +} + +// A BlockKind identifies the purpose of a block. +// It also determines the possible types of its Stmt field. +type BlockKind uint8 + +const ( + KindInvalid BlockKind = iota // Stmt=nil + + KindUnreachable // unreachable block after {Branch,Return}Stmt / no-return call ExprStmt + KindBody // function body BlockStmt + KindForBody // body of ForStmt + KindForDone // block after ForStmt + KindForLoop // head of ForStmt + KindForPost // post condition of ForStmt + KindIfDone // block after IfStmt + KindIfElse // else block of IfStmt + KindIfThen // then block of IfStmt + KindLabel // labeled block of BranchStmt (Stmt may be nil for dangling label) + KindRangeBody // body of RangeStmt + KindRangeDone // block after RangeStmt + KindRangeLoop // head of RangeStmt + KindSelectCaseBody // body of SelectStmt + KindSelectDone // block after SelectStmt + KindSelectAfterCase // block after a CommClause + KindSwitchCaseBody // body of CaseClause + KindSwitchDone // block after {Type.}SwitchStmt + KindSwitchNextCase // secondary expression of a multi-expression CaseClause +) + +func (kind BlockKind) String() string { + return [...]string{ + KindInvalid: "Invalid", + KindUnreachable: "Unreachable", + KindBody: "Body", + KindForBody: "ForBody", + KindForDone: "ForDone", + KindForLoop: "ForLoop", + KindForPost: "ForPost", + KindIfDone: "IfDone", + KindIfElse: "IfElse", + KindIfThen: "IfThen", + KindLabel: "Label", + KindRangeBody: "RangeBody", + KindRangeDone: "RangeDone", + KindRangeLoop: "RangeLoop", + KindSelectCaseBody: "SelectCaseBody", + KindSelectDone: "SelectDone", + KindSelectAfterCase: "SelectAfterCase", + KindSwitchCaseBody: "SwitchCaseBody", + KindSwitchDone: "SwitchDone", + KindSwitchNextCase: "SwitchNextCase", + }[kind] +} + +// New returns a new control-flow graph for the specified function body, +// which must be non-nil. +// +// The CFG builder calls mayReturn to determine whether a given function +// call may return. For example, calls to panic, os.Exit, and log.Fatal +// do not return, so the builder can remove infeasible graph edges +// following such calls. The builder calls mayReturn only for a +// CallExpr beneath an ExprStmt. +func New(body *ast.BlockStmt, mayReturn func(*ast.CallExpr) bool) *CFG { + b := builder{ + mayReturn: mayReturn, + } + b.current = b.newBlock(KindBody, body) + b.stmt(body) + + // Compute liveness (reachability from entry point), + // breadth-first, marking Block.Live flags. + q := make([]*Block, 0, len(b.blocks)) + q = append(q, b.blocks[0]) // entry point + for len(q) > 0 { + b := q[len(q)-1] + q = q[:len(q)-1] + + if !b.Live { + b.Live = true + q = append(q, b.Succs...) + } + } + + // Does control fall off the end of the function's body? + // Make implicit return explicit. + if b.current != nil && b.current.Live { + b.current.returns = true + b.add(&ast.ReturnStmt{ + Return: body.End() - 1, + }) + } + + // Is any return (or defer+recover) block reachable? + noreturn := true + for _, bl := range b.blocks { + if bl.Live && bl.returns { + noreturn = false + break + } + } + + return &CFG{Blocks: b.blocks, noreturn: noreturn} +} + +func (b *Block) String() string { + return fmt.Sprintf("block %d (%s)", b.Index, b.comment(nil)) +} + +func (b *Block) comment(fset *token.FileSet) string { + s := b.Kind.String() + if fset != nil && b.Stmt != nil { + s = fmt.Sprintf("%s@L%d", s, fset.Position(b.Stmt.Pos()).Line) + } + return s +} + +// Return returns the return statement at the end of this block if present, nil +// otherwise. +// +// When control falls off the end of the function, the ReturnStmt is synthetic +// and its [ast.Node.End] position may be beyond the end of the file. +// +// A function that contains no return statement (explicit or implied) +// may yet return normally, and may even return a nonzero value. For example: +// +// func() (res any) { +// defer func() { res = recover() }() +// panic(123) +// } +func (b *Block) Return() (ret *ast.ReturnStmt) { + if len(b.Nodes) > 0 { + ret, _ = b.Nodes[len(b.Nodes)-1].(*ast.ReturnStmt) + } + return +} + +// Format formats the control-flow graph for ease of debugging. +func (g *CFG) Format(fset *token.FileSet) string { + var buf bytes.Buffer + for _, b := range g.Blocks { + fmt.Fprintf(&buf, ".%d: # %s\n", b.Index, b.comment(fset)) + for _, n := range b.Nodes { + fmt.Fprintf(&buf, "\t%s\n", formatNode(fset, n)) + } + if len(b.Succs) > 0 { + fmt.Fprintf(&buf, "\tsuccs:") + for _, succ := range b.Succs { + fmt.Fprintf(&buf, " %d", succ.Index) + } + buf.WriteByte('\n') + } + buf.WriteByte('\n') + } + return buf.String() +} + +// Dot returns the control-flow graph in the [Dot graph description language]. +// Use a command such as 'dot -Tsvg' to render it in a form viewable in a browser. +// This method is provided as a debugging aid; the details of the +// output are unspecified and may change. +// +// [Dot graph description language]: ​​https://en.wikipedia.org/wiki/DOT_(graph_description_language) +func (g *CFG) Dot(fset *token.FileSet) string { + var buf bytes.Buffer + buf.WriteString("digraph CFG {\n") + buf.WriteString(" node [shape=box];\n") + for _, b := range g.Blocks { + // node label + var text bytes.Buffer + text.WriteString(b.comment(fset)) + for _, n := range b.Nodes { + fmt.Fprintf(&text, "\n%s", formatNode(fset, n)) + } + + // node and edges + fmt.Fprintf(&buf, " n%d [label=%q];\n", b.Index, &text) + for _, succ := range b.Succs { + fmt.Fprintf(&buf, " n%d -> n%d;\n", b.Index, succ.Index) + } + } + buf.WriteString("}\n") + return buf.String() +} + +func formatNode(fset *token.FileSet, n ast.Node) string { + var buf bytes.Buffer + format.Node(&buf, fset, n) + // Indent secondary lines by a tab. + return string(bytes.Replace(buf.Bytes(), []byte("\n"), []byte("\n\t"), -1)) +} diff --git a/vendor/golang.org/x/tools/go/loader/loader.go b/vendor/golang.org/x/tools/go/loader/loader.go index 9c5f7db..3c9d4fe 100644 --- a/vendor/golang.org/x/tools/go/loader/loader.go +++ b/vendor/golang.org/x/tools/go/loader/loader.go @@ -738,7 +738,9 @@ func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.Fil // Preprocess CgoFiles and parse the outputs (sequentially). if which == 'g' && bp.CgoFiles != nil { + ioLimit <- true cgofiles, err := cgo.ProcessFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode) + <-ioLimit if err != nil { errs = append(errs, err) } else { diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 680a70c..8e60cbb 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -61,13 +61,42 @@ func (r *responseDeduper) addAll(dr *DriverResponse) { } func (r *responseDeduper) addPackage(p *Package) { - if r.seenPackages[p.ID] != nil { + if prev := r.seenPackages[p.ID]; prev != nil { + // Package already seen in a previous response. Merge the file lists, + // removing duplicates. This can happen when the same package appears + // in multiple driver responses that are being merged together. + prev.GoFiles = appendUniqueStrings(prev.GoFiles, p.GoFiles) + prev.CompiledGoFiles = appendUniqueStrings(prev.CompiledGoFiles, p.CompiledGoFiles) + prev.OtherFiles = appendUniqueStrings(prev.OtherFiles, p.OtherFiles) + prev.IgnoredFiles = appendUniqueStrings(prev.IgnoredFiles, p.IgnoredFiles) + prev.EmbedFiles = appendUniqueStrings(prev.EmbedFiles, p.EmbedFiles) + prev.EmbedPatterns = appendUniqueStrings(prev.EmbedPatterns, p.EmbedPatterns) return } r.seenPackages[p.ID] = p r.dr.Packages = append(r.dr.Packages, p) } +// appendUniqueStrings appends elements from src to dst, skipping duplicates. +func appendUniqueStrings(dst, src []string) []string { + if len(src) == 0 { + return dst + } + + seen := make(map[string]bool, len(dst)) + for _, s := range dst { + seen[s] = true + } + + for _, s := range src { + if !seen[s] { + dst = append(dst, s) + } + } + + return dst +} + func (r *responseDeduper) addRoot(id string) { if r.seenRoots[id] { return @@ -178,11 +207,10 @@ func goListDriver(cfg *Config, runner *gocommand.Runner, overlay string, pattern // doesn't exist. extractQueries: for _, pattern := range patterns { - eqidx := strings.Index(pattern, "=") - if eqidx < 0 { + query, value, ok := strings.Cut(pattern, "=") + if !ok { restPatterns = append(restPatterns, pattern) } else { - query, value := pattern[:eqidx], pattern[eqidx+len("="):] switch query { case "file": containFiles = append(containFiles, value) @@ -534,8 +562,18 @@ func (state *golistState) createDriverResponse(words ...string) (*DriverResponse } else { // golang/go#38990: go list silently fails to do cgo processing pkg.CompiledGoFiles = nil + + var msg strings.Builder + fmt.Fprintf(&msg, "go list failed to return CompiledGoFiles for %q.\n", p.Name) + + for _, err := range p.DepsErrors { + msg.WriteString(strings.TrimSpace(err.Err)) + msg.WriteByte('\n') + } + + msg.WriteString("This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.") pkg.Errors = append(pkg.Errors, Error{ - Msg: "go list failed to return CompiledGoFiles. This may indicate failure to perform cgo processing; try building at the command line. See https://golang.org/issue/38990.", + Msg: msg.String(), Kind: ListError, }) } @@ -832,6 +870,8 @@ func golistargs(cfg *Config, words []string, goVersion int) []string { // go list doesn't let you pass -test and -find together, // probably because you'd just get the TestMain. fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)), + // VCS information is not needed when not printing Stale or StaleReason fields + "-buildvcs=false", } // golang/go#60456: with go1.21 and later, go list serves pgo variants, which diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index ff60738..1e5549a 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -284,6 +284,8 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { } } + ld.externalDriver = external + return ld.refine(response) } @@ -401,6 +403,10 @@ func mergeResponses(responses ...*DriverResponse) *DriverResponse { if len(responses) == 0 { return nil } + // No dedup needed + if len(responses) == 1 { + return responses[0] + } response := newDeduper() response.dr.NotHandled = false response.dr.Compiler = responses[0].Compiler @@ -533,6 +539,11 @@ type Package struct { // depsErrors is the DepsErrors field from the go list response, if any. depsErrors []*packagesinternal.PackageError + + // exportDataError is the error encountered reading export data, if any. + // Decoding export data should ordinarily be infallible, so this typically + // indicates a producer/consumer version skew. + exportDataError error } // Module provides module information for a package. @@ -692,10 +703,11 @@ type loaderPackage struct { type loader struct { pkgs map[string]*loaderPackage // keyed by Package.ID Config - sizes types.Sizes // non-nil if needed by mode - parseCache map[string]*parseValue - parseCacheMu sync.Mutex - exportMu sync.Mutex // enforces mutual exclusion of exportdata operations + sizes types.Sizes // non-nil if needed by mode + parseCache map[string]*parseValue + parseCacheMu sync.Mutex + exportMu sync.Mutex // enforces mutual exclusion of exportdata operations + externalDriver bool // true if an external GOPACKAGESDRIVER handled the request // Config.Mode contains the implied mode (see impliedLoadMode). // Implied mode contains all the fields we need the data for. @@ -803,6 +815,12 @@ func (ld *loader) refine(response *DriverResponse) ([]*Package, error) { needsrc: needsrc, goVersion: response.GoVersion, } + // Don't trust the driver to respond with duplicate-free + // package names (go.dev/issue/63822). + if _, ok := ld.pkgs[lpkg.ID]; ok { + return nil, fmt.Errorf("%s response contained duplicate packages for ID %q", + cond(ld.externalDriver, "go/packages driver", "go list"), lpkg.ID) + } ld.pkgs[lpkg.ID] = lpkg if rootIndex >= 0 { initial[rootIndex] = lpkg @@ -1066,10 +1084,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { } // TODO(adonovan): this condition looks wrong: - // I think it should be lpkg.needtypes && !lpg.needsrc, + // I think it should be lpkg.needtypes && !lpkg.needsrc, // so that NeedSyntax without NeedTypes can be satisfied by export data. if !lpkg.needsrc { if err := ld.loadFromExportData(lpkg); err != nil { + lpkg.exportDataError = err lpkg.Errors = append(lpkg.Errors, Error{ Pos: "-", Msg: err.Error(), @@ -1208,7 +1227,13 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { if ipkg.Types != nil && ipkg.Types.Complete() { return ipkg.Types, nil } - log.Fatalf("internal error: package %q without types was imported from %q", path, lpkg) + + // If types are unavailable, there must be an export data error. + if ipkg.exportDataError != nil { + return nil, ipkg.exportDataError + } + + log.Fatalf("internal error: expected complete types for package %q", path) panic("unreachable") }) @@ -1226,6 +1251,10 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { } if lpkg.Module != nil && lpkg.Module.GoVersion != "" { tc.GoVersion = "go" + lpkg.Module.GoVersion + } else if ld.externalDriver && lpkg.goVersion != 0 { + // Module information is missing when GOPACKAGESDRIVER is used, + // so use the go version from the driver response. + tc.GoVersion = fmt.Sprintf("go1.%d", lpkg.goVersion) } if (ld.Mode & typecheckCgo) != 0 { if !typesinternal.SetUsesCgo(tc) { @@ -1566,3 +1595,11 @@ func usesExportData(cfg *Config) bool { } type unit struct{} + +func cond[T any](cond bool, t, f T) T { + if cond { + return t + } else { + return f + } +} diff --git a/vendor/golang.org/x/tools/go/packages/visit.go b/vendor/golang.org/x/tools/go/packages/visit.go index c546b1b..06747a9 100644 --- a/vendor/golang.org/x/tools/go/packages/visit.go +++ b/vendor/golang.org/x/tools/go/packages/visit.go @@ -5,11 +5,11 @@ package packages import ( - "cmp" "fmt" "iter" "os" - "slices" + + "golang.org/x/tools/internal/moremaps" ) // Visit visits all the packages in the import graph whose roots are @@ -40,7 +40,7 @@ func Visit(pkgs []*Package, pre func(*Package) bool, post func(*Package)) { seen[pkg] = true if pre == nil || pre(pkg) { - for _, imp := range sorted(pkg.Imports) { // for determinism + for _, imp := range moremaps.Sorted(pkg.Imports) { // for determinism visit(imp) } } @@ -88,7 +88,7 @@ func Postorder(pkgs []*Package) iter.Seq[*Package] { visit = func(pkg *Package) bool { if !seen[pkg] { seen[pkg] = true - for _, imp := range sorted(pkg.Imports) { // for determinism + for _, imp := range moremaps.Sorted(pkg.Imports) { // for determinism if !visit(imp) { return false } @@ -106,28 +106,3 @@ func Postorder(pkgs []*Package) iter.Seq[*Package] { } } } - -// -- copied from golang.org.x/tools/gopls/internal/util/moremaps -- - -// sorted returns an iterator over the entries of m in key order. -func sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] { - // TODO(adonovan): use maps.Sorted if proposal #68598 is accepted. - return func(yield func(K, V) bool) { - keys := keySlice(m) - slices.Sort(keys) - for _, k := range keys { - if !yield(k, m[k]) { - break - } - } - } -} - -// KeySlice returns the keys of the map M, like slices.Collect(maps.Keys(m)). -func keySlice[M ~map[K]V, K comparable, V any](m M) []K { - r := make([]K, 0, len(m)) - for k := range m { - r = append(r, k) - } - return r -} diff --git a/vendor/golang.org/x/tools/go/ssa/builder.go b/vendor/golang.org/x/tools/go/ssa/builder.go index a75257c..a663af8 100644 --- a/vendor/golang.org/x/tools/go/ssa/builder.go +++ b/vendor/golang.org/x/tools/go/ssa/builder.go @@ -85,6 +85,7 @@ import ( "slices" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" "golang.org/x/tools/internal/versions" ) @@ -100,6 +101,7 @@ var ( // Type constants. tBool = types.Typ[types.Bool] tByte = types.Typ[types.Byte] + tRune = types.Universe.Lookup("rune").Type() // prints as "rune" (Typ[Rune] is same as Int32) tInt = types.Typ[types.Int] tInvalid = types.Typ[types.Invalid] tString = types.Typ[types.String] @@ -123,7 +125,7 @@ var ( // The ssa:deferstack intrinsic returns the current function's defer stack. vDeferStack = &Builtin{ name: "ssa:deferstack", - sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false), + sig: types.NewSignatureType(nil, nil, nil, nil, typesinternal.TupleOf(tDeferStack), false), } ) @@ -582,33 +584,6 @@ func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb * } return } - - if _, ok := loc.(*address); ok { - if isNonTypeParamInterface(loc.typ()) { - // e.g. var x interface{} = T{...} - // Can't in-place initialize an interface value. - // Fall back to copying. - } else { - // x = T{...} or x := T{...} - addr := loc.address(fn) - if sb != nil { - b.compLit(fn, addr, e, isZero, sb) - } else { - var sb storebuf - b.compLit(fn, addr, e, isZero, &sb) - sb.emit(fn) - } - - // Subtle: emit debug ref for aggregate types only; - // slice and map are handled by store ops in compLit. - switch typeparams.CoreType(loc.typ()).(type) { - case *types.Struct, *types.Array: - emitDebugRef(fn, e, addr, true) - } - - return - } - } } // simple case: just copy @@ -824,8 +799,8 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { } callee := v.(*Function) // (func) if callee.typeparams.Len() > 0 { - targs := fn.subst.types(instanceArgs(fn.info, e)) - callee = callee.instance(targs, b) + targs := fn.subtargs(e) + callee = callee.instance(nil, targs, b) } return callee } @@ -846,15 +821,16 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { case types.MethodExpr: // (*T).f or T.f, the method f from the method-set of type T. // The result is a "thunk". - thunk := createThunk(fn.Prog, sel) + targs := fn.subtargs(e.Sel) + thunk := createThunk(fn.Prog, sel, targs) b.enqueue(thunk) - return emitConv(fn, thunk, fn.typ(tv.Type)) + return thunk case types.MethodVal: // e.f where e is an expression and f is a method. // The result is a "bound". - obj := sel.obj.(*types.Func) - rt := fn.typ(recvType(obj)) + m := sel.obj.(*types.Func) + rt := fn.typ(recvType(m)) wantAddr := isPointer(rt) escaping := true v := b.receiver(fn, e.X, wantAddr, escaping, sel) @@ -885,19 +861,25 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { emitTypeAssert(fn, v, rt, e.Sel.Pos()) } } - if targs := receiverTypeArgs(obj); len(targs) > 0 { - // obj is generic. - obj = fn.Prog.canon.instantiateMethod(obj, fn.subst.types(targs), fn.Prog.ctxt) + + if rtargs := fn.subrtargs(m); len(rtargs) > 0 { + m = fn.Prog.canon.instantiateMethod(m, rtargs, fn.Prog.ctxt) } - bound := createBound(fn.Prog, obj) + + targs := fn.subtargs(e.Sel) + bound := createBound(fn.Prog, m, targs) b.enqueue(bound) + // The assignment may widen a type parameter to its + // interface bound (case #3 of go.dev/issue.78110). + v = emitConv(fn, v, bound.FreeVars[0].Type()) + c := &MakeClosure{ Fn: bound, Bindings: []Value{v}, } c.setPos(e.Sel.Pos()) - c.setType(fn.typ(tv.Type)) + c.setType(bound.Signature) return fn.emit(c) case types.FieldVal: @@ -1010,8 +992,15 @@ func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, se func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { c.pos = e.Lparen - // Is this a method call? - if selector, ok := ast.Unparen(e.Fun).(*ast.SelectorExpr); ok { + // Is this a (possibly generic) method call? + m := ast.Unparen(e.Fun) + switch e := m.(type) { + case *ast.IndexExpr: + m = e.X + case *ast.IndexListExpr: + m = e.X + } + if selector, ok := m.(*ast.SelectorExpr); ok { sel := fn.selection(selector) if sel != nil && sel.kind == types.MethodVal { obj := sel.obj.(*types.Func) @@ -1026,7 +1015,8 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { c.Method = obj } else { // "Call"-mode call. - c.Value = fn.Prog.objectMethod(obj, b) + targs := fn.subtargs(selector.Sel) + c.Value = fn.Prog.objectMethod(obj, targs, b) c.Args = append(c.Args, v) } return @@ -1270,7 +1260,7 @@ func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 { // x := T{a: 1} // x = T{a: x.a} // -// all the reads must occur before all the writes. Thus all stores to +// all the reads must occur before all the writes. Thus all stores to // loc are emitted to the storebuf sb for later execution. // // A CompositeLit may have pointer type only in the recursive (nested) @@ -1287,28 +1277,35 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero sb.store(&address{addr, e.Lbrace, nil}, zeroConst(zt)) isZero = true } + var fIndices []int for i, e := range e.Elts { - fieldIndex := i - pos := e.Pos() - if kv, ok := e.(*ast.KeyValueExpr); ok { + var ( + pos token.Pos + fType types.Type + ) + + if kv, ok := e.(*ast.KeyValueExpr); ok { // tagged field fname := kv.Key.(*ast.Ident).Name - for i, n := 0, t.NumFields(); i < n; i++ { - sf := t.Field(i) - if sf.Name() == fname { - fieldIndex = i - pos = kv.Colon - e = kv.Value - break - } - } + obj, index, _ := types.LookupFieldOrMethod(t, true, fn.declaredPackage().Pkg, fname) + fIndices = append(fIndices[:0], index...) + pos = kv.Colon + e = kv.Value + fType = obj.Type() + } else { // untagged field + fIndices = append(fIndices[:0], i) + pos = e.Pos() + fType = t.Field(i).Type() } - sf := t.Field(fieldIndex) + + last := len(fIndices) - 1 + v := emitImplicitSelections(fn, addr, fIndices[:last], pos) + faddr := &FieldAddr{ - X: addr, - Field: fieldIndex, + X: v, + Field: fIndices[last], } faddr.setPos(pos) - faddr.setType(types.NewPointer(sf.Type())) + faddr.setType(types.NewPointer(fType)) fn.emit(faddr) b.assign(fn, &address{addr: faddr, pos: pos, expr: e}, e, isZero, sb) } @@ -1467,13 +1464,14 @@ func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { var nextCond *BasicBlock for _, cond := range cc.List { nextCond = fn.newBasicBlock("switch.next") - // TODO(adonovan): opt: when tag==vTrue, we'd - // get better code if we use b.cond(cond) - // instead of BinOp(EQL, tag, b.expr(cond)) - // followed by If. Don't forget conversions - // though. - cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos()) - emitIf(fn, cond, body, nextCond) + // For boolean switches, emit short-circuit control flow, + // just like an if/else-chain. + if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) { + b.cond(fn, cond, body, nextCond) + } else { + c := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos()) + emitIf(fn, c, body, nextCond) + } fn.currentBlock = nextCond } fn.currentBlock = body @@ -1722,7 +1720,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) { for _, st := range states { if st.Dir == types.RecvOnly { chtyp := typeparams.CoreType(fn.typ(st.Chan.Type())).(*types.Chan) - vars = append(vars, anonVar(chtyp.Elem())) + vars = append(vars, newVar("", chtyp.Elem())) } } sel.setType(types.NewTuple(vars...)) @@ -2149,13 +2147,6 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. // done: (target of break) // - if tk == nil { - tk = tInvalid - } - if tv == nil { - tv = tInvalid - } - rng := &Range{X: x} rng.setPos(pos) rng.setType(tRangeIter) @@ -2165,14 +2156,29 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. emitJump(fn, loop) fn.currentBlock = loop + var ak, av types.Type + isString := false + if m, ok := typeparams.CoreType(x.Type()).(*types.Map); ok { + ak, av = m.Key(), m.Elem() + } else { + isString = true + ak, av = tInt, tRune + } + if tk == nil { + ak = tInvalid + } + if tv == nil { + av = tInvalid + } + okv := &Next{ Iter: it, - IsString: isBasic(typeparams.CoreType(x.Type())), + IsString: isString, } okv.setType(types.NewTuple( varOk, - newVar("k", tk), - newVar("v", tv), + newVar("k", ak), + newVar("v", av), )) fn.emit(okv) @@ -2181,11 +2187,14 @@ func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, pos token. emitIf(fn, emitExtract(fn, okv, 0), body, done) fn.currentBlock = body - if tk != tInvalid { - k = emitExtract(fn, okv, 1) + // The assignment may widen a map or string + // key/value to a variable's interface type + // (cases #1 and #2 of go.dev/issue/78110). + if tk != nil { + k = emitConv(fn, emitExtract(fn, okv, 1), tk) } - if tv != tInvalid { - v = emitExtract(fn, okv, 2) + if tv != nil { + v = emitConv(fn, emitExtract(fn, okv, 2), tv) } return } diff --git a/vendor/golang.org/x/tools/go/ssa/const.go b/vendor/golang.org/x/tools/go/ssa/const.go index 91ed6f2..73a8213 100644 --- a/vendor/golang.org/x/tools/go/ssa/const.go +++ b/vendor/golang.org/x/tools/go/ssa/const.go @@ -131,7 +131,9 @@ func nillable(t types.Type) bool { return u != nil && nillable(u) }) } - switch t.Underlying().(type) { + switch t := t.Underlying().(type) { + case *types.Basic: + return t.Kind() == types.UnsafePointer case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: return true case *types.Interface: diff --git a/vendor/golang.org/x/tools/go/ssa/create.go b/vendor/golang.org/x/tools/go/ssa/create.go index bbf8856..3d20e55 100644 --- a/vendor/golang.org/x/tools/go/ssa/create.go +++ b/vendor/golang.org/x/tools/go/ssa/create.go @@ -15,7 +15,6 @@ import ( "os" "sync" - "golang.org/x/tools/internal/ssainternal" "golang.org/x/tools/internal/versions" ) @@ -116,33 +115,26 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function { sig := obj.Type().(*types.Signature) - // Collect type parameters. - var tparams *types.TypeParamList - if rtparams := sig.RecvTypeParams(); rtparams.Len() > 0 { - tparams = rtparams // method of generic type - } else if sigparams := sig.TypeParams(); sigparams.Len() > 0 { - tparams = sigparams // generic function - } - /* declared function/method (from syntax or export data) */ fn := &Function{ - name: name, - object: obj, - Signature: sig, - build: (*builder).buildFromSyntax, - syntax: syntax, - info: info, - goversion: goversion, - pos: obj.Pos(), - Pkg: nil, // may be set by caller - Prog: prog, - typeparams: tparams, + name: name, + object: obj, + Signature: sig, + build: (*builder).buildFromSyntax, + syntax: syntax, + info: info, + goversion: goversion, + pos: obj.Pos(), + Pkg: nil, // may be set by caller + Prog: prog, + recvtypeparams: sig.RecvTypeParams(), + typeparams: sig.TypeParams(), } if fn.syntax == nil { fn.Synthetic = "from type information" fn.build = (*builder).buildParamsOnly } - if tparams.Len() > 0 { + if fn.hasTypeParams() { fn.generic = new(generic) } return fn @@ -314,19 +306,13 @@ func (prog *Program) ImportedPackage(path string) *Package { return prog.imported[path] } -// setNoReturn sets the predicate used by the SSA builder to decide -// whether a call to the specified named function cannot return, -// allowing the builder to prune control-flow edges following the -// call, thus improving the precision of downstream analysis. +// SetNoReturn sets the predicate used when building the ssa.Program +// prog that reports whether a given function cannot return. +// This may be used to prune spurious control flow edges +// after (e.g.) log.Fatal, improving the precision of analyses. // -// TODO(adonovan): add (*Program).SetNoReturn to the public API. -func (prog *Program) setNoReturn(noReturn func(*types.Func) bool) { +// A typical implementation is the [ctrlflow.CFGs.NoReturn] method from +// [golang.org/x/tools/go/analysis/passes/ctrlflow]. +func (prog *Program) SetNoReturn(noReturn func(*types.Func) bool) { prog.noReturn = noReturn } - -func init() { - // SetNoReturn exposes Program.setNoReturn to the buildssa analyzer. - ssainternal.SetNoReturn = func(prog any, noReturn func(*types.Func) bool) { - prog.(*Program).setNoReturn(noReturn) - } -} diff --git a/vendor/golang.org/x/tools/go/ssa/emit.go b/vendor/golang.org/x/tools/go/ssa/emit.go index 31aa5de..fd54b3d 100644 --- a/vendor/golang.org/x/tools/go/ssa/emit.go +++ b/vendor/golang.org/x/tools/go/ssa/emit.go @@ -248,7 +248,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value { // Record the types of operands to MakeInterface, if // non-parameterized, as they are the set of runtime types. t := val.Type() - if f.typeparams.Len() == 0 || !f.Prog.isParameterized(t) { + if !f.Prog.isParameterized(t) { addMakeInterfaceType(f.Prog, t) } diff --git a/vendor/golang.org/x/tools/go/ssa/instantiate.go b/vendor/golang.org/x/tools/go/ssa/instantiate.go index 5862440..4eb53aa 100644 --- a/vendor/golang.org/x/tools/go/ssa/instantiate.go +++ b/vendor/golang.org/x/tools/go/ssa/instantiate.go @@ -19,13 +19,13 @@ type generic struct { } // instance returns a Function that is the instantiation of generic -// origin function fn with the type arguments targs. +// origin function fn with the type arguments rtargs and targs. // // Any created instance is added to cr. // // Acquires fn.generic.instancesMu. -func (fn *Function) instance(targs []types.Type, b *builder) *Function { - key := fn.Prog.canon.List(targs) +func (fn *Function) instance(rtargs, targs []types.Type, b *builder) *Function { + key := fn.Prog.canon.List(slices.Concat(rtargs, targs)) gen := fn.generic @@ -33,7 +33,7 @@ func (fn *Function) instance(targs []types.Type, b *builder) *Function { defer gen.instancesMu.Unlock() inst, ok := gen.instances[key] if !ok { - inst = createInstance(fn, targs) + inst = createInstance(fn, rtargs, targs) inst.buildshared = b.shared() b.enqueue(inst) @@ -48,20 +48,46 @@ func (fn *Function) instance(targs []types.Type, b *builder) *Function { } // createInstance returns the instantiation of generic function fn using targs. +// If fn is a method on a generic type, fn's receiver type will be instantiated +// using rtargs. // // Requires fn.generic.instancesMu. -func createInstance(fn *Function, targs []types.Type) *Function { +func createInstance(fn *Function, rtargs, targs []types.Type) *Function { prog := fn.Prog // Compute signature. var sig *types.Signature var obj *types.Func if recv := fn.Signature.Recv(); recv != nil { - // method - obj = prog.canon.instantiateMethod(fn.object, targs, prog.ctxt) - sig = obj.Type().(*types.Signature) + // method, len(rtargs) > 0 || len(targs) > 0 + if len(rtargs) > 0 { + // possibly generic method on generic type + obj = prog.canon.instantiateMethod(fn.object, rtargs, prog.ctxt) + } else { + // generic method on non-generic type + obj = fn.object // instantiation does not exist yet + } + if len(targs) > 0 { + // generic method + instSig, err := types.Instantiate(prog.ctxt, obj.Signature(), targs, false) + if err != nil { + panic(err) + } + instance, ok := instSig.(*types.Signature) + if !ok { + panic("Instantiate of a Signature returned a non-signature") + } + // Do not canonicalize generic methods, because the receiver is not + // part of a Signature's type identity. For example, + // (*G[int]).m[int] and (G[int]).n[int] may be identical types even + // though their receiver types differ. + sig = instance + } else { + // non-generic method on generic type + sig = obj.Signature() + } } else { - // function + // function, len(rtargs) == 0 && len(targs) > 0 instSig, err := types.Instantiate(prog.ctxt, fn.Signature, targs, false) if err != nil { panic(err) @@ -80,10 +106,10 @@ func createInstance(fn *Function, targs []types.Type) *Function { subst *subster build buildFunc ) - if prog.mode&InstantiateGenerics != 0 && !prog.isParameterized(targs...) { + if prog.mode&InstantiateGenerics != 0 && !prog.isParameterized(slices.Concat(rtargs, targs)...) { synthetic = fmt.Sprintf("instance of %s", fn.Name()) if fn.syntax != nil { - subst = makeSubster(prog.ctxt, obj, fn.typeparams, targs) + subst = makeSubster(prog.ctxt, obj, fn.recvtypeparams, rtargs, fn.typeparams, targs) build = (*builder).buildFromSyntax } else { build = (*builder).buildParamsOnly @@ -93,9 +119,14 @@ func createInstance(fn *Function, targs []types.Type) *Function { build = (*builder).buildInstantiationWrapper } + name := fn.Name() + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) // may not be unique + } + /* generic instance or instantiation wrapper */ return &Function{ - name: fmt.Sprintf("%s%s", fn.Name(), targs), // may not be unique + name: name, object: obj, Signature: sig, Synthetic: synthetic, @@ -107,6 +138,8 @@ func createInstance(fn *Function, targs []types.Type) *Function { pos: obj.Pos(), Pkg: nil, Prog: fn.Prog, + recvtypeparams: fn.recvtypeparams, // share with origin + recvtypeargs: rtargs, typeparams: fn.typeparams, // share with origin typeargs: targs, subst: subst, diff --git a/vendor/golang.org/x/tools/go/ssa/methods.go b/vendor/golang.org/x/tools/go/ssa/methods.go index 4b116f4..8de0785 100644 --- a/vendor/golang.org/x/tools/go/ssa/methods.go +++ b/vendor/golang.org/x/tools/go/ssa/methods.go @@ -32,8 +32,8 @@ func (prog *Program) MethodValue(sel *types.Selection) *Function { return nil // interface method or type parameter } - if prog.isParameterized(T) { - return nil // generic method + if prog.isParameterized(T, sel.Type()) { + return nil // method on generic type or generic method } if prog.mode&LogSource != 0 { @@ -61,11 +61,11 @@ func (prog *Program) MethodValue(sel *types.Selection) *Function { needsPromotion := len(sel.Index()) > 1 needsIndirection := !isPointer(recvType(obj)) && isPointer(T) if needsPromotion || needsIndirection { - fn = createWrapper(prog, toSelection(sel)) + fn = createWrapper(prog, toSelection(sel), nil) fn.buildshared = b.shared() b.enqueue(fn) } else { - fn = prog.objectMethod(obj, &b) + fn = prog.objectMethod(obj, nil, &b) } if fn.Signature.Recv() == nil { panic(fn) @@ -91,25 +91,22 @@ func (prog *Program) MethodValue(sel *types.Selection) *Function { // objectMethod panics if the function is not a method. // // Acquires prog.objectMethodsMu. -func (prog *Program) objectMethod(obj *types.Func, b *builder) *Function { +func (prog *Program) objectMethod(obj *types.Func, targs []types.Type, b *builder) *Function { sig := obj.Type().(*types.Signature) if sig.Recv() == nil { panic("not a method: " + obj.String()) } + // Instantiation of generic? + if orig := obj.Origin(); orig != obj || len(targs) > 0 { + return prog.objectMethod(orig, nil, b).instance(receiverTypeArgs(obj), targs, b) + } + // Belongs to a created package? if fn := prog.FuncValue(obj); fn != nil { return fn } - // Instantiation of generic? - if originObj := obj.Origin(); originObj != obj { - origin := prog.objectMethod(originObj, b) - assert(origin.typeparams.Len() > 0, "origin is not generic") - targs := receiverTypeArgs(obj) - return origin.instance(targs, b) - } - // Consult/update cache of methods created from types.Func. prog.objectMethodsMu.Lock() defer prog.objectMethodsMu.Unlock() @@ -170,10 +167,18 @@ func (prog *Program) RuntimeTypes() []types.Type { // eliminates the need to eagerly compute all the element // types during SSA building. var runtimeTypes []types.Type - add := func(t types.Type) { runtimeTypes = append(runtimeTypes, t) } var set typeutil.Map // for de-duping identical types for t := range prog.makeInterfaceTypes { - typesinternal.ForEachElement(&set, &prog.MethodSets, t, add) + typesinternal.ForEachElement(prog.MethodSets.MethodSet, t, func(t types.Type, access bool) bool { + if !access { + return false // inaccessible to reflection + } + seen, _ := set.Set(t, true).(bool) + if !seen { + runtimeTypes = append(runtimeTypes, t) + } + return seen + }) } return runtimeTypes diff --git a/vendor/golang.org/x/tools/go/ssa/sanity.go b/vendor/golang.org/x/tools/go/ssa/sanity.go index c47a137..824252d 100644 --- a/vendor/golang.org/x/tools/go/ssa/sanity.go +++ b/vendor/golang.org/x/tools/go/ssa/sanity.go @@ -16,6 +16,8 @@ import ( "os" "slices" "strings" + + "golang.org/x/tools/internal/typeparams" ) type sanity struct { @@ -154,12 +156,17 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *Lookup: case *MakeChan: case *MakeClosure: - numFree := len(instr.Fn.(*Function).FreeVars) - numBind := len(instr.Bindings) - if numFree != numBind { + fn := instr.Fn.(*Function) + if numFree, numBind := len(fn.FreeVars), len(instr.Bindings); numFree != numBind { s.errorf("MakeClosure has %d Bindings for function %s with %d free vars", numBind, instr.Fn, numFree) - + } else { + for i, fv := range fn.FreeVars { + if !types.Identical(instr.Bindings[i].Type(), fv.Type()) { + s.errorf("MakeClosure binding %d for %s has type %s, expected %s", + i, fv.Name(), instr.Bindings[i].Type(), fv.Type()) + } + } } if recv := instr.Type().(*types.Signature).Recv(); recv != nil { s.errorf("MakeClosure's type includes receiver %s", recv.Type()) @@ -170,12 +177,42 @@ func (s *sanity) checkInstr(idx int, instr Instruction) { case *MakeSlice: case *MapUpdate: case *Next: + rng, ok := instr.Iter.(*Range) + if !ok { + s.errorf("Next: Iter is %T, not *Range", instr.Iter) + } + if rng.Type() != tRangeIter { + s.errorf("Next: Iter has type %s, expected %s", rng.Type(), tRangeIter) + } + var ek, ev types.Type + switch xt := typeparams.CoreType(rng.X.Type()).(type) { + case *types.Basic: + if types.Default(xt) != tString { + s.errorf("Next: basic operand of Next.Iter (Range) is %s, want string or untyped string", xt) + } + ek, ev = tInt, tRune + case *types.Map: + ek, ev = xt.Key(), xt.Elem() + } + + res := instr.Type().(*types.Tuple) // (ok bool, k K, v V), but K or V may be invalid if unused + if !types.Identical(res.At(1).Type(), ek) && res.At(1).Type() != tInvalid { + s.errorf("Next: key type %s does not match map key type %s", res.At(1).Type(), ek) + } + if !types.Identical(res.At(2).Type(), ev) && res.At(2).Type() != tInvalid { + s.errorf("Next: value type %s does not match map value type %s", res.At(2).Type(), ev) + } + case *Range: case *RunDefers: case *Select: case *Send: case *Slice: case *Store: + if !types.Identical(instr.Val.Type(), typeparams.CoreType(instr.Addr.Type()).(*types.Pointer).Elem()) { + s.errorf("Store: value type %s does not match address type %s", + instr.Val.Type(), instr.Addr.Type()) + } case *TypeAssert: case *UnOp: case *DebugRef: @@ -417,7 +454,7 @@ func (s *sanity) checkFunctionParams() { } if !types.Identical(sigType, param.Type()) { - s.errorf("expect type %s in signature but got type %s in param %d", param.Type(), sigType, i) + s.errorf("expect type %s in signature but got type %s in param %d", sigType, param.Type(), i) } } } @@ -492,16 +529,16 @@ func (s *sanity) checkFunction(fn *Function) bool { strings.HasSuffix(fn.name, "Error") || strings.HasPrefix(fn.Synthetic, "instance ") || strings.HasPrefix(fn.Synthetic, "instantiation ") || - (fn.parent != nil && len(fn.typeargs) > 0) /* anon fun in instance */ { + fn.parent != nil && fn.parent.hasTypeArgs() /* anon fun in instance */ { // ok } else { s.errorf("nil Pkg") } } if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn { - if len(fn.typeargs) > 0 && fn.Prog.mode&InstantiateGenerics != 0 { + if fn.hasTypeArgs() && fn.Prog.mode&InstantiateGenerics != 0 { // ok (instantiation with InstantiateGenerics on) - } else if fn.topLevelOrigin != nil && len(fn.typeargs) > 0 { + } else if fn.hasTypeArgs() && fn.topLevelOrigin != nil { // ok (we always have the syntax set for instantiation) } else if _, rng := fn.syntax.(*ast.RangeStmt); rng && fn.Synthetic == "range-over-func yield" { // ok (range-func-yields are both synthetic and keep syntax) diff --git a/vendor/golang.org/x/tools/go/ssa/ssa.go b/vendor/golang.org/x/tools/go/ssa/ssa.go index 7c84494..514ba2b 100644 --- a/vendor/golang.org/x/tools/go/ssa/ssa.go +++ b/vendor/golang.org/x/tools/go/ssa/ssa.go @@ -13,7 +13,11 @@ import ( "go/constant" "go/token" "go/types" + "reflect" + "slices" + "strings" "sync" + "unsafe" "golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/internal/typeparams" @@ -364,8 +368,10 @@ type Function struct { referrers []Instruction // referring instructions (iff Parent() != nil) anonIdx int32 // position of a nested function in parent's AnonFuncs. fn.Parent()!=nil => fn.Parent().AnonFunc[fn.anonIdx] == fn. - typeparams *types.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function - typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function + recvtypeparams *types.TypeParamList // receiver type parameters of this function. recvtypeparams.Len() > 0 => method on generic or instance of generic type + recvtypeargs []types.Type // type arguments that instantiated recvtypeparams. len(recvtypeargs) > 0 => method on instance of generic type + typeparams *types.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function or method + typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function or method topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil. generic *generic // instances of this function, if generic @@ -1577,18 +1583,73 @@ func (v *Function) Referrers() *[]Instruction { // TypeParams are the function's type parameters if generic or the // type parameters that were instantiated if fn is an instantiation. +// +// Specifically, the resulting list behaves like: +// +// func f // [] +// func f[P] // [P] +// func (T) m // [] +// func (T) m[P] // [P] +// func (T[P]) m // [P] +// func (T[P]) m[Q] // [P (index=0), Q (index=0)] +// +// Note that receiver type parameters precede other type parameters. +// Also, type parameters may have the same index if they come from +// different source type parameter lists. func (fn *Function) TypeParams() *types.TypeParamList { - return fn.typeparams + return consTypeParamLists(fn.recvtypeparams, fn.typeparams) +} + +func consTypeParamLists(l, r *types.TypeParamList) *types.TypeParamList { + if l.Len() == 0 { + return r + } + if r.Len() == 0 { + return l + } + + tpars := make([]*types.TypeParam, l.Len()+r.Len()) + for i := range l.Len() { + tpars[i] = l.At(i) + } + for i := range r.Len() { + tpars[i+l.Len()] = r.At(i) + } + // This logic unsafely assumes (and asserts) that the layout of the + // TypeParamList is identical to that of a slice of TypeParams. This + // is a hack while we work on getting a constructor for TypeParamList + // approved (see go.dev/issue/79603). + t := reflect.TypeFor[types.TypeParamList]() + if t.NumField() != 1 { + panic("TypeParamList has unexpected fields") + } + if f := t.Field(0); f.Offset != 0 || f.Type != reflect.TypeFor[[]*types.TypeParam]() { + panic("TypeParamList field is not []*TypeParam") + } + return (*types.TypeParamList)(unsafe.Pointer(&tpars)) } // TypeArgs are the types that TypeParams() were instantiated by to create fn // from fn.Origin(). -func (fn *Function) TypeArgs() []types.Type { return fn.typeargs } +// +// Specifically, the resulting slice behaves like: +// +// f // [] +// f[int] // [int] +// T.m // [] +// T.m[int] // [int] +// T[int].m // [int] +// T[int].m[uint] // [int, uint] +// +// Note that receiver type arguments precede other type arguments. +func (fn *Function) TypeArgs() []types.Type { + return slices.Concat(fn.recvtypeargs, fn.typeargs) +} // Origin returns the generic function from which fn was instantiated, // or nil if fn is not an instantiation. func (fn *Function) Origin() *Function { - if fn.parent != nil && len(fn.typeargs) > 0 { + if fn.parent != nil && fn.parent.hasTypeArgs() { // Nested functions are BUILT at a different time than their instances. // Build declared package if not yet BUILT. This is not an expected use // case, but is simple and robust. @@ -1597,12 +1658,48 @@ func (fn *Function) Origin() *Function { return origin(fn) } +// hasTypeParams returns whether fn has any type parameters +func (fn *Function) hasTypeParams() bool { + return fn.recvtypeparams.Len()+fn.typeparams.Len() > 0 +} + +// hasTypeArgs returns whether fn has any type arguments +func (fn *Function) hasTypeArgs() bool { + return len(fn.recvtypeargs)+len(fn.typeargs) > 0 +} + +// subrtargs returns fn's receiver type parameters substituted with receiver type arguments +func (fn *Function) subrtargs(m *types.Func) []types.Type { + return fn.subst.types(receiverTypeArgs(m)) +} + +// subtargs returns fn's type parameters substituted with (possibly implied) type arguments +func (fn *Function) subtargs(id *ast.Ident) []types.Type { + return fn.subst.types(instanceArgs(fn.info, id)) +} + +// targstr returns a comma-separated string of the types in targs +func targstr(targs []types.Type) string { + var sb strings.Builder + if len(targs) > 0 { + sb.WriteString("[") + for i := range targs { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(targs[i].String()) + } + sb.WriteString("]") + } + return sb.String() +} + // origin is the function that fn is an instantiation of. Returns nil if fn is // not an instantiation. // // Precondition: fn and the origin function are done building. func origin(fn *Function) *Function { - if fn.parent != nil && len(fn.typeargs) > 0 { + if fn.parent != nil && fn.parent.hasTypeArgs() { return origin(fn.parent).AnonFuncs[fn.anonIdx] } return fn.topLevelOrigin diff --git a/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go b/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go index 7300d2b..3cfc2ba 100644 --- a/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go +++ b/vendor/golang.org/x/tools/go/ssa/ssautil/visit.go @@ -74,8 +74,11 @@ func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool { methodsOf := func(T types.Type) { if !types.IsInterface(T) { mset := prog.MethodSets.MethodSet(T) - for method := range mset.Methods() { - function(prog.MethodValue(method)) + for sel := range mset.Methods() { + // Skip generic methods. + if sel.Obj().(*types.Func).Signature().TypeParams() == nil { + function(prog.MethodValue(sel)) + } } } } @@ -104,6 +107,7 @@ func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool { // Consider only named types. // (Ignore aliases and unsafe.Pointer.) if named, ok := t.Type().(*types.Named); ok { + // Skip generic types. if named.TypeParams() == nil { methodsOf(named) // T methodsOf(types.NewPointer(named)) // *T @@ -117,6 +121,11 @@ func AllFunctions(prog *ssa.Program) map[*ssa.Function]bool { switch mem := mem.(type) { case *ssa.Function: // Visit all package-level declared functions. + // + // (This may include generic functions, which is + // inconsistent with the treatment of methods: + // we skip both generic methods, + // and methods of generic types.) function(mem) case *ssa.Type: diff --git a/vendor/golang.org/x/tools/go/ssa/subst.go b/vendor/golang.org/x/tools/go/ssa/subst.go index 5799a07..00b56ef 100644 --- a/vendor/golang.org/x/tools/go/ssa/subst.go +++ b/vendor/golang.org/x/tools/go/ssa/subst.go @@ -5,6 +5,7 @@ package ssa import ( + "fmt" "go/types" "golang.org/x/tools/go/types/typeutil" @@ -56,18 +57,25 @@ type subster struct { // TODO(taking): consider adding Pos } -// Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache. -// targs should not contain any types in tparams. +// Returns a subster that replaces rtparams[i] with rtargs[i] and tparams[i] with targs[i]. +// Uses ctxt as a cache. rtargs and targs should not contain any types in rtparams or tparams. // fn is the generic function for which we are substituting. -func makeSubster(ctxt *types.Context, fn *types.Func, tparams *types.TypeParamList, targs []types.Type) *subster { - assert(tparams.Len() == len(targs), "makeSubster argument count must match") +func makeSubster(ctxt *types.Context, fn *types.Func, rtparams *types.TypeParamList, rtargs []types.Type, tparams *types.TypeParamList, targs []types.Type) *subster { + got := len(rtargs) + len(targs) + want := rtparams.Len() + tparams.Len() + if got != want { + panic(fmt.Sprintf("makeSubster argument count must match: got %d; want %d", got, want)) + } subst := &subster{ - replacements: make(map[*types.TypeParam]types.Type, tparams.Len()), + replacements: make(map[*types.TypeParam]types.Type, want), cache: make(map[types.Type]types.Type), origin: fn.Origin(), ctxt: ctxt, } + for i := 0; i < rtparams.Len(); i++ { + subst.replacements[rtparams.At(i)] = rtargs[i] + } for i := 0; i < tparams.Len(); i++ { subst.replacements[tparams.At(i)] = targs[i] } @@ -319,10 +327,10 @@ func (subst *subster) interface_(iface *types.Interface) *types.Interface { func (subst *subster) alias(t *types.Alias) types.Type { // See subster.named. This follows the same strategy. - tparams := aliases.TypeParams(t) - targs := aliases.TypeArgs(t) + tparams := t.TypeParams() + targs := t.TypeArgs() tname := t.Obj() - torigin := aliases.Origin(t) + torigin := t.Origin() if !declaredWithin(tname, subst.origin) { // t is declared outside of the function origin. So t is a package level type alias. @@ -361,15 +369,10 @@ func (subst *subster) alias(t *types.Alias) types.Type { } // Substitute rhs. - rhs := subst.typ(aliases.Rhs(t)) + rhs := subst.typ(t.Rhs()) // Create the fresh alias. - // - // Until 1.27, the result of aliases.NewAlias(...).Type() cannot guarantee it is a *types.Alias. - // However, as t is an *alias.Alias and t is well-typed, then aliases must have been enabled. - // Follow this decision, and always enable aliases here. - const enabled = true - obj := aliases.NewAlias(enabled, tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams) + obj := aliases.New(tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams) // Substitute into all of the constraints after they are created. for i, ntp := range newTParams { diff --git a/vendor/golang.org/x/tools/go/ssa/util.go b/vendor/golang.org/x/tools/go/ssa/util.go index 42f9621..5dfca72 100644 --- a/vendor/golang.org/x/tools/go/ssa/util.go +++ b/vendor/golang.org/x/tools/go/ssa/util.go @@ -181,19 +181,13 @@ func newVar(name string, typ types.Type) *types.Var { return types.NewParam(token.NoPos, nil, name, typ) } -// anonVar creates an anonymous 'var' for use in a types.Tuple. -func anonVar(typ types.Type) *types.Var { - return newVar("", typ) -} - -var lenResults = types.NewTuple(anonVar(tInt)) +var lenResults = typesinternal.TupleOf(tInt) // makeLen returns the len builtin specialized to type func(T)int. func makeLen(T types.Type) *Builtin { - lenParams := types.NewTuple(anonVar(T)) return &Builtin{ name: "len", - sig: types.NewSignatureType(nil, nil, nil, lenParams, lenResults, false), + sig: types.NewSignatureType(nil, nil, nil, typesinternal.TupleOf(T), lenResults, false), } } diff --git a/vendor/golang.org/x/tools/go/ssa/wrappers.go b/vendor/golang.org/x/tools/go/ssa/wrappers.go index aeb160e..fbb0674 100644 --- a/vendor/golang.org/x/tools/go/ssa/wrappers.go +++ b/vendor/golang.org/x/tools/go/ssa/wrappers.go @@ -26,6 +26,7 @@ import ( "go/types" "golang.org/x/tools/internal/typeparams" + "golang.org/x/tools/internal/typesinternal" ) // -- wrappers ----------------------------------------------------------- @@ -40,14 +41,14 @@ import ( // following axes of variation when making changes: // - optional receiver indirection // - optional implicit field selections +// - optional method type arguments // - meth.Obj() may denote a concrete or an interface method // - the result may be a thunk or a wrapper. -func createWrapper(prog *Program, sel *selection) *Function { - obj := sel.obj.(*types.Func) // the declared function - sig := sel.typ.(*types.Signature) // type of this wrapper +func createWrapper(prog *Program, sel *selection, targs []types.Type) *Function { + obj := sel.obj.(*types.Func) // the declared function + name, sig := maybeInstance(prog, obj.Name(), sel.typ.(*types.Signature), targs) var recv *types.Var // wrapper's receiver or thunk's params[0] - name := obj.Name() var description string if sel.kind == types.MethodExpr { name += "$thunk" @@ -58,7 +59,7 @@ func createWrapper(prog *Program, sel *selection) *Function { recv = sig.Recv() } - description = fmt.Sprintf("%s for %s", description, sel.obj) + description = fmt.Sprintf("%s for %s", description, obj) if prog.mode&LogSource != 0 { defer logStack("create %s to (%s)", description, recv.Type())() } @@ -71,6 +72,7 @@ func createWrapper(prog *Program, sel *selection) *Function { Synthetic: description, Prog: prog, pos: obj.Pos(), + typeargs: targs, // wrappers have no syntax build: (*builder).buildWrapper, syntax: nil, @@ -79,6 +81,20 @@ func createWrapper(prog *Program, sel *selection) *Function { } } +// maybeInstance returns name and sig instantiated to reflect any type arguments in targs. +func maybeInstance(prog *Program, name string, sig *types.Signature, targs []types.Type) (string, *types.Signature) { + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) + instSig, err := types.Instantiate(prog.ctxt, sig, targs, false) + if err != nil { + // validate was false, we should never get an error + panic(err) + } + sig = prog.canon.Type(instSig).(*types.Signature) + } + return name, sig +} + // buildWrapper builds fn.Body for a method wrapper. func (b *builder) buildWrapper(fn *Function) { var recv *types.Var // wrapper's receiver or thunk's params[0] @@ -103,10 +119,12 @@ func (b *builder) buildWrapper(fn *Function) { // For simple indirection wrappers, perform an informative nil-check: // "value method (T).f called using nil *T pointer" if len(indices) == 1 && !isPointer(recvType(fn.object)) { + params := typesinternal.TupleOf(fn.method.recv, tString, tString) + results := typesinternal.TupleOf(fn.method.recv) var c Call c.Call.Value = &Builtin{ name: "ssa:wrapnilchk", - sig: types.NewSignatureType(nil, nil, nil, types.NewTuple(anonVar(fn.method.recv), anonVar(tString), anonVar(tString)), types.NewTuple(anonVar(fn.method.recv)), false), + sig: types.NewSignatureType(nil, nil, nil, params, results, false), } c.Call.Args = []Value{ v, @@ -137,7 +155,7 @@ func (b *builder) buildWrapper(fn *Function) { if !isPointer(r) { v = emitLoad(fn, v) } - c.Call.Value = fn.Prog.objectMethod(fn.object, b) + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) c.Call.Args = append(c.Call.Args, v) } else { c.Call.Method = fn.object @@ -184,19 +202,22 @@ func createParams(fn *Function, start int) { // Unlike createWrapper, createBound need perform no indirection or field // selections because that can be done before the closure is // constructed. -func createBound(prog *Program, obj *types.Func) *Function { +func createBound(prog *Program, obj *types.Func, targs []types.Type) *Function { description := fmt.Sprintf("bound method wrapper for %s", obj) if prog.mode&LogSource != 0 { defer logStack("%s", description)() } + name, sig := maybeInstance(prog, obj.Name(), obj.Type().(*types.Signature), targs) + /* bound method wrapper */ fn := &Function{ - name: obj.Name() + "$bound", + name: name + "$bound", object: obj, - Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver + Signature: changeRecv(sig, nil), // drop receiver Synthetic: description, Prog: prog, pos: obj.Pos(), + typeargs: targs, // wrappers have no syntax build: (*builder).buildBound, syntax: nil, @@ -215,7 +236,7 @@ func (b *builder) buildBound(fn *Function) { recv := fn.FreeVars[0] if !types.IsInterface(recvType(fn.object)) { // concrete - c.Call.Value = fn.Prog.objectMethod(fn.object, b) + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) c.Call.Args = []Value{recv} } else { c.Call.Method = fn.object @@ -246,12 +267,12 @@ func (b *builder) buildBound(fn *Function) { // f is a synthetic wrapper defined as if by: // // f := func(t T) { return t.meth() } -func createThunk(prog *Program, sel *selection) *Function { +func createThunk(prog *Program, sel *selection, targs []types.Type) *Function { if sel.kind != types.MethodExpr { panic(sel) } - fn := createWrapper(prog, sel) + fn := createWrapper(prog, sel, targs) if fn.Signature.Recv() != nil { panic(fn) // unexpected receiver } diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go index 6646bf5..9c7401c 100644 --- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go +++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go @@ -24,12 +24,14 @@ package objectpath import ( + "encoding/binary" "fmt" "go/types" + "slices" "strconv" "strings" + "sync" - "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/typesinternal" ) @@ -125,12 +127,74 @@ func For(obj types.Object) (Path, error) { // 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 + pkgIndexMu sync.Mutex + pkgIndex map[*types.Package]*pkgIndex +} + +// A traversal encapsulates the state of a single traversal of the object/type graph. +type traversal struct { + pkg *types.Package + ix *pkgIndex // non-nil if we are building the index + + target types.Object // the sought symbol (if ix == nil) + found Path // the found path (if ix == nil) + + // These maps are used to short circuit cycles through + // interface methods, such as occur in the following example: + // + // type I interface { f() interface{I} } + // + // See golang/go#68046 for details. + seenTParamNames map[*types.TypeName]bool // global cycle breaking through type parameters + seenMethods map[*types.Func]bool // global cycle breaking through recursive interfaces +} + +// A pkgIndex holds a compressed index of objectpaths of all symbols +// (fields, methods, params) requiring search for an entire package. +// +// The first time a search for a given package is requested, we simply +// traverse the type graph for the target object, maintaining the +// current object path as a stack. If we find the target object, we +// save the path and terminate the main loop (but it's not worth +// breaking out of the current recursion). +// +// On the second search (a pkgIndex exists but its data is nil), we +// build an index of the traversal, which we use for all subsequent +// searches. +// +// The traversal index is encoded in the data field as a list of records, +// one per node, in preorder. Records are of two types: +// +// - A record for a package-level object consists of a pair +// (parent, nameIndex uvarint), where parent is zero and +// nameIndex is the index of the object's name in the sorted +// pkg.Scope().Names() slice. +// +// - A record for a nested node (a segment of an object path) +// consists of (parent uvarint, op byte, index uvarint), where +// parent is the index of the record for the parent node, +// op is the destructuring operator, and index (if op = [AFMTr]) +// is its integer operand. +// +// Since data[0] = 0 all nodes have positive offsets. In effect the +// encoding is a trie in which each node stores one path segment +// and points to the node for its prefix. +// +// TODO(adonovan): opt: evaluate an only 2-level tree with nodes for +// package-level objects and the-rest-of-the-path. One calculation +// suggested that it might be similar speed but 30% more compact. +type pkgIndex struct { + pkg *types.Package + data []byte // encoding of traversal; nil if not yet constructed + scopeNames []string // memo of pkg.Scope().Names() to avoid O(n) alloc/sort at lookup + offsets map[types.Object]uint32 // each object's node offset within encoded traversal data } // 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. // +// For is safe for concurrent use. +// // The For function guarantees to return a path only for the following objects: // - package-level types // - exported package-level non-types @@ -212,10 +276,9 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { 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 { + if pkg.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() { @@ -233,19 +296,18 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { // 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. + // A var, if not package-level, must be a + // parameter (incl. receiver) or result, or a struct field. + if obj.Kind() == types.LocalVar { + return "", fmt.Errorf("no path for local %v", obj) + } case *types.Func: // A func, if not package-level, must be a method. @@ -262,89 +324,314 @@ func (enc *Encoder) For(obj types.Object) (Path, error) { panic(obj) } - // 4. Search the API for the path to the var (field/param/result) or method. + enc.pkgIndexMu.Lock() + defer enc.pkgIndexMu.Unlock() - // 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 + // 4. Search the object/type graph for the path to + // the var (field/param/result) or method. + ix, ok := enc.pkgIndex[pkg] + if !ok { + // First search: don't build an index, just traverse. + // This avoids allocation in [For], whose Encoder + // lives for a single call. + ix = &pkgIndex{pkg: pkg} + + if enc.pkgIndex == nil { + enc.pkgIndex = make(map[*types.Package]*pkgIndex) + } + enc.pkgIndex[pkg] = ix // build the index next time + + f := traversal{pkg: pkg, target: obj} + f.traverse() + + if f.found != "" { + return f.found, nil + } + } else { + // Second search: build an index while traversing. + if ix.data == nil { + ix.offsets = make(map[types.Object]uint32) + ix.data = []byte{0} // offset 0 is sentinel + (&traversal{pkg: pkg, ix: ix}).traverse() } - path := append(empty, o.Name()...) - path = append(path, opType) - - T := o.Type() - if alias, ok := T.(*types.Alias); ok { - if r := findTypeParam(obj, aliases.TypeParams(alias), path, opTypeParam); r != nil { - return Path(r), nil - } - if r := find(obj, aliases.Rhs(alias), append(path, opRhs)); r != nil { - return Path(r), nil - } - - } else if tname.IsAlias() { - // legacy alias - if r := find(obj, T, path); r != nil { - return Path(r), nil - } - - } else if named, ok := T.(*types.Named); ok { - // defined (named) type - if r := findTypeParam(obj, named.TypeParams(), path, opTypeParam); r != nil { - return Path(r), nil - } - if r := find(obj, named.Underlying(), append(path, opUnderlying)); 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)); r != nil { - return Path(r), nil - } - } - continue - } - - // Inspect declared methods of defined types. - if T, ok := types.Unalias(o.Type()).(*types.Named); ok { - path = append(path, opType) - // The method index here is always with respect - // to the underlying go/types data structures, - // which ultimately derives from source order - // and must be preserved by export data. - 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)); r != nil { - return Path(r), nil - } - } + // Second and later searches: consult the index. + if offset, ok := ix.offsets[obj]; ok { + return ix.path(offset), nil } } return "", fmt.Errorf("can't find path for %v in %s", obj, pkg.Path()) } -func appendOpArg(path []byte, op byte, arg int) []byte { +// traverse performs a complete traversal of all symbols reachable from the package. +func (tr *traversal) traverse() { + scope := tr.pkg.Scope() + names := scope.Names() + if tr.ix != nil { + tr.ix.scopeNames = names + } + + empty := make([]byte, 0, 48) // initial space for stack (ix == nil) + + // First inspect package-level type names. + // In the presence of path aliases, these give + // the best paths because non-types may + // refer to types, but not the reverse. + for i, name := range names { + if tr.found != "" { + return // found (ix == nil) + } + + obj := scope.Lookup(name) + if _, ok := obj.(*types.TypeName); !ok { + continue // handle non-types in second pass + } + + // emit (name, opType) + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + path = append(path, opType) + } else { + offset = tr.ix.emitPackageLevel(i) + tr.ix.offsets[obj] = offset + offset = tr.ix.emitPathSegment(offset, opType, -1) + } + + // A TypeName (for Named or Alias) may have type parameters. + switch t := obj.Type().(type) { + case *types.Alias: + tr.tparams(t.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opRhs, -1, t.Rhs()) + case *types.Named: + tr.tparams(t.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opUnderlying, -1, t.Underlying()) + } + } + + // Then inspect everything else: + // exported non-types, and declared methods of defined types. + for i, name := range names { + if tr.found != "" { + return // found (ix == nil) + } + + obj := scope.Lookup(name) + + if tname, ok := obj.(*types.TypeName); !ok { + if obj.Exported() { + // exported non-type (const, var, func) + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + } else { + offset = tr.ix.emitPackageLevel(i) + tr.ix.offsets[obj] = offset + } + tr.typ(path, offset, opType, -1, obj.Type()) + } + + } else if T, ok := types.Unalias(tname.Type()).(*types.Named); ok { + // defined type + var path []byte + var offset uint32 + if tr.ix == nil { + path = append(empty, name...) + path = append(path, opType) + } else { + // Inv: map entry for obj was populated in first pass. + offset = tr.ix.emitPathSegment(tr.ix.offsets[obj], opType, -1) + } + + // Inspect declared methods of defined types. + // + // The method index here is always with respect + // to the underlying go/types data structures, + // which ultimately derives from source order + // and must be preserved by export data. + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + tr.object(path, offset, opMethod, i, m) + } + } + } +} + +func (tr *traversal) visitType(path []byte, offset uint32, T types.Type) { + switch T := T.(type) { + case *types.Alias: + tr.typ(path, offset, opRhs, -1, T.Rhs()) + + case *types.Basic, *types.Named: + // Named types belonging to pkg were handled already, + // so T must belong to another package. No path. + return + + case *types.Pointer, *types.Slice, *types.Array, *types.Chan: + type hasElem interface{ Elem() types.Type } // note: includes Map + tr.typ(path, offset, opElem, -1, T.(hasElem).Elem()) + + case *types.Map: + tr.typ(path, offset, opKey, -1, T.Key()) + tr.typ(path, offset, opElem, -1, T.Elem()) + + case *types.Signature: + tr.tparams(T.RecvTypeParams(), path, offset, opRecvTypeParam) + tr.tparams(T.TypeParams(), path, offset, opTypeParam) + tr.typ(path, offset, opParams, -1, T.Params()) + tr.typ(path, offset, opResults, -1, T.Results()) + + case *types.Struct: + for i := 0; i < T.NumFields(); i++ { + tr.object(path, offset, opField, i, T.Field(i)) + } + + case *types.Tuple: + for i := 0; i < T.Len(); i++ { + tr.object(path, offset, opAt, i, T.At(i)) + } + + case *types.Interface: + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + if m.Pkg() != nil && m.Pkg() != tr.pkg { + continue // embedded method from another package + } + if !tr.seenMethods[m] { + if tr.seenMethods == nil { + tr.seenMethods = make(map[*types.Func]bool) + } + tr.seenMethods[m] = true + tr.object(path, offset, opMethod, i, m) + } + } + + case *types.TypeParam: + tname := T.Obj() + if tname.Pkg() != nil && tname.Pkg() != tr.pkg { + return // type parameter from another package + } + if !tr.seenTParamNames[tname] { + if tr.seenTParamNames == nil { + tr.seenTParamNames = make(map[*types.TypeName]bool) + } + tr.seenTParamNames[tname] = true + tr.object(path, offset, opObj, -1, tname) + tr.typ(path, offset, opConstraint, -1, T.Constraint()) + } + } +} + +func (tr *traversal) tparams(list *types.TypeParamList, path []byte, offset uint32, op byte) { + for i := 0; i < list.Len(); i++ { + tr.typ(path, offset, op, i, list.At(i)) + } +} + +// typ descends the type graph edge (op, index), then proceeds to traverse type t. +func (tr *traversal) typ(path []byte, offset uint32, op byte, index int, t types.Type) { + if tr.ix == nil { + path = appendOpArg(path, op, index) + } else { + offset = tr.ix.emitPathSegment(offset, op, index) + } + tr.visitType(path, offset, t) +} + +// object descends the type graph edge (op, index), records object +// obj, then proceeds to traverse its type. +func (tr *traversal) object(path []byte, offset uint32, op byte, index int, obj types.Object) { + if tr.ix == nil { + path = appendOpArg(path, op, index) + if obj == tr.target && tr.found == "" { + tr.found = Path(path) + } + path = append(path, opType) + } else { + offset = tr.ix.emitPathSegment(offset, op, index) + if _, ok := tr.ix.offsets[obj]; !ok { + tr.ix.offsets[obj] = offset + } + offset = tr.ix.emitPathSegment(offset, opType, -1) + } + tr.visitType(path, offset, obj.Type()) +} + +// emitPackageLevel encodes a record for a package-level symbol, +// identified by its index in ix.scopeNames. +func (p *pkgIndex) emitPackageLevel(index int) uint32 { + off := uint32(len(p.data)) + p.data = append(p.data, 0) // zero varint => no parent + p.data = binary.AppendUvarint(p.data, uint64(index)) + return off +} + +// emitPathSegment emits a record for a non-initial object path segment. +func (p *pkgIndex) emitPathSegment(parent uint32, op byte, index int) uint32 { + off := uint32(len(p.data)) + p.data = binary.AppendUvarint(p.data, uint64(parent)) + p.data = append(p.data, op) + switch op { + case opAt, opField, opMethod, opTypeParam, opRecvTypeParam: + p.data = binary.AppendUvarint(p.data, uint64(index)) + } + return off +} + +// path returns the Path for the encoded node at the specified offset. +func (p *pkgIndex) path(offset uint32) Path { + var elems []string // path elements in reverse + for { + // Read parent index. + parent, n := binary.Uvarint(p.data[offset:]) + offset += uint32(n) + + if parent == 0 { + break // root (end of path) + } + + op := p.data[offset] + offset++ + + // The [AFMTr] operators have a numeric operand. + switch op { + case opAt, opField, opMethod, opTypeParam, opRecvTypeParam: + val, n := binary.Uvarint(p.data[offset:]) + offset += uint32(n) + elems = append(elems, strconv.Itoa(int(val))) + } + + elems = append(elems, string([]byte{op})) + + offset = uint32(parent) + } + idx, _ := binary.Uvarint(p.data[offset:]) + + // Convert index to Path string. + name := p.scopeNames[idx] + sz := len(name) + for _, elem := range elems { + sz += len(elem) + } + var buf strings.Builder + buf.Grow(sz) + buf.WriteString(name) + for _, elem := range slices.Backward(elems) { + buf.WriteString(elem) + } + return Path(buf.String()) +} + +// appendOpArg appends (op, index) to the object path. +// A negative index is ignored. +func appendOpArg(path []byte, op byte, index int) []byte { path = append(path, op) - path = strconv.AppendInt(path, int64(arg), 10) + if index >= 0 { + path = strconv.AppendInt(path, int64(index), 10) + } return path } @@ -443,138 +730,6 @@ func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) { // 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. -// -// The seenMethods map is used internally to short circuit cycles through -// interface methods, such as occur in the following example: -// -// type I interface { f() interface{I} } -// -// See golang/go#68046 for details. -func find(obj types.Object, T types.Type, path []byte) []byte { - return (&finder{obj: obj}).find(T, path) -} - -// finder closes over search state for a call to find. -type finder struct { - obj types.Object // the sought object - seenTParamNames map[*types.TypeName]bool // for cycle breaking through type parameters - seenMethods map[*types.Func]bool // for cycle breaking through recursive interfaces -} - -func (f *finder) find(T types.Type, path []byte) []byte { - switch T := T.(type) { - case *types.Alias: - return f.find(types.Unalias(T), path) - 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 f.find(T.Elem(), append(path, opElem)) - case *types.Slice: - return f.find(T.Elem(), append(path, opElem)) - case *types.Array: - return f.find(T.Elem(), append(path, opElem)) - case *types.Chan: - return f.find(T.Elem(), append(path, opElem)) - case *types.Map: - if r := f.find(T.Key(), append(path, opKey)); r != nil { - return r - } - return f.find(T.Elem(), append(path, opElem)) - case *types.Signature: - if r := f.findTypeParam(T.RecvTypeParams(), path, opRecvTypeParam); r != nil { - return r - } - if r := f.findTypeParam(T.TypeParams(), path, opTypeParam); r != nil { - return r - } - if r := f.find(T.Params(), append(path, opParams)); r != nil { - return r - } - return f.find(T.Results(), append(path, opResults)) - case *types.Struct: - for i := 0; i < T.NumFields(); i++ { - fld := T.Field(i) - path2 := appendOpArg(path, opField, i) - if fld == f.obj { - return path2 // found field var - } - if r := f.find(fld.Type(), append(path2, opType)); 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 == f.obj { - return path2 // found param/result var - } - if r := f.find(v.Type(), append(path2, opType)); r != nil { - return r - } - } - return nil - case *types.Interface: - for i := 0; i < T.NumMethods(); i++ { - m := T.Method(i) - if f.seenMethods[m] { - return nil - } - path2 := appendOpArg(path, opMethod, i) - if m == f.obj { - return path2 // found interface method - } - if f.seenMethods == nil { - f.seenMethods = make(map[*types.Func]bool) - } - f.seenMethods[m] = true - if r := f.find(m.Type(), append(path2, opType)); r != nil { - return r - } - } - return nil - case *types.TypeParam: - name := T.Obj() - if f.seenTParamNames[name] { - return nil - } - if name == f.obj { - return append(path, opObj) - } - if f.seenTParamNames == nil { - f.seenTParamNames = make(map[*types.TypeName]bool) - } - f.seenTParamNames[name] = true - if r := f.find(T.Constraint(), append(path, opConstraint)); r != nil { - return r - } - return nil - } - panic(T) -} - -func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, op byte) []byte { - return (&finder{obj: obj}).findTypeParam(list, path, op) -} - -func (f *finder) findTypeParam(list *types.TypeParamList, path []byte, op byte) []byte { - for i := 0; i < list.Len(); i++ { - tparam := list.At(i) - path2 := appendOpArg(path, op, i) - if r := f.find(tparam, path2); 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) { pathstr := string(p) @@ -694,14 +849,11 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { case opRhs: if alias, ok := t.(*types.Alias); ok { - t = aliases.Rhs(alias) - } else if false && aliases.Enabled() { - // The Enabled check is too expensive, so for now we - // simply assume that aliases are not enabled. - // + t = alias.Rhs() + } else if false { // Now that go1.24 is assured, we should be able to - // replace this with "if true {", but it causes tests - // to fail. TODO(adonovan): investigate. + // replace this with "if true {", but it causes objectpath + // tests to fail. TODO(adonovan): investigate. return nil, fmt.Errorf("cannot apply %q to %s (got %T, want alias)", code, t, t) } @@ -712,7 +864,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { } tparams := hasTypeParams.TypeParams() if n := tparams.Len(); index >= n { - return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + return nil, fmt.Errorf("type parameter index %d out of range [0-%d)", index, n) } t = tparams.At(index) @@ -723,7 +875,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { } rtparams := sig.RecvTypeParams() if n := rtparams.Len(); index >= n { - return nil, fmt.Errorf("tuple index %d out of range [0-%d)", index, n) + return nil, fmt.Errorf("receiver type parameter index %d out of range [0-%d)", index, n) } t = rtparams.At(index) @@ -798,23 +950,3 @@ func Object(pkg *types.Package, p Path) (types.Object, error) { return obj, nil // success } - -// 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/go/types/typeutil/callee.go b/vendor/golang.org/x/tools/go/types/typeutil/callee.go index 3d24a8c..b64a8f4 100644 --- a/vendor/golang.org/x/tools/go/types/typeutil/callee.go +++ b/vendor/golang.org/x/tools/go/types/typeutil/callee.go @@ -7,7 +7,8 @@ package typeutil import ( "go/ast" "go/types" - _ "unsafe" // for linkname + + "golang.org/x/tools/internal/typesinternal" ) // Callee returns the named target of a function call, if any: @@ -19,14 +20,7 @@ import ( // Note: for calls of instantiated functions and methods, Callee returns // the corresponding generic function or method on the generic type. func Callee(info *types.Info, call *ast.CallExpr) types.Object { - obj := info.Uses[usedIdent(info, call.Fun)] - if obj == nil { - return nil - } - if _, ok := obj.(*types.TypeName); ok { - return nil - } - return obj + return typesinternal.Callee(info, call) } // StaticCallee returns the target (function or method) of a static function @@ -35,52 +29,5 @@ func Callee(info *types.Info, call *ast.CallExpr) types.Object { // Note: for calls of instantiated functions and methods, StaticCallee returns // the corresponding generic function or method on the generic type. func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func { - obj := info.Uses[usedIdent(info, call.Fun)] - fn, _ := obj.(*types.Func) - if fn == nil || interfaceMethod(fn) { - return nil - } - return fn -} - -// usedIdent is the implementation of [internal/typesinternal.UsedIdent]. -// It returns the identifier associated with e. -// See typesinternal.UsedIdent for a fuller description. -// This function should live in typesinternal, but cannot because it would -// create an import cycle. -// -//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent -func usedIdent(info *types.Info, e ast.Expr) *ast.Ident { - if info.Types == nil || info.Uses == nil { - panic("one of info.Types or info.Uses is nil; both must be populated") - } - // Look through type instantiation if necessary. - switch d := ast.Unparen(e).(type) { - case *ast.IndexExpr: - if info.Types[d.Index].IsType() { - e = d.X - } - case *ast.IndexListExpr: - e = d.X - } - - switch e := ast.Unparen(e).(type) { - // info.Uses always has the object we want, even for selector expressions. - // We don't need info.Selections. - // See go/types/recording.go:recordSelection. - case *ast.Ident: - return e - case *ast.SelectorExpr: - return e.Sel - } - return nil -} - -// interfaceMethod reports whether its argument is a method of an interface. -// This function should live in typesinternal, but cannot because it would create an import cycle. -// -//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod -func interfaceMethod(f *types.Func) bool { - recv := f.Signature().Recv() - return recv != nil && types.IsInterface(recv.Type()) + return typesinternal.StaticCallee(info, call) } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases.go b/vendor/golang.org/x/tools/internal/aliases/aliases.go index b9425f5..a4ae04b 100644 --- a/vendor/golang.org/x/tools/internal/aliases/aliases.go +++ b/vendor/golang.org/x/tools/internal/aliases/aliases.go @@ -9,30 +9,10 @@ import ( "go/types" ) -// Package aliases defines backward compatible shims -// for the types.Alias type representation added in 1.22. -// This defines placeholders for x/tools until 1.26. - -// NewAlias creates a new TypeName in Package pkg that +// New creates a new TypeName in Package pkg that // is an alias for the type rhs. -// -// The enabled parameter determines whether the resulting [TypeName]'s -// type is an [types.Alias]. Its value must be the result of a call to -// [Enabled], which computes the effective value of -// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled -// function is expensive and should be called once per task (e.g. -// package import), not once per call to NewAlias. -// -// Precondition: enabled || len(tparams)==0. -// If materialized aliases are disabled, there must not be any type parameters. -func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { - if enabled { - tname := types.NewTypeName(pos, pkg, name, nil) - SetTypeParams(types.NewAlias(tname, rhs), tparams) - return tname - } - if len(tparams) > 0 { - panic("cannot create an alias with type parameters when gotypesalias is not enabled") - } - return types.NewTypeName(pos, pkg, name, rhs) +func New(pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { + tname := types.NewTypeName(pos, pkg, name, nil) + types.NewAlias(tname, rhs).SetTypeParams(tparams) + return tname } diff --git a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go b/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go deleted file mode 100644 index 7716a33..0000000 --- a/vendor/golang.org/x/tools/internal/aliases/aliases_go122.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aliases - -import ( - "go/ast" - "go/parser" - "go/token" - "go/types" -) - -// Rhs returns the type on the right-hand side of the alias declaration. -func Rhs(alias *types.Alias) types.Type { - if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok { - return alias.Rhs() // go1.23+ - } - - // go1.22's Alias didn't have the Rhs method, - // so Unalias is the best we can do. - return types.Unalias(alias) -} - -// TypeParams returns the type parameter list of the alias. -func TypeParams(alias *types.Alias) *types.TypeParamList { - if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok { - return alias.TypeParams() // go1.23+ - } - return nil -} - -// SetTypeParams sets the type parameters of the alias type. -func SetTypeParams(alias *types.Alias, tparams []*types.TypeParam) { - if alias, ok := any(alias).(interface { - SetTypeParams(tparams []*types.TypeParam) - }); ok { - alias.SetTypeParams(tparams) // go1.23+ - } else if len(tparams) > 0 { - panic("cannot set type parameters of an Alias type in go1.22") - } -} - -// TypeArgs returns the type arguments used to instantiate the Alias type. -func TypeArgs(alias *types.Alias) *types.TypeList { - if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok { - return alias.TypeArgs() // go1.23+ - } - return nil // empty (go1.22) -} - -// Origin returns the generic Alias type of which alias is an instance. -// If alias is not an instance of a generic alias, Origin returns alias. -func Origin(alias *types.Alias) *types.Alias { - if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok { - return alias.Origin() // go1.23+ - } - return alias // not an instance of a generic alias (go1.22) -} - -// Enabled reports whether [NewAlias] should create [types.Alias] types. -// -// This function is expensive! Call it sparingly. -func Enabled() bool { - // The only reliable way to compute the answer is to invoke go/types. - // We don't parse the GODEBUG environment variable, because - // (a) it's tricky to do so in a manner that is consistent - // with the godebug package; in particular, a simple - // substring check is not good enough. The value is a - // rightmost-wins list of options. But more importantly: - // (b) it is impossible to detect changes to the effective - // setting caused by os.Setenv("GODEBUG"), as happens in - // many tests. Therefore any attempt to cache the result - // is just incorrect. - fset := token.NewFileSet() - f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", parser.SkipObjectResolution) - pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) - _, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias) - return enabled -} diff --git a/vendor/golang.org/x/tools/internal/event/core/event.go b/vendor/golang.org/x/tools/internal/event/core/event.go index ade5d1e..42c2188 100644 --- a/vendor/golang.org/x/tools/internal/event/core/event.go +++ b/vendor/golang.org/x/tools/internal/event/core/event.go @@ -7,6 +7,7 @@ package core import ( "fmt" + "iter" "time" "golang.org/x/tools/internal/event/label" @@ -34,10 +35,8 @@ func (ev Event) Format(f fmt.State, r rune) { if !ev.at.IsZero() { fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) } - for index := 0; ev.Valid(index); index++ { - if l := ev.Label(index); l.Valid() { - fmt.Fprintf(f, "\n\t%v", l) - } + for l := range ev.Labels() { + fmt.Fprintf(f, "\n\t%v", l) } } @@ -52,6 +51,22 @@ func (ev Event) Label(index int) label.Label { return ev.dynamic[index-len(ev.static)] } +// Labels returns an iterator over the event's valid labels. +func (ev Event) Labels() iter.Seq[label.Label] { + return func(yield func(label.Label) bool) { + for _, l := range ev.static { + if l.Valid() && !yield(l) { + return + } + } + for _, l := range ev.dynamic { + if l.Valid() && !yield(l) { + return + } + } + } +} + func (ev Event) Find(key label.Key) label.Label { for _, l := range ev.static { if l.Key() == key { diff --git a/vendor/golang.org/x/tools/internal/event/keys/keys.go b/vendor/golang.org/x/tools/internal/event/keys/keys.go index 4cfa51b..ac78bc3 100644 --- a/vendor/golang.org/x/tools/internal/event/keys/keys.go +++ b/vendor/golang.org/x/tools/internal/event/keys/keys.go @@ -6,14 +6,13 @@ package keys import ( "fmt" - "io" "math" "strconv" "golang.org/x/tools/internal/event/label" ) -// Value represents a key for untyped values. +// Value is a [label.Key] for untyped values. type Value struct { name string description string @@ -27,11 +26,11 @@ func New(name, description string) *Value { func (k *Value) Name() string { return k.name } func (k *Value) Description() string { return k.description } -func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { - fmt.Fprint(w, k.From(l)) +func (k *Value) Append(buf []byte, l label.Label) []byte { + return fmt.Append(buf, k.From(l)) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *Value) Get(lm label.Map) any { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -39,7 +38,7 @@ func (k *Value) Get(lm label.Map) any { return nil } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *Value) From(t label.Label) any { return t.UnpackValue() } // Of creates a new Label with this key and the supplied value. @@ -54,7 +53,7 @@ type Tag struct { description string } -// NewTag creates a new Key for tagging labels. +// NewTag creates a new [label.Key] for tagging labels. func NewTag(name, description string) *Tag { return &Tag{name: name, description: description} } @@ -62,18 +61,18 @@ func NewTag(name, description string) *Tag { func (k *Tag) Name() string { return k.name } func (k *Tag) Description() string { return k.description } -func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} +func (k *Tag) Append(buf []byte, l label.Label) []byte { return buf } // New creates a new Label with this key. func (k *Tag) New() label.Label { return label.OfValue(k, nil) } -// Int represents a key +// Int is a [label.Key] for signed integers. type Int struct { name string description string } -// NewInt creates a new Key for int values. +// NewInt returns a new [label.Key] for int64 values. func NewInt(name, description string) *Int { return &Int{name: name, description: description} } @@ -81,381 +80,92 @@ func NewInt(name, description string) *Int { func (k *Int) Name() string { return k.name } func (k *Int) Description() string { return k.description } -func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +func (k *Int) Append(buf []byte, l label.Label) []byte { + return strconv.AppendInt(buf, k.From(l), 10) } // Of creates a new Label with this key and the supplied value. -func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } +func (k *Int) Of(v int) label.Label { return k.Of64(int64(v)) } -// Get can be used to get a label for the key from a label.Map. -func (k *Int) Get(lm label.Map) int { +// Of64 creates a new Label with this key and the supplied value. +func (k *Int) Of64(v int64) label.Label { return label.Of64(k, uint64(v)) } + +// Get returns the label for the key of a label.Map. +func (k *Int) Get(lm label.Map) int64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } +// From returns the value of a Label. +func (k *Int) From(t label.Label) int64 { return int64(t.Unpack64()) } -// Int8 represents a key -type Int8 struct { +// Uint is a [label.Key] for unsigned integers. +type Uint struct { name string description string } -// NewInt8 creates a new Key for int8 values. -func NewInt8(name, description string) *Int8 { - return &Int8{name: name, description: description} +// NewUint creates a new [label.Key] for unsigned values. +func NewUint(name, description string) *Uint { + return &Uint{name: name, description: description} } -func (k *Int8) Name() string { return k.name } -func (k *Int8) Description() string { return k.description } +func (k *Uint) Name() string { return k.name } +func (k *Uint) Description() string { return k.description } -func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +func (k *Uint) Append(buf []byte, l label.Label) []byte { + return strconv.AppendUint(buf, k.From(l), 10) } // Of creates a new Label with this key and the supplied value. -func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } +func (k *Uint) Of(v uint64) label.Label { return label.Of64(k, v) } -// Get can be used to get a label for the key from a label.Map. -func (k *Int8) Get(lm label.Map) int8 { +// Get returns the label for the key of a label.Map. +func (k *Uint) Get(lm label.Map) uint64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } +// From returns the value of a Label. +func (k *Uint) From(t label.Label) uint64 { return t.Unpack64() } -// Int16 represents a key -type Int16 struct { +// Float is a label.Key for floating-point values. +type Float struct { name string description string } -// NewInt16 creates a new Key for int16 values. -func NewInt16(name, description string) *Int16 { - return &Int16{name: name, description: description} +// NewFloat creates a new [label.Key] for floating-point values. +func NewFloat(name, description string) *Float { + return &Float{name: name, description: description} } -func (k *Int16) Name() string { return k.name } -func (k *Int16) Description() string { return k.description } +func (k *Float) Name() string { return k.name } +func (k *Float) Description() string { return k.description } -func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +func (k *Float) Append(buf []byte, l label.Label) []byte { + return strconv.AppendFloat(buf, k.From(l), 'E', -1, 64) } // Of creates a new Label with this key and the supplied value. -func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int16) Get(lm label.Map) int16 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } - -// Int32 represents a key -type Int32 struct { - name string - description string -} - -// NewInt32 creates a new Key for int32 values. -func NewInt32(name, description string) *Int32 { - return &Int32{name: name, description: description} -} - -func (k *Int32) Name() string { return k.name } -func (k *Int32) Description() string { return k.description } - -func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int32) Get(lm label.Map) int32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } - -// Int64 represents a key -type Int64 struct { - name string - description string -} - -// NewInt64 creates a new Key for int64 values. -func NewInt64(name, description string) *Int64 { - return &Int64{name: name, description: description} -} - -func (k *Int64) Name() string { return k.name } -func (k *Int64) Description() string { return k.description } - -func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendInt(buf, k.From(l), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *Int64) Get(lm label.Map) int64 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } - -// UInt represents a key -type UInt struct { - name string - description string -} - -// NewUInt creates a new Key for uint values. -func NewUInt(name, description string) *UInt { - return &UInt{name: name, description: description} -} - -func (k *UInt) Name() string { return k.name } -func (k *UInt) Description() string { return k.description } - -func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt) Get(lm label.Map) uint { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } - -// UInt8 represents a key -type UInt8 struct { - name string - description string -} - -// NewUInt8 creates a new Key for uint8 values. -func NewUInt8(name, description string) *UInt8 { - return &UInt8{name: name, description: description} -} - -func (k *UInt8) Name() string { return k.name } -func (k *UInt8) Description() string { return k.description } - -func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt8) Get(lm label.Map) uint8 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } - -// UInt16 represents a key -type UInt16 struct { - name string - description string -} - -// NewUInt16 creates a new Key for uint16 values. -func NewUInt16(name, description string) *UInt16 { - return &UInt16{name: name, description: description} -} - -func (k *UInt16) Name() string { return k.name } -func (k *UInt16) Description() string { return k.description } - -func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt16) Get(lm label.Map) uint16 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } - -// UInt32 represents a key -type UInt32 struct { - name string - description string -} - -// NewUInt32 creates a new Key for uint32 values. -func NewUInt32(name, description string) *UInt32 { - return &UInt32{name: name, description: description} -} - -func (k *UInt32) Name() string { return k.name } -func (k *UInt32) Description() string { return k.description } - -func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt32) Get(lm label.Map) uint32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } - -// UInt64 represents a key -type UInt64 struct { - name string - description string -} - -// NewUInt64 creates a new Key for uint64 values. -func NewUInt64(name, description string) *UInt64 { - return &UInt64{name: name, description: description} -} - -func (k *UInt64) Name() string { return k.name } -func (k *UInt64) Description() string { return k.description } - -func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendUint(buf, k.From(l), 10)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } - -// Get can be used to get a label for the key from a label.Map. -func (k *UInt64) Get(lm label.Map) uint64 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } - -// Float32 represents a key -type Float32 struct { - name string - description string -} - -// NewFloat32 creates a new Key for float32 values. -func NewFloat32(name, description string) *Float32 { - return &Float32{name: name, description: description} -} - -func (k *Float32) Name() string { return k.name } -func (k *Float32) Description() string { return k.description } - -func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Float32) Of(v float32) label.Label { - return label.Of64(k, uint64(math.Float32bits(v))) -} - -// Get can be used to get a label for the key from a label.Map. -func (k *Float32) Get(lm label.Map) float32 { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return 0 -} - -// From can be used to get a value from a Label. -func (k *Float32) From(t label.Label) float32 { - return math.Float32frombits(uint32(t.Unpack64())) -} - -// Float64 represents a key -type Float64 struct { - name string - description string -} - -// NewFloat64 creates a new Key for int64 values. -func NewFloat64(name, description string) *Float64 { - return &Float64{name: name, description: description} -} - -func (k *Float64) Name() string { return k.name } -func (k *Float64) Description() string { return k.description } - -func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Float64) Of(v float64) label.Label { +func (k *Float) Of(v float64) label.Label { return label.Of64(k, math.Float64bits(v)) } -// Get can be used to get a label for the key from a label.Map. -func (k *Float64) Get(lm label.Map) float64 { +// Get returns the label for the key of a label.Map. +func (k *Float) Get(lm label.Map) float64 { if t := lm.Find(k); t.Valid() { return k.From(t) } return 0 } -// From can be used to get a value from a Label. -func (k *Float64) From(t label.Label) float64 { +// From returns the value of a Label. +func (k *Float) From(t label.Label) float64 { return math.Float64frombits(t.Unpack64()) } @@ -473,14 +183,14 @@ func NewString(name, description string) *String { func (k *String) Name() string { return k.name } func (k *String) Description() string { return k.description } -func (k *String) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendQuote(buf, k.From(l))) +func (k *String) Append(buf []byte, l label.Label) []byte { + return strconv.AppendQuote(buf, k.From(l)) } // Of creates a new Label with this key and the supplied value. func (k *String) Of(v string) label.Label { return label.OfString(k, v) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *String) Get(lm label.Map) string { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -488,53 +198,16 @@ func (k *String) Get(lm label.Map) string { return "" } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *String) From(t label.Label) string { return t.UnpackString() } -// Boolean represents a key -type Boolean struct { - name string - description string -} - -// NewBoolean creates a new Key for bool values. -func NewBoolean(name, description string) *Boolean { - return &Boolean{name: name, description: description} -} - -func (k *Boolean) Name() string { return k.name } -func (k *Boolean) Description() string { return k.description } - -func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { - w.Write(strconv.AppendBool(buf, k.From(l))) -} - -// Of creates a new Label with this key and the supplied value. -func (k *Boolean) Of(v bool) label.Label { - if v { - return label.Of64(k, 1) - } - return label.Of64(k, 0) -} - -// Get can be used to get a label for the key from a label.Map. -func (k *Boolean) Get(lm label.Map) bool { - if t := lm.Find(k); t.Valid() { - return k.From(t) - } - return false -} - -// From can be used to get a value from a Label. -func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } - // Error represents a key type Error struct { name string description string } -// NewError creates a new Key for int64 values. +// NewError returns a new [label.Key] for error values. func NewError(name, description string) *Error { return &Error{name: name, description: description} } @@ -542,14 +215,14 @@ func NewError(name, description string) *Error { func (k *Error) Name() string { return k.name } func (k *Error) Description() string { return k.description } -func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { - io.WriteString(w, k.From(l).Error()) +func (k *Error) Append(buf []byte, l label.Label) []byte { + return append(buf, k.From(l).Error()...) } -// Of creates a new Label with this key and the supplied value. +// Of returns a new Label with this key and the supplied value. func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } -// Get can be used to get a label for the key from a label.Map. +// Get returns the label for the key of a label.Map. func (k *Error) Get(lm label.Map) error { if t := lm.Find(k); t.Valid() { return k.From(t) @@ -557,7 +230,7 @@ func (k *Error) Get(lm label.Map) error { return nil } -// From can be used to get a value from a Label. +// From returns the value of a Label. func (k *Error) From(t label.Label) error { err, _ := t.UnpackValue().(error) return err diff --git a/vendor/golang.org/x/tools/internal/event/label/label.go b/vendor/golang.org/x/tools/internal/event/label/label.go index c37584a..e84226f 100644 --- a/vendor/golang.org/x/tools/internal/event/label/label.go +++ b/vendor/golang.org/x/tools/internal/event/label/label.go @@ -19,12 +19,8 @@ type Key interface { Name() string // Description returns a string that can be used to describe the value. Description() string - - // Format is used in formatting to append the value of the label to the - // supplied buffer. - // The formatter may use the supplied buf as a scratch area to avoid - // allocations. - Format(w io.Writer, buf []byte, l Label) + // Append appends the formatted value of the label to the supplied buffer. + Append(buf []byte, l Label) []byte } // Label holds a key and value pair. @@ -131,8 +127,7 @@ func (t Label) Format(f fmt.State, r rune) { } io.WriteString(f, t.Key().Name()) io.WriteString(f, "=") - var buf [128]byte - t.Key().Format(f, buf[:0], t) + f.Write(t.Key().Append(nil, t)) // ignore error } func (l *list) Valid(index int) bool { diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go index 2bef2b0..686f171 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go @@ -242,7 +242,6 @@ import ( "strings" "golang.org/x/tools/go/types/objectpath" - "golang.org/x/tools/internal/aliases" ) // IExportShallow encodes "shallow" export data for the specified package. @@ -767,11 +766,11 @@ func (p *iexporter) doDecl(obj types.Object) { } if obj.IsAlias() { - alias, materialized := t.(*types.Alias) // may fail when aliases are not enabled + alias, materialized := t.(*types.Alias) // perhaps false for certain built-ins? var tparams *types.TypeParamList if materialized { - tparams = aliases.TypeParams(alias) + tparams = alias.TypeParams() } if tparams.Len() == 0 { w.tag(aliasTag) @@ -785,7 +784,7 @@ func (p *iexporter) doDecl(obj types.Object) { if materialized { // Preserve materialized aliases, // even of non-exported types. - t = aliases.Rhs(alias) + t = alias.Rhs() } w.typ(t, obj.Pkg()) break @@ -824,6 +823,9 @@ func (p *iexporter) doDecl(obj types.Object) { w.pos(m.Pos()) w.string(m.Name()) sig, _ := m.Type().(*types.Signature) + if w.p.version >= iexportVersionGenericMethods && w.bool(sig.TypeParams().Len() > 0) { + w.tparamList(obj.Name()+"."+m.Name(), sig.TypeParams(), obj.Pkg()) + } // Receiver type parameters are type arguments of the receiver type, so // their name must be qualified before exporting recv. @@ -1011,11 +1013,11 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { } switch t := t.(type) { case *types.Alias: - if targs := aliases.TypeArgs(t); targs.Len() > 0 { + if targs := t.TypeArgs(); targs.Len() > 0 { w.startType(instanceType) w.pos(t.Obj().Pos()) w.typeList(targs, pkg) - w.typ(aliases.Origin(t), pkg) + w.typ(t.Origin(), pkg) return } w.startType(aliasType) diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go index 4d6d500..7b1723e 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go @@ -48,13 +48,14 @@ func (r *intReader) uint64() uint64 { // Keep this in sync with constants in iexport.go. const ( - iexportVersionGo1_11 = 0 - iexportVersionPosCol = 1 - iexportVersionGo1_18 = 2 - iexportVersionGenerics = 2 - iexportVersion = iexportVersionGenerics + iexportVersionGo1_11 = 0 + iexportVersionPosCol = 1 + iexportVersionGo1_18 = 2 + iexportVersionGenerics = 2 + iexportVersionGenericMethods = 3 + iexportVersion = iexportVersionGenericMethods - iexportVersionCurrent = 2 + iexportVersionCurrent = 3 ) type ident struct { @@ -179,9 +180,9 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte version = int64(r.uint64()) switch version { - case iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11: + case iexportVersionGenericMethods, iexportVersionGo1_18, iexportVersionPosCol, iexportVersionGo1_11: default: - if version > iexportVersionGo1_18 { + if version > iexportVersionGenericMethods { errorf("unstable iexport format version %d, just rebuild compiler and std library", version) } else { errorf("unknown iexport format version %d", version) @@ -210,7 +211,6 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte p := iimporter{ version: int(version), ipath: path, - aliases: aliases.Enabled(), shallow: shallow, reportf: reportf, @@ -370,7 +370,6 @@ type iimporter struct { version int ipath string - aliases bool shallow bool reportf ReportFunc // if non-nil, used to report bugs @@ -576,7 +575,7 @@ func (r *importReader) obj(pkg *types.Package, name string) { tparams = r.tparamList() } typ := r.typ() - obj := aliases.NewAlias(r.p.aliases, pos, pkg, name, typ, tparams) + obj := aliases.New(pos, pkg, name, typ, tparams) markBlack(obj) // workaround for golang/go#69912 r.declare(obj) @@ -616,6 +615,10 @@ func (r *importReader) obj(pkg *types.Package, name string) { for n := r.uint64(); n > 0; n-- { mpos := r.pos() mname := r.ident() + var tpars []*types.TypeParam + if r.p.version >= iexportVersionGenericMethods && r.bool() { + tpars = r.tparamList() + } recv := r.param(pkg) // If the receiver has any targs, set those as the @@ -630,8 +633,7 @@ func (r *importReader) obj(pkg *types.Package, name string) { rparams[i] = types.Unalias(targs.At(i)).(*types.TypeParam) } } - msig := r.signature(pkg, recv, rparams, nil) - + msig := r.signature(pkg, recv, rparams, tpars) named.AddMethod(types.NewFunc(mpos, pkg, mname, msig)) } } diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go similarity index 84% rename from vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go rename to vendor/golang.org/x/tools/internal/gcimporter/ureader.go index 37b4a39..5d3b7c8 100644 --- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go +++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader.go @@ -11,6 +11,7 @@ import ( "go/token" "go/types" "sort" + "strings" "golang.org/x/tools/internal/aliases" "golang.org/x/tools/internal/pkgbits" @@ -26,7 +27,6 @@ type pkgReader struct { ctxt *types.Context imports map[string]*types.Package // previously imported packages, indexed by path - aliases bool // create types.Alias nodes // lazily initialized arrays corresponding to the unified IR // PosBase, Pkg, and Type sections, respectively. @@ -36,6 +36,10 @@ type pkgReader struct { // laterFns holds functions that need to be invoked at the end of // import reading. + // + // TODO(mdempsky): Is it safe to have a single "later" slice or do + // we need to have multiple passes? See comments on CL 386002 and + // go.dev/issue/52104. laterFns []func() // laterFors is used in case of 'type A B' to ensure that B is processed before A. laterFors map[types.Type]int @@ -98,7 +102,6 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st ctxt: ctxt, imports: imports, - aliases: aliases.Enabled(), posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)), pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)), @@ -160,12 +163,11 @@ type reader struct { // A readerDict holds the state for type parameters that parameterize // the current unified IR element. type readerDict struct { - // bounds is a slice of typeInfos corresponding to the underlying - // bounds of the element's type parameters. - bounds []typeInfo + rtbounds []typeInfo // contains constraint types for each parameter in rtparams + rtparams []*types.TypeParam // contains receiver type parameters for an element - // tparams is a slice of the constructed TypeParams for the element. - tparams []*types.TypeParam + tbounds []typeInfo // contains constraint types for each parameter in tparams + tparams []*types.TypeParam // contains type parameters for an element // derived is a slice of types derived from tparams, which may be // instantiated while reading the current element. @@ -355,7 +357,11 @@ func (r *reader) doTyp() (res types.Type) { return name.Type() case pkgbits.TypeTypeParam: - return r.dict.tparams[r.Len()] + n := r.Len() + if n < len(r.dict.rtbounds) { + return r.dict.rtparams[n] + } + return r.dict.tparams[n-len(r.dict.rtbounds)] case pkgbits.TypeArray: len := int64(r.Uint64()) @@ -518,6 +524,12 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { return objPkg, objName } + // TODO(mark): This, like the above splitVargenSuffix, is not ideal. + // Ignore generic methods promoted to global scope. + if strings.Contains(objName, ".") { + return objPkg, objName + } + if objPkg.Scope().Lookup(objName) == nil { dict := pr.objDictIdx(idx) @@ -536,10 +548,10 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { pos := r.pos() var tparams []*types.TypeParam if r.Version().Has(pkgbits.AliasTypeParamNames) { - tparams = r.typeParamNames() + tparams = r.typeParamNames(false) } typ := r.typ() - declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ, tparams)) + declare(aliases.New(pos, objPkg, objName, typ, tparams)) case pkgbits.ObjConst: pos := r.pos() @@ -549,7 +561,10 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { case pkgbits.ObjFunc: pos := r.pos() - tparams := r.typeParamNames() + if r.Version().Has(pkgbits.GenericMethods) { + assert(!r.Bool()) // generic methods are read in their defining type + } + tparams := r.typeParamNames(false) sig := r.signature(nil, nil, tparams) declare(types.NewFunc(pos, objPkg, objName, sig)) @@ -560,7 +575,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { named := types.NewNamed(obj, nil, nil) declare(obj) - named.SetTypeParams(r.typeParamNames()) + named.SetTypeParams(r.typeParamNames(false)) setUnderlying := func(underlying types.Type) { // If the underlying type is an interface, we need to @@ -618,6 +633,29 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) { named.AddMethod(r.method()) } + if r.Version().Has(pkgbits.GenericMethods) { + for range r.Len() { + // Careful: objIdx is used to read in package-scoped declarations, which + // methods are not. Instead, decode it here. This makes it easier to + // associate it with the type and avoids the main objIdx loop. + idx := r.Reloc(pkgbits.RelocObj) + + r := pr.tempReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1) + r.dict = pr.objDictIdx(idx) + + pos := r.pos() + assert(r.Bool()) // generic method + pkg, name := r.selector() + rtparams := r.typeParamNames(true) + recv := r.param() + tparams := r.typeParamNames(false) + sig := r.signature(recv, rtparams, tparams) + + pr.retireReader(r) + named.AddMethod(types.NewFunc(pos, pkg, name, sig)) + } + } + case pkgbits.ObjVar: pos := r.pos() typ := r.typ() @@ -640,9 +678,20 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { errorf("unexpected object with %v implicit type parameter(s)", implicits) } - dict.bounds = make([]typeInfo, r.Len()) - for i := range dict.bounds { - dict.bounds[i] = r.typInfo() + nreceivers := 0 + if r.Version().Has(pkgbits.GenericMethods) { + nreceivers = r.Len() + } + nexplicits := r.Len() + + dict.rtbounds = make([]typeInfo, nreceivers) + for i := range dict.rtbounds { + dict.rtbounds[i] = r.typInfo() + } + + dict.tbounds = make([]typeInfo, nexplicits) + for i := range dict.tbounds { + dict.tbounds[i] = r.typInfo() } dict.derived = make([]derivedInfo, r.Len()) @@ -661,15 +710,24 @@ func (pr *pkgReader) objDictIdx(idx pkgbits.Index) *readerDict { return &dict } -func (r *reader) typeParamNames() []*types.TypeParam { +func (r *reader) typeParamNames(isGenMeth bool) []*types.TypeParam { r.Sync(pkgbits.SyncTypeParamNames) - // Note: This code assumes it only processes objects without - // implement type parameters. This is currently fine, because - // reader is only used to read in exported declarations, which are - // always package scoped. + // Note: This code assumes there are no implicit type parameters. + // This is fine since it only reads exported declarations, which + // never have implicits. - if len(r.dict.bounds) == 0 { + var in []typeInfo + var out *[]*types.TypeParam + if isGenMeth { + in = r.dict.rtbounds + out = &r.dict.rtparams + } else { + in = r.dict.tbounds + out = &r.dict.tparams + } + + if len(in) == 0 { return nil } @@ -678,40 +736,34 @@ func (r *reader) typeParamNames() []*types.TypeParam { // create all the TypeNames and TypeParams, then we construct and // set the bound type. - r.dict.tparams = make([]*types.TypeParam, len(r.dict.bounds)) - for i := range r.dict.bounds { + // We have to save tparams outside of the closure, because typeParamNames + // can be called multiple times with the same dictionary instance. + tparams := make([]*types.TypeParam, len(in)) + *out = tparams + + for i := range in { pos := r.pos() pkg, name := r.localIdent() tname := types.NewTypeName(pos, pkg, name, nil) - r.dict.tparams[i] = types.NewTypeParam(tname, nil) + tparams[i] = types.NewTypeParam(tname, nil) } - typs := make([]types.Type, len(r.dict.bounds)) - for i, bound := range r.dict.bounds { - typs[i] = r.p.typIdx(bound, r.dict) + // The reader dictionary will continue mutating before we have time + // to call delayed functions; make a local copy of the constraints. + types := make([]types.Type, len(in)) + for i, info := range in { + types[i] = r.p.typIdx(info, r.dict) } - // TODO(mdempsky): This is subtle, elaborate further. - // - // We have to save tparams outside of the closure, because - // typeParamNames() can be called multiple times with the same - // dictionary instance. - // - // Also, this needs to happen later to make sure SetUnderlying has - // been called. - // - // TODO(mdempsky): Is it safe to have a single "later" slice or do - // we need to have multiple passes? See comments on CL 386002 and - // go.dev/issue/52104. - tparams := r.dict.tparams + // This needs to happen later to make sure SetUnderlying has been called. r.p.later(func() { - for i, typ := range typs { + for i, typ := range types { tparams[i].SetConstraint(typ) } }) - return r.dict.tparams + return tparams } func (r *reader) method() *types.Func { @@ -719,7 +771,7 @@ func (r *reader) method() *types.Func { pos := r.pos() pkg, name := r.selector() - rparams := r.typeParamNames() + rparams := r.typeParamNames(false) sig := r.signature(r.param(), rparams, nil) _ = r.pos() // TODO(mdempsky): Remove; this is a hacker for linker.go. diff --git a/vendor/golang.org/x/tools/internal/gocommand/version.go b/vendor/golang.org/x/tools/internal/gocommand/version.go index 446c584..d82f13a 100644 --- a/vendor/golang.org/x/tools/internal/gocommand/version.go +++ b/vendor/golang.org/x/tools/internal/gocommand/version.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "regexp" + "slices" "strings" ) @@ -26,6 +27,9 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { inv.BuildFlags = nil // This is not a build command. inv.ModFlag = "" inv.ModFile = "" + // Set GO111MODULE=off so that we are immune to errors in go.{work,mod}. + // Unfortunately, this breaks the Go 1.21+ toolchain directive and + // may affect the set of ReleaseTags; see #68495. inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off") stdoutBytes, err := r.Run(ctx, inv) @@ -38,9 +42,9 @@ func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) { } // Split up "[go1.1 go1.15]" and return highest go1.X value. tags := strings.Fields(stdout[1 : len(stdout)-2]) - for i := len(tags) - 1; i >= 0; i-- { + for _, tag := range slices.Backward(tags) { var version int - if _, err := fmt.Sscanf(tags[i], "go1.%d", &version); err != nil { + if _, err := fmt.Sscanf(tag, "go1.%d", &version); err != nil { continue } return version, nil diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go index 1b4dc0c..6aa1e27 100644 --- a/vendor/golang.org/x/tools/internal/imports/fix.go +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -32,6 +32,7 @@ import ( "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/gocommand" "golang.org/x/tools/internal/gopathwalk" + "golang.org/x/tools/internal/modindex" "golang.org/x/tools/internal/stdlib" ) @@ -273,7 +274,6 @@ func (p *pass) loadPackageNames(ctx context.Context, imports []*ImportInfo) erro } unknown = append(unknown, imp.ImportPath) } - names, err := p.source.LoadPackageNames(ctx, p.srcDir, unknown) if err != nil { return err @@ -321,6 +321,7 @@ func (p *pass) importIdentifier(imp *ImportInfo) string { // load reads in everything necessary to run a pass, and reports whether the // file already has all the imports it needs. It fills in p.missingRefs with the // file's missing symbols, if any, or removes unused imports if not. +// This is called 3(!) times: self, otherFiles, loadRealPackageNames func (p *pass) load(ctx context.Context) ([]*ImportFix, bool) { p.knownPackages = map[string]*PackageInfo{} p.missingRefs = References{} @@ -578,6 +579,17 @@ func getFixes(ctx context.Context, fset *token.FileSet, f *ast.File, filename st } func getFixesWithSource(ctx context.Context, fset *token.FileSet, f *ast.File, filename string, goroot string, logf func(string, ...any), source Source) ([]*ImportFix, error) { + // If there is an Index for the GOMODCACHE, remember that, and later make it so that the + // directory walk doesn't go into the module cache, since we already have all the information + var ix *modindex.Index + src, ok := source.(*ProcessEnvSource) + if ok { + var err error + if ix, err = modindex.Read(src.env.Env["GOMODCACHE"]); err != nil { + ix = nil // don't use it if there was an error + } + } + // This logic is defensively duplicated from getFixes. abs, err := filepath.Abs(filename) if err != nil { @@ -636,6 +648,20 @@ func getFixesWithSource(ctx context.Context, fset *token.FileSet, f *ast.File, f } p.loadRealPackageNames = true p.otherFiles = otherFiles + if ix != nil { + src, ok := p.source.(*ProcessEnvSource) + if ok { + // For safety, clone the env so that we don't modify the caller's env. + env := *src.env + env.Env = maps.Clone(src.env.Env) + src.env = &env + // avoid looking in the module cache, as we have the index instead: + // This makes a later call to newModuleresolver (from + // LoadPackageNames) produce a resolver that will not look + // in the module cache + src.env.Env["GOMODCACHE"] = "" + } + } if fixes, done := p.load(ctx); done { return fixes, nil } @@ -650,7 +676,7 @@ func getFixesWithSource(ctx context.Context, fset *token.FileSet, f *ast.File, f // Go look for candidates in $GOPATH, etc. We don't necessarily load // the real exports of sibling imports, so keep assuming their contents. - if err := addExternalCandidates(ctx, p, p.missingRefs, filename); err != nil { + if err := addExternalCandidates(ctx, p, p.missingRefs, filename, ix); err != nil { return nil, err } @@ -1185,7 +1211,7 @@ type scanCallback struct { exportsLoaded func(pkg *pkg, exports []stdlib.Symbol) } -func addExternalCandidates(ctx context.Context, pass *pass, refs References, filename string) error { +func addExternalCandidates(ctx context.Context, pass *pass, refs References, filename string, ix *modindex.Index) error { ctx, done := event.Start(ctx, "imports.addExternalCandidates") defer done() @@ -1194,6 +1220,24 @@ func addExternalCandidates(ctx context.Context, pass *pass, refs References, fil return err } + // Add candidates from the module cache. + if ix != nil { + for k, v := range refs { + for n := range v { + cands := ix.Lookup(k, n, false) + for _, cand := range cands { + x := &Result{ + &ImportInfo{ImportPath: cand.ImportPath}, + &PackageInfo{Name: cand.PkgName, + Exports: map[string]bool{cand.Name: true}, + }, + } + results = append(results, x) + } + } + } + } + for _, result := range results { if result == nil { continue @@ -1650,9 +1694,7 @@ func (s *symbolSearcher) search(ctx context.Context, candidates []pkgDistance, p }() // Start the search. - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for i, c := range candidates { select { case loadExportsSem <- struct{}{}: @@ -1681,7 +1723,7 @@ func (s *symbolSearcher) search(ctx context.Context, candidates []pkgDistance, p rescv[i] <- pkg // may be nil }() } - }() + }) // Await the first (best) result. for _, resc := range rescv { diff --git a/vendor/golang.org/x/tools/internal/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go index b5f5218..0722938 100644 --- a/vendor/golang.org/x/tools/internal/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -74,6 +74,10 @@ func Process(filename string, src []byte, opt *Options) (formatted []byte, err e // Note that filename's directory influences which imports can be chosen, // so it is important that filename be accurate. func FixImports(ctx context.Context, filename string, src []byte, goroot string, logf func(string, ...any), source Source) (fixes []*ImportFix, err error) { + if source == nil { + // In case someone adds a defective call from a new place + panic("source is nil") + } ctx, done := event.Start(ctx, "imports.FixImports") defer done() diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go index df94ec8..a05faf9 100644 --- a/vendor/golang.org/x/tools/internal/imports/mod.go +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -166,10 +166,7 @@ func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleRe } } - r.moduleCacheDir = gomodcacheForEnv(goenv) - if r.moduleCacheDir == "" { - return nil, fmt.Errorf("cannot resolve GOMODCACHE") - } + r.moduleCacheDir = goenv["GOMODCACHE"] sort.Slice(r.modsByModPath, func(i, j int) bool { count := func(x int) int { @@ -238,26 +235,6 @@ func newModuleResolver(e *ProcessEnv, moduleCacheCache *DirInfoCache) (*ModuleRe return r, nil } -// gomodcacheForEnv returns the GOMODCACHE value to use based on the given env -// map, which must have GOMODCACHE and GOPATH populated. -// -// TODO(rfindley): this is defensive refactoring. -// 1. Is this even relevant anymore? Can't we just read GOMODCACHE. -// 2. Use this to separate module cache scanning from other scanning. -func gomodcacheForEnv(goenv map[string]string) string { - if gmc := goenv["GOMODCACHE"]; gmc != "" { - // golang/go#67156: ensure that the module cache is clean, since it is - // assumed as a prefix to directories scanned by gopathwalk, which are - // themselves clean. - return filepath.Clean(gmc) - } - gopaths := filepath.SplitList(goenv["GOPATH"]) - if len(gopaths) == 0 { - return "" - } - return filepath.Join(gopaths[0], "/pkg/mod") -} - func (r *ModuleResolver) initAllMods() error { stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-e", "-json", "...") if err != nil { @@ -679,11 +656,11 @@ func modRelevance(mod *gocommand.ModuleJSON) float64 { _, versionString, ok := module.SplitPathVersion(mod.Path) if ok { - index := strings.Index(versionString, "v") - if index == -1 { + _, after, ok := strings.Cut(versionString, "v") + if !ok { return relevance } - if versionNumber, err := strconv.ParseFloat(versionString[index+1:], 64); err == nil { + if versionNumber, err := strconv.ParseFloat(after, 64); err == nil { relevance += versionNumber / 1000 } } diff --git a/vendor/golang.org/x/tools/internal/imports/source_env.go b/vendor/golang.org/x/tools/internal/imports/source_env.go index ec996c3..272ae80 100644 --- a/vendor/golang.org/x/tools/internal/imports/source_env.go +++ b/vendor/golang.org/x/tools/internal/imports/source_env.go @@ -53,7 +53,7 @@ func (s *ProcessEnvSource) ResolveReferences(ctx context.Context, filename strin found := make(map[string][]pkgDistance) callback := &scanCallback{ rootFound: func(gopathwalk.Root) bool { - return true // We want everything. + return true }, dirFound: func(pkg *pkg) bool { return pkgIsCandidate(filename, refs, pkg) diff --git a/vendor/golang.org/x/tools/internal/imports/source_modindex.go b/vendor/golang.org/x/tools/internal/imports/source_modindex.go deleted file mode 100644 index ca745d4..0000000 --- a/vendor/golang.org/x/tools/internal/imports/source_modindex.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package imports - -import ( - "context" - "sync" - "time" - - "golang.org/x/tools/internal/modindex" -) - -// This code is here rather than in the modindex package -// to avoid import loops - -// TODO(adonovan): this code is only used by a test in this package. -// Can we delete it? Or is there a plan to call NewIndexSource from -// cmd/goimports? - -// implements Source using modindex, so only for module cache. -// -// this is perhaps over-engineered. A new Index is read at first use. -// And then Update is called after every 15 minutes, and a new Index -// is read if the index changed. It is not clear the Mutex is needed. -type IndexSource struct { - modcachedir string - mu sync.Mutex - index *modindex.Index // (access via getIndex) - expires time.Time -} - -// create a new Source. Called from NewView in cache/session.go. -func NewIndexSource(cachedir string) *IndexSource { - return &IndexSource{modcachedir: cachedir} -} - -func (s *IndexSource) LoadPackageNames(ctx context.Context, srcDir string, paths []ImportPath) (map[ImportPath]PackageName, error) { - /// This is used by goimports to resolve the package names of imports of the - // current package, which is irrelevant for the module cache. - return nil, nil -} - -func (s *IndexSource) ResolveReferences(ctx context.Context, filename string, missing References) ([]*Result, error) { - index, err := s.getIndex() - if err != nil { - return nil, err - } - var cs []modindex.Candidate - for pkg, nms := range missing { - for nm := range nms { - x := index.Lookup(pkg, nm, false) - cs = append(cs, x...) - } - } - found := make(map[string]*Result) - for _, c := range cs { - var x *Result - if x = found[c.ImportPath]; x == nil { - x = &Result{ - Import: &ImportInfo{ - ImportPath: c.ImportPath, - Name: "", - }, - Package: &PackageInfo{ - Name: c.PkgName, - Exports: make(map[string]bool), - }, - } - found[c.ImportPath] = x - } - x.Package.Exports[c.Name] = true - } - var ans []*Result - for _, x := range found { - ans = append(ans, x) - } - return ans, nil -} - -func (s *IndexSource) getIndex() (*modindex.Index, error) { - s.mu.Lock() - defer s.mu.Unlock() - - // (s.index = nil => s.expires is zero, - // so the first condition is strictly redundant. - // But it makes the postcondition very clear.) - if s.index == nil || time.Now().After(s.expires) { - index, err := modindex.Update(s.modcachedir) - if err != nil { - return nil, err - } - s.index = index - s.expires = index.ValidAt.Add(15 * time.Minute) // (refresh period) - } - // Inv: s.index != nil - - return s.index, nil -} diff --git a/vendor/golang.org/x/tools/internal/modindex/index.go b/vendor/golang.org/x/tools/internal/modindex/index.go index c41d1dd..2938104 100644 --- a/vendor/golang.org/x/tools/internal/modindex/index.go +++ b/vendor/golang.org/x/tools/internal/modindex/index.go @@ -10,7 +10,6 @@ import ( "encoding/csv" "fmt" "io" - "log" "os" "path/filepath" "strconv" @@ -107,14 +106,14 @@ var IndexDir string = func() string { var err error dir, err = os.UserCacheDir() // shouldn't happen, but TempDir is better than - // creating ./go/imports + // creating ./goimports if err != nil { dir = os.TempDir() } } dir = filepath.Join(dir, "goimports") if err := os.MkdirAll(dir, 0777); err != nil { - log.Printf("failed to create modcache index dir: %v", err) + dir = "" // #75505, people complain about the error message } return dir }() @@ -127,6 +126,9 @@ func Read(gomodcache string) (*Index, error) { if err != nil { return nil, err } + if IndexDir == "" { + return nil, os.ErrNotExist + } // Read the "link" file for the specified gomodcache directory. // It names the payload file. @@ -186,6 +188,9 @@ func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) { ) for scan.Scan() { v := scan.Text() + if len(v) < 2 { + return nil, fmt.Errorf("malformed line: %q, %d entries", v, len(entries)) + } if v[0] == ':' { if curEntry != nil { entries = append(entries, *curEntry) @@ -227,6 +232,9 @@ func readIndexFrom(gomodcache string, r io.Reader) (*Index, error) { // write writes the index file and updates the index directory to refer to it. func write(gomodcache string, ix *Index) error { + if IndexDir == "" { + return os.ErrNotExist + } // Write the index into a payload file with a fresh name. f, err := os.CreateTemp(IndexDir, fmt.Sprintf("index-%d-*", CurrentVersion)) if err != nil { diff --git a/vendor/golang.org/x/tools/internal/modindex/modindex.go b/vendor/golang.org/x/tools/internal/modindex/modindex.go index 5fa285d..16a7cab 100644 --- a/vendor/golang.org/x/tools/internal/modindex/modindex.go +++ b/vendor/golang.org/x/tools/internal/modindex/modindex.go @@ -73,7 +73,9 @@ func build(gomodcache string, old *Index) (*Index, bool, error) { // Set the time window. var start time.Time // = dawn of time if old != nil { - start = old.ValidAt + // -1s to accommodate skew between (fine-grained) time.Now + // and (coarse) file system's mtime clock. + start = old.ValidAt.Add(-1 * time.Second) } now := time.Now() end := now.Add(24 * time.Hour) // safely in the future diff --git a/vendor/golang.org/x/tools/internal/moremaps/maps.go b/vendor/golang.org/x/tools/internal/moremaps/maps.go new file mode 100644 index 0000000..a1bae07 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/moremaps/maps.go @@ -0,0 +1,116 @@ +// Copyright 2023 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 moremaps contains more functions for working with maps. +package moremaps + +import ( + "cmp" + "iter" + "maps" + "slices" +) + +// Arbitrary returns an arbitrary (key, value) entry from the map and ok is true, if +// the map is not empty. Otherwise, it returns zero values for K and V, and false. +func Arbitrary[K comparable, V any](m map[K]V) (_ K, _ V, ok bool) { + for k, v := range m { + return k, v, true + } + return +} + +// Group returns a new non-nil map containing the elements of s grouped by the +// keys returned from the key func. +func Group[K comparable, V any](s []V, key func(V) K) map[K][]V { + m := make(map[K][]V) + for _, v := range s { + k := key(v) + m[k] = append(m[k], v) + } + return m +} + +// KeySlice returns the keys of the map M, like slices.Collect(maps.Keys(m)). +func KeySlice[M ~map[K]V, K comparable, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + +// ValueSlice returns the values of the map M, like slices.Collect(maps.Values(m)). +func ValueSlice[M ~map[K]V, K comparable, V any](m M) []V { + r := make([]V, 0, len(m)) + for _, v := range m { + r = append(r, v) + } + return r +} + +// SameKeys reports whether x and y have equal sets of keys. +func SameKeys[K comparable, V1, V2 any](x map[K]V1, y map[K]V2) bool { + ignoreValues := func(V1, V2) bool { return true } + return maps.EqualFunc(x, y, ignoreValues) +} + +// Sorted returns an iterator over the entries of m in key order. +func Sorted[M ~map[K]V, K cmp.Ordered, V any](m M) iter.Seq2[K, V] { + // TODO(adonovan): use maps.Sorted if proposal #68598 is accepted. + return func(yield func(K, V) bool) { + keys := KeySlice(m) + slices.Sort(keys) + for _, k := range keys { + if !yield(k, m[k]) { + break + } + } + } +} + +// SortedFunc returns an iterator over the entries of m in the key order determined by cmp. +func SortedFunc[M ~map[K]V, K comparable, V any](m M, cmp func(x, y K) int) iter.Seq2[K, V] { + // TODO(adonovan): use maps.SortedFunc if proposal #68598 is accepted. + return func(yield func(K, V) bool) { + keys := KeySlice(m) + slices.SortFunc(keys, cmp) + for _, k := range keys { + if !yield(k, m[k]) { + break + } + } + } +} + +// Delete is like delete(m, k) but reports whether deletion occurred. +func Delete[M ~map[K]V, K comparable, V any](m M, k K) bool { + pre := len(m) + delete(m, k) + return pre != len(m) +} + +// Entry is a key-value pair obtained from a map. +type Entry[K comparable, V any] struct { + Key K + Value V +} + +// Entries returns a new unordered array of the entries of a map. +func Entries[M ~map[K]V, K comparable, V any](m M) []Entry[K, V] { + entries := make([]Entry[K, V], 0, len(m)) + for k, v := range m { + entries = append(entries, Entry[K, V]{k, v}) + } + return entries +} + +// FromEntries returns a new map into which the entries have been inserted in order. +func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V { + m := make(map[K]V, len(entries)) + for _, e := range entries { + m[e.Key] = e.Value + } + return m +} diff --git a/vendor/golang.org/x/tools/internal/pkgbits/version.go b/vendor/golang.org/x/tools/internal/pkgbits/version.go index 53af9df..0db9652 100644 --- a/vendor/golang.org/x/tools/internal/pkgbits/version.go +++ b/vendor/golang.org/x/tools/internal/pkgbits/version.go @@ -28,6 +28,15 @@ const ( // - remove derived info "needed" bool V2 + // V3: introduces a more compact format for composite literal element lists + // - negative lengths indicate that (some) elements may have keys + // - positive lengths indicate that no element has a key + // - a negative struct field index indicates an embedded field + V3 + + // V4: encodes generic methods as standalone function objects + V4 + numVersions = iota ) @@ -61,6 +70,12 @@ const ( // whether a type was a derived type. DerivedInfoNeeded + // Composite literals use a more compact format for element lists. + CompactCompLiterals + + // Generic methods may appear as standalone function objects. + GenericMethods + numFields = iota ) @@ -68,6 +83,8 @@ const ( var introduced = [numFields]Version{ Flags: V1, AliasTypeParamNames: V2, + CompactCompLiterals: V3, + GenericMethods: V4, } // removed is the version a field was removed in or 0 for fields diff --git a/vendor/golang.org/x/tools/internal/ssainternal/ssainternal.go b/vendor/golang.org/x/tools/internal/ssainternal/ssainternal.go deleted file mode 100644 index 686c5d9..0000000 --- a/vendor/golang.org/x/tools/internal/ssainternal/ssainternal.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2025 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package ssainternal exposes setters for internals of go/ssa. -// It cannot actually depend on symbols from go/ssa. -package ssainternal - -import "go/types" - -// SetNoReturn sets the predicate used when building the ssa.Program -// prog that reports whether a given function cannot return. -// This may be used to prune spurious control flow edges -// after (e.g.) log.Fatal, improving the precision of analyses. -// -// You must link [golang.org/x/tools/go/ssa] into your application for -// this function to be non-nil. -// -// TODO(adonovan): add (*ssa.Program).SetNoReturn to the public API. -var SetNoReturn = func(prog any, noreturn func(*types.Func) bool) { - panic("golang.org/x/tools/go/ssa not linked into application") -} diff --git a/vendor/golang.org/x/tools/internal/stdlib/deps.go b/vendor/golang.org/x/tools/internal/stdlib/deps.go index f7b9c12..b049e5f 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/deps.go +++ b/vendor/golang.org/x/tools/internal/stdlib/deps.go @@ -12,364 +12,387 @@ type pkginfo struct { } var deps = [...]pkginfo{ - {"archive/tar", "\x03p\x03F=\x01\n\x01$\x01\x01\x02\x05\b\x02\x01\x02\x02\f"}, - {"archive/zip", "\x02\x04f\a\x03\x13\x021=\x01+\x05\x01\x0f\x03\x02\x0e\x04"}, - {"bufio", "\x03p\x86\x01D\x14"}, - {"bytes", "s+[\x03\fG\x02\x02"}, + {"archive/tar", "\x03|\x03F>\x01\n\x01&\x01\x01\x02\x05\b\x02\x01\x02\x02\r"}, + {"archive/zip", "\x02\x04k\x0e\x03\x12\x022>\x01-\x05\x01\x0f\x03\x02\x0f\x04"}, + {"bufio", "\x03|\x87\x01F\x15"}, + {"bytes", "\x7f*]\x03\fJ\x02\x02"}, {"cmp", ""}, - {"compress/bzip2", "\x02\x02\xf5\x01A"}, - {"compress/flate", "\x02q\x03\x83\x01\f\x033\x01\x03"}, - {"compress/gzip", "\x02\x04f\a\x03\x15nT"}, - {"compress/lzw", "\x02q\x03\x83\x01"}, - {"compress/zlib", "\x02\x04f\a\x03\x13\x01o"}, - {"container/heap", "\xbb\x02"}, + {"compress/bzip2", "\x02\x02\x82\x02C"}, + {"compress/flate", "\x02}\x03\x84\x01\f\x034\x02\x03"}, + {"compress/gzip", "\x02\x04k\x0e\x03\x14pW"}, + {"compress/lzw", "\x02}\x03\x84\x01"}, + {"compress/zlib", "\x02\x04k\x0e\x03\x12\x01q"}, + {"container/heap", "\xca\x02"}, {"container/list", ""}, {"container/ring", ""}, - {"context", "s\\p\x01\r"}, - {"crypto", "\x89\x01pC"}, - {"crypto/aes", "\x10\n\t\x99\x02"}, - {"crypto/cipher", "\x03 \x01\x01 \x12\x1c,Z"}, - {"crypto/des", "\x10\x15 .,\x9d\x01\x03"}, - {"crypto/dsa", "E\x04*\x86\x01\r"}, - {"crypto/ecdh", "\x03\v\f\x10\x04\x17\x04\x0e\x1c\x86\x01"}, - {"crypto/ecdsa", "\x0e\x05\x03\x04\x01\x10\b\v\x06\x01\x04\r\x01\x1c\x86\x01\r\x05K\x01"}, - {"crypto/ed25519", "\x0e\x1e\x12\a\v\a\x1c\x86\x01C"}, - {"crypto/elliptic", "3@\x86\x01\r9"}, - {"crypto/fips140", "\"\x05"}, - {"crypto/hkdf", "/\x15\x01.\x16"}, - {"crypto/hmac", "\x1a\x16\x14\x01\x122"}, - {"crypto/internal/boring", "\x0e\x02\rl"}, - {"crypto/internal/boring/bbig", "\x1a\xec\x01M"}, - {"crypto/internal/boring/bcache", "\xc0\x02\x13"}, + {"context", "\x7f]r\x01\x0e"}, + {"crypto", "\x94\x01rE"}, + {"crypto/aes", "\x10\v\v\xa5\x02"}, + {"crypto/cipher", "\x03#\x01\x01 \x13$+\\"}, + {"crypto/des", "\x10\x18 7+\xa1\x01\x03"}, + {"crypto/dsa", "H\x034\x87\x01\r"}, + {"crypto/ecdh", "\x03\v\r\x12\x04\x17\x03\x10$\x87\x01"}, + {"crypto/ecdsa", "\x0e\x05\x03\x05\x01\x12\b\v\x06\x01\x03\x0f\x01$\x87\x01\r\x05O\x01"}, + {"crypto/ed25519", "\x0e!\x12\a\x03\t\a$I>E"}, + {"crypto/elliptic", "6I\x87\x01\r;"}, + {"crypto/fips140", "%\x05\x9e\x01\x9b\x01"}, + {"crypto/hkdf", "2\x15\x017\x15"}, + {"crypto/hmac", "\x1b\x18\x14\x01\x139"}, + {"crypto/hpke", "\x03\v\x02\x03\x04\x01\x0e\x01\x05\x1f\x06\a\x01\x01%\x03\x12\x16\x9f\x01\x1d"}, + {"crypto/internal/boring", "\x0e\x02\x0ev"}, + {"crypto/internal/boring/bbig", "\x1b\xf8\x01P"}, + {"crypto/internal/boring/bcache", "\xcf\x02\x14"}, {"crypto/internal/boring/sig", ""}, {"crypto/internal/constanttime", ""}, - {"crypto/internal/cryptotest", "\x03\r\n\b&\x0f\x19\x06\x13\x12 \x04\x06\t\x19\x01\x11\x11\x1b\x01\a\x05\b\x03\x05\v"}, - {"crypto/internal/entropy", "J"}, - {"crypto/internal/entropy/v1.0.0", "C0\x95\x018\x13"}, - {"crypto/internal/fips140", "B1\xbf\x01\v\x16"}, - {"crypto/internal/fips140/aes", "\x03\x1f\x03\x02\x14\x05\x01\x01\x06+\x95\x014"}, - {"crypto/internal/fips140/aes/gcm", "\"\x01\x02\x02\x02\x12\x05\x01\a+\x92\x01"}, - {"crypto/internal/fips140/alias", "\xd3\x02"}, - {"crypto/internal/fips140/bigmod", "'\x19\x01\a+\x95\x01"}, - {"crypto/internal/fips140/check", "\"\x0e\a\t\x02\xb7\x01Z"}, - {"crypto/internal/fips140/check/checktest", "'\x8b\x02!"}, - {"crypto/internal/fips140/drbg", "\x03\x1e\x01\x01\x04\x14\x05\t\x01)\x86\x01\x0f7\x01"}, - {"crypto/internal/fips140/ecdh", "\x03\x1f\x05\x02\n\r3\x86\x01\x0f7"}, - {"crypto/internal/fips140/ecdsa", "\x03\x1f\x04\x01\x02\a\x03\x06:\x16pF"}, - {"crypto/internal/fips140/ed25519", "\x03\x1f\x05\x02\x04\f:\xc9\x01\x03"}, - {"crypto/internal/fips140/edwards25519", "\x1e\t\a\x123\x95\x017"}, - {"crypto/internal/fips140/edwards25519/field", "'\x14\x053\x95\x01"}, - {"crypto/internal/fips140/hkdf", "\x03\x1f\x05\t\a<\x16"}, - {"crypto/internal/fips140/hmac", "\x03\x1f\x15\x01\x01:\x16"}, - {"crypto/internal/fips140/mldsa", "\x03\x1b\x04\x05\x02\x0e\x01\x03\x053\x95\x017"}, - {"crypto/internal/fips140/mlkem", "\x03\x1f\x05\x02\x0f\x03\x053\xcc\x01"}, - {"crypto/internal/fips140/nistec", "\x1e\t\r\f3\x95\x01*\r\x14"}, - {"crypto/internal/fips140/nistec/fiat", "'\x148\x95\x01"}, - {"crypto/internal/fips140/pbkdf2", "\x03\x1f\x05\t\a<\x16"}, - {"crypto/internal/fips140/rsa", "\x03\x1b\x04\x04\x01\x02\x0e\x01\x01\x028\x16pF"}, - {"crypto/internal/fips140/sha256", "\x03\x1f\x1e\x01\a+\x16\x7f"}, - {"crypto/internal/fips140/sha3", "\x03\x1f\x19\x05\x012\x95\x01K"}, - {"crypto/internal/fips140/sha512", "\x03\x1f\x1e\x01\a+\x16\x7f"}, - {"crypto/internal/fips140/ssh", "'b"}, - {"crypto/internal/fips140/subtle", "\x1e\a\x1b\xc8\x01"}, - {"crypto/internal/fips140/tls12", "\x03\x1f\x05\t\a\x02:\x16"}, - {"crypto/internal/fips140/tls13", "\x03\x1f\x05\b\b\t3\x16"}, - {"crypto/internal/fips140cache", "\xb2\x02\r&"}, + {"crypto/internal/cryptotest", "\x03\r\v\n%\x11\x1a\r\x12\x12!\x04\x06\n\x19\x01\x11\x11\x1d\x01\a\x03\x02\b\x02\x01\x05\f"}, + {"crypto/internal/cryptotest/wycheproof", "\x0e\x12T\x01\f\x01r@\x05\x03\x15\x10"}, + {"crypto/internal/cryptotest/x509limbo", "u\n\x03\xba\x01\x03\x1e"}, + {"crypto/internal/entropy", "M"}, + {"crypto/internal/entropy/v1.0.0", "F9\x96\x01:\x14"}, + {"crypto/internal/fips140", "E:\xc2\x01\v\x17"}, + {"crypto/internal/fips140/aes", "\x03\"\x03\x02\x14\x05\x01\x01\x055\x96\x016"}, + {"crypto/internal/fips140/aes/gcm", "%\x01\x02\x02\x02\x12\x05\x01\x065\x93\x01"}, + {"crypto/internal/fips140/alias", "\xe3\x02"}, + {"crypto/internal/fips140/bigmod", "*\x19\x01\x065\x96\x01"}, + {"crypto/internal/fips140/check", "%\x0e\a\t\x02\xc1\x01]"}, + {"crypto/internal/fips140/check/checktest", "*\x97\x02\""}, + {"crypto/internal/fips140/drbg", "\x03!\x01\x01\x04\x14\x05\n2\x87\x01\x0f9\x01"}, + {"crypto/internal/fips140/ecdh", "\x03\"\x05\x02\n\r<\x87\x01\x0f9"}, + {"crypto/internal/fips140/ecdsa", "\x03\"\x04\x01\x02\a\x03\x06C\x15r\x0f9"}, + {"crypto/internal/fips140/ed25519", "\x03\"\x05\x02\x04\fC\xcc\x01\x03"}, + {"crypto/internal/fips140/edwards25519", "\x1f\v\a\x12<\x96\x019"}, + {"crypto/internal/fips140/edwards25519/field", "*\x14\x05<\x96\x01"}, + {"crypto/internal/fips140/hkdf", "\x03\"\x05\t\aE\x15"}, + {"crypto/internal/fips140/hmac", "\x03\"\x15\x01\x01C\x15"}, + {"crypto/internal/fips140/mldsa", "\x03\x1c\x06\x05\x02\x0e\x01\x03\x05<\x96\x019"}, + {"crypto/internal/fips140/mlkem", "\x03\"\x05\x02\x0f\x03\x05<\xcf\x01"}, + {"crypto/internal/fips140/nistec", "\x1f\v\r\f<\x96\x01,\r\x15"}, + {"crypto/internal/fips140/nistec/fiat", "*\x14A\x96\x01"}, + {"crypto/internal/fips140/pbkdf2", "\x03\"\x05\t\aE\x15"}, + {"crypto/internal/fips140/rsa", "\x03\x1c\x06\x04\x01\x02\x0e\x01\x01\x02A\x15rH"}, + {"crypto/internal/fips140/sha256", "\x03\"\x1e\x01\x065\x15\x81\x01"}, + {"crypto/internal/fips140/sha3", "\x03\"\x19\x05\x01;\x96\x01N"}, + {"crypto/internal/fips140/sha512", "\x03\"\x1e\x01\x065\x15\x81\x01"}, + {"crypto/internal/fips140/ssh", "*j"}, + {"crypto/internal/fips140/subtle", "\x1f\t\x1b\xd2\x01"}, + {"crypto/internal/fips140/tls12", "\x03\"\x05\t\a\x02C\x15"}, + {"crypto/internal/fips140/tls13", "\x03\"\x05\b\b\t<\x15"}, + {"crypto/internal/fips140cache", "\xc1\x02\r."}, {"crypto/internal/fips140deps", ""}, - {"crypto/internal/fips140deps/byteorder", "\x9f\x01"}, - {"crypto/internal/fips140deps/cpu", "\xb4\x01\a"}, - {"crypto/internal/fips140deps/godebug", "\xbc\x01"}, - {"crypto/internal/fips140deps/time", "\xcd\x02"}, - {"crypto/internal/fips140hash", "8\x1d4\xca\x01"}, - {"crypto/internal/fips140only", ")\x0e\x01\x01P3="}, + {"crypto/internal/fips140deps/byteorder", "\xaa\x01"}, + {"crypto/internal/fips140deps/cpu", "\xbf\x01\b"}, + {"crypto/internal/fips140deps/godebug", "\xc8\x01"}, + {"crypto/internal/fips140deps/time", "\xe3\x02"}, + {"crypto/internal/fips140hash", ";\x1e;\xcf\x01"}, + {"crypto/internal/fips140only", "\x17\x15\x0e\x01\x01Xr"}, {"crypto/internal/fips140test", ""}, - {"crypto/internal/hpke", "\x03\v\x01\x01\x03\x055\x03\x04\x01\x01\x16\a\x03\x13\xcc\x01"}, - {"crypto/internal/impl", "\xbd\x02"}, - {"crypto/internal/randutil", "\xf9\x01\x12"}, - {"crypto/internal/sysrand", "sq! \r\r\x01\x01\f\x06"}, - {"crypto/internal/sysrand/internal/seccomp", "s"}, - {"crypto/md5", "\x0e7.\x16\x16i"}, - {"crypto/mlkem", "\x0e$"}, - {"crypto/mlkem/mlkemtest", "2\x1b&"}, - {"crypto/pbkdf2", "5\x0f\x01.\x16"}, - {"crypto/rand", "\x1a\b\a\x1c\x04\x01)\x86\x01\rM"}, - {"crypto/rc4", "% .\xc9\x01"}, - {"crypto/rsa", "\x0e\f\x01\v\x10\x0e\x01\x04\a\a\x1c\x03\x133=\f\x01"}, - {"crypto/sha1", "\x0e\f+\x03+\x16\x16\x15T"}, - {"crypto/sha256", "\x0e\f\x1dR"}, - {"crypto/sha3", "\x0e*Q\xca\x01"}, - {"crypto/sha512", "\x0e\f\x1fP"}, - {"crypto/subtle", "\x1e\x1d\x9f\x01X"}, - {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x02\x01\x01\t\x01\x0e\n\x01\n\x05\x04\x01\x01\x01\x01\x02\x01\x02\x01\x17\x02\x03\x13\x16\x15\b=\x16\x16\r\b\x01\x01\x01\x02\x01\r\x06\x02\x01\x0f"}, - {"crypto/tls/internal/fips140tls", "\x17\xa9\x02"}, - {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x016\x06\x01\x01\x02\x05\x0e\x06\x02\x02\x03F\x03:\x01\x02\b\x01\x01\x02\a\x10\x05\x01\x06\a\b\x02\x01\x02\x0e\x02\x01\x01\x02\x03\x01"}, - {"crypto/x509/pkix", "i\x06\a\x90\x01G"}, - {"database/sql", "\x03\nP\x16\x03\x83\x01\v\a\"\x05\b\x02\x03\x01\r\x02\x02\x02"}, - {"database/sql/driver", "\rf\x03\xb7\x01\x0f\x11"}, - {"debug/buildinfo", "\x03]\x02\x01\x01\b\a\x03g\x1a\x02\x01+\x0f\x1f"}, - {"debug/dwarf", "\x03i\a\x03\x83\x011\x11\x01\x01"}, - {"debug/elf", "\x03\x06V\r\a\x03g\x1b\x01\f \x17\x01\x16"}, - {"debug/gosym", "\x03i\n\xc5\x01\x01\x01\x02"}, - {"debug/macho", "\x03\x06V\r\ng\x1c,\x17\x01"}, - {"debug/pe", "\x03\x06V\r\a\x03g\x1c,\x17\x01\x16"}, - {"debug/plan9obj", "l\a\x03g\x1c,"}, - {"embed", "s+B\x19\x01S"}, + {"crypto/internal/impl", "\xcc\x02"}, + {"crypto/internal/rand", "\x1b\x11 |>]"}, + {"crypto/internal/randutil", "\x86\x02\x12"}, + {"crypto/internal/sysrand", "\x7fr!\"\r\r\x01\x01\r\x06"}, + {"crypto/internal/sysrand/internal/seccomp", "\x7f"}, + {"crypto/md5", "\x0e:7\x15\x16k"}, + {"crypto/mldsa", "\x0e&K\x87\x01"}, + {"crypto/mlkem", "\x0e'"}, + {"crypto/mlkem/mlkemtest", "5\x13\t."}, + {"crypto/pbkdf2", "8\x0f\x017\x15"}, + {"crypto/rand", "\x1b\x11\x1c\x034\x87\x01\rP"}, + {"crypto/rc4", "( 7\xcc\x01"}, + {"crypto/rsa", "\x0e\r\x01\r\x10\x0e\x01\x03\t\a$\x03\x124>\f\x01"}, + {"crypto/sha1", "\x0e\r-\x025\x15\x16\x15V"}, + {"crypto/sha256", "\x0e\r\x1fZ"}, + {"crypto/sha3", "\x0e-Y\xcf\x01"}, + {"crypto/sha512", "\x0e\r!X"}, + {"crypto/subtle", "\x1f\x1f\xa9\x01|"}, + {"crypto/tls", "\x03\b\x02\x01\x01\x01\x01\x02\x01\x01\x01\x01\x01\x01\x01\x01\v\x01\x18\x01\x0f\x01\x01\x03\x01\x01\x01\x01\x02\x01\x02\x01\x1f\x02\x03\x12\x16\x15\t>\x16\x18\r\b\x01\x01\x01\x02\x01\x0e\x06\x03\x01\x15"}, + {"crypto/tls/internal/fips140tls", "\x17\xb8\x02"}, + {"crypto/x509", "\x03\v\x01\x01\x01\x01\x01\x01\x01\x01\x018\x01\x06\x01\x01\x02\x05\x0f\x06\t\x02\x03F\x03;\x01\x02\b\x01\x01\x02\a\x12\x05\x01\x06\a\b\x02\x01\x02\x0f\x02\x01\x01\x02\x04\x01"}, + {"crypto/x509/pkix", "n\x06\x0e\x91\x019\x11"}, + {"database/sql", "\x03\nT\x01\x1d\x03\x84\x01\v\a$\x05\b\x02\x03\x01\x0e\x02\x02\x02\x01"}, + {"database/sql/driver", "\rU\x1d\x03\xba\x01\x0f\x12\a"}, + {"database/sql/internal", ""}, + {"debug/buildinfo", "\x03b\x02\x01\x01\b\x0e\x03h\x1a\x02\x01-\x0f "}, + {"debug/dwarf", "\x03n\x0e\x03\x84\x013\x11\x01\x01"}, + {"debug/elf", "\x03\x06[\r\x0e\x03h\x1b\x01\f\"\x17\x01\x17"}, + {"debug/gosym", "\x03n\x11#\xa5\x01\x01\x01\x02"}, + {"debug/macho", "\x03\x06[\r\x11h\x1c.\x17\x01"}, + {"debug/pe", "\x03\x06[\r\x0e\x03h\x1c.\x17\x01\x17"}, + {"debug/plan9obj", "q\x0e\x03h\x1c."}, + {"embed", "\x7f*D\x19\x01V"}, {"embed/internal/embedtest", ""}, {"encoding", ""}, - {"encoding/ascii85", "\xf9\x01C"}, - {"encoding/asn1", "\x03p\x03g(\x01'\r\x02\x01\x10\x03\x01"}, - {"encoding/base32", "\xf9\x01A\x02"}, - {"encoding/base64", "\x9f\x01ZA\x02"}, - {"encoding/binary", "s\x86\x01\f(\r\x05"}, - {"encoding/csv", "\x02\x01p\x03\x83\x01D\x12\x02"}, - {"encoding/gob", "\x02e\x05\a\x03g\x1c\v\x01\x03\x1d\b\x12\x01\x0f\x02"}, - {"encoding/hex", "s\x03\x83\x01A\x03"}, - {"encoding/json", "\x03\x01c\x04\b\x03\x83\x01\f(\r\x02\x01\x02\x10\x01\x01\x02"}, - {"encoding/pem", "\x03h\b\x86\x01A\x03"}, - {"encoding/xml", "\x02\x01d\f\x03\x83\x014\x05\n\x01\x02\x10\x02"}, - {"errors", "\xcf\x01\x84\x01"}, - {"expvar", "pLA\b\v\x15\r\b\x02\x03\x01\x11"}, - {"flag", "g\f\x03\x83\x01,\b\x05\b\x02\x01\x10"}, - {"fmt", "sF'\x19\f \b\r\x02\x03\x12"}, - {"go/ast", "\x03\x01r\x0f\x01s\x03)\b\r\x02\x01\x12\x02"}, - {"go/build", "\x02\x01p\x03\x01\x02\x02\b\x02\x01\x17\x1f\x04\x02\b\x1c\x13\x01+\x01\x04\x01\a\b\x02\x01\x12\x02\x02"}, - {"go/build/constraint", "s\xc9\x01\x01\x12\x02"}, - {"go/constant", "v\x10\x7f\x01\x024\x01\x02\x12"}, - {"go/doc", "\x04r\x01\x05\n=61\x10\x02\x01\x12\x02"}, - {"go/doc/comment", "\x03s\xc4\x01\x01\x01\x01\x12\x02"}, - {"go/format", "\x03s\x01\f\x01\x02sD"}, - {"go/importer", "x\a\x01\x02\x04\x01r9"}, - {"go/internal/gccgoimporter", "\x02\x01]\x13\x03\x04\f\x01p\x02,\x01\x05\x11\x01\f\b"}, - {"go/internal/gcimporter", "\x02t\x10\x010\x05\r0,\x15\x03\x02"}, - {"go/internal/scannerhooks", "\x86\x01"}, - {"go/internal/srcimporter", "v\x01\x01\v\x03\x01r,\x01\x05\x12\x02\x14"}, - {"go/parser", "\x03p\x03\x01\x02\b\x04\x01s\x01+\x06\x12"}, - {"go/printer", "v\x01\x02\x03\ns\f \x15\x02\x01\x02\v\x05\x02"}, - {"go/scanner", "\x03s\v\x05s2\x10\x01\x13\x02"}, - {"go/token", "\x04r\x86\x01>\x02\x03\x01\x0f\x02"}, - {"go/types", "\x03\x01\x06i\x03\x01\x03\t\x03\x024\x063\x04\x03\t \x06\a\b\x01\x01\x01\x02\x01\x0f\x02\x02"}, - {"go/version", "\xc1\x01|"}, - {"hash", "\xf9\x01"}, - {"hash/adler32", "s\x16\x16"}, - {"hash/crc32", "s\x16\x16\x15\x8b\x01\x01\x13"}, - {"hash/crc64", "s\x16\x16\xa0\x01"}, - {"hash/fnv", "s\x16\x16i"}, - {"hash/maphash", "\x89\x01\x11<}"}, - {"html", "\xbd\x02\x02\x12"}, - {"html/template", "\x03m\x06\x19-=\x01\n!\x05\x01\x02\x03\f\x01\x02\f\x01\x03\x02"}, - {"image", "\x02q\x1fg\x0f4\x03\x01"}, + {"encoding/ascii85", "\x86\x02E"}, + {"encoding/asn1", "\x03|\x03h(\x01)\r\x02\x01\x11\x03\x01"}, + {"encoding/base32", "\x86\x02C\x02"}, + {"encoding/base64", "\xaa\x01\\C\x02"}, + {"encoding/binary", "\x7f\x87\x01\f*\r\x05"}, + {"encoding/csv", "\x02\x01|\x03\x84\x01F\x13\x02"}, + {"encoding/gob", "\x02j\x05\x0e\x03h\x1c\v\x01\x03\x1f\b\x12\x01\x10\x02"}, + {"encoding/hex", "\x7f\x03\x84\x01C\x03"}, + {"encoding/json", "\x03\x01h\n\x01\x01\x02\x01\x01\x03\x03\x84\x016\x0f\x01"}, + {"encoding/json/internal", "\x7f"}, + {"encoding/json/internal/jsonflags", "v"}, + {"encoding/json/internal/jsonopts", "v\x01"}, + {"encoding/json/internal/jsontest", "\x03g\x15\x03\x83\x01\x01\x012\b\b\x03\x02\x0f"}, + {"encoding/json/internal/jsonwire", "\x04s\b\x87\x01\f7\x02\x01\x13\x01\x01"}, + {"encoding/json/jsontext", "\x03s\x01\x01\x02\x05\x87\x01\x03\t\x034\x02\x01\x02\x13"}, + {"encoding/json/v2", "\x03\x01h\x03\x01\x01\x03\x02\x01\x01\x02\x01\x04\x03\x84\x01\f\x03'\r\x02\x01\x02\x0f\x02\x02"}, + {"encoding/pem", "\x03m\x0f\x87\x01C\x03"}, + {"encoding/xml", "\x02\x01i\x13\x03\x84\x016\x05\n\x01\x02\x11\x02"}, + {"errors", "\xdc\x01\x87\x01"}, + {"expvar", "uSB\b\v\x17\r\b\x02\x03\x01\x12"}, + {"flag", "l\x13\x03\x84\x01.\b\x05\b\x02\x01\x11"}, + {"fmt", "\x7fE)\x19\f\"\b\r\x02\x03\x13"}, + {"go/ast", "\x03\x01~\x0e\x01u\x03+\b\r\x02\x01\x13\x02"}, + {"go/build", "\x02\x01|\x03\x01\x02\x02\a\x02\x01\x17 \x04\x02\t\x1c\x13\x01-\x01\x04\x01\a\b\x02\x01\x13\x02\x02"}, + {"go/build/constraint", "\x7f\xcc\x01\x01\x13\x02"}, + {"go/constant", "\x82\x01\x0f\x81\x01\x01\x026\x01\x02\x13"}, + {"go/doc", "\x04~\x01\x05\t>73\x10\x02\x01\x13\x02"}, + {"go/doc/comment", "\x03\x7f\xc7\x01\x01\x01\x01\x13\x02"}, + {"go/format", "\x03\x7f\x01\v\x01\x02uF"}, + {"go/importer", "\x84\x01\a\x01\x01\x04\x01t;"}, + {"go/internal/gccgoimporter", "\x02\x01b\x1a\x03\x04\v\x01r\x02.\x01\x05\x11\x01\r\b"}, + {"go/internal/gcimporter", "\x02\x80\x01\x0f\x010\x140.\x15\x03\x02"}, + {"go/internal/srcimporter", "\x82\x01\x01\x01\n\x03\x01t.\x01\x05\x12\x02\x15"}, + {"go/parser", "\x03|\x03\x01\x02\v\x01u\x01-\x06\x12"}, + {"go/printer", "\x82\x01\x01\x02\x03\tu\f\"\x15\x02\x01\x02\f\x05\x02"}, + {"go/scanner", "\x03\x7f\x0fu4\x10\x01\x14\x02"}, + {"go/token", "\x04~\x87\x01@\x02\x03\x01\x10\x02"}, + {"go/types", "\x03\x01\x06u\x03\x01\x03\b\x03\x02\x0654\x04\x03\t\"\x06\a\b\x01\x01\x01\x02\x01\x10\x02\x02"}, + {"go/version", "\xcd\x01\x7f"}, + {"hash", "\x86\x02"}, + {"hash/adler32", "\x7f\x15\x16"}, + {"hash/crc32", "\x7f\x15\x16\x15\x8f\x01\x01\x14"}, + {"hash/crc64", "\x7f\x15\x16\xa4\x01"}, + {"hash/fnv", "\x7f\x15\x16k"}, + {"hash/maphash", "\x94\x01\x11>\x80\x01"}, + {"html", "\xcc\x02\x02\x13"}, + {"html/template", "\x03r\r\x18.>\x01\n#\x05\x01\x02\x03\n\x02\x01\x02\r\x01\x03\x02"}, + {"image", "\x02}\x1ei\x0f6\x03\x01"}, {"image/color", ""}, - {"image/color/palette", "\x92\x01"}, - {"image/draw", "\x91\x01\x01\x04"}, - {"image/gif", "\x02\x01\x05k\x03\x1b\x01\x01\x01\vZ\x0f"}, - {"image/internal/imageutil", "\x91\x01"}, - {"image/jpeg", "\x02q\x1e\x01\x04c"}, - {"image/png", "\x02\ac\n\x13\x02\x06\x01gC"}, - {"index/suffixarray", "\x03i\a\x86\x01\f+\n\x01"}, - {"internal/abi", "\xbb\x01\x98\x01"}, - {"internal/asan", "\xd3\x02"}, - {"internal/bisect", "\xb2\x02\r\x01"}, - {"internal/buildcfg", "vHg\x06\x02\x05\n\x01"}, - {"internal/bytealg", "\xb4\x01\x9f\x01"}, + {"image/color/palette", "\x9d\x01"}, + {"image/draw", "\x9c\x01\x01\x04"}, + {"image/gif", "\x02\x01\x05w\x03\x1a\x01\x01\x01\v\\\x0f"}, + {"image/internal/imageutil", "\x9c\x01"}, + {"image/jpeg", "\x02}\x1d\x01\x04e"}, + {"image/png", "\x02\ah\x11\x12\x02\x06\x01iE"}, + {"index/suffixarray", "\x03n\x0e\x87\x01\f-\n\x01"}, + {"internal/abi", "\xc7\x01\x9c\x01"}, + {"internal/asan", "\xe3\x02"}, + {"internal/bisect", "\xc1\x02\r\x01"}, + {"internal/buildcfg", "\x82\x01Hj\x06\x02\x05\n\x01"}, + {"internal/bytealg", "\xbf\x01\xa4\x01"}, {"internal/byteorder", ""}, {"internal/cfg", ""}, - {"internal/cgrouptest", "v[T\x06\x0f\x02\x01\x04\x01"}, - {"internal/chacha8rand", "\x9f\x01\x15\a\x98\x01"}, + {"internal/cgrouptest", "\x82\x01\\V\x06\x0f\x02\x01\x04\x01"}, + {"internal/chacha8rand", "\xaa\x01\x15\b\x9c\x01"}, {"internal/copyright", ""}, {"internal/coverage", ""}, {"internal/coverage/calloc", ""}, - {"internal/coverage/cfile", "p\x06\x17\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01\"\x02',\x06\a\n\x01\x03\r\x06"}, - {"internal/coverage/cformat", "\x04r.\x04Q\v6\x01\x02\r"}, - {"internal/coverage/cmerge", "v.a"}, - {"internal/coverage/decodecounter", "l\n.\v\x02H,\x17\x17"}, - {"internal/coverage/decodemeta", "\x02j\n\x17\x17\v\x02H,"}, - {"internal/coverage/encodecounter", "\x02j\n.\f\x01\x02F\v!\x15"}, - {"internal/coverage/encodemeta", "\x02\x01i\n\x13\x04\x17\r\x02F,."}, - {"internal/coverage/pods", "\x04r.\x81\x01\x06\x05\n\x02\x01"}, - {"internal/coverage/rtcov", "\xd3\x02"}, - {"internal/coverage/slicereader", "l\n\x83\x01Z"}, - {"internal/coverage/slicewriter", "v\x83\x01"}, - {"internal/coverage/stringtab", "v9\x04F"}, + {"internal/coverage/cfile", "u\r\x16\x17\x01\x02\x01\x01\x01\x01\x01\x01\x01$\x02'.\x06\a\n\x01\x03\x0e\x06"}, + {"internal/coverage/cformat", "\x04~-\x04S\v8\x01\x02\x0e"}, + {"internal/coverage/cmerge", "\x82\x01-c"}, + {"internal/coverage/decodecounter", "q\x11-\v\x02J.\x17\x18"}, + {"internal/coverage/decodemeta", "\x02o\x11\x16\x17\v\x02J."}, + {"internal/coverage/encodecounter", "\x02o\x11-\f\x01\x02H\v#\x15"}, + {"internal/coverage/encodemeta", "\x02\x01n\x11\x12\x04\x17\r\x02H./"}, + {"internal/coverage/pods", "\x04~-\x85\x01\x06\x05\n\x02\x01"}, + {"internal/coverage/rtcov", "\xe3\x02"}, + {"internal/coverage/slicereader", "q\x11\x84\x01]"}, + {"internal/coverage/slicewriter", "\x82\x01\x84\x01"}, + {"internal/coverage/stringtab", "\x82\x018\x04H"}, {"internal/coverage/test", ""}, {"internal/coverage/uleb128", ""}, - {"internal/cpu", "\xd3\x02"}, - {"internal/dag", "\x04r\xc4\x01\x03"}, - {"internal/diff", "\x03s\xc5\x01\x02"}, - {"internal/exportdata", "\x02\x01p\x03\x02e\x1c,\x01\x05\x11\x01\x02"}, - {"internal/filepathlite", "s+B\x1a@"}, - {"internal/fmtsort", "\x04\xa9\x02\r"}, - {"internal/fuzz", "\x03\nG\x18\x04\x03\x03\x01\f\x036=\f\x03\x1d\x01\x05\x02\x05\n\x01\x02\x01\x01\f\x04\x02"}, + {"internal/cpu", "\xe3\x02"}, + {"internal/dag", "\x04~\xc7\x01\x03"}, + {"internal/diff", "\x03\x7f\xc8\x01\x02"}, + {"internal/exportdata", "\x02\x01|\x03\x02f\x1c.\x01\x05\x11\x01\x02"}, + {"internal/filepathlite", "\x7f*D\x1aB"}, + {"internal/fmtsort", "\x04\xb8\x02\r"}, + {"internal/fuzz", "\x03\nK\x19\x04\n\x03\x01\v\x037>\f\x03\x1f\x01\x05\x02\x05\n\x01\x02\x01\x01\r\x04\x02"}, + {"internal/gate", "\r"}, {"internal/goarch", ""}, - {"internal/godebug", "\x9c\x01!\x82\x01\x01\x13"}, + {"internal/godebug", "\xa7\x01\"\x85\x01\x01\x14"}, {"internal/godebugs", ""}, {"internal/goexperiment", ""}, {"internal/goos", ""}, - {"internal/goroot", "\xa5\x02\x01\x05\x12\x02"}, + {"internal/goroot", "\xb4\x02\x01\x05\x12\x02"}, {"internal/gover", "\x04"}, {"internal/goversion", ""}, - {"internal/lazyregexp", "\xa5\x02\v\r\x02"}, - {"internal/lazytemplate", "\xf9\x01,\x18\x02\f"}, - {"internal/msan", "\xd3\x02"}, + {"internal/lazyregexp", "\xb4\x02\v\r\x02"}, + {"internal/lazytemplate", "\x86\x02.\x18\x02\r"}, + {"internal/msan", "\xe3\x02"}, + {"internal/nettest", "\x03\nrG@\f\n\x12\x06\x15\x05\x0f"}, {"internal/nettrace", ""}, - {"internal/obscuretestdata", "k\x8e\x01,"}, - {"internal/oserror", "s"}, - {"internal/pkgbits", "\x03Q\x18\a\x03\x04\fs\r\x1f\r\n\x01"}, + {"internal/obscuretestdata", "p\x96\x01."}, + {"internal/oserror", "\x7f"}, + {"internal/pkgbits", "\x03U\x19\x0e\x03\x04\vu\r!\r\n\x01"}, {"internal/platform", ""}, - {"internal/poll", "sl\x05\x159\r\x01\x01\f\x06"}, - {"internal/profile", "\x03\x04l\x03\x83\x017\n\x01\x01\x01\x10"}, + {"internal/poll", "\x7fm\x05\x15;\r\x01\x01\r\x06"}, + {"internal/profile", "\x03\x04x\x03\x84\x019\n\x01\x01\x01\x11"}, {"internal/profilerecord", ""}, - {"internal/race", "\x9a\x01\xb9\x01"}, - {"internal/reflectlite", "\x9a\x01!;\""}, + {"internal/runtime/atomic", "\xc7\x01\x9c\x01"}, + {"internal/runtime/cgroup", "\xa9\x01?\x04w"}, + {"internal/runtime/exithook", "\xdd\x01\x86\x01"}, + {"internal/runtime/gc", "\xc7\x01"}, + {"internal/runtime/gc/internal/gen", "\ng\x11\x17m\x04\v\x1f\b\x10\x02"}, + {"internal/runtime/gc/scan", "\xbf\x01\b\x19\a|"}, + {"internal/runtime/maps", "\xa5\x01\x01\x04\x15\b\x03\a\n\t\x03.N"}, + {"internal/runtime/math", "\xc7\x01"}, {"internal/runtime/pprof/label", ""}, {"internal/runtime/startlinetest", ""}, - {"internal/runtime/sys", "\xbb\x01\x04"}, - {"internal/runtime/syscall/linux", "\xbb\x01\x98\x01"}, + {"internal/runtime/sys", "\xc7\x01\x04"}, + {"internal/runtime/syscall/linux", "\xc7\x01\x9c\x01"}, {"internal/runtime/wasitest", ""}, - {"internal/saferio", "\xf9\x01Z"}, - {"internal/singleflight", "\xbf\x02"}, - {"internal/strconv", "\x88\x02K"}, - {"internal/stringslite", "\x9e\x01\xb5\x01"}, - {"internal/sync", "\x9a\x01!\x13r\x13"}, - {"internal/synctest", "\x9a\x01\xb9\x01"}, - {"internal/syscall/execenv", "\xc1\x02"}, - {"internal/syscall/unix", "\xb2\x02\x0e\x01\x12"}, - {"internal/sysinfo", "\x02\x01\xb1\x01E,\x18\x02"}, + {"internal/saferio", "\x86\x02]"}, + {"internal/singleflight", "\xce\x02"}, + {"internal/strconv", "\x95\x02N"}, + {"internal/stringslite", "\xa9\x01\xba\x01"}, + {"internal/sync", "\xa5\x01\"\x14t\x14"}, + {"internal/synctest", "\xa5\x01\xbe\x01"}, + {"internal/syscall/execenv", "\xd0\x02"}, + {"internal/syscall/unix", "\xec\x01U\x0e\x01\x13"}, + {"internal/sysinfo", "\x02\x01\xbc\x01G.\x18\x02"}, {"internal/syslist", ""}, - {"internal/testenv", "\x03\nf\x02\x01*\x1b\x0f0+\x01\x05\a\n\x01\x02\x02\x01\v"}, - {"internal/testhash", "\x03\x86\x01p\x118\v"}, - {"internal/testlog", "\xbf\x02\x01\x13"}, - {"internal/testpty", "s\x03\xaf\x01"}, - {"internal/trace", "\x02\x01\x01\x06b\a\x03w\x03\x03\x06\x03\t5\x01\x01\x01\x10\x06"}, - {"internal/trace/internal/testgen", "\x03i\nu\x03\x02\x03\x011\v\r\x10"}, - {"internal/trace/internal/tracev1", "\x03\x01h\a\x03}\x06\f5\x01"}, - {"internal/trace/raw", "\x02j\nz\x03\x06C\x01\x12"}, - {"internal/trace/testtrace", "\x02\x01p\x03q\x04\x03\x05\x01\x05,\v\x02\b\x02\x01\x05"}, + {"internal/testenv", "\x03\nr\x02\x01)\x1c\x100-\x01\x05\a\n\x01\x02\x02\x01\f"}, + {"internal/testhash", "\x03\x91\x01r\x11:\f"}, + {"internal/testlog", "\xce\x02\x01\x14"}, + {"internal/testpty", "\x7f\x03\xb2\x01"}, + {"internal/trace", "\x02\x01\x01\x06g\x0e\x03x\x03\x03\x06\x03\t-\n\x01\x01\x01\x11\x06"}, + {"internal/trace/internal/testgen", "\x03n\x11v\x03\x02\x03\x013\v\r\x11"}, + {"internal/trace/internal/tracev1", "\x03\x01m\x0e\x03~\x06\f7\x01"}, + {"internal/trace/raw", "\x02o\x11{\x03\x06E\x01\x13"}, + {"internal/trace/testtrace", "\x02\x01|\x03r\x04\x03\x05\x01\x05.\v\x02\b\x02\x01\x05"}, {"internal/trace/tracev2", ""}, - {"internal/trace/traceviewer", "\x02c\v\x06\x1a<\x1f\a\a\x04\b\v\x15\x01\x05\a\n\x01\x02\x0e"}, + {"internal/trace/traceviewer", "\x02h\v\r\x19>\x1f\a\a\x04\b\v\x17\x01\x05\a\n\x01\x02\x0f"}, {"internal/trace/traceviewer/format", ""}, - {"internal/trace/version", "vz\t"}, - {"internal/txtar", "\x03s\xaf\x01\x18"}, - {"internal/types/errors", "\xbc\x02"}, - {"internal/unsafeheader", "\xd3\x02"}, - {"internal/xcoff", "_\r\a\x03g\x1c,\x17\x01"}, - {"internal/zstd", "l\a\x03\x83\x01\x0f"}, - {"io", "s\xcc\x01"}, - {"io/fs", "s+*11\x10\x13\x04"}, - {"io/ioutil", "\xf9\x01\x01+\x15\x03"}, - {"iter", "\xce\x01d!"}, - {"log", "v\x83\x01\x05'\r\r\x01\r"}, + {"internal/trace/version", "\x82\x01{\t"}, + {"internal/txtar", "\x03\x7f\xb2\x01\x18"}, + {"internal/types/errors", "\xcb\x02"}, + {"internal/unsafeheader", "\xe3\x02"}, + {"internal/xcoff", "d\r\x0e\x03h\x1c.\x17\x01"}, + {"internal/zstd", "q\x0e\x03\x84\x01\x0f"}, + {"io", "\x7f\xcf\x01"}, + {"io/fs", "\x7f*,13\x10\x14\x04"}, + {"io/ioutil", "\x86\x02\x01-\x15\x03"}, + {"iter", "\xdb\x01f\""}, + {"log", "\x82\x01\x84\x01\x05)\r\r\x01\x0e"}, {"log/internal", ""}, - {"log/slog", "\x03\nZ\t\x03\x03\x83\x01\x04\x01\x02\x02\x03(\x05\b\x02\x01\x02\x01\r\x02\x02\x02"}, + {"log/slog", "\x03\n_\t\n\x03H<\x04\x01\x02\x02\x03*\x05\b\x02\x01\x02\x01\x0e\x02\x02\x02"}, {"log/slog/internal", ""}, - {"log/slog/internal/benchmarks", "\rf\x03\x83\x01\x06\x03:\x11"}, - {"log/slog/internal/buffer", "\xbf\x02"}, - {"log/syslog", "s\x03\x87\x01\x12\x16\x18\x02\x0e"}, - {"maps", "\xfc\x01W"}, - {"math", "\xb4\x01TK"}, - {"math/big", "\x03p\x03)\x15E\f\x03\x020\x02\x01\x02\x14"}, - {"math/big/internal/asmgen", "\x03\x01r\x92\x012\x03"}, - {"math/bits", "\xd3\x02"}, - {"math/cmplx", "\x85\x02\x03"}, - {"math/rand", "\xbc\x01I:\x01\x13"}, - {"math/rand/v2", "s,\x03c\x03K"}, - {"mime", "\x02\x01h\b\x03\x83\x01\v!\x15\x03\x02\x10\x02"}, - {"mime/multipart", "\x02\x01M#\x03F=\v\x01\a\x02\x15\x02\x06\x0f\x02\x01\x16"}, - {"mime/quotedprintable", "\x02\x01s\x83\x01"}, - {"net", "\x04\tf+\x1e\n\x05\x13\x01\x01\x04\x15\x01%\x06\r\b\x05\x01\x01\f\x06\a"}, - {"net/http", "\x02\x01\x03\x01\x04\x02C\b\x13\x01\a\x03F=\x01\x03\a\x01\x03\x02\x02\x01\x02\x06\x02\x01\x01\n\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\r\x02\x02\x02\b\x01\x01\x01"}, - {"net/http/cgi", "\x02V\x1b\x03\x83\x01\x04\a\v\x01\x13\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x10\x0e"}, - {"net/http/cookiejar", "\x04o\x03\x99\x01\x01\b\a\x05\x16\x03\x02\x0e\x04"}, - {"net/http/fcgi", "\x02\x01\n_\a\x03\x83\x01\x16\x01\x01\x14\x18\x02\x0e"}, - {"net/http/httptest", "\x02\x01\nK\x02\x1b\x01\x83\x01\x04\x12\x01\n\t\x02\x17\x01\x02\x0e\x0e"}, - {"net/http/httptrace", "\rKnI\x14\n "}, - {"net/http/httputil", "\x02\x01\nf\x03\x83\x01\x04\x0f\x03\x01\x05\x02\x01\v\x01\x19\x02\x01\r\x0e"}, - {"net/http/internal", "\x02\x01p\x03\x83\x01"}, - {"net/http/internal/ascii", "\xbd\x02\x12"}, - {"net/http/internal/httpcommon", "\rf\x03\x9f\x01\x0e\x01\x17\x01\x01\x02\x1c\x02"}, - {"net/http/internal/testcert", "\xbd\x02"}, - {"net/http/pprof", "\x02\x01\ni\x19-\x02\x0e-\x04\x13\x14\x01\r\x04\x03\x01\x02\x01\x10"}, + {"log/slog/internal/benchmarks", "\rr\x03\x84\x01\x06\x03<\x12"}, + {"log/slog/internal/buffer", "\xce\x02"}, + {"log/syslog", "\x7f\x03\x88\x01\x12\x18\x18\x02\x0f"}, + {"maps", "\x89\x02Z"}, + {"math", "\xbf\x01VN"}, + {"math/big", "\x03|\x03(\x15G\f\x03\x022\x02\x01\x02\x15"}, + {"math/big/internal/asmgen", "\x03\x01~\x93\x014\x03"}, + {"math/bits", "\xe3\x02"}, + {"math/cmplx", "\x92\x02\x03"}, + {"math/rand", "\xc8\x01J<\x01\x14"}, + {"math/rand/v2", "\x7f+\x03e\x03N"}, + {"mime", "\x02\x01m\x0f\x03\x84\x01\v#\x15\x03\x02\x11\x02"}, + {"mime/multipart", "\x02\x01Q+\x03F>\v\x01\a\x02\x17\x02\x06\x0f\x02\x01\x17"}, + {"mime/quotedprintable", "\x02\x01\x7f\x84\x01"}, + {"net", "\x04\tr*\x1f\v\x05\x13\x01\x01\x04\x15\x01'\x06\r\b\x05\x01\x01\r\x06\t"}, + {"net/http", "\x02\x01\x03\x01\x04\x02O\x14\x0f\x03F>\x01\x03\a\x01\x06\x01\x01\x02\x06\x02\x01\x01\f\x01\x01\x05\x01\x02\x05\b\x01\x01\x01\x02\x01\x0e\x02\x02\x02\n\x01\x03"}, + {"net/http/cgi", "\x02Z#\x03\x84\x01\x04\a\v\x01\x15\x01\x01\x01\x04\x01\x05\x02\b\x02\x01\x11\x10"}, + {"net/http/cookiejar", "\x04{\x03\x9a\x01\x01\b\t\x05\x16\x03\x02\x0f\x04"}, + {"net/http/fcgi", "\x02\x01\nd\x0e\x03\x84\x01\x16\x01\x01\x16\x18\x02\x0f"}, + {"net/http/httptest", "\x02\x01\nO\x02#\x01P4\x04\x12\x01\f\t\x02\r\n\x01\x02\x03\f\x06\n"}, + {"net/http/httptrace", "\rOwI\x16+"}, + {"net/http/httputil", "\x02\x01\nr\x03F>\x04\x0f\x03\x01\x05\x02\x01\r\x01\x19\x02\x0f\x10"}, + {"net/http/internal", "\x02\x01n\x0e\x03\x84\x01"}, + {"net/http/internal/ascii", "\xcc\x02\x13"}, + {"net/http/internal/http2", "\x02\x01\x03\x01\x06G\b\x15\x0e\x03\x84\x01\x01\x03\b\x03\x02\x03\x02\x06\x02\x03\x01\n\x01\x01\b\x05\b\x02\x01\x02\x01\x0e\x10\x02\x02"}, + {"net/http/internal/httpcommon", "\rr\x03\xa0\x01\x10\x01\x17\x01\x01\x02\x1f\x02"}, + {"net/http/internal/httpsfv", "\xc9\x02\x02\x01\x11\x04"}, + {"net/http/internal/testcert", "\xcc\x02"}, + {"net/http/pprof", "\x02\x01\nu\x18.\x11-\x04\x13\x16\x01\r\x04\x03\x01\x02\x01\x11"}, {"net/internal/cgotest", ""}, - {"net/internal/socktest", "v\xc9\x01\x02"}, - {"net/mail", "\x02q\x03\x83\x01\x04\x0f\x03\x14\x1a\x02\x0e\x04"}, - {"net/netip", "\x04o+\x01f\x034\x16"}, - {"net/rpc", "\x02l\x05\x03\x10\ni\x04\x12\x01\x1d\r\x03\x02"}, - {"net/rpc/jsonrpc", "p\x03\x03\x83\x01\x16\x11\x1f"}, - {"net/smtp", "\x193\f\x13\b\x03\x83\x01\x16\x14\x1a"}, - {"net/textproto", "\x02\x01p\x03\x83\x01\f\n-\x01\x02\x14"}, - {"net/url", "s\x03Fc\v\x10\x02\x01\x16"}, - {"os", "s+\x01\x19\x03\x10\x14\x01\x03\x01\x05\x10\x018\b\x05\x01\x01\f\x06"}, - {"os/exec", "\x03\nfI'\x01\x15\x01+\x06\a\n\x01\x04\f"}, - {"os/exec/internal/fdtest", "\xc1\x02"}, - {"os/signal", "\r\x98\x02\x15\x05\x02"}, - {"os/user", "\x02\x01p\x03\x83\x01,\r\n\x01\x02"}, - {"path", "s+\xb3\x01"}, - {"path/filepath", "s+\x1aB+\r\b\x03\x04\x10"}, - {"plugin", "s"}, - {"reflect", "s'\x04\x1d\x13\b\x04\x05\x17\x06\t-\n\x03\x10\x02\x02"}, + {"net/internal/socktest", "\x82\x01\xcc\x01\x02"}, + {"net/mail", "\x02}\x03\x84\x01\x04\x0f\x03\x16\x1a\x02\x0f\x04"}, + {"net/netip", "\x04{*\x01h\x036\x17"}, + {"net/rpc", "\x02q\f\x03\x0f\nk\x04\x12\x01\x1f\r\x03\x02"}, + {"net/rpc/jsonrpc", "u\n\x03\x84\x01\x16\x13\x1f"}, + {"net/smtp", "\x196\r\x14\x0f\x03\x84\x01\x16\x16\x1a"}, + {"net/textproto", "\x02\x01|\x03\x84\x01\f\n/\x01\x02\x15"}, + {"net/url", "\x7f\x03Ff\v\x10\x02\x01\x17"}, + {"os", "\x7f*\x01\x19\x04\x11\x14\x01\x03\x01\x05\x10\x01:\b\x05\x01\x01\r\x06"}, + {"os/exec", "\x03\nrI(\x01\x15\x01-\x06\a\n\x01\x03\x01\r"}, + {"os/exec/internal/fdtest", "\xd0\x02"}, + {"os/signal", "\r\xa7\x02\x15\x05\x02"}, + {"os/user", "\x02\x01|\x03\x84\x01.\r\n\x01\x02"}, + {"path", "\x7f*\xb8\x01"}, + {"path/filepath", "\x7f*\x1aD-\r\b\x03\x04\x11"}, + {"plugin", "\x7f"}, + {"reflect", "\x7f&\x04\x1e\x03\x11\b\x04\x05\x17\x06\t/\n\x03\x11\x02\x02"}, {"reflect/internal/example1", ""}, {"reflect/internal/example2", ""}, - {"regexp", "\x03\xf6\x018\t\x02\x01\x02\x10\x02"}, - {"regexp/syntax", "\xba\x02\x01\x01\x01\x02\x10\x02"}, - {"runtime", "\x9a\x01\x04\x01\x03\f\x06\a\x02\x01\x01\x0e\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18K"}, - {"runtime/coverage", "\xa6\x01S"}, - {"runtime/debug", "vUZ\r\b\x02\x01\x10\x06"}, - {"runtime/metrics", "\xbd\x01H-!"}, - {"runtime/pprof", "\x02\x01\x01\x03\x06_\a\x03$$\x0f\v!\f \r\b\x01\x01\x01\x02\x02\t\x03\x06"}, - {"runtime/race", "\xb8\x02"}, + {"regexp", "\x03\x83\x02\x037\t\x02\x01\x02\x11\x02"}, + {"regexp/syntax", "\xc9\x02\x01\x01\x01\x02\x11\x02"}, + {"runtime", "\xa5\x01\x04\x01\x03\f\x06\b\x02\x01\x01\x0f\x03\x01\x01\x01\x02\x01\x01\x01\x02\x01\x04\x01\x10\x18N"}, + {"runtime/coverage", "\xb1\x01U"}, + {"runtime/debug", "\x82\x01V\\\r\b\x02\x01\x11\x06"}, + {"runtime/metrics", "\xc9\x01I/\""}, + {"runtime/pprof", "\x02\x01\x01\x03\x06d\x0e\x03#5\v!\f\"\r\b\x01\x01\x01\x02\x02\n\x03\x06"}, + {"runtime/race", "\xc7\x02"}, {"runtime/race/internal/amd64v1", ""}, - {"runtime/trace", "\rf\x03z\t9\b\x05\x01\r\x06"}, - {"slices", "\x04\xf8\x01\fK"}, - {"sort", "\xcf\x0192"}, - {"strconv", "s+A\x01q"}, - {"strings", "s'\x04B\x19\x03\f7\x10\x02\x02"}, + {"runtime/trace", "\rr\x03{\t;\b\x05\x01\x0e\x06"}, + {"slices", "\x04\x85\x02\fN"}, + {"sort", "\xdc\x0194"}, + {"strconv", "\x7f*C\x01t"}, + {"strings", "\x7f&\x04D\x19\x03\f9\x11\x02\x02"}, {"structs", ""}, - {"sync", "\xce\x01\x13\x01P\x0e\x13"}, - {"sync/atomic", "\xd3\x02"}, - {"syscall", "s(\x03\x01\x1c\n\x03\x06\r\x04S\b\x05\x01\x13"}, - {"testing", "\x03\nf\x02\x01X\x17\x14\f\x05\x1b\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\r\x02\x04"}, - {"testing/fstest", "s\x03\x83\x01\x01\n&\x10\x03\b\b"}, - {"testing/internal/testdeps", "\x02\v\xad\x01/\x10,\x03\x05\x03\x06\a\x02\x0e"}, - {"testing/iotest", "\x03p\x03\x83\x01\x04"}, - {"testing/quick", "u\x01\x8f\x01\x05#\x10\x10"}, - {"testing/slogtest", "\rf\x03\x89\x01.\x05\x10\v"}, - {"testing/synctest", "\xe2\x01`\x11"}, - {"text/scanner", "\x03s\x83\x01,*\x02"}, - {"text/tabwriter", "v\x83\x01X"}, - {"text/template", "s\x03C@\x01\n \x01\x05\x01\x02\x05\v\x02\r\x03\x02"}, - {"text/template/parse", "\x03s\xbc\x01\n\x01\x12\x02"}, - {"time", "s+\x1e$(*\r\x02\x12"}, - {"time/tzdata", "s\xce\x01\x12"}, + {"sync", "\xdb\x01\x02\x11\x01R\x0e\x14"}, + {"sync/atomic", "\xe3\x02"}, + {"syscall", "\x7f'\x03\x01\x1d\n\x04\x06\r\x04U\b\x05\x01\x14"}, + {"testing", "\x03\nr\x02\x01Y\x17\x14\f\x05\x1d\x06\x02\x05\x02\x05\x01\x02\x01\x02\x01\x0e\x02\x04"}, + {"testing/cryptotest", "TV\\\x126\x03\x12"}, + {"testing/fstest", "\x7f\x03\x84\x01\x01\n(\x10\x03\t\b"}, + {"testing/internal/testdeps", "\x02\v\xb8\x011\x10.\x03\x05\x03\x06\a\x02\x0f"}, + {"testing/iotest", "\x03|\x03\x84\x01\x04"}, + {"testing/quick", "\x81\x01\x01\x90\x01\x05%\x10\x11"}, + {"testing/slogtest", "\rr\x03\x8a\x010\x05\x10\f"}, + {"testing/synctest", "\xef\x01b\f\x06"}, + {"text/scanner", "\x03\x7f\x84\x01.+\x02"}, + {"text/tabwriter", "\x82\x01\x84\x01["}, + {"text/template", "\x7f\x03BB\x01\n\"\x01\x05\x01\x02\x05\v\x02\x0e\x03\x02"}, + {"text/template/parse", "\x03\x7f\xbf\x01\n\x01\x13\x02"}, + {"time", "\x7f*D(,\r\x02\x13"}, + {"time/tzdata", "\x7f\xd1\x01\x13"}, {"unicode", ""}, {"unicode/utf16", ""}, {"unicode/utf8", ""}, - {"unique", "\x9a\x01!%\x01Q\r\x01\x13\x12"}, + {"unique", "\xa5\x01\"&\x01S\r\x01\x14\x19"}, {"unsafe", ""}, - {"vendor/golang.org/x/crypto/chacha20", "\x10\\\a\x95\x01*&"}, - {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\\\a\xe1\x01\x04\x01\a"}, - {"vendor/golang.org/x/crypto/cryptobyte", "i\n\x03\x90\x01' \n"}, + {"uuid", "\x03\x01P\x1d\x03\v\xcf\x01\x0f"}, + {"vendor/golang.org/x/crypto/chacha20", "\x10a\x0e\x96\x01,)"}, + {"vendor/golang.org/x/crypto/chacha20poly1305", "\x10\aZ\x0e\xe6\x01\x05\x01\f"}, + {"vendor/golang.org/x/crypto/cryptobyte", "n\x11\x03\x91\x01)!\v"}, {"vendor/golang.org/x/crypto/cryptobyte/asn1", ""}, - {"vendor/golang.org/x/crypto/internal/alias", "\xd3\x02"}, - {"vendor/golang.org/x/crypto/internal/poly1305", "W\x15\x9c\x01"}, - {"vendor/golang.org/x/net/dns/dnsmessage", "s\xc7\x01"}, - {"vendor/golang.org/x/net/http/httpguts", "\x8f\x02\x14\x1a\x14\r"}, - {"vendor/golang.org/x/net/http/httpproxy", "s\x03\x99\x01\x10\x05\x01\x18\x14\r"}, - {"vendor/golang.org/x/net/http2/hpack", "\x03p\x03\x83\x01F"}, - {"vendor/golang.org/x/net/idna", "v\x8f\x018\x14\x10\x02\x01"}, - {"vendor/golang.org/x/net/nettest", "\x03i\a\x03\x83\x01\x11\x05\x16\x01\f\n\x01\x02\x02\x01\v"}, - {"vendor/golang.org/x/sys/cpu", "\xa5\x02\r\n\x01\x16"}, - {"vendor/golang.org/x/text/secure/bidirule", "s\xde\x01\x11\x01"}, - {"vendor/golang.org/x/text/transform", "\x03p\x86\x01X"}, - {"vendor/golang.org/x/text/unicode/bidi", "\x03\bk\x87\x01>\x16"}, - {"vendor/golang.org/x/text/unicode/norm", "l\n\x83\x01F\x12\x11"}, - {"weak", "\x9a\x01\x98\x01!"}, + {"vendor/golang.org/x/crypto/hkdf", "\x18\x01f\x15r"}, + {"vendor/golang.org/x/crypto/internal/alias", "\xe3\x02"}, + {"vendor/golang.org/x/crypto/internal/poly1305", "[\x16\xa4\x01"}, + {"vendor/golang.org/x/net/dns/dnsmessage", "\x7f\xca\x01"}, + {"vendor/golang.org/x/net/http/httpguts", "\x9c\x02\x16\x1a\x15\x10"}, + {"vendor/golang.org/x/net/http/httpproxy", "\x7f\x03\x9a\x01\x12\x05\x01\x18\x15\x10"}, + {"vendor/golang.org/x/net/http2/hpack", "\x03|\x03\x84\x01H"}, + {"vendor/golang.org/x/net/http3", "\x9d\x02@\x06\x0f\x04"}, + {"vendor/golang.org/x/net/idna", "\x82\x01\x90\x01:\x13\x02\x17\x02\x01"}, + {"vendor/golang.org/x/net/internal/http3", "\rO#\x03\x84\x01\v\x04\a\x01\x05\x10\x01\x16\x02\x01\x02\x0f\x10\x02\x04\x03"}, + {"vendor/golang.org/x/net/internal/httpcommon", "\rr\x03\xa0\x01\x10\x01\x17\x01\x01\x02\x1f\x02"}, + {"vendor/golang.org/x/net/internal/quic/quicwire", "q"}, + {"vendor/golang.org/x/net/nettest", "\x03n\x0e\x03\x84\x01\x11\x05\x18\x01\f\n\x01\x02\x02\x01\f"}, + {"vendor/golang.org/x/net/quic", "\x03\n\x01\x01\x01\t;\x04\x04\x15\x03\v\x03\x12r\x06\x06\x06\x04\x12\x06\x15\x02\x01\x02\x01\x01\r\x06\x02\x01\x01\x02\v"}, + {"vendor/golang.org/x/sys/cpu", "\xb4\x02\r\n\x01\x17"}, + {"vendor/golang.org/x/text/secure/bidirule", "\x7f\xe2\x01\x18\x01"}, + {"vendor/golang.org/x/text/transform", "\x03|\x87\x01["}, + {"vendor/golang.org/x/text/unicode/bidi", "\x03\bw\x88\x01@\x17"}, + {"vendor/golang.org/x/text/unicode/norm", "q\x11\x84\x01H\x13\x18"}, + {"weak", "\xa5\x01\x9c\x01\""}, } // bootstrap is the list of bootstrap packages extracted from cmd/dist. @@ -406,6 +429,7 @@ var bootstrap = map[string]bool{ "cmd/compile/internal/logopt": true, "cmd/compile/internal/loong64": true, "cmd/compile/internal/loopvar": true, + "cmd/compile/internal/midway": true, "cmd/compile/internal/mips": true, "cmd/compile/internal/mips64": true, "cmd/compile/internal/noder": true, @@ -510,6 +534,7 @@ var bootstrap = map[string]bool{ "internal/race": true, "internal/runtime/gc": true, "internal/saferio": true, + "internal/strconv": true, "internal/syscall/unix": true, "internal/types/errors": true, "internal/unsafeheader": true, diff --git a/vendor/golang.org/x/tools/internal/stdlib/manifest.go b/vendor/golang.org/x/tools/internal/stdlib/manifest.go index f1e2462..e11abf0 100644 --- a/vendor/golang.org/x/tools/internal/stdlib/manifest.go +++ b/vendor/golang.org/x/tools/internal/stdlib/manifest.go @@ -270,6 +270,7 @@ var PackageSymbols = map[string][]Symbol{ {"ContainsRune", Func, 7, "func(b []byte, r rune) bool"}, {"Count", Func, 0, "func(s []byte, sep []byte) int"}, {"Cut", Func, 18, "func(s []byte, sep []byte) (before []byte, after []byte, found bool)"}, + {"CutLast", Func, 27, "func(s []byte, sep []byte) (before []byte, after []byte, found bool)"}, {"CutPrefix", Func, 20, "func(s []byte, prefix []byte) (after []byte, found bool)"}, {"CutSuffix", Func, 20, "func(s []byte, suffix []byte) (before []byte, found bool)"}, {"Equal", Func, 0, "func(a []byte, b []byte) bool"}, @@ -538,6 +539,7 @@ var PackageSymbols = map[string][]Symbol{ {"MD4", Const, 0, ""}, {"MD5", Const, 0, ""}, {"MD5SHA1", Const, 0, ""}, + {"MLDSAMu", Const, 27, ""}, {"MessageSigner", Type, 25, ""}, {"PrivateKey", Type, 0, ""}, {"PublicKey", Type, 2, ""}, @@ -626,7 +628,7 @@ var PackageSymbols = map[string][]Symbol{ {"PublicKey", Type, 0, ""}, {"PublicKey.Parameters", Field, 0, ""}, {"PublicKey.Y", Field, 0, ""}, - {"Sign", Func, 0, "func(rand io.Reader, priv *PrivateKey, hash []byte) (r *big.Int, s *big.Int, err error)"}, + {"Sign", Func, 0, "func(random io.Reader, priv *PrivateKey, hash []byte) (r *big.Int, s *big.Int, err error)"}, {"Verify", Func, 0, "func(pub *PublicKey, hash []byte, r *big.Int, s *big.Int) bool"}, }, "crypto/ecdh": { @@ -674,7 +676,7 @@ var PackageSymbols = map[string][]Symbol{ {"(PublicKey).Params", Method, 0, ""}, {"(PublicKey).ScalarBaseMult", Method, 0, ""}, {"(PublicKey).ScalarMult", Method, 0, ""}, - {"GenerateKey", Func, 0, "func(c elliptic.Curve, rand io.Reader) (*PrivateKey, error)"}, + {"GenerateKey", Func, 0, "func(c elliptic.Curve, r io.Reader) (*PrivateKey, error)"}, {"ParseRawPrivateKey", Func, 25, "func(curve elliptic.Curve, data []byte) (*PrivateKey, error)"}, {"ParseUncompressedPublicKey", Func, 25, "func(curve elliptic.Curve, data []byte) (*PublicKey, error)"}, {"PrivateKey", Type, 0, ""}, @@ -685,7 +687,7 @@ var PackageSymbols = map[string][]Symbol{ {"PublicKey.X", Field, 0, ""}, {"PublicKey.Y", Field, 0, ""}, {"Sign", Func, 0, "func(rand io.Reader, priv *PrivateKey, hash []byte) (r *big.Int, s *big.Int, err error)"}, - {"SignASN1", Func, 15, "func(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error)"}, + {"SignASN1", Func, 15, "func(r io.Reader, priv *PrivateKey, hash []byte) ([]byte, error)"}, {"Verify", Func, 0, "func(pub *PublicKey, hash []byte, r *big.Int, s *big.Int) bool"}, {"VerifyASN1", Func, 15, "func(pub *PublicKey, hash []byte, sig []byte) bool"}, }, @@ -696,7 +698,7 @@ var PackageSymbols = map[string][]Symbol{ {"(PrivateKey).Seed", Method, 13, ""}, {"(PrivateKey).Sign", Method, 13, ""}, {"(PublicKey).Equal", Method, 15, ""}, - {"GenerateKey", Func, 13, "func(rand io.Reader) (PublicKey, PrivateKey, error)"}, + {"GenerateKey", Func, 13, "func(random io.Reader) (PublicKey, PrivateKey, error)"}, {"NewKeyFromSeed", Func, 13, "func(seed []byte) PrivateKey"}, {"Options", Type, 20, ""}, {"Options.Context", Field, 20, ""}, @@ -745,7 +747,9 @@ var PackageSymbols = map[string][]Symbol{ }, "crypto/fips140": { {"Enabled", Func, 24, "func() bool"}, + {"Enforced", Func, 26, "func() bool"}, {"Version", Func, 26, "func() string"}, + {"WithoutEnforcement", Func, 26, "func(f func())"}, }, "crypto/hkdf": { {"Expand", Func, 24, "func[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error)"}, @@ -756,12 +760,94 @@ var PackageSymbols = map[string][]Symbol{ {"Equal", Func, 1, "func(mac1 []byte, mac2 []byte) bool"}, {"New", Func, 0, "func(h func() hash.Hash, key []byte) hash.Hash"}, }, + "crypto/hpke": { + {"(*Recipient).Export", Method, 26, ""}, + {"(*Recipient).Open", Method, 26, ""}, + {"(*Sender).Export", Method, 26, ""}, + {"(*Sender).Seal", Method, 26, ""}, + {"(AEAD).ID", Method, 26, ""}, + {"(KDF).ID", Method, 26, ""}, + {"(KEM).DeriveKeyPair", Method, 26, ""}, + {"(KEM).GenerateKey", Method, 26, ""}, + {"(KEM).ID", Method, 26, ""}, + {"(KEM).NewPrivateKey", Method, 26, ""}, + {"(KEM).NewPublicKey", Method, 26, ""}, + {"(PrivateKey).Bytes", Method, 26, ""}, + {"(PrivateKey).KEM", Method, 26, ""}, + {"(PrivateKey).PublicKey", Method, 26, ""}, + {"(PublicKey).Bytes", Method, 26, ""}, + {"(PublicKey).KEM", Method, 26, ""}, + {"AES128GCM", Func, 26, "func() AEAD"}, + {"AES256GCM", Func, 26, "func() AEAD"}, + {"ChaCha20Poly1305", Func, 26, "func() AEAD"}, + {"DHKEM", Func, 26, "func(curve ecdh.Curve) KEM"}, + {"ExportOnly", Func, 26, "func() AEAD"}, + {"HKDFSHA256", Func, 26, "func() KDF"}, + {"HKDFSHA384", Func, 26, "func() KDF"}, + {"HKDFSHA512", Func, 26, "func() KDF"}, + {"MLKEM1024", Func, 26, "func() KEM"}, + {"MLKEM1024P384", Func, 26, "func() KEM"}, + {"MLKEM768", Func, 26, "func() KEM"}, + {"MLKEM768P256", Func, 26, "func() KEM"}, + {"MLKEM768X25519", Func, 26, "func() KEM"}, + {"NewAEAD", Func, 26, "func(id uint16) (AEAD, error)"}, + {"NewDHKEMPrivateKey", Func, 26, "func(priv ecdh.KeyExchanger) (PrivateKey, error)"}, + {"NewDHKEMPublicKey", Func, 26, "func(pub *ecdh.PublicKey) (PublicKey, error)"}, + {"NewHybridPrivateKey", Func, 26, "func(pq crypto.Decapsulator, t ecdh.KeyExchanger) (PrivateKey, error)"}, + {"NewHybridPublicKey", Func, 26, "func(pq crypto.Encapsulator, t *ecdh.PublicKey) (PublicKey, error)"}, + {"NewKDF", Func, 26, "func(id uint16) (KDF, error)"}, + {"NewKEM", Func, 26, "func(id uint16) (KEM, error)"}, + {"NewMLKEMPrivateKey", Func, 26, "func(priv crypto.Decapsulator) (PrivateKey, error)"}, + {"NewMLKEMPublicKey", Func, 26, "func(pub crypto.Encapsulator) (PublicKey, error)"}, + {"NewRecipient", Func, 26, "func(enc []byte, k PrivateKey, kdf KDF, aead AEAD, info []byte) (*Recipient, error)"}, + {"NewSender", Func, 26, "func(pk PublicKey, kdf KDF, aead AEAD, info []byte) (enc []byte, s *Sender, err error)"}, + {"Open", Func, 26, "func(k PrivateKey, kdf KDF, aead AEAD, info []byte, ciphertext []byte) ([]byte, error)"}, + {"Recipient", Type, 26, ""}, + {"SHAKE128", Func, 26, "func() KDF"}, + {"SHAKE256", Func, 26, "func() KDF"}, + {"Seal", Func, 26, "func(pk PublicKey, kdf KDF, aead AEAD, info []byte, plaintext []byte) ([]byte, error)"}, + {"Sender", Type, 26, ""}, + }, "crypto/md5": { {"BlockSize", Const, 0, ""}, {"New", Func, 0, "func() hash.Hash"}, {"Size", Const, 0, ""}, {"Sum", Func, 2, "func(data []byte) [16]byte"}, }, + "crypto/mldsa": { + {"(*Options).HashFunc", Method, 27, ""}, + {"(*PrivateKey).Bytes", Method, 27, ""}, + {"(*PrivateKey).Equal", Method, 27, ""}, + {"(*PrivateKey).Public", Method, 27, ""}, + {"(*PrivateKey).PublicKey", Method, 27, ""}, + {"(*PrivateKey).Sign", Method, 27, ""}, + {"(*PrivateKey).SignDeterministic", Method, 27, ""}, + {"(*PublicKey).Bytes", Method, 27, ""}, + {"(*PublicKey).Equal", Method, 27, ""}, + {"(*PublicKey).Parameters", Method, 27, ""}, + {"(Parameters).PublicKeySize", Method, 27, ""}, + {"(Parameters).SignatureSize", Method, 27, ""}, + {"(Parameters).String", Method, 27, ""}, + {"GenerateKey", Func, 27, "func(params Parameters) (*PrivateKey, error)"}, + {"MLDSA44", Func, 27, "func() Parameters"}, + {"MLDSA44PublicKeySize", Const, 27, ""}, + {"MLDSA44SignatureSize", Const, 27, ""}, + {"MLDSA65", Func, 27, "func() Parameters"}, + {"MLDSA65PublicKeySize", Const, 27, ""}, + {"MLDSA65SignatureSize", Const, 27, ""}, + {"MLDSA87", Func, 27, "func() Parameters"}, + {"MLDSA87PublicKeySize", Const, 27, ""}, + {"MLDSA87SignatureSize", Const, 27, ""}, + {"NewPrivateKey", Func, 27, "func(params Parameters, seed []byte) (*PrivateKey, error)"}, + {"NewPublicKey", Func, 27, "func(params Parameters, encoding []byte) (*PublicKey, error)"}, + {"Options", Type, 27, ""}, + {"Options.Context", Field, 27, ""}, + {"Parameters", Type, 27, ""}, + {"PrivateKey", Type, 27, ""}, + {"PrivateKeySize", Const, 27, ""}, + {"PublicKey", Type, 27, ""}, + {"Verify", Func, 27, "func(pk *PublicKey, message []byte, signature []byte, opts *Options) error"}, + }, "crypto/mlkem": { {"(*DecapsulationKey1024).Bytes", Method, 24, ""}, {"(*DecapsulationKey1024).Decapsulate", Method, 24, ""}, @@ -801,7 +887,7 @@ var PackageSymbols = map[string][]Symbol{ }, "crypto/rand": { {"Int", Func, 0, "func(rand io.Reader, max *big.Int) (n *big.Int, err error)"}, - {"Prime", Func, 0, "func(rand io.Reader, bits int) (*big.Int, error)"}, + {"Prime", Func, 0, "func(r io.Reader, bits int) (*big.Int, error)"}, {"Read", Func, 0, "func(b []byte) (n int, err error)"}, {"Reader", Var, 0, ""}, {"Text", Func, 24, "func() string"}, @@ -865,7 +951,7 @@ var PackageSymbols = map[string][]Symbol{ {"PublicKey.E", Field, 0, ""}, {"PublicKey.N", Field, 0, ""}, {"SignPKCS1v15", Func, 0, "func(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error)"}, - {"SignPSS", Func, 2, "func(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error)"}, + {"SignPSS", Func, 2, "func(random io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error)"}, {"VerifyPKCS1v15", Func, 0, "func(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error"}, {"VerifyPSS", Func, 2, "func(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts *PSSOptions) error"}, }, @@ -1070,6 +1156,7 @@ var PackageSymbols = map[string][]Symbol{ {"ConnectionState.ECHAccepted", Field, 23, ""}, {"ConnectionState.HandshakeComplete", Field, 0, ""}, {"ConnectionState.HelloRetryRequest", Field, 26, ""}, + {"ConnectionState.LocalCertificate", Field, 27, ""}, {"ConnectionState.NegotiatedProtocol", Field, 0, ""}, {"ConnectionState.NegotiatedProtocolIsMutual", Field, 0, ""}, {"ConnectionState.OCSPResponse", Field, 5, ""}, @@ -1102,6 +1189,10 @@ var PackageSymbols = map[string][]Symbol{ {"InsecureCipherSuites", Func, 14, "func() []*CipherSuite"}, {"Listen", Func, 0, "func(network string, laddr string, config *Config) (net.Listener, error)"}, {"LoadX509KeyPair", Func, 0, "func(certFile string, keyFile string) (Certificate, error)"}, + {"MLDSA44", Const, 27, ""}, + {"MLDSA65", Const, 27, ""}, + {"MLDSA87", Const, 27, ""}, + {"MLKEM1024", Const, 27, ""}, {"NewLRUClientSessionCache", Func, 3, "func(capacity int) ClientSessionCache"}, {"NewListener", Func, 0, "func(inner net.Listener, config *Config) net.Listener"}, {"NewResumptionState", Func, 21, "func(ticket []byte, state *SessionState) (*ClientSessionState, error)"}, @@ -1116,6 +1207,7 @@ var PackageSymbols = map[string][]Symbol{ {"ParseSessionState", Func, 21, "func(data []byte) (*SessionState, error)"}, {"QUICClient", Func, 21, "func(config *QUICConfig) *QUICConn"}, {"QUICConfig", Type, 21, ""}, + {"QUICConfig.ClientHelloInfoConn", Field, 27, ""}, {"QUICConfig.EnableSessionEvents", Field, 23, ""}, {"QUICConfig.TLSConfig", Field, 21, ""}, {"QUICConn", Type, 21, ""}, @@ -1158,6 +1250,8 @@ var PackageSymbols = map[string][]Symbol{ {"RequestClientCert", Const, 0, ""}, {"RequireAndVerifyClientCert", Const, 0, ""}, {"RequireAnyClientCert", Const, 0, ""}, + {"SecP256r1MLKEM768", Const, 26, ""}, + {"SecP384r1MLKEM1024", Const, 26, ""}, {"Server", Func, 0, "func(conn net.Conn, config *Config) *Conn"}, {"SessionState", Type, 21, ""}, {"SessionState.EarlyData", Field, 21, ""}, @@ -1222,6 +1316,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*RevocationList).CheckSignatureFrom", Method, 19, ""}, {"(CertificateInvalidError).Error", Method, 0, ""}, {"(ConstraintViolationError).Error", Method, 0, ""}, + {"(ExtKeyUsage).OID", Method, 26, ""}, {"(ExtKeyUsage).String", Method, 26, ""}, {"(HostnameError).Error", Method, 0, ""}, {"(InsecureAlgorithmError).Error", Method, 6, ""}, @@ -1281,6 +1376,7 @@ var PackageSymbols = map[string][]Symbol{ {"Certificate.PublicKeyAlgorithm", Field, 0, ""}, {"Certificate.Raw", Field, 0, ""}, {"Certificate.RawIssuer", Field, 0, ""}, + {"Certificate.RawSignatureAlgorithm", Field, 27, ""}, {"Certificate.RawSubject", Field, 0, ""}, {"Certificate.RawSubjectPublicKeyInfo", Field, 0, ""}, {"Certificate.RawTBSCertificate", Field, 0, ""}, @@ -1309,6 +1405,7 @@ var PackageSymbols = map[string][]Symbol{ {"CertificateRequest.PublicKey", Field, 3, ""}, {"CertificateRequest.PublicKeyAlgorithm", Field, 3, ""}, {"CertificateRequest.Raw", Field, 3, ""}, + {"CertificateRequest.RawSignatureAlgorithm", Field, 27, ""}, {"CertificateRequest.RawSubject", Field, 3, ""}, {"CertificateRequest.RawSubjectPublicKeyInfo", Field, 3, ""}, {"CertificateRequest.RawTBSCertificateRequest", Field, 3, ""}, @@ -1369,6 +1466,10 @@ var PackageSymbols = map[string][]Symbol{ {"KeyUsageKeyEncipherment", Const, 0, ""}, {"MD2WithRSA", Const, 0, ""}, {"MD5WithRSA", Const, 0, ""}, + {"MLDSA", Const, 27, ""}, + {"MLDSA44", Const, 27, ""}, + {"MLDSA65", Const, 27, ""}, + {"MLDSA87", Const, 27, ""}, {"MarshalECPrivateKey", Func, 2, "func(key *ecdsa.PrivateKey) ([]byte, error)"}, {"MarshalPKCS1PrivateKey", Func, 0, "func(key *rsa.PrivateKey) []byte"}, {"MarshalPKCS1PublicKey", Func, 10, "func(key *rsa.PublicKey) []byte"}, @@ -1380,6 +1481,7 @@ var PackageSymbols = map[string][]Symbol{ {"NoValidChains", Const, 24, ""}, {"NotAuthorizedToSign", Const, 0, ""}, {"OID", Type, 22, ""}, + {"OIDFromASN1OID", Func, 26, "func(asn1OID asn1.ObjectIdentifier) (OID, error)"}, {"OIDFromInts", Func, 22, "func(oid []uint64) (OID, error)"}, {"PEMCipher", Type, 1, ""}, {"PEMCipher3DES", Const, 1, ""}, @@ -1414,6 +1516,7 @@ var PackageSymbols = map[string][]Symbol{ {"RevocationList.Number", Field, 15, ""}, {"RevocationList.Raw", Field, 19, ""}, {"RevocationList.RawIssuer", Field, 19, ""}, + {"RevocationList.RawSignatureAlgorithm", Field, 27, ""}, {"RevocationList.RawTBSRevocationList", Field, 19, ""}, {"RevocationList.RevokedCertificateEntries", Field, 21, ""}, {"RevocationList.RevokedCertificates", Field, 15, ""}, @@ -1594,6 +1697,7 @@ var PackageSymbols = map[string][]Symbol{ {"(Scanner).Scan", Method, 0, ""}, {"ColumnType", Type, 8, ""}, {"Conn", Type, 9, ""}, + {"ConvertAssign", Func, 27, "func(scanCtx driver.ScanContext, dest any, src driver.Value) error"}, {"DB", Type, 0, ""}, {"DBStats", Type, 5, ""}, {"DBStats.Idle", Field, 11, ""}, @@ -1690,10 +1794,11 @@ var PackageSymbols = map[string][]Symbol{ {"(Rows).Next", Method, 0, ""}, {"(RowsAffected).LastInsertId", Method, 0, ""}, {"(RowsAffected).RowsAffected", Method, 0, ""}, - {"(RowsColumnScanner).Close", Method, 26, ""}, - {"(RowsColumnScanner).Columns", Method, 26, ""}, - {"(RowsColumnScanner).Next", Method, 26, ""}, - {"(RowsColumnScanner).ScanColumn", Method, 26, ""}, + {"(RowsColumnScanner).Close", Method, 27, ""}, + {"(RowsColumnScanner).Columns", Method, 27, ""}, + {"(RowsColumnScanner).Next", Method, 27, ""}, + {"(RowsColumnScanner).NextRow", Method, 27, ""}, + {"(RowsColumnScanner).ScanColumn", Method, 27, ""}, {"(RowsColumnTypeDatabaseTypeName).Close", Method, 8, ""}, {"(RowsColumnTypeDatabaseTypeName).ColumnTypeDatabaseTypeName", Method, 8, ""}, {"(RowsColumnTypeDatabaseTypeName).Columns", Method, 8, ""}, @@ -1765,13 +1870,14 @@ var PackageSymbols = map[string][]Symbol{ {"ResultNoRows", Var, 0, ""}, {"Rows", Type, 0, ""}, {"RowsAffected", Type, 0, ""}, - {"RowsColumnScanner", Type, 26, ""}, + {"RowsColumnScanner", Type, 27, ""}, {"RowsColumnTypeDatabaseTypeName", Type, 8, ""}, {"RowsColumnTypeLength", Type, 8, ""}, {"RowsColumnTypeNullable", Type, 8, ""}, {"RowsColumnTypePrecisionScale", Type, 8, ""}, {"RowsColumnTypeScanType", Type, 8, ""}, {"RowsNextResultSet", Type, 8, ""}, + {"ScanContext", Type, 27, ""}, {"SessionResetter", Type, 10, ""}, {"Stmt", Type, 0, ""}, {"StmtExecContext", Type, 8, ""}, @@ -4989,24 +5095,32 @@ var PackageSymbols = map[string][]Symbol{ {"(*InvalidUnmarshalError).Error", Method, 0, ""}, {"(*MarshalerError).Error", Method, 0, ""}, {"(*MarshalerError).Unwrap", Method, 13, ""}, + {"(*Number).UnmarshalJSONFrom", Method, 27, ""}, {"(*RawMessage).MarshalJSON", Method, 0, ""}, {"(*RawMessage).UnmarshalJSON", Method, 0, ""}, {"(*SyntaxError).Error", Method, 0, ""}, {"(*UnmarshalFieldError).Error", Method, 0, ""}, {"(*UnmarshalTypeError).Error", Method, 0, ""}, + {"(*UnmarshalTypeError).Unwrap", Method, 27, ""}, {"(*UnsupportedTypeError).Error", Method, 0, ""}, {"(*UnsupportedValueError).Error", Method, 0, ""}, {"(Delim).String", Method, 5, ""}, {"(Marshaler).MarshalJSON", Method, 0, ""}, {"(Number).Float64", Method, 1, ""}, {"(Number).Int64", Method, 1, ""}, + {"(Number).MarshalJSONTo", Method, 27, ""}, {"(Number).String", Method, 1, ""}, {"(RawMessage).MarshalJSON", Method, 8, ""}, {"(Unmarshaler).UnmarshalJSON", Method, 0, ""}, + {"CallMethodsWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"Compact", Func, 0, "func(dst *bytes.Buffer, src []byte) error"}, {"Decoder", Type, 0, ""}, + {"DefaultOptionsV1", Func, 27, "func() Options"}, {"Delim", Type, 5, ""}, {"Encoder", Type, 0, ""}, + {"FormatByteArrayAsArray", Func, 27, "func(v bool) Options"}, + {"FormatBytesWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"FormatDurationAsNano", Func, 27, "func(v bool) Options"}, {"HTMLEscape", Func, 0, "func(dst *bytes.Buffer, src []byte)"}, {"Indent", Func, 0, "func(dst *bytes.Buffer, src []byte, prefix string, indent string) error"}, {"InvalidUTF8Error", Type, 0, ""}, @@ -5019,19 +5133,29 @@ var PackageSymbols = map[string][]Symbol{ {"MarshalerError", Type, 0, ""}, {"MarshalerError.Err", Field, 0, ""}, {"MarshalerError.Type", Field, 0, ""}, + {"MatchCaseSensitiveDelimiter", Func, 27, "func(v bool) Options"}, + {"MergeWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"NewDecoder", Func, 0, "func(r io.Reader) *Decoder"}, {"NewEncoder", Func, 0, "func(w io.Writer) *Encoder"}, {"Number", Type, 1, ""}, + {"OmitEmptyWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"Options", Type, 27, ""}, + {"ParseBytesWithLooseRFC4648", Func, 27, "func(v bool) Options"}, + {"ParseTimeWithLooseRFC3339", Func, 27, "func(v bool) Options"}, {"RawMessage", Type, 0, ""}, + {"ReportErrorsWithLegacySemantics", Func, 27, "func(v bool) Options"}, + {"StringifyWithLegacySemantics", Func, 27, "func(v bool) Options"}, {"SyntaxError", Type, 0, ""}, {"SyntaxError.Offset", Field, 0, ""}, {"Token", Type, 5, ""}, {"Unmarshal", Func, 0, "func(data []byte, v any) error"}, + {"UnmarshalArrayFromAnyLength", Func, 27, "func(v bool) Options"}, {"UnmarshalFieldError", Type, 0, ""}, {"UnmarshalFieldError.Field", Field, 0, ""}, {"UnmarshalFieldError.Key", Field, 0, ""}, {"UnmarshalFieldError.Type", Field, 0, ""}, {"UnmarshalTypeError", Type, 0, ""}, + {"UnmarshalTypeError.Err", Field, 27, ""}, {"UnmarshalTypeError.Field", Field, 8, ""}, {"UnmarshalTypeError.Offset", Field, 5, ""}, {"UnmarshalTypeError.Struct", Field, 8, ""}, @@ -5045,6 +5169,158 @@ var PackageSymbols = map[string][]Symbol{ {"UnsupportedValueError.Value", Field, 0, ""}, {"Valid", Func, 9, "func(data []byte) bool"}, }, + "encoding/json/jsontext": { + {"(*Decoder).InputOffset", Method, 27, ""}, + {"(*Decoder).Options", Method, 27, ""}, + {"(*Decoder).PeekKind", Method, 27, ""}, + {"(*Decoder).ReadToken", Method, 27, ""}, + {"(*Decoder).ReadValue", Method, 27, ""}, + {"(*Decoder).Reset", Method, 27, ""}, + {"(*Decoder).SkipValue", Method, 27, ""}, + {"(*Decoder).StackDepth", Method, 27, ""}, + {"(*Decoder).StackIndex", Method, 27, ""}, + {"(*Decoder).StackPointer", Method, 27, ""}, + {"(*Decoder).UnreadBuffer", Method, 27, ""}, + {"(*Encoder).AvailableBuffer", Method, 27, ""}, + {"(*Encoder).Options", Method, 27, ""}, + {"(*Encoder).OutputOffset", Method, 27, ""}, + {"(*Encoder).Reset", Method, 27, ""}, + {"(*Encoder).StackDepth", Method, 27, ""}, + {"(*Encoder).StackIndex", Method, 27, ""}, + {"(*Encoder).StackPointer", Method, 27, ""}, + {"(*Encoder).WriteToken", Method, 27, ""}, + {"(*Encoder).WriteValue", Method, 27, ""}, + {"(*SyntacticError).Error", Method, 27, ""}, + {"(*SyntacticError).Unwrap", Method, 27, ""}, + {"(*Value).Canonicalize", Method, 27, ""}, + {"(*Value).Compact", Method, 27, ""}, + {"(*Value).Format", Method, 27, ""}, + {"(*Value).Indent", Method, 27, ""}, + {"(*Value).UnmarshalJSON", Method, 27, ""}, + {"(Kind).String", Method, 27, ""}, + {"(Pointer).AppendToken", Method, 27, ""}, + {"(Pointer).Contains", Method, 27, ""}, + {"(Pointer).IsValid", Method, 27, ""}, + {"(Pointer).LastToken", Method, 27, ""}, + {"(Pointer).Parent", Method, 27, ""}, + {"(Pointer).Tokens", Method, 27, ""}, + {"(Token).Bool", Method, 27, ""}, + {"(Token).Clone", Method, 27, ""}, + {"(Token).Float", Method, 27, ""}, + {"(Token).Float32", Method, 27, ""}, + {"(Token).Int", Method, 27, ""}, + {"(Token).Kind", Method, 27, ""}, + {"(Token).String", Method, 27, ""}, + {"(Token).Uint", Method, 27, ""}, + {"(Value).Clone", Method, 27, ""}, + {"(Value).IsValid", Method, 27, ""}, + {"(Value).Kind", Method, 27, ""}, + {"(Value).MarshalJSON", Method, 27, ""}, + {"(Value).String", Method, 27, ""}, + {"AllowDuplicateNames", Func, 27, "func(v bool) Options"}, + {"AllowInvalidUTF8", Func, 27, "func(v bool) Options"}, + {"AppendFloat", Func, 27, "func(dst []byte, src float64, bits int) []byte"}, + {"AppendFormat", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes, opts ...Options) ([]byte, error)"}, + {"AppendQuote", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)"}, + {"AppendUnquote", Func, 27, "func[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)"}, + {"BeginArray", Var, 27, ""}, + {"BeginObject", Var, 27, ""}, + {"Bool", Func, 27, "func(b bool) Token"}, + {"CanonicalizeRawFloats", Func, 27, "func(v bool) Options"}, + {"CanonicalizeRawInts", Func, 27, "func(v bool) Options"}, + {"Decoder", Type, 27, ""}, + {"Encoder", Type, 27, ""}, + {"EndArray", Var, 27, ""}, + {"EndObject", Var, 27, ""}, + {"ErrDuplicateName", Var, 27, ""}, + {"ErrNonStringName", Var, 27, ""}, + {"EscapeForHTML", Func, 27, "func(v bool) Options"}, + {"EscapeForJS", Func, 27, "func(v bool) Options"}, + {"False", Var, 27, ""}, + {"Float", Func, 27, "func(n float64) Token"}, + {"Float32", Func, 27, "func(n float32) Token"}, + {"Int", Func, 27, "func(n int64) Token"}, + {"Internal", Var, 27, ""}, + {"Kind", Type, 27, ""}, + {"KindBeginArray", Const, 27, ""}, + {"KindBeginObject", Const, 27, ""}, + {"KindEndArray", Const, 27, ""}, + {"KindEndObject", Const, 27, ""}, + {"KindFalse", Const, 27, ""}, + {"KindInvalid", Const, 27, ""}, + {"KindNull", Const, 27, ""}, + {"KindNumber", Const, 27, ""}, + {"KindString", Const, 27, ""}, + {"KindTrue", Const, 27, ""}, + {"Multiline", Func, 27, "func(v bool) Options"}, + {"NewDecoder", Func, 27, "func(r io.Reader, opts ...Options) *Decoder"}, + {"NewEncoder", Func, 27, "func(w io.Writer, opts ...Options) *Encoder"}, + {"Null", Var, 27, ""}, + {"Options", Type, 27, ""}, + {"Pointer", Type, 27, ""}, + {"PreserveRawStrings", Func, 27, "func(v bool) Options"}, + {"ReorderRawObjects", Func, 27, "func(v bool) Options"}, + {"SpaceAfterColon", Func, 27, "func(v bool) Options"}, + {"SpaceAfterComma", Func, 27, "func(v bool) Options"}, + {"String", Func, 27, "func(s string) Token"}, + {"SyntacticError", Type, 27, ""}, + {"SyntacticError.ByteOffset", Field, 27, ""}, + {"SyntacticError.Err", Field, 27, ""}, + {"SyntacticError.JSONPointer", Field, 27, ""}, + {"Token", Type, 27, ""}, + {"True", Var, 27, ""}, + {"Uint", Func, 27, "func(n uint64) Token"}, + {"Value", Type, 27, ""}, + {"WithIndent", Func, 27, "func(indent string) Options"}, + {"WithIndentPrefix", Func, 27, "func(prefix string) Options"}, + }, + "encoding/json/v2": { + {"(*SemanticError).Error", Method, 27, ""}, + {"(*SemanticError).Unwrap", Method, 27, ""}, + {"(Marshaler).MarshalJSON", Method, 27, ""}, + {"(MarshalerTo).MarshalJSONTo", Method, 27, ""}, + {"(Unmarshaler).UnmarshalJSON", Method, 27, ""}, + {"(UnmarshalerFrom).UnmarshalJSONFrom", Method, 27, ""}, + {"DefaultOptionsV2", Func, 27, "func() Options"}, + {"Deterministic", Func, 27, "func(v bool) Options"}, + {"ErrUnknownName", Var, 27, ""}, + {"FormatNilMapAsNull", Func, 27, "func(v bool) Options"}, + {"FormatNilSliceAsNull", Func, 27, "func(v bool) Options"}, + {"GetOption", Func, 27, "func[T any](opts Options, setter func(T) Options) (T, bool)"}, + {"JoinMarshalers", Func, 27, "func(ms ...*Marshalers) *Marshalers"}, + {"JoinOptions", Func, 27, "func(srcs ...Options) Options"}, + {"JoinUnmarshalers", Func, 27, "func(us ...*Unmarshalers) *Unmarshalers"}, + {"Marshal", Func, 27, "func(in any, opts ...Options) (out []byte, err error)"}, + {"MarshalEncode", Func, 27, "func(out *jsontext.Encoder, in any, opts ...Options) (err error)"}, + {"MarshalFunc", Func, 27, "func[T any](fn func(T) ([]byte, error)) *Marshalers"}, + {"MarshalToFunc", Func, 27, "func[T any](fn func(*jsontext.Encoder, T) error) *Marshalers"}, + {"MarshalWrite", Func, 27, "func(out io.Writer, in any, opts ...Options) (err error)"}, + {"Marshaler", Type, 27, ""}, + {"MarshalerTo", Type, 27, ""}, + {"Marshalers", Type, 27, ""}, + {"MatchCaseInsensitiveNames", Func, 27, "func(v bool) Options"}, + {"OmitZeroStructFields", Func, 27, "func(v bool) Options"}, + {"Options", Type, 27, ""}, + {"RejectUnknownMembers", Func, 27, "func(v bool) Options"}, + {"SemanticError", Type, 27, ""}, + {"SemanticError.ByteOffset", Field, 27, ""}, + {"SemanticError.Err", Field, 27, ""}, + {"SemanticError.GoType", Field, 27, ""}, + {"SemanticError.JSONKind", Field, 27, ""}, + {"SemanticError.JSONPointer", Field, 27, ""}, + {"SemanticError.JSONValue", Field, 27, ""}, + {"StringifyNumbers", Func, 27, "func(v bool) Options"}, + {"Unmarshal", Func, 27, "func(in []byte, out any, opts ...Options) (err error)"}, + {"UnmarshalDecode", Func, 27, "func(in *jsontext.Decoder, out any, opts ...Options) (err error)"}, + {"UnmarshalFromFunc", Func, 27, "func[T any](fn func(*jsontext.Decoder, T) error) *Unmarshalers"}, + {"UnmarshalFunc", Func, 27, "func[T any](fn func([]byte, T) error) *Unmarshalers"}, + {"UnmarshalRead", Func, 27, "func(in io.Reader, out any, opts ...Options) (err error)"}, + {"Unmarshaler", Type, 27, ""}, + {"UnmarshalerFrom", Type, 27, ""}, + {"Unmarshalers", Type, 27, ""}, + {"WithMarshalers", Func, 27, "func(v *Marshalers) Options"}, + {"WithUnmarshalers", Func, 27, "func(v *Unmarshalers) Options"}, + }, "encoding/pem": { {"Block", Type, 0, ""}, {"Block.Bytes", Field, 0, ""}, @@ -5953,6 +6229,7 @@ var PackageSymbols = map[string][]Symbol{ {"Shift", Func, 5, "func(x Value, op token.Token, s uint) Value"}, {"Sign", Func, 5, "func(x Value) int"}, {"String", Const, 5, ""}, + {"StringLen", Func, 27, "func(x Value) int64"}, {"StringVal", Func, 5, "func(x Value) string"}, {"ToComplex", Func, 6, "func(x Value) Value"}, {"ToFloat", Func, 6, "func(x Value) Value"}, @@ -6134,6 +6411,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*ErrorList).Add", Method, 0, ""}, {"(*ErrorList).RemoveMultiples", Method, 0, ""}, {"(*ErrorList).Reset", Method, 0, ""}, + {"(*Scanner).End", Method, 27, ""}, {"(*Scanner).Init", Method, 0, ""}, {"(*Scanner).Scan", Method, 0, ""}, {"(Error).Error", Method, 0, ""}, @@ -6173,6 +6451,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*File).SetLines", Method, 0, ""}, {"(*File).SetLinesForContent", Method, 0, ""}, {"(*File).Size", Method, 0, ""}, + {"(*File).String", Method, 27, ""}, {"(*FileSet).AddExistingFiles", Method, 25, ""}, {"(*FileSet).AddFile", Method, 0, ""}, {"(*FileSet).Base", Method, 0, ""}, @@ -6480,6 +6759,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Tuple).Variables", Method, 24, ""}, {"(*TypeList).At", Method, 18, ""}, {"(*TypeList).Len", Method, 18, ""}, + {"(*TypeList).String", Method, 27, ""}, {"(*TypeList).Types", Method, 24, ""}, {"(*TypeName).Exported", Method, 5, ""}, {"(*TypeName).Id", Method, 5, ""}, @@ -6498,6 +6778,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*TypeParam).Underlying", Method, 18, ""}, {"(*TypeParamList).At", Method, 18, ""}, {"(*TypeParamList).Len", Method, 18, ""}, + {"(*TypeParamList).String", Method, 27, ""}, {"(*TypeParamList).TypeParams", Method, 24, ""}, {"(*Union).Len", Method, 18, ""}, {"(*Union).String", Method, 18, ""}, @@ -6522,9 +6803,14 @@ var PackageSymbols = map[string][]Symbol{ {"(Checker).PkgNameOf", Method, 22, ""}, {"(Checker).TypeOf", Method, 5, ""}, {"(Error).Error", Method, 5, ""}, + {"(Hasher).Equal", Method, 27, ""}, + {"(Hasher).Hash", Method, 27, ""}, + {"(HasherIgnoreTags).Equal", Method, 27, ""}, + {"(HasherIgnoreTags).Hash", Method, 27, ""}, {"(Importer).Import", Method, 5, ""}, {"(ImporterFrom).Import", Method, 6, ""}, {"(ImporterFrom).ImportFrom", Method, 6, ""}, + {"(Instance).String", Method, 27, ""}, {"(Object).Exported", Method, 5, ""}, {"(Object).Id", Method, 5, ""}, {"(Object).Name", Method, 5, ""}, @@ -6594,6 +6880,8 @@ var PackageSymbols = map[string][]Symbol{ {"Float32", Const, 5, ""}, {"Float64", Const, 5, ""}, {"Func", Type, 5, ""}, + {"Hasher", Type, 27, ""}, + {"HasherIgnoreTags", Type, 27, ""}, {"Id", Func, 5, "func(pkg *Package, name string) string"}, {"Identical", Func, 5, "func(x Type, y Type) bool"}, {"IdenticalIgnoreTags", Func, 8, "func(x Type, y Type) bool"}, @@ -6828,9 +7116,13 @@ var PackageSymbols = map[string][]Symbol{ {"(*Hash).Write", Method, 14, ""}, {"(*Hash).WriteByte", Method, 14, ""}, {"(*Hash).WriteString", Method, 14, ""}, + {"(ComparableHasher).Equal", Method, 27, ""}, + {"(ComparableHasher).Hash", Method, 27, ""}, {"Bytes", Func, 19, "func(seed Seed, b []byte) uint64"}, {"Comparable", Func, 24, "func[T comparable](seed Seed, v T) uint64"}, + {"ComparableHasher", Type, 27, ""}, {"Hash", Type, 14, ""}, + {"Hasher", Type, 27, ""}, {"MakeSeed", Func, 14, "func() Seed"}, {"Seed", Type, 14, ""}, {"String", Func, 19, "func(seed Seed, s string) uint64"}, @@ -7986,6 +8278,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Int).CmpAbs", Method, 10, ""}, {"(*Int).Div", Method, 0, ""}, {"(*Int).DivMod", Method, 0, ""}, + {"(*Int).Divide", Method, 27, ""}, {"(*Int).Exp", Method, 0, ""}, {"(*Int).FillBytes", Method, 15, ""}, {"(*Int).Float64", Method, 21, ""}, @@ -8070,9 +8363,11 @@ var PackageSymbols = map[string][]Symbol{ {"Accuracy", Type, 5, ""}, {"AwayFromZero", Const, 5, ""}, {"Below", Const, 5, ""}, + {"Ceil", Const, 27, ""}, {"ErrNaN", Type, 5, ""}, {"Exact", Const, 5, ""}, {"Float", Type, 5, ""}, + {"Floor", Const, 27, ""}, {"Int", Type, 0, ""}, {"Jacobi", Func, 5, "func(x *Int, y *Int) int"}, {"MaxBase", Const, 0, ""}, @@ -8084,12 +8379,14 @@ var PackageSymbols = map[string][]Symbol{ {"NewRat", Func, 0, "func(a int64, b int64) *Rat"}, {"ParseFloat", Func, 5, "func(s string, base int, prec uint, mode RoundingMode) (f *Float, b int, err error)"}, {"Rat", Type, 0, ""}, + {"Round", Const, 27, ""}, {"RoundingMode", Type, 5, ""}, {"ToNearestAway", Const, 5, ""}, {"ToNearestEven", Const, 5, ""}, {"ToNegativeInf", Const, 5, ""}, {"ToPositiveInf", Const, 5, ""}, {"ToZero", Const, 5, ""}, + {"Trunc", Const, 27, ""}, {"Word", Type, 0, ""}, }, "math/bits": { @@ -8241,6 +8538,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Rand).Int64", Method, 22, ""}, {"(*Rand).Int64N", Method, 22, ""}, {"(*Rand).IntN", Method, 22, ""}, + {"(*Rand).N", Method, 27, ""}, {"(*Rand).NormFloat64", Method, 22, ""}, {"(*Rand).Perm", Method, 22, ""}, {"(*Rand).Shuffle", Method, 22, ""}, @@ -8855,6 +9153,7 @@ var PackageSymbols = map[string][]Symbol{ {"CrossOriginProtection", Type, 25, ""}, {"DefaultClient", Var, 0, ""}, {"DefaultMaxHeaderBytes", Const, 0, ""}, + {"DefaultMaxHeaderValueCount", Const, 27, ""}, {"DefaultMaxIdleConnsPerHost", Const, 0, ""}, {"DefaultServeMux", Var, 0, ""}, {"DefaultTransport", Var, 0, ""}, @@ -8936,7 +9235,7 @@ var PackageSymbols = map[string][]Symbol{ {"NoBody", Var, 8, ""}, {"NotFound", Func, 0, "func(w ResponseWriter, r *Request)"}, {"NotFoundHandler", Func, 0, "func() Handler"}, - {"ParseCookie", Func, 23, "func(line string) ([]*Cookie, error)"}, + {"ParseCookie", Func, 23, "func(line string) (#rv1 []*Cookie, #rv2 error)"}, {"ParseHTTPVersion", Func, 0, "func(vers string) (major int, minor int, ok bool)"}, {"ParseSetCookie", Func, 23, "func(line string) (*Cookie, error)"}, {"ParseTime", Func, 1, "func(text string) (t time.Time, err error)"}, @@ -9012,12 +9311,14 @@ var PackageSymbols = map[string][]Symbol{ {"Server.BaseContext", Field, 13, ""}, {"Server.ConnContext", Field, 13, ""}, {"Server.ConnState", Field, 3, ""}, + {"Server.DisableClientPriority", Field, 27, ""}, {"Server.DisableGeneralOptionsHandler", Field, 20, ""}, {"Server.ErrorLog", Field, 3, ""}, {"Server.HTTP2", Field, 24, ""}, {"Server.Handler", Field, 0, ""}, {"Server.IdleTimeout", Field, 8, ""}, {"Server.MaxHeaderBytes", Field, 0, ""}, + {"Server.MaxHeaderValueCount", Field, 27, ""}, {"Server.Protocols", Field, 24, ""}, {"Server.ReadHeaderTimeout", Field, 8, ""}, {"Server.ReadTimeout", Field, 0, ""}, @@ -9177,6 +9478,7 @@ var PackageSymbols = map[string][]Symbol{ {"NewRequestWithContext", Func, 23, "func(ctx context.Context, method string, target string, body io.Reader) *http.Request"}, {"NewServer", Func, 0, "func(handler http.Handler) *Server"}, {"NewTLSServer", Func, 0, "func(handler http.Handler) *Server"}, + {"NewTestServer", Func, 27, "func(t testing.TB, handler http.Handler) *Server"}, {"NewUnstartedServer", Func, 0, "func(handler http.Handler) *Server"}, {"ResponseRecorder", Type, 0, ""}, {"ResponseRecorder.Body", Field, 0, ""}, @@ -9547,6 +9849,7 @@ var PackageSymbols = map[string][]Symbol{ {"(*Error).Timeout", Method, 6, ""}, {"(*Error).Unwrap", Method, 13, ""}, {"(*URL).AppendBinary", Method, 24, ""}, + {"(*URL).Clone", Method, 27, ""}, {"(*URL).EscapedFragment", Method, 15, ""}, {"(*URL).EscapedPath", Method, 5, ""}, {"(*URL).Hostname", Method, 8, ""}, @@ -9567,6 +9870,7 @@ var PackageSymbols = map[string][]Symbol{ {"(EscapeError).Error", Method, 0, ""}, {"(InvalidHostError).Error", Method, 6, ""}, {"(Values).Add", Method, 0, ""}, + {"(Values).Clone", Method, 27, ""}, {"(Values).Del", Method, 0, ""}, {"(Values).Encode", Method, 0, ""}, {"(Values).Get", Method, 0, ""}, @@ -10744,6 +11048,7 @@ var PackageSymbols = map[string][]Symbol{ {"ContainsRune", Func, 0, "func(s string, r rune) bool"}, {"Count", Func, 0, "func(s string, substr string) int"}, {"Cut", Func, 18, "func(s string, sep string) (before string, after string, found bool)"}, + {"CutLast", Func, 27, "func(s string, sep string) (before string, after string, found bool)"}, {"CutPrefix", Func, 20, "func(s string, prefix string) (after string, found bool)"}, {"CutSuffix", Func, 20, "func(s string, suffix string) (before string, found bool)"}, {"EqualFold", Func, 0, "func(s string, t string) bool"}, @@ -17367,6 +17672,9 @@ var PackageSymbols = map[string][]Symbol{ {"Testing", Func, 21, "func() bool"}, {"Verbose", Func, 1, "func() bool"}, }, + "testing/cryptotest": { + {"SetGlobalRandom", Func, 26, "func(t *testing.T, seed uint64)"}, + }, "testing/fstest": { {"(MapFS).Glob", Method, 16, ""}, {"(MapFS).Lstat", Method, 25, ""}, @@ -17424,6 +17732,7 @@ var PackageSymbols = map[string][]Symbol{ {"TestHandler", Func, 21, "func(h slog.Handler, results func() []map[string]any) error"}, }, "testing/synctest": { + {"Sleep", Func, 27, "func(d time.Duration)"}, {"Test", Func, 25, "func(t *testing.T, f func(*testing.T))"}, {"Wait", Func, 25, "func()"}, }, @@ -17928,6 +18237,7 @@ var PackageSymbols = map[string][]Symbol{ {"Bassa_Vah", Var, 4, ""}, {"Batak", Var, 0, ""}, {"Bengali", Var, 0, ""}, + {"Beria_Erfe", Var, 27, ""}, {"Bhaiksuki", Var, 7, ""}, {"Bidi_Control", Var, 0, ""}, {"Bopomofo", Var, 0, ""}, @@ -17977,6 +18287,7 @@ var PackageSymbols = map[string][]Symbol{ {"Extender", Var, 0, ""}, {"FoldCategory", Var, 0, ""}, {"FoldScript", Var, 0, ""}, + {"Garay", Var, 27, ""}, {"Georgian", Var, 0, ""}, {"Glagolitic", Var, 0, ""}, {"Gothic", Var, 0, ""}, @@ -17986,6 +18297,7 @@ var PackageSymbols = map[string][]Symbol{ {"Gujarati", Var, 0, ""}, {"Gunjala_Gondi", Var, 13, ""}, {"Gurmukhi", Var, 0, ""}, + {"Gurung_Khema", Var, 27, ""}, {"Han", Var, 0, ""}, {"Hangul", Var, 0, ""}, {"Hanifi_Rohingya", Var, 13, ""}, @@ -17997,6 +18309,9 @@ var PackageSymbols = map[string][]Symbol{ {"Hyphen", Var, 0, ""}, {"IDS_Binary_Operator", Var, 0, ""}, {"IDS_Trinary_Operator", Var, 0, ""}, + {"IDS_Unary_Operator", Var, 27, ""}, + {"ID_Compat_Math_Continue", Var, 27, ""}, + {"ID_Compat_Math_Start", Var, 27, ""}, {"Ideographic", Var, 0, ""}, {"Imperial_Aramaic", Var, 0, ""}, {"In", Func, 2, "func(r rune, ranges ...*RangeTable) bool"}, @@ -18030,6 +18345,7 @@ var PackageSymbols = map[string][]Symbol{ {"Khmer", Var, 0, ""}, {"Khojki", Var, 4, ""}, {"Khudawadi", Var, 4, ""}, + {"Kirat_Rai", Var, 27, ""}, {"L", Var, 0, ""}, {"LC", Var, 25, ""}, {"Lao", Var, 0, ""}, @@ -18073,6 +18389,7 @@ var PackageSymbols = map[string][]Symbol{ {"Miao", Var, 1, ""}, {"Mn", Var, 0, ""}, {"Modi", Var, 4, ""}, + {"Modifier_Combining_Mark", Var, 27, ""}, {"Mongolian", Var, 0, ""}, {"Mro", Var, 4, ""}, {"Multani", Var, 5, ""}, @@ -18093,6 +18410,7 @@ var PackageSymbols = map[string][]Symbol{ {"Nyiakeng_Puachue_Hmong", Var, 14, ""}, {"Ogham", Var, 0, ""}, {"Ol_Chiki", Var, 0, ""}, + {"Ol_Onal", Var, 27, ""}, {"Old_Hungarian", Var, 5, ""}, {"Old_Italic", Var, 0, ""}, {"Old_North_Arabian", Var, 4, ""}, @@ -18162,6 +18480,7 @@ var PackageSymbols = map[string][]Symbol{ {"Sharada", Var, 1, ""}, {"Shavian", Var, 0, ""}, {"Siddham", Var, 4, ""}, + {"Sidetic", Var, 27, ""}, {"SignWriting", Var, 5, ""}, {"SimpleFold", Func, 0, "func(r rune) rune"}, {"Sinhala", Var, 0, ""}, @@ -18175,6 +18494,7 @@ var PackageSymbols = map[string][]Symbol{ {"Space", Var, 0, ""}, {"SpecialCase", Type, 0, ""}, {"Sundanese", Var, 0, ""}, + {"Sunuwar", Var, 27, ""}, {"Syloti_Nagri", Var, 0, ""}, {"Symbol", Var, 0, ""}, {"Syriac", Var, 0, ""}, @@ -18183,6 +18503,7 @@ var PackageSymbols = map[string][]Symbol{ {"Tai_Le", Var, 0, ""}, {"Tai_Tham", Var, 0, ""}, {"Tai_Viet", Var, 0, ""}, + {"Tai_Yo", Var, 27, ""}, {"Takri", Var, 1, ""}, {"Tamil", Var, 0, ""}, {"Tangsa", Var, 21, ""}, @@ -18200,7 +18521,10 @@ var PackageSymbols = map[string][]Symbol{ {"ToLower", Func, 0, "func(r rune) rune"}, {"ToTitle", Func, 0, "func(r rune) rune"}, {"ToUpper", Func, 0, "func(r rune) rune"}, + {"Todhri", Var, 27, ""}, + {"Tolong_Siki", Var, 27, ""}, {"Toto", Var, 21, ""}, + {"Tulu_Tigalari", Var, 27, ""}, {"TurkishCase", Var, 0, ""}, {"Ugaritic", Var, 0, ""}, {"Unified_Ideograph", Var, 0, ""}, @@ -18268,6 +18592,21 @@ var PackageSymbols = map[string][]Symbol{ {"String", Func, 0, ""}, {"StringData", Func, 0, ""}, }, + "uuid": { + {"(*UUID).UnmarshalText", Method, 27, ""}, + {"(UUID).AppendText", Method, 27, ""}, + {"(UUID).Compare", Method, 27, ""}, + {"(UUID).MarshalText", Method, 27, ""}, + {"(UUID).String", Method, 27, ""}, + {"Max", Func, 27, "func() UUID"}, + {"MustParse", Func, 27, "func(s string) UUID"}, + {"New", Func, 27, "func() UUID"}, + {"NewV4", Func, 27, "func() UUID"}, + {"NewV7", Func, 27, "func() UUID"}, + {"Nil", Func, 27, "func() UUID"}, + {"Parse", Func, 27, "func(s string) (UUID, error)"}, + {"UUID", Type, 27, ""}, + }, "weak": { {"(Pointer).Value", Method, 24, ""}, {"Make", Func, 24, "func[T any](ptr *T) Pointer[T]"}, diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go index 27a2b17..2e05de4 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go +++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go @@ -11,7 +11,9 @@ import ( // CoreType returns the core type of T or nil if T does not have a core type. // -// See https://go.dev/ref/spec#Core_types for the definition of a core type. +// As of Go1.25, the notion of a core type has been removed from the language spec. +// See https://go.dev/blog/coretypes for more details. +// TODO(mkalil): We should eventually consider removing all uses of CoreType. func CoreType(T types.Type) types.Type { U := T.Underlying() if _, ok := U.(*types.Interface); !ok { @@ -34,7 +36,7 @@ func CoreType(T types.Type) types.Type { } if identical == len(terms) { - // https://go.dev/ref/spec#Core_types + // From the deprecated core types spec: // "There is a single type U which is the underlying type of all types in the type set of T" return U } @@ -42,7 +44,7 @@ func CoreType(T types.Type) types.Type { if !ok { return nil // no core type as identical < len(terms) and U is not a channel. } - // https://go.dev/ref/spec#Core_types + // From the deprecated core types spec: // "the type chan E if T contains only bidirectional channels, or the type chan<- E or // <-chan E depending on the direction of the directional channels present." for chans := identical; chans < len(terms); chans++ { diff --git a/vendor/golang.org/x/tools/internal/typeparams/free.go b/vendor/golang.org/x/tools/internal/typeparams/free.go index 709d2fc..4c39187 100644 --- a/vendor/golang.org/x/tools/internal/typeparams/free.go +++ b/vendor/golang.org/x/tools/internal/typeparams/free.go @@ -6,8 +6,6 @@ package typeparams import ( "go/types" - - "golang.org/x/tools/internal/aliases" ) // Free is a memoization of the set of free type parameters within a @@ -38,7 +36,7 @@ func (w *Free) Has(typ types.Type) (res bool) { break case *types.Alias: - if aliases.TypeParams(t).Len() > aliases.TypeArgs(t).Len() { + if t.TypeParams().Len() > t.TypeArgs().Len() { return true // This is an uninstantiated Alias. } // The expansion of an alias can have free type parameters, diff --git a/vendor/golang.org/x/tools/internal/typesinternal/assignedaddress.go b/vendor/golang.org/x/tools/internal/typesinternal/assignedaddress.go new file mode 100644 index 0000000..020defc --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/assignedaddress.go @@ -0,0 +1,128 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" +) + +// IsAssignedOrAddressTaken reports whether the expression cur denotes a +// variable and appears in a context that assigns it or that takes its address, +// potentially leading to indirect assignment. +// +// These examples cause IsAssignedOrAddressTaken on the identifier for x to +// return true: +// +// x = 1 +// x++ +// x[i] = 1 (assume x is an array) +// x.a[i] = 1 (assume x.a is a non-pointer struct field) +// use(&x) +// +// whereas these cause it to return false: +// +// y = x +// f(x) +// use(x.a[i]) +// use(*x) +// +// The expression may itself be a compound, for example: +// +// use(&(*ptr)) => IsAssignedOrAddressTaken("*ptr") = true +// x.a[i] = 1 => IsAssignedOrAddressTaken("x.a") = true +// _ = x.a[i] => IsAssignedOrAddressTaken("x.a") = false +// +// A variable's declaration is not considered to be an assignment: +// +// var x int => IsAssignedOrAddressTaken(x) = false +// x := 1 => IsAssignedOrAddressTaken(x) = false +// +// TODO(adonovan): revisit the surprising behavior for declarations. +func IsAssignedOrAddressTaken(info *types.Info, cur inspector.Cursor) bool { + // Unfortunately we can't simply use info.Types[e].Assignable() + // as it is always true for a variable even when that variable is + // used only as an r-value. So we must inspect enclosing syntax. +outer: + // Ascend to outermost aggregate of which + // original cur is a part: + // x -> (x) | x.f | x[i] | x[i:j] + for cur = range cur.Enclosing() { + switch cur.ParentEdgeKind() { + case edge.ParenExpr_X: + // If x is an lvalue, then (x) is an lvalue. + case edge.SelectorExpr_X: + // If x is an lvalue, then x.f is an lvalue iff + // the selection does not traverse a pointer. + sel := cur.Parent().Node().(*ast.SelectorExpr) + if seln, ok := info.Selections[sel]; ok { + // Note: there is a bug in Indirect() where it spuriously returns true + // when both the selection receiver and parameter are pointers. However, + // it's okay in this case because there is no address taken when a + // pointer receiver method is called on a pointer type. + if seln.Indirect() { + return false + } + if seln.Kind() == types.MethodVal { + sig := seln.Obj().Type().(*types.Signature) + if is[*types.Pointer](sig.Recv().Type().Underlying()) { + t := seln.Recv() + // The receiver may be an embedded field, so we need + // to get the inner-most type (right before the method + // call in seln.Index()) + for _, idx := range seln.Index()[:len(seln.Index())-1] { + t = t.Underlying().(*types.Struct).Field(idx).Type() + } + if !is[*types.Pointer](t.Underlying()) { + return true // takes address of receiver + } + } + return false + } + } + case edge.IndexExpr_X, edge.SliceExpr_X: + // If x[i] or x[i:j] is an lvalue, + // then x is an lvalue iff x is an array. + if !is[*types.Array](info.TypeOf(cur.Node().(ast.Expr)).Underlying()) { + return false + } + default: + break outer + } + } + switch cur.ParentEdgeKind() { + case edge.AssignStmt_Lhs: + assign := cur.Parent().Node().(*ast.AssignStmt) + if assign.Tok != token.DEFINE { + return true // x = j or x += j + } + id := cur.Node().(*ast.Ident) + // Re-assigned identifiers are recorded in the Uses map. + if _, ok := info.Uses[id]; ok { + return true // reassignment of x (x, y := 1, 2) + } + case edge.RangeStmt_Key, edge.RangeStmt_Value: + rng := cur.Parent().Node().(*ast.RangeStmt) + if rng.Tok == token.ASSIGN { + return true // "for k, v = range x" is like an AssignStmt to k, v + } + case edge.IncDecStmt_X: + return true // x++, x-- + case edge.UnaryExpr_X: + if cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND { + return true // &x + } + } + return false +} + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/classify_call.go b/vendor/golang.org/x/tools/internal/typesinternal/classify_call.go index 7ebe976..d5c40a2 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/classify_call.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/classify_call.go @@ -8,7 +8,6 @@ import ( "fmt" "go/ast" "go/types" - _ "unsafe" // for go:linkname hack ) // CallKind describes the function position of an [*ast.CallExpr]. @@ -72,11 +71,15 @@ func ClassifyCall(info *types.Info, call *ast.CallExpr) CallKind { if tv.IsBuiltin() { return CallBuiltin } - obj := info.Uses[UsedIdent(info, call.Fun)] + id := UsedIdent(info, call.Fun) + if id == nil { + return CallDynamic + } + obj := info.Uses[id] // Classify the call by the type of the object, if any. switch obj := obj.(type) { case *types.Func: - if interfaceMethod(obj) { + if isInterfaceMethod(obj) { return CallInterface } return CallStatic @@ -127,11 +130,69 @@ func ClassifyCall(info *types.Info, call *ast.CallExpr) CallKind { // Note: if e is an instantiated function or method, UsedIdent returns // the corresponding generic function or method on the generic type. func UsedIdent(info *types.Info, e ast.Expr) *ast.Ident { - return usedIdent(info, e) + if info.Types == nil || info.Uses == nil { + panic("one of info.Types or info.Uses is nil; both must be populated") + } + // Look through type instantiation if necessary. + switch d := ast.Unparen(e).(type) { + case *ast.IndexExpr: + if info.Types[d.Index].IsType() { + e = d.X + } + case *ast.IndexListExpr: + e = d.X + } + + switch e := ast.Unparen(e).(type) { + // info.Uses always has the object we want, even for selector expressions. + // We don't need info.Selections. + // See go/types/recording.go:recordSelection. + case *ast.Ident: + return e + case *ast.SelectorExpr: + return e.Sel + } + return nil } -//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent -func usedIdent(info *types.Info, e ast.Expr) *ast.Ident +// See [golang.org/x/tools/go/types/typeutil.Callee]. +func Callee(info *types.Info, call *ast.CallExpr) types.Object { + id := UsedIdent(info, call.Fun) + if id == nil { + return nil + } + obj := info.Uses[id] + if obj == nil { + return nil + } + if _, ok := obj.(*types.TypeName); ok { + return nil + } + if fn, ok := obj.(*types.Func); ok { + return fn.Origin() + } + return obj +} -//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod -func interfaceMethod(f *types.Func) bool +// See [golang.org/x/tools/go/types/typeutil.StaticCallee]. +func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func { + id := UsedIdent(info, call.Fun) + if id == nil { + return nil + } + obj := info.Uses[id] + if obj == nil { + return nil + } + fn, _ := obj.(*types.Func) + if fn == nil || isInterfaceMethod(fn) { + return nil + } + return fn.Origin() +} + +// isInterfaceMethod reports whether its argument is a method of an interface. +func isInterfaceMethod(f *types.Func) bool { + recv := f.Signature().Recv() + return recv != nil && types.IsInterface(recv.Type()) +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/element.go b/vendor/golang.org/x/tools/internal/typesinternal/element.go index 5fe4d8a..bab37fb 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/element.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/element.go @@ -7,8 +7,6 @@ package typesinternal import ( "fmt" "go/types" - - "golang.org/x/tools/go/types/typeutil" ) // ForEachElement calls f for type T and each type reachable from its @@ -16,27 +14,30 @@ import ( // type constructors; in addition, for each named type N, the type *N // is added to the result as it may have additional methods. // -// The caller must provide an initially empty set used to de-duplicate -// identical types, potentially across multiple calls to ForEachElement. -// (Its final value holds all the elements seen, matching the arguments -// passed to f.) +// The access argument passed to f indicates whether the type is +// inaccessible to reflection (for example, intermediate tuple types +// or underlying types of named types). // -// TODO(adonovan): share/harmonize with go/callgraph/rta. -func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) { - var visit func(T types.Type, skip bool) - visit = func(T types.Type, skip bool) { - if !skip { - if seen, _ := rtypes.Set(T, true).(bool); seen { - return // de-dup - } - - f(T) // notify caller of new element type +// The result of f indicates whether the caller has seen this type +// already, so we can prune the traversal. +// +// methodSetOf abstracts (*typeutil.MethodSetCache).MethodSet, +// avoiding an import cycle. +func ForEachElement(methodSetOf func(types.Type) *types.MethodSet, T types.Type, f func(T types.Type, access bool) bool) { + var visit func(T types.Type, access bool) + visit = func(T types.Type, access bool) { + if f(T, access) { + return // duplicate; prune descent } // Recursion over signatures of each method. - tmset := msets.MethodSet(T) + tmset := methodSetOf(T) for method := range tmset.Methods() { sig := method.Type().(*types.Signature) + if sig.TypeParams() != nil { + continue // skip type-parameterized methods + } + // It is tempting to call visit(sig, false) // but, as noted in golang.org/cl/65450043, // the Signature.Recv field is ignored by @@ -61,13 +62,13 @@ func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T type // // TODO(adonovan): document whether or not it is // safe to skip non-exported methods (as RTA does). - visit(sig.Params(), true) // skip the Tuple - visit(sig.Results(), true) // skip the Tuple + visit(sig.Params(), false) // the Tuple is inaccessible + visit(sig.Results(), false) // the Tuple is inaccessible } switch T := T.(type) { case *types.Alias: - visit(types.Unalias(T), skip) // emulates the pre-Alias behavior + visit(types.Unalias(T), access) // emulates the pre-Alias behavior case *types.Basic: // nop @@ -76,58 +77,58 @@ func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T type // nop---handled by recursion over method set. case *types.Pointer: - visit(T.Elem(), false) + visit(T.Elem(), true) case *types.Slice: - visit(T.Elem(), false) + visit(T.Elem(), true) case *types.Chan: - visit(T.Elem(), false) + visit(T.Elem(), true) case *types.Map: - visit(T.Key(), false) - visit(T.Elem(), false) + visit(T.Key(), true) + visit(T.Elem(), true) case *types.Signature: if T.Recv() != nil { panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) } - visit(T.Params(), true) // skip the Tuple - visit(T.Results(), true) // skip the Tuple + visit(T.Params(), false) // the Tuple is inaccessible + visit(T.Results(), false) // the Tuple is inaccessible case *types.Named: // A pointer-to-named type can be derived from a named // type via reflection. It may have methods too. - visit(types.NewPointer(T), false) + visit(types.NewPointer(T), true) // Consider 'type T struct{S}' where S has methods. // Reflection provides no way to get from T to struct{S}, // only to S, so the method set of struct{S} is unwanted, - // so set 'skip' flag during recursion. - visit(T.Underlying(), true) // skip the unnamed type + // so mark it inaccessible during recursion. + visit(T.Underlying(), false) // skip the unnamed type case *types.Array: - visit(T.Elem(), false) + visit(T.Elem(), true) case *types.Struct: for i, n := 0, T.NumFields(); i < n; i++ { // TODO(adonovan): document whether or not // it is safe to skip non-exported fields. - visit(T.Field(i).Type(), false) + visit(T.Field(i).Type(), true) } case *types.Tuple: for i, n := 0, T.Len(); i < n; i++ { - visit(T.At(i).Type(), false) + visit(T.At(i).Type(), true) } case *types.TypeParam, *types.Union: // forEachReachable must not be called on parameterized types. - panic(T) + panic(fmt.Sprintf("ForEachElement called on type containing %T", T)) default: - panic(T) + panic(fmt.Sprintf("ForEachElement called on unexpected type %T", T)) } } - visit(T, false) + visit(T, true) } diff --git a/vendor/golang.org/x/tools/internal/typesinternal/toonew.go b/vendor/golang.org/x/tools/internal/typesinternal/toonew.go index cc86487..386c59c 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/toonew.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/toonew.go @@ -13,20 +13,30 @@ import ( // TooNewStdSymbols computes the set of package-level symbols // exported by pkg that are not available at the specified version. -// The result maps each symbol to its minimum version. // // The pkg is allowed to contain type errors. -func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]string { - disallowed := make(map[types.Object]string) +func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]stdlib.Symbol { + disallowed := make(map[types.Object]stdlib.Symbol) + + // Some symbols are accessible before their release but + // only with specific build tags unknown to us here. + // Avoid false positives in such cases. + if pkg.Path() == "testing/synctest" && versions.AtLeast(version, "go1.24") { + // requires go1.24 && goexperiment.synctest || go1.25 + return disallowed + } + if (pkg.Path() == "encoding/json/v2" || pkg.Path() == "encoding/json/jsontext") && versions.AtLeast(version, "go1.25") { + // requires go1.25 && goexperiment.jsonv2 || go1.27 + return disallowed + } // Pass 1: package-level symbols. symbols := stdlib.PackageSymbols[pkg.Path()] for _, sym := range symbols { - symver := sym.Version.String() - if versions.Before(version, symver) { + if versions.Before(version, sym.Version.String()) { switch sym.Kind { case stdlib.Func, stdlib.Var, stdlib.Const, stdlib.Type: - disallowed[pkg.Scope().Lookup(sym.Name)] = symver + disallowed[pkg.Scope().Lookup(sym.Name)] = sym } } } @@ -60,28 +70,36 @@ func TooNewStdSymbols(pkg *types.Package, version string) map[types.Object]strin // spuriously cause the analyzer to report a reference to a // too-new symbol even though this expression compiles just // fine (with the fake implementation) using go1.21. + var noSym stdlib.Symbol + depth := make(map[types.Object]int) for _, sym := range symbols { - symVersion := sym.Version.String() - if !versions.Before(version, symVersion) { + if !versions.Before(version, sym.Version.String()) { continue // allowed } var obj types.Object + var indices []int switch sym.Kind { case stdlib.Field: typename, name := sym.SplitField() - if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == "" { - obj, _, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name) + if t := pkg.Scope().Lookup(typename); t != nil && disallowed[t] == noSym { + obj, indices, _ = types.LookupFieldOrMethod(t.Type(), false, pkg, name) } case stdlib.Method: ptr, recvname, name := sym.SplitMethod() - if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == "" { - obj, _, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name) + if t := pkg.Scope().Lookup(recvname); t != nil && disallowed[t] == noSym { + obj, indices, _ = types.LookupFieldOrMethod(t.Type(), ptr, pkg, name) } } if obj != nil { - disallowed[obj] = symVersion + // In the presence of embedding, two or more "pkg.T.name" + // strings may map to the same types.Object. + // Prefer the Object with the shorter index path. + if min, ok := depth[obj]; !ok || len(indices) < min { + depth[obj] = len(indices) + disallowed[obj] = sym + } } } diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go index 5100166..9fd48b0 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/types.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -22,10 +22,10 @@ import ( "go/ast" "go/token" "go/types" + "iter" "reflect" "golang.org/x/tools/go/ast/inspector" - "golang.org/x/tools/internal/aliases" ) func SetUsesCgo(conf *types.Config) bool { @@ -142,7 +142,7 @@ var ( func Origin(t NamedOrAlias) NamedOrAlias { switch t := t.(type) { case *types.Alias: - return aliases.Origin(t) + return t.Origin() case *types.Named: return t.Origin() } @@ -195,3 +195,86 @@ func Imports(pkg *types.Package, path string) bool { } return false } + +// ObjectKind returns a description of the object's kind. +// +// from objectKind in go/types +func ObjectKind(obj types.Object) string { + switch obj := obj.(type) { + case *types.PkgName: + return "package name" + case *types.Const: + return "constant" + case *types.TypeName: + if obj.IsAlias() { + return "type alias" + } else if _, ok := obj.Type().(*types.TypeParam); ok { + return "type parameter" + } else { + return "defined type" + } + case *types.Var: + switch obj.Kind() { + case PackageVar: + return "package-level variable" + case LocalVar: + return "local variable" + case RecvVar: + return "receiver" + case ParamVar: + return "parameter" + case ResultVar: + return "result variable" + case FieldVar: + return "struct field" + } + case *types.Func: + if obj.Signature().Recv() != nil { + return "method" + } else { + return "function" + } + case *types.Label: + return "label" + case *types.Builtin: + return "built-in function" + case *types.Nil: + return "untyped nil" + } + return "unknown symbol" +} + +// ImplicitFieldSelections returns the sequence of implicit embedded fields +// traversed by the given selection. It skips the final leaf field or method. +// The boolean component indicates whether the traversal traversed a pointer. +func ImplicitFieldSelections(seln types.Selection) iter.Seq2[*types.Var, bool] { + return func(yield func(*types.Var, bool) bool) { + var ( + t = seln.Recv() + indices = seln.Index() + ) + for _, idx := range indices[:len(indices)-1] { + ptr, isPtr := t.Underlying().(*types.Pointer) + if isPtr { + t = ptr.Elem() + } + structType, ok := t.Underlying().(*types.Struct) + if !ok { + break + } + field := structType.Field(idx) + if !yield(field, isPtr) { + break + } + t = field.Type() + } + } +} + +func TupleOf(elems ...types.Type) *types.Tuple { + params := make([]*types.Var, len(elems)) + for i, elem := range elems { + params[i] = types.NewParam(token.NoPos, nil, "", elem) + } + return types.NewTuple(params...) +} diff --git a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go index d612a71..706ad33 100644 --- a/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go +++ b/vendor/golang.org/x/tools/internal/typesinternal/zerovalue.go @@ -259,13 +259,13 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { case *types.Signature: var params []*ast.Field for v := range t.Params().Variables() { + var names []*ast.Ident + if v.Name() != "" { + names = []*ast.Ident{ast.NewIdent(v.Name())} + } params = append(params, &ast.Field{ - Type: TypeExpr(v.Type(), qual), - Names: []*ast.Ident{ - { - Name: v.Name(), - }, - }, + Type: TypeExpr(v.Type(), qual), + Names: names, }) } if t.Variadic() { @@ -328,10 +328,10 @@ func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { return expr case *types.Struct: - return ast.NewIdent(t.String()) + return ast.NewIdent(types.TypeString(t, qual)) case *types.Interface: - return ast.NewIdent(t.String()) + return ast.NewIdent(types.TypeString(t, qual)) case *types.Union: if t.Len() == 0 { diff --git a/vendor/golang.org/x/tools/internal/versions/features.go b/vendor/golang.org/x/tools/internal/versions/features.go index cdd36c3..360a5b5 100644 --- a/vendor/golang.org/x/tools/internal/versions/features.go +++ b/vendor/golang.org/x/tools/internal/versions/features.go @@ -19,6 +19,7 @@ const ( Go1_24 = "go1.24" Go1_25 = "go1.25" Go1_26 = "go1.26" + Go1_27 = "go1.27" ) // Future is an invalid unknown Go version sometime in the future. diff --git a/vendor/golang.org/x/vuln/cmd/govulncheck/gotypesalias.go b/vendor/golang.org/x/vuln/cmd/govulncheck/gotypesalias.go deleted file mode 100644 index 288c10c..0000000 --- a/vendor/golang.org/x/vuln/cmd/govulncheck/gotypesalias.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2024 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build go1.23 - -//go:debug gotypesalias=1 - -package main - -// Materialize aliases whenever the go toolchain version is after 1.23 (#69772). -// Remove this file after go.mod >= 1.23 (which implies gotypesalias=1). diff --git a/vendor/golang.org/x/vuln/cmd/govulncheck/main.go b/vendor/golang.org/x/vuln/cmd/govulncheck/main.go index 73e3370..b15397d 100644 --- a/vendor/golang.org/x/vuln/cmd/govulncheck/main.go +++ b/vendor/golang.org/x/vuln/cmd/govulncheck/main.go @@ -6,6 +6,7 @@ package main import ( "context" + "errors" "fmt" "os" @@ -23,11 +24,20 @@ func main() { if err == nil { err = cmd.Wait() } - switch err := err.(type) { - case nil: - case interface{ ExitCode() int }: - os.Exit(err.ExitCode()) - default: + if err != nil { + var e interface{ ExitCode() int } + if errors.As(err, &e) { + printErrorToStderr := true + if _, ok := err.(interface{ ExitCode() int }); ok { + // Avoid printing the error to stderr if the exit code error wasn't + // wrapped with another error providing context. + printErrorToStderr = false + } + if printErrorToStderr { + fmt.Fprintln(os.Stderr, err) + } + os.Exit(e.ExitCode()) + } fmt.Fprintln(os.Stderr, err) os.Exit(1) } diff --git a/vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go b/vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go index 49869ce..8cb7d69 100644 --- a/vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go +++ b/vendor/golang.org/x/vuln/internal/buildinfo/additions_buildinfo.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.18 -// +build go1.18 package buildinfo diff --git a/vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go b/vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go index ddbdea0..b94878f 100644 --- a/vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go +++ b/vendor/golang.org/x/vuln/internal/buildinfo/additions_scan.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.18 -// +build go1.18 package buildinfo diff --git a/vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go b/vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go index f29dffa..15243ae 100644 --- a/vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go +++ b/vendor/golang.org/x/vuln/internal/buildinfo/buildinfo.go @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. //go:build go1.18 -// +build go1.18 package buildinfo diff --git a/vendor/golang.org/x/vuln/internal/scan/errors.go b/vendor/golang.org/x/vuln/internal/scan/errors.go index c28e3c5..905e1e6 100644 --- a/vendor/golang.org/x/vuln/internal/scan/errors.go +++ b/vendor/golang.org/x/vuln/internal/scan/errors.go @@ -11,7 +11,7 @@ import ( //lint:file-ignore ST1005 Ignore staticcheck message about error formatting var ( - // ErrVulnerabilitiesFound indicates that vulnerabilities were detected + // errVulnerabilitiesFound indicates that vulnerabilities were detected // when running govulncheck. This returns exit status 3 when running // without the -json flag. errVulnerabilitiesFound = &exitCodeError{message: "vulnerabilities found", code: 3} @@ -25,6 +25,18 @@ var ( // govulncheck and exit with status 2. errUsage = &exitCodeError{message: "invalid usage", code: 2} + // errNoPatterns indicates that no package patterns were provided if the scan level is WantPackages. + errNoPatterns = &exitCodeError{ + message: "no package patterns provided\n\nTo scan the current module, run: govulncheck ./...", + code: 2, + } + + // errNoPackagesMatched indicates that the provided patterns matched no packages if scan level is WantPackages. + errNoPackagesMatched = &exitCodeError{ + message: "no packages matched the provided patterns", + code: 2, + } + // errGoVersionMismatch is used to indicate that there is a mismatch between // the Go version used to build govulncheck and the one currently on PATH. errGoVersionMismatch = errors.New(`Loading packages failed, possibly due to a mismatch between the Go version diff --git a/vendor/golang.org/x/vuln/internal/scan/flags.go b/vendor/golang.org/x/vuln/internal/scan/flags.go index 5512540..e67c0a1 100644 --- a/vendor/golang.org/x/vuln/internal/scan/flags.go +++ b/vendor/golang.org/x/vuln/internal/scan/flags.go @@ -25,6 +25,7 @@ type config struct { test bool show ShowFlag format FormatFlag + version bool env []string } @@ -72,6 +73,7 @@ Usage: cfg.patterns = flags.Args() if version { cfg.show = append(cfg.show, "version") + cfg.version = true } cfg.ScanLevel = govulncheck.ScanLevel(scanFlag) cfg.ScanMode = govulncheck.ScanMode(modeFlag) diff --git a/vendor/golang.org/x/vuln/internal/scan/run.go b/vendor/golang.org/x/vuln/internal/scan/run.go index f29b9d3..560a3ad 100644 --- a/vendor/golang.org/x/vuln/internal/scan/run.go +++ b/vendor/golang.org/x/vuln/internal/scan/run.go @@ -22,9 +22,8 @@ import ( "golang.org/x/vuln/internal/sarif" ) -// RunGovulncheck performs main govulncheck functionality and exits the -// program upon success with an appropriate exit status. Otherwise, -// returns an error. +// RunGovulncheck performs main govulncheck functionality. +// On failure, the returned error wraps an exit code error (see scan.Cmd.Wait). func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Writer, stderr io.Writer, args []string) error { cfg := &config{env: env} if err := parseFlags(cfg, stderr, args); err != nil { @@ -55,6 +54,12 @@ func RunGovulncheck(ctx context.Context, env []string, r io.Reader, stdout io.Wr return err } + if cfg.version { + // If the -version flag is passed, exit before doing anything else. This is different than + // passing -show which includes "version". + return nil + } + incTelemetryFlagCounters(cfg) switch cfg.ScanMode { @@ -104,7 +109,7 @@ func prepareConfig(ctx context.Context, cfg *config, client *client.Client) { // this binary used from the build info. func scannerVersion(cfg *config, bi *debug.BuildInfo) { if bi.Path != "" { - cfg.ScannerName = path.Base(bi.Path) + cfg.ScannerName = strings.TrimSuffix(path.Base(bi.Path), ".test") } if bi.Main.Version != "" && bi.Main.Version != "(devel)" { cfg.ScannerVersion = bi.Main.Version diff --git a/vendor/golang.org/x/vuln/internal/scan/source.go b/vendor/golang.org/x/vuln/internal/scan/source.go index e923211..b34e443 100644 --- a/vendor/golang.org/x/vuln/internal/scan/source.go +++ b/vendor/golang.org/x/vuln/internal/scan/source.go @@ -24,7 +24,7 @@ func runSource(ctx context.Context, handler govulncheck.Handler, cfg *config, cl defer derrors.Wrap(&err, "govulncheck") if cfg.ScanLevel.WantPackages() && len(cfg.patterns) == 0 { - return nil // don't throw an error here + return errNoPatterns } if !gomodExists(dir) { return errNoGoMod @@ -43,7 +43,7 @@ func runSource(ctx context.Context, handler govulncheck.Handler, cfg *config, cl } if cfg.ScanLevel.WantPackages() && len(graph.TopPkgs()) == 0 { - return nil // early exit + return errNoPackagesMatched } return vulncheck.Source(ctx, handler, &cfg.Config, client, graph) } diff --git a/vendor/golang.org/x/vuln/internal/vulncheck/utils.go b/vendor/golang.org/x/vuln/internal/vulncheck/utils.go index e752f4a..fb32a2c 100644 --- a/vendor/golang.org/x/vuln/internal/vulncheck/utils.go +++ b/vendor/golang.org/x/vuln/internal/vulncheck/utils.go @@ -93,7 +93,7 @@ func callGraph(ctx context.Context, prog *ssa.Program, entries []*ssa.Function) // - pointer designation * is skipped // - full path prefix is skipped as well func dbTypeFormat(t types.Type) string { - switch tt := t.(type) { + switch tt := types.Unalias(t).(type) { case *types.Pointer: return dbTypeFormat(tt.Elem()) case *types.Named: diff --git a/vendor/golang.org/x/vuln/scan/scan.go b/vendor/golang.org/x/vuln/scan/scan.go index 0aa9975..459fc36 100644 --- a/vendor/golang.org/x/vuln/scan/scan.go +++ b/vendor/golang.org/x/vuln/scan/scan.go @@ -89,6 +89,16 @@ func (c *Cmd) Start() error { // Wait waits for the command to exit. The command must have been started by // Start. // +// If the command fails to run or does not complete successfully, the returned +// error wraps an error implementing: +// +// interface { +// ExitCode() int +// } +// +// Callers can use errors.As to retrieve the exit code. Other errors may be +// returned for other problems (e.g. I/O issues). +// // Wait releases any resources associated with the Cmd. func (c *Cmd) Wait() error { if c.done == nil { diff --git a/vendor/honnef.co/go/tools/LICENSE b/vendor/honnef.co/go/tools/LICENSE new file mode 100644 index 0000000..dfd0314 --- /dev/null +++ b/vendor/honnef.co/go/tools/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Dominik Honnef + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY b/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY new file mode 100644 index 0000000..f2c0fa9 --- /dev/null +++ b/vendor/honnef.co/go/tools/LICENSE-THIRD-PARTY @@ -0,0 +1,121 @@ +Staticcheck and its related tools make use of third party projects, +either by reusing their code, or by statically linking them into +resulting binaries. These projects are: + +* The Go Programming Language - https://golang.org/ + golang.org/x/mod - https://github.com/golang/mod + golang.org/x/tools - https://github.com/golang/tools + golang.org/x/sys - https://github.com/golang/sys + golang.org/x/xerrors - https://github.com/golang/xerrors + + Copyright (c) 2009 The Go Authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +* github.com/BurntSushi/toml - https://github.com/BurntSushi/toml + + The MIT License (MIT) + + Copyright (c) 2013 TOML authors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +* gogrep - https://github.com/mvdan/gogrep + + Copyright (c) 2017, Daniel Martí. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +* gosmith - https://github.com/dvyukov/gosmith + + Copyright (c) 2014 Dmitry Vyukov. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * The name of Dmitry Vyukov may be used to endorse or promote + products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go b/vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go new file mode 100644 index 0000000..3ad6888 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/callcheck/callcheck.go @@ -0,0 +1,161 @@ +// Package callcheck provides a framework for validating arguments in function calls. +package callcheck + +import ( + "fmt" + "go/ast" + "go/constant" + "go/types" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" +) + +type Call struct { + Pass *analysis.Pass + Instr ir.CallInstruction + Args []*Argument + + Parent *ir.Function + + invalids []string +} + +func (c *Call) Invalid(msg string) { + c.invalids = append(c.invalids, msg) +} + +type Argument struct { + Value Value + invalids []string +} + +type Value struct { + Value ir.Value +} + +func (arg *Argument) Invalid(msg string) { + arg.invalids = append(arg.invalids, msg) +} + +type Check func(call *Call) + +func Analyzer(rules map[string]Check) func(pass *analysis.Pass) (any, error) { + return func(pass *analysis.Pass) (any, error) { + return checkCalls(pass, rules) + } +} + +func checkCalls(pass *analysis.Pass, rules map[string]Check) (any, error) { + cb := func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function) { + obj, ok := callee.Object().(*types.Func) + if !ok { + return + } + + r, ok := rules[typeutil.FuncName(obj)] + if !ok { + return + } + var args []*Argument + irargs := site.Common().Args + if callee.Signature.Recv() != nil { + irargs = irargs[1:] + } + for _, arg := range irargs { + if iarg, ok := arg.(*ir.MakeInterface); ok { + arg = iarg.X + } + args = append(args, &Argument{Value: Value{arg}}) + } + call := &Call{ + Pass: pass, + Instr: site, + Args: args, + Parent: site.Parent(), + } + r(call) + + var astcall *ast.CallExpr + switch source := site.Source().(type) { + case *ast.CallExpr: + astcall = source + case *ast.DeferStmt: + astcall = source.Call + case *ast.GoStmt: + astcall = source.Call + case nil: + // TODO(dh): I am not sure this can actually happen. If it + // can't, we should remove this case, and also stop + // checking for astcall == nil in the code that follows. + default: + panic(fmt.Sprintf("unhandled case %T", source)) + } + + for idx, arg := range call.Args { + for _, e := range arg.invalids { + if astcall != nil { + if idx < len(astcall.Args) { + report.Report(pass, astcall.Args[idx], e) + } else { + // this is an instance of fn1(fn2()) where fn2 + // returns multiple values. Report the error + // at the next-best position that we have, the + // first argument. An example of a check that + // triggers this is checkEncodingBinaryRules. + report.Report(pass, astcall.Args[0], e) + } + } else { + report.Report(pass, site, e) + } + } + } + for _, e := range call.invalids { + report.Report(pass, call.Instr, e) + } + } + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + eachCall(fn, cb) + } + return nil, nil +} + +func eachCall(fn *ir.Function, cb func(caller *ir.Function, site ir.CallInstruction, callee *ir.Function)) { + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + if site, ok := instr.(ir.CallInstruction); ok { + if g := site.Common().StaticCallee(); g != nil { + cb(fn, site, g) + } + } + } + } +} + +func ExtractConstExpectKind(v Value, kind constant.Kind) *ir.Const { + k := extractConst(v.Value) + if k == nil || k.Value == nil || k.Value.Kind() != kind { + return nil + } + return k +} + +func ExtractConst(v Value) *ir.Const { + return extractConst(v.Value) +} + +func extractConst(v ir.Value) *ir.Const { + v = irutil.Flatten(v) + switch v := v.(type) { + case *ir.Const: + return v + case *ir.MakeInterface: + return extractConst(v.X) + default: + return nil + } +} diff --git a/vendor/honnef.co/go/tools/analysis/code/code.go b/vendor/honnef.co/go/tools/analysis/code/code.go new file mode 100644 index 0000000..e87a1d1 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/code/code.go @@ -0,0 +1,591 @@ +// Package code answers structural and type questions about Go code. +package code + +import ( + "fmt" + "go/ast" + "go/build/constraint" + "go/constant" + "go/token" + "go/types" + "go/version" + "path/filepath" + "slices" + "strings" + + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/facts/purity" + "honnef.co/go/tools/analysis/facts/tokenfile" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +type Positioner interface { + Pos() token.Pos +} + +func IsOfStringConvertibleByteSlice(pass *analysis.Pass, expr ast.Expr) bool { + typ, ok := pass.TypesInfo.TypeOf(expr).Underlying().(*types.Slice) + if !ok { + return false + } + elem := types.Unalias(typ.Elem()) + if version.Compare(LanguageVersion(pass, expr), "go1.18") >= 0 { + // Before Go 1.18, one could not directly convert from []T (where 'type T byte') + // to string. See also https://github.com/golang/go/issues/23536. + elem = elem.Underlying() + } + return types.Identical(elem, types.Typ[types.Byte]) +} + +func IsOfPointerToTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool { + ptr, ok := types.Unalias(pass.TypesInfo.TypeOf(expr)).(*types.Pointer) + if !ok { + return false + } + return typeutil.IsTypeWithName(ptr.Elem(), name) +} + +func IsOfTypeWithName(pass *analysis.Pass, expr ast.Expr, name string) bool { + return typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(expr), name) +} + +func IsInTest(pass *analysis.Pass, node Positioner) bool { + // FIXME(dh): this doesn't work for global variables with + // initializers + f := pass.Fset.File(node.Pos()) + return f != nil && strings.HasSuffix(f.Name(), "_test.go") +} + +// IsMain reports whether the package being processed is a package +// main. +func IsMain(pass *analysis.Pass) bool { + return pass.Pkg.Name() == "main" +} + +// IsMainLike reports whether the package being processed is a +// main-like package. A main-like package is a package that is +// package main, or that is intended to be used by a tool framework +// such as cobra to implement a command. +// +// Note that this function errs on the side of false positives; it may +// return true for packages that aren't main-like. IsMainLike is +// intended for analyses that wish to suppress diagnostics for +// main-like packages to avoid false positives. +func IsMainLike(pass *analysis.Pass) bool { + if pass.Pkg.Name() == "main" { + return true + } + for _, imp := range pass.Pkg.Imports() { + if imp.Path() == "github.com/spf13/cobra" { + return true + } + } + return false +} + +func SelectorName(pass *analysis.Pass, expr *ast.SelectorExpr) string { + info := pass.TypesInfo + sel := info.Selections[expr] + if sel == nil { + switch x := expr.X.(type) { + case *ast.Ident: + pkg, ok := info.ObjectOf(x).(*types.PkgName) + if !ok { + return fmt.Sprintf("(%s).%s", info.TypeOf(x), expr.Sel.Name) + } + return fmt.Sprintf("%s.%s", pkg.Imported().Path(), expr.Sel.Name) + case *ast.SelectorExpr: + return fmt.Sprintf("(%s).%s", SelectorName(pass, x), expr.Sel.Name) + default: + panic(fmt.Sprintf("unsupported selector: %v", expr)) + } + } + if v, ok := sel.Obj().(*types.Var); ok && v.IsField() { + return fmt.Sprintf("(%s).%s", typeutil.DereferenceR(sel.Recv()), sel.Obj().Name()) + } else { + return fmt.Sprintf("(%s).%s", sel.Recv(), sel.Obj().Name()) + } +} + +func IsNil(pass *analysis.Pass, expr ast.Expr) bool { + return pass.TypesInfo.Types[expr].IsNil() +} + +func BoolConst(pass *analysis.Pass, expr ast.Expr) bool { + val := pass.TypesInfo.ObjectOf(expr.(*ast.Ident)).(*types.Const).Val() + return constant.BoolVal(val) +} + +func IsBoolConst(pass *analysis.Pass, expr ast.Expr) bool { + // We explicitly don't support typed bools because more often than + // not, custom bool types are used as binary enums and the explicit + // comparison is desired. We err on the side of false negatives and + // treat aliases like other custom types. + + ident, ok := expr.(*ast.Ident) + if !ok { + return false + } + obj := pass.TypesInfo.ObjectOf(ident) + c, ok := obj.(*types.Const) + if !ok { + return false + } + basic, ok := c.Type().(*types.Basic) + if !ok { + return false + } + if basic.Kind() != types.UntypedBool && basic.Kind() != types.Bool { + return false + } + return true +} + +func ExprToInt(pass *analysis.Pass, expr ast.Expr) (int64, bool) { + tv := pass.TypesInfo.Types[expr] + if tv.Value == nil { + return 0, false + } + if tv.Value.Kind() != constant.Int { + return 0, false + } + return constant.Int64Val(tv.Value) +} + +func ExprToString(pass *analysis.Pass, expr ast.Expr) (string, bool) { + val := pass.TypesInfo.Types[expr].Value + if val == nil { + return "", false + } + if val.Kind() != constant.String { + return "", false + } + return constant.StringVal(val), true +} + +func CallName(pass *analysis.Pass, call *ast.CallExpr) string { + // See the comment in typeutil.FuncName for why this doesn't require special handling + // of aliases. + + fun := ast.Unparen(call.Fun) + + // Instantiating a function cannot return another generic function, so doing this once is enough + switch idx := fun.(type) { + case *ast.IndexExpr: + fun = idx.X + case *ast.IndexListExpr: + fun = idx.X + } + + // (foo)[T] is not a valid instantiation, so no need to unparen again. + + switch fun := fun.(type) { + case *ast.SelectorExpr: + fn, ok := pass.TypesInfo.ObjectOf(fun.Sel).(*types.Func) + if !ok { + return "" + } + return typeutil.FuncName(fn) + case *ast.Ident: + obj := pass.TypesInfo.ObjectOf(fun) + switch obj := obj.(type) { + case *types.Func: + return typeutil.FuncName(obj) + case *types.Builtin: + return obj.Name() + default: + return "" + } + default: + return "" + } +} + +func IsCallTo(pass *analysis.Pass, node ast.Node, name string) bool { + // See the comment in typeutil.FuncName for why this doesn't require special handling + // of aliases. + + call, ok := node.(*ast.CallExpr) + if !ok { + return false + } + return CallName(pass, call) == name +} + +func IsCallToAny(pass *analysis.Pass, node ast.Node, names ...string) bool { + // See the comment in typeutil.FuncName for why this doesn't require special handling + // of aliases. + + call, ok := node.(*ast.CallExpr) + if !ok { + return false + } + q := CallName(pass, call) + return slices.Contains(names, q) +} + +func File(pass *analysis.Pass, node Positioner) *ast.File { + m := pass.ResultOf[tokenfile.Analyzer].(map[*token.File]*ast.File) + return m[pass.Fset.File(node.Pos())] +} + +// BuildConstraints returns the build constraints for file f. It considers both //go:build lines as well as +// GOOS and GOARCH in file names. +func BuildConstraints(pass *analysis.Pass, f *ast.File) (constraint.Expr, bool) { + var expr constraint.Expr + for _, cmt := range f.Comments { + if len(cmt.List) == 0 { + continue + } + for _, el := range cmt.List { + if el.Pos() > f.Package { + break + } + if line := el.Text; strings.HasPrefix(line, "//go:build") { + var err error + expr, err = constraint.Parse(line) + if err != nil { + expr = nil + } + break + } + } + } + + name := pass.Fset.PositionFor(f.Pos(), false).Filename + oexpr := constraintsFromName(name) + if oexpr != nil { + if expr == nil { + expr = oexpr + } else { + expr = &constraint.AndExpr{X: expr, Y: oexpr} + } + } + + return expr, expr != nil +} + +func constraintsFromName(name string) constraint.Expr { + name = filepath.Base(name) + name = strings.TrimSuffix(name, ".go") + name = strings.TrimSuffix(name, "_test") + var goos, goarch string + switch strings.Count(name, "_") { + case 0: + // No GOOS or GOARCH in the file name. + case 1: + _, c, _ := strings.Cut(name, "_") + if _, ok := knowledge.KnownGOOS[c]; ok { + goos = c + } else if _, ok := knowledge.KnownGOARCH[c]; ok { + goarch = c + } + default: + n := strings.LastIndex(name, "_") + if _, ok := knowledge.KnownGOOS[name[n+1:]]; ok { + // The file name is *_stuff_GOOS.go + goos = name[n+1:] + } else if _, ok := knowledge.KnownGOARCH[name[n+1:]]; ok { + // The file name is *_GOOS_GOARCH.go or *_stuff_GOARCH.go + goarch = name[n+1:] + _, c, _ := strings.Cut(name[:n], "_") + if _, ok := knowledge.KnownGOOS[c]; ok { + // The file name is *_GOOS_GOARCH.go + goos = c + } + } else { + // The file name could also be something like foo_windows_nonsense.go — and because nonsense + // isn't a known GOARCH, "windows" won't be interpreted as a GOOS, either. + } + } + + var expr constraint.Expr + if goos != "" { + expr = &constraint.TagExpr{Tag: goos} + } + if goarch != "" { + if expr == nil { + expr = &constraint.TagExpr{Tag: goarch} + } else { + expr = &constraint.AndExpr{X: expr, Y: &constraint.TagExpr{Tag: goarch}} + } + } + return expr +} + +// IsGenerated reports whether pos is in a generated file. It ignores +// //line directives. +func IsGenerated(pass *analysis.Pass, pos token.Pos) bool { + _, ok := Generator(pass, pos) + return ok +} + +// Generator returns the generator that generated the file containing +// pos. It ignores //line directives. +func Generator(pass *analysis.Pass, pos token.Pos) (generated.Generator, bool) { + file := pass.Fset.PositionFor(pos, false).Filename + m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator) + g, ok := m[file] + return g, ok +} + +// MayHaveSideEffects reports whether expr may have side effects. If +// the purity argument is nil, this function implements a purely +// syntactic check, meaning that any function call may have side +// effects, regardless of the called function's body. Otherwise, +// purity will be consulted to determine the purity of function calls. +func MayHaveSideEffects(pass *analysis.Pass, expr ast.Expr, purity purity.Result) bool { + switch expr := expr.(type) { + case *ast.BadExpr: + return true + case *ast.Ellipsis: + return MayHaveSideEffects(pass, expr.Elt, purity) + case *ast.FuncLit: + // the literal itself cannot have side effects, only calling it + // might, which is handled by CallExpr. + return false + case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType: + // types cannot have side effects + return false + case *ast.BasicLit: + return false + case *ast.BinaryExpr: + return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Y, purity) + case *ast.CallExpr: + if purity == nil { + return true + } + switch obj := typeutil.Callee(pass.TypesInfo, expr).(type) { + case *types.Func: + if _, ok := purity[obj]; !ok { + return true + } + case *types.Builtin: + switch obj.Name() { + case "len", "cap": + default: + return true + } + default: + return true + } + for _, arg := range expr.Args { + if MayHaveSideEffects(pass, arg, purity) { + return true + } + } + return false + case *ast.CompositeLit: + if MayHaveSideEffects(pass, expr.Type, purity) { + return true + } + for _, elt := range expr.Elts { + if MayHaveSideEffects(pass, elt, purity) { + return true + } + } + return false + case *ast.Ident: + return false + case *ast.IndexExpr: + return MayHaveSideEffects(pass, expr.X, purity) || MayHaveSideEffects(pass, expr.Index, purity) + case *ast.IndexListExpr: + // In theory, none of the checks are necessary, as IndexListExpr only involves types. But there is no harm in + // being safe. + if MayHaveSideEffects(pass, expr.X, purity) { + return true + } + for _, idx := range expr.Indices { + if MayHaveSideEffects(pass, idx, purity) { + return true + } + } + return false + case *ast.KeyValueExpr: + return MayHaveSideEffects(pass, expr.Key, purity) || MayHaveSideEffects(pass, expr.Value, purity) + case *ast.SelectorExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.SliceExpr: + return MayHaveSideEffects(pass, expr.X, purity) || + MayHaveSideEffects(pass, expr.Low, purity) || + MayHaveSideEffects(pass, expr.High, purity) || + MayHaveSideEffects(pass, expr.Max, purity) + case *ast.StarExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.TypeAssertExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case *ast.UnaryExpr: + if MayHaveSideEffects(pass, expr.X, purity) { + return true + } + return expr.Op == token.ARROW || expr.Op == token.AND + case *ast.ParenExpr: + return MayHaveSideEffects(pass, expr.X, purity) + case nil: + return false + default: + panic(fmt.Sprintf("internal error: unhandled type %T", expr)) + } +} + +// LanguageVersion returns the version of the Go language that node has access to. This +// might differ from the version of the Go standard library. +func LanguageVersion(pass *analysis.Pass, node Positioner) string { + // As of Go 1.21, two places can specify the minimum Go version: + // - 'go' directives in go.mod and go.work files + // - individual files by using '//go:build' + // + // Individual files can upgrade to a higher version than the module version. Individual files + // can also downgrade to a lower version, but only if the module version is at least Go 1.21. + // + // The restriction on downgrading doesn't matter to us. All language changes before Go 1.22 will + // not type-check on versions that are too old, and thus never reach our analyzes. In practice, + // such ineffective downgrading will always be useless, as the compiler will not restrict the + // language features used, and doesn't ever rely on minimum versions to restrict the use of the + // standard library. However, for us, both choices (respecting or ignoring ineffective + // downgrading) have equal complexity, but only respecting it has a non-zero chance of reducing + // noisy positives. + // + // The minimum Go versions are exposed via go/ast.File.GoVersion and go/types.Package.GoVersion. + // ast.File's version is populated by the parser, whereas types.Package's version is populated + // from the Go version specified in the types.Config, which is set by our package loader, based + // on the module information provided by go/packages, via 'go list -json'. + // + // As of Go 1.21, standard library packages do not present themselves as modules, and thus do + // not have a version set on their types.Package. In this case, we fall back to the version + // provided by our '-go' flag. In most cases, '-go' defaults to 'module', which falls back to + // the Go version that Staticcheck was built with when no module information exists. In the + // future, the standard library will hopefully be a proper module (see + // https://github.com/golang/go/issues/61174#issuecomment-1622471317). In that case, the version + // of standard library packages will match that of the used Go version. At that point, + // Staticcheck will refuse to work with Go versions that are too new, to avoid misinterpreting + // code due to language changes. + // + // We also lack module information when building in GOPATH mode. In this case, the implied + // language version is at most Go 1.21, as per https://github.com/golang/go/issues/60915. We + // don't handle this yet, and it will not matter until Go 1.22. + // + // It is not clear how per-file downgrading behaves in GOPATH mode. On the one hand, no module + // version at all is provided, which should preclude per-file downgrading. On the other hand, + // https://github.com/golang/go/issues/60915 suggests that the language version is at most 1.21 + // in GOPATH mode, which would allow per-file downgrading. Again it doesn't affect us, as all + // relevant language changes before Go 1.22 will lead to type-checking failures and never reach + // us. + // + // Per-file upgrading is permitted in GOPATH mode. + + // If the file has its own Go version, we will return that. Otherwise, we default to + // the type checker's GoVersion, which is populated from either the Go module, or from + // our '-go' flag. + return pass.TypesInfo.FileVersions[File(pass, node)] +} + +// StdlibVersion returns the version of the Go standard library that node can expect to +// have access to. This might differ from the language version for versions of Go older +// than 1.21. +func StdlibVersion(pass *analysis.Pass, node Positioner) string { + // The Go version as specified in go.mod or via the '-go' flag + n := pass.Pkg.GoVersion() + + f := File(pass, node) + if f == nil { + panic(fmt.Sprintf("no file found for node with position %s", pass.Fset.PositionFor(node.Pos(), false))) + } + + if nf := f.GoVersion; nf != "" { + if version.Compare(n, "go1.21") == -1 { + // Before Go 1.21, the Go version set in go.mod specified the maximum language + // version available to the module. It wasn't uncommon to set the version to + // Go 1.20 but restrict usage of 1.20 functionality (both language and stdlib) + // to files tagged for 1.20, and supporting a lower version overall. As such, + // a file tagged lower than the module version couldn't expect to have access + // to the standard library of the version set in go.mod. + // + // At the same time, a file tagged higher than the module version, while not + // able to use newer language features, would still have been able to use a + // newer standard library. + // + // While Go 1.21's behavior has been backported to 1.19.11 and 1.20.6, users' + // expectations have not. + return nf + } else { + // Go 1.21 and newer refuse to build modules that depend on versions newer + // than the used version of the Go toolchain. This means that in a 1.22 module + // with a file tagged as 1.17, the file can expect to have access to 1.22's + // standard library (but not to 1.22 language features). A file tagged with a + // version higher than the minimum version has access to the newer standard + // library (and language features.) + // + // Do note that strictly speaking we're conflating the Go version and the + // module version in our check. Nothing is stopping a user from using Go 1.17 + // (which didn't implement the new rules for versions in go.mod) to build a Go + // 1.22 module, in which case a file tagged with go1.17 will not have access to the 1.22 + // standard library. However, we believe that if a module requires 1.21 or + // newer, then the author clearly expects the new behavior, and doesn't care + // for the old one. Otherwise they would've specified an older version. + // + // In other words, the module version also specifies what it itself actually means, with + // >=1.21 being a minimum version for the toolchain, and <1.21 being a maximum version for + // the language. + + if version.Compare(nf, n) == 1 { + return nf + } + } + } + + return n +} + +var integerLiteralQ = pattern.MustParse(`(IntegerLiteral tv)`) + +func IntegerLiteral(pass *analysis.Pass, node ast.Node) (types.TypeAndValue, bool) { + m, ok := Match(pass, integerLiteralQ, node) + if !ok { + return types.TypeAndValue{}, false + } + return m.State["tv"].(types.TypeAndValue), true +} + +func IsIntegerLiteral(pass *analysis.Pass, node ast.Node, value constant.Value) bool { + tv, ok := IntegerLiteral(pass, node) + if !ok { + return false + } + return constant.Compare(tv.Value, token.EQL, value) +} + +// IsMethod reports whether expr is a method call of a named method with signature meth. +// If name is empty, it is not checked. +// For now, method expressions (Type.Method(recv, ..)) are not considered method calls. +func IsMethod(pass *analysis.Pass, expr *ast.SelectorExpr, name string, meth *types.Signature) bool { + if name != "" && expr.Sel.Name != name { + return false + } + sel, ok := pass.TypesInfo.Selections[expr] + if !ok || sel.Kind() != types.MethodVal { + return false + } + return types.Identical(sel.Type(), meth) +} + +func RefersTo(pass *analysis.Pass, expr ast.Expr, ident types.Object) bool { + found := false + fn := func(node ast.Node) bool { + ident2, ok := node.(*ast.Ident) + if !ok { + return true + } + if ident == pass.TypesInfo.ObjectOf(ident2) { + found = true + return false + } + return true + } + ast.Inspect(expr, fn) + return found +} diff --git a/vendor/honnef.co/go/tools/analysis/code/visit.go b/vendor/honnef.co/go/tools/analysis/code/visit.go new file mode 100644 index 0000000..18271e9 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/code/visit.go @@ -0,0 +1,153 @@ +package code + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/types" + "iter" + "slices" + + typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var RequiredAnalyzers = []*analysis.Analyzer{inspect.Analyzer, typeindexanalyzer.Analyzer} + +func Cursor(pass *analysis.Pass) inspector.Cursor { + return pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Root() +} + +func Preorder(pass *analysis.Pass, fn func(ast.Node), types ...ast.Node) { + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Preorder(types, fn) +} + +func PreorderStack(pass *analysis.Pass, fn func(ast.Node, []ast.Node), types ...ast.Node) { + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).WithStack(types, func(n ast.Node, push bool, stack []ast.Node) (proceed bool) { + if push { + fn(n, stack) + } + return true + }) +} + +func Matches(pass *analysis.Pass, qs ...pattern.Pattern) iter.Seq2[ast.Node, *pattern.Matcher] { + return func(yield func(ast.Node, *pattern.Matcher) bool) { + for _, q := range qs { + if !CouldMatchAny(pass, q) { + continue + } + + if len(q.RootCallSymbols) != 0 { + index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) + for _, isym := range q.RootCallSymbols { + var obj types.Object + if isym.Type == "" { + obj = index.Object(isym.Path, isym.Ident) + } else { + obj = index.Selection(isym.Path, isym.Type, isym.Ident) + } + for c := range index.Calls(obj) { + node := c.Node() + if m, ok := Match(pass, q, node); ok { + if !yield(node, m) { + return + } + } + } + } + } else { + ins := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + fn := func(node ast.Node, push bool) bool { + if !push { + return true + } + + if m, ok := Match(pass, q, node); ok { + return yield(node, m) + } + return true + } + ins.Nodes(q.EntryNodes, fn) + } + } + } +} + +func Match(pass *analysis.Pass, q pattern.Pattern, node ast.Node) (*pattern.Matcher, bool) { + // Note that we ignore q.Relevant – callers of Match usually use + // AST inspectors that already filter on nodes we're interested + // in. + m := &pattern.Matcher{TypesInfo: pass.TypesInfo} + ok := m.Match(q, node) + return m, ok +} + +func CouldMatchAny(pass *analysis.Pass, qs ...pattern.Pattern) bool { + index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) + var do func(node pattern.Node) bool + do = func(node pattern.Node) bool { + switch node := node.(type) { + case pattern.Any: + return true + case pattern.Or: + return slices.ContainsFunc(node.Nodes, do) + case pattern.And: + for _, child := range node.Nodes { + if !do(child) { + return false + } + } + return true + case pattern.IndexSymbol: + if node.Type == "" { + return index.Object(node.Path, node.Ident) != nil + } else { + return index.Selection(node.Path, node.Type, node.Ident) != nil + } + default: + panic(fmt.Sprintf("internal error: unexpected type %T", node)) + } + } + + for _, q := range qs { + if do(q.SymbolsPattern) { + return true + } + } + return false +} + +func MatchAndEdit(pass *analysis.Pass, before, after pattern.Pattern, node ast.Node) (*pattern.Matcher, []analysis.TextEdit, bool) { + m, ok := Match(pass, before, node) + if !ok { + return m, nil, false + } + r := pattern.NodeToAST(after.Root, m.State) + buf := &bytes.Buffer{} + format.Node(buf, pass.Fset, r) + edit := []analysis.TextEdit{{ + Pos: node.Pos(), + End: node.End(), + NewText: buf.Bytes(), + }} + return m, edit, true +} + +func EditMatch(pass *analysis.Pass, node ast.Node, m *pattern.Matcher, after pattern.Pattern) []analysis.TextEdit { + r := pattern.NodeToAST(after.Root, m.State) + buf := &bytes.Buffer{} + format.Node(buf, pass.Fset, r) + edit := []analysis.TextEdit{{ + Pos: node.Pos(), + End: node.End(), + NewText: buf.Bytes(), + }} + return edit +} diff --git a/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go b/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go new file mode 100644 index 0000000..efa573c --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/dfa/dense/flow.go @@ -0,0 +1,60 @@ +// Copyright 2026 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 flow implements a monotone flow analysis framework. +package dense + +import ( + "cmp" + "slices" + + "honnef.co/go/tools/internal/xtools-internal/graph" +) + +const debug = false + +// Analysis is the result of a monotone analysis. Fact is the type of elements +// in the analysis semilattice, and represents the outcome of the analysis at +// every node and edge. +type Analysis[Fact any, NodeID comparable] struct { + nodeMap *graph.Index[NodeID] + ins []Fact // By NodeID + edges []edgeFact[Fact] // Sorted by (from, to) +} + +// In returns the analysis fact on entry to nid. This is the merge of the facts +// on all incoming edges. +func (a *Analysis[Fact, NodeID]) In(nid NodeID) Fact { + return a.ins[a.nodeMap.Index(nid)] +} + +// Edge returns the analysis fact propagated on edge from ==> to. +func (a *Analysis[Fact, NodeID]) Edge(from, to NodeID) Fact { + i, found := slices.BinarySearchFunc(a.edges, a.edge(from, to), edgeFact[Fact].compare) + if !found { + panic("no such edge") + } + return a.edges[i].fact +} + +func (a *Analysis[Fact, NodeID]) edge(from, to NodeID) edge { + fromNum, toNum := a.nodeMap.Index(from), a.nodeMap.Index(to) + return edge{fromNum, toNum} +} + +type edge struct { + from, to int +} + +func (e edge) compare(f edge) int { + if v := cmp.Compare(e.from, f.from); v != 0 { + return v + } + return cmp.Compare(e.to, f.to) +} + +type edgeFact[Fact any] struct { + edge + fact Fact +} diff --git a/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go b/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go new file mode 100644 index 0000000..7837f64 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/dfa/dense/forward.go @@ -0,0 +1,271 @@ +// Copyright 2026 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 dense + +import ( + "container/heap" + "log" + "slices" + + "honnef.co/go/tools/analysis/dfa" + "honnef.co/go/tools/internal/xtools-internal/graph" +) + +// Forward performs a forward monotone analysis over a control flow graph. +// +// The entry map provides initial state for entry blocks (blocks with zero +// predecessors). For each edge, it calls transfer(fact, edge), where fact is +// the analysis state on entry to edge.Pred. The transfer function must return +// the outgoing analysis state of the edge (which may be fact, if the edge has +// no effect on the analysis state). +func Forward[L dfa.Semilattice[Fact], Fact any, NodeID comparable](g graph.Graph[NodeID], entry map[NodeID]Fact, transfer func(from, to NodeID, fact Fact) Fact) *Analysis[Fact, NodeID] { + cg, nodeMap := graph.Compact(g) + + nNodes := cg.NumNodes() + fb := &fwdBuilder[L, Fact, NodeID]{ + cfg: cg, + nodeMap: nodeMap, + transfer: transfer, + blocks: make([]blockInfo[Fact], nNodes), + } + fb.queue.init(cg) + + // Initialize each node. + totalEdges := 0 + for ni := range nNodes { + b := &fb.blocks[ni] + + // Construct back-edges. + // + // I experimented with making Graph support iterating over in-edges, but + // in practice that just meant each Graph implementation had a copy of + // this logic. So instead we keep Graph as simple as possible and + // compute the auxiliary data in the algorithm. One drawback of this is + // that, for the [Transpose] graph, this information is redundant with + // the underlying graph. We could potentially special-case that. + outs := 0 + for succID := range cg.Out(ni) { + succ := &fb.blocks[succID] + succ.preds = append(succ.preds, blockEdge{ni, outs}) + outs++ + totalEdges++ + } + + // Initialize in & out states. + fact, ok := entry[nodeMap.Value(ni)] + if !ok { + fact = fb.l.Ident() + } + b.in = fact + b.out = slices.Repeat([]Fact{fb.l.Ident()}, outs) + + // Enqueue block. + // + // It's tempting to enqueue only the entry blocks, but this is wrong. + // The entry map may be empty if there are no interesting entry states, + // but the transfer function may still introduce interesting states + // anywhere. + b.dirty = true + fb.queue.enqueue(ni) + } + + // Propagate over blocks. + fb.propagate() + + // Collect the final analysis results. + a := Analysis[Fact, NodeID]{ + nodeMap: nodeMap, + ins: make([]Fact, nNodes), + edges: make([]edgeFact[Fact], 0, totalEdges), + } + for pred := range nNodes { + a.ins[pred] = fb.blocks[pred].in + i := 0 + for succ := range cg.Out(pred) { + edge := edge{pred, succ} + a.edges = append(a.edges, edgeFact[Fact]{edge, fb.blocks[pred].out[i]}) + i++ + } + } + slices.SortFunc(a.edges, func(a, b edgeFact[Fact]) int { return a.edge.compare(b.edge) }) + return &a +} + +// fwdBuilder is the state used during [Forward] analysis. +type fwdBuilder[L dfa.Semilattice[Fact], Fact any, NodeID comparable] struct { + l L // Lattice + + cfg graph.Graph[int] // Control flow graph (compact) + nodeMap *graph.Index[NodeID] // Map from cfg to original NodeIDs + + // transfer is the edge transfer function. + transfer func(from, to NodeID, fact Fact) Fact + + blocks []blockInfo[Fact] + + queue nodeHeap +} + +type blockInfo[Fact any] struct { + dirty bool // The in fact has never been propagated. + + preds []blockEdge + + in Fact + out []Fact // Corresponds to i'th out edge +} + +type blockEdge struct { + node int + i int // Out edge index +} + +// nodeHeap implements a heap of NodeIDs, ordered topologically. +// +// We use this ordering so forward analysis converges more quickly. +type nodeHeap struct { + heap []int // Remaining nodes in the current sweep + deferred []int // Nodes of next sweep + inQueue []int64 // Bitmap over node IDs + prio []int // NodeID -> priority + currentPrio int // Priority of last dequeued node, or -1 +} + +func (h *nodeHeap) init(g graph.Graph[int]) { + nNodes := g.NumNodes() + *h = nodeHeap{ + inQueue: make([]int64, (nNodes+63)/64), + prio: make([]int, nNodes), + currentPrio: -1, + } + for p, nid := range graph.ReversePostorder(g) { + h.prio[nid] = p + } +} + +func (h *nodeHeap) enqueue(nid int) { + if h.inQueue[nid/64]&(1<<(nid%64)) != 0 { + return + } + h.inQueue[nid/64] |= 1 << (nid % 64) + + if h.currentPrio >= 0 && h.prio[nid] <= h.currentPrio { + // This is a retreating edge, self-edge, or other update to a node + // already passed in this sweep. Coalesce it into the next sweep. + h.deferred = append(h.deferred, nid) + } else { + heap.Push(h, nid) + } +} + +func (h *nodeHeap) dequeue() int { + if len(h.heap) == 0 { + // Start the next RPO sweep. + h.heap, h.deferred = h.deferred, h.heap[:0] + h.currentPrio = -1 + heap.Init(h) + } + + nid := h.heap[0] + heap.Pop(h) + h.inQueue[nid/64] &^= 1 << (nid % 64) + h.currentPrio = h.prio[nid] + return nid +} + +func (h *nodeHeap) pending() bool { return len(h.heap) != 0 || len(h.deferred) != 0 } +func (h nodeHeap) Len() int { return len(h.heap) } +func (h nodeHeap) Less(i, j int) bool { return h.prio[h.heap[i]] < h.prio[h.heap[j]] } +func (h nodeHeap) Swap(i, j int) { h.heap[i], h.heap[j] = h.heap[j], h.heap[i] } +func (h *nodeHeap) Push(x any) { h.heap = append(h.heap, x.(int)) } +func (h *nodeHeap) Pop() any { + n := len(h.heap) + x := h.heap[n-1] + h.heap = h.heap[:n-1] + return x +} + +func (fb *fwdBuilder[L, Fact, NodeID]) merge(a, b Fact) Fact { + if fb.l.Equals(a, b) { + return a + } + return fb.l.Merge(a, b) +} + +func (fb *fwdBuilder[L, Fact, NodeID]) propagate() { + for fb.queue.pending() { + bi := fb.queue.dequeue() + block := &fb.blocks[bi] + + // Merge predecessor facts to compute updated "in" fact. + var in Fact + first := true + for _, edge := range block.preds { + pred := &fb.blocks[edge.node] + var edgeFact Fact + if pred.dirty { + // We haven't visited this predecessor yet, so it doesn't have + // meaningful out facts. + edgeFact = fb.l.Ident() + } else { + edgeFact = pred.out[edge.i] + } + if first { + if debug { + log.Printf("propagate to node %d", bi) + } + in = edgeFact + first = false + } else { + in = fb.merge(in, edgeFact) + } + if debug { + log.Printf(" from node %d: %v", edge.node, edgeFact) + } + } + if first { + // No predecessors. + if debug { + log.Printf("node %d gets initial state", bi) + } + in = block.in + } + + if !block.dirty && fb.l.Equals(in, block.in) { + // No change to block input, which means the transfer function + // results also won't change from the last time we ran it. + if debug { + log.Printf(" initial state unchanged: %v", in) + } + continue + } + if debug { + log.Printf(" new initial state: %v", in) + } + block.in = in + + // Apply transfer function. + predID := fb.nodeMap.Value(bi) + i := 0 + for succNum := range fb.cfg.Out(bi) { + edgeFact := fb.transfer(predID, fb.nodeMap.Value(succNum), in) + if block.dirty || !fb.l.Equals(block.out[i], edgeFact) { + // Out fact changed, so recompute the target block. + if debug { + log.Printf(" to node %d: %v", succNum, edgeFact) + } + block.out[i] = edgeFact + fb.queue.enqueue(succNum) + } else { + if debug { + log.Printf(" to node %d: no change", succNum) + } + } + i++ + } + + block.dirty = false + } +} diff --git a/vendor/honnef.co/go/tools/analysis/dfa/dot.go b/vendor/honnef.co/go/tools/analysis/dfa/dot.go new file mode 100644 index 0000000..1895aa8 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/dfa/dot.go @@ -0,0 +1,48 @@ +package dfa + +import ( + "fmt" + "strings" +) + +// Dot returns a directed graph in [Graphviz] format that represents the finite +// join-semilattice ⟨S, ≤⟩. Vertices represent elements in S and edges +// represent the ≤ relation between elements. We map from ⟨S, ∨⟩ to ⟨S, ≤⟩ by +// computing x ∨ y for all elements in [S]², where x ≤ y iff x ∨ y == y. +// +// The resulting graph can be filtered through [tred] to compute the transitive +// reduction of the graph, the visualisation of which corresponds to the Hasse +// diagram of the semilattice. +// +// [Graphviz]: https://graphviz.org/ +// [tred]: https://graphviz.org/docs/cli/tred/ +func Dot[L Semilattice[Elem], Elem any](states []Elem) string { + var sb strings.Builder + sb.WriteString("digraph{\n") + sb.WriteString("rankdir=\"BT\"\n") + + for i, v := range states { + if vs, ok := any(v).(fmt.Stringer); ok { + fmt.Fprintf(&sb, "n%d [label=%q]\n", i, vs) + } else { + fmt.Fprintf(&sb, "n%d [label=%q]\n", i, fmt.Sprintf("%v", v)) + } + } + + var l L + + for dx, x := range states { + for dy, y := range states { + if dx == dy { + continue + } + + if l.Equals(l.Merge(x, y), y) { + fmt.Fprintf(&sb, "n%d -> n%d\n", dx, dy) + } + } + } + + sb.WriteString("}") + return sb.String() +} diff --git a/vendor/honnef.co/go/tools/analysis/dfa/lattice.go b/vendor/honnef.co/go/tools/analysis/dfa/lattice.go new file mode 100644 index 0000000..672875b --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/dfa/lattice.go @@ -0,0 +1,147 @@ +// Copyright 2026 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 dfa + +import ( + "fmt" + "maps" + "slices" +) + +// A Semilattice describes a bounded semilattice over Elem. +// That is, a partial order over values of type Elem, with a binary +// Merge operator and an identity element. +// +// This is typically implemented by a stateless type, and acts as a factory for +// lattice elements. +type Semilattice[Elem any] interface { + // Ident returns the identity element of this lattice, that is the unit of + // the Merge operation. + Ident() Elem + + // Equals returns whether a and b are the same element. + Equals(a, b Elem) bool + + // Merge combines two lattice values, such as the two possible values of a + // variable at the end of an if/else statement. + // + // Merge must satisfy the following identities, where we use ∧ for Merge, = + // for Equals, and 𝟏 for Ident: + // + // - Associativity: x ∧ (y ∧ z) = (x ∧ y) ∧ z + // - Commutativity: x ∧ y = y ∧ x + // - Idempotency: x ∧ x = x + // - Identity: x ∧ 𝟏 = x + Merge(a, b Elem) Elem +} + +// A MapLattice implements [Semilattice][map[Key]Elem]. The values in the map +// are themselves defined by [Semilattice] L. +// +// Any elements missing from the map are implicitly L's identity element, and +// L's identity element never appears as a value in the map. +// +// For densely numbered keys, consider using [DenseMapLattice] instead. +type MapLattice[Key comparable, Elem any, L Semilattice[Elem]] struct { + l L +} + +func (m MapLattice[Key, Elem, L]) Ident() map[Key]Elem { + return nil +} + +func (m MapLattice[Key, Elem, L]) Equals(a, b map[Key]Elem) bool { + return maps.EqualFunc(a, b, m.l.Equals) +} + +func (m MapLattice[Key, Elem, L]) Merge(a, b map[Key]Elem) map[Key]Elem { + if len(a) == 0 { + return b + } else if len(b) == 0 { + return a + } + + // We need to consider the union of keys in a and b. + out := make(map[Key]Elem) + id := m.l.Ident() + for k, av := range a { + bv, ok := b[k] + if !ok { + // Because Merge(x, Ident()) == x, we can skip calling L.Merge. + out[k] = av + continue + } + + w := m.l.Merge(av, bv) + if m.l.Equals(w, id) { + // In a semilattice, Merge(x, y) = Ident is only possible when x == + // Ident and y == Ident. + panic(fmt.Sprintf( + "%T is not a semilattice: Merge(%v, %v) returned Ident for non-Ident arguments", + m.l, av, bv)) + } + out[k] = w + } + // We considered keys that are only in a, and in both a and b. Now we just + // need to handle keys that are only in b. + for k, v2 := range b { + if _, ok := a[k]; !ok { + out[k] = v2 + } + } + + return out +} + +// A DenseMapLattice implements [Semilattice][[]Elem]. It is like a [MapLattice] +// that is indexed by integers. The values in the map are themselves defined by +// [Semilattice] L. +// +// Unlike [MapLattice], L's identity element may appear as a value in the map, +// to allow for gaps in the numbering of keys when the identity element is +// Elem's zero value. +type DenseMapLattice[Elem any, L Semilattice[Elem]] struct { + l L +} + +func (s DenseMapLattice[Elem, L]) Ident() []Elem { + return nil +} + +func (s DenseMapLattice[Elem, L]) Equals(a, b []Elem) bool { + nmin := min(len(a), len(b)) + ident := s.l.Ident() + + // Check that up to nmin, all elements in a and b match. If one of a or b + // is longer, then its tail nmin:nmax must only contain identity elements. + return slices.EqualFunc(a[:nmin], b[:nmin], s.l.Equals) && + !slices.ContainsFunc(a[nmin:], func(e Elem) bool { + return !s.l.Equals(e, ident) + }) && + !slices.ContainsFunc(b[nmin:], func(e Elem) bool { + return !s.l.Equals(e, ident) + }) +} + +func (s DenseMapLattice[Elem, L]) Merge(a, b []Elem) []Elem { + if len(a) == 0 { + return b + } else if len(b) == 0 { + return a + } + out := make([]Elem, max(len(a), len(b))) + for k := range max(len(a), len(b)) { + av := s.l.Ident() + bv := s.l.Ident() + if k < len(a) { + av = a[k] + } + if k < len(b) { + bv = b[k] + } + out[k] = s.l.Merge(av, bv) + } + return out +} diff --git a/vendor/honnef.co/go/tools/analysis/edit/edit.go b/vendor/honnef.co/go/tools/analysis/edit/edit.go new file mode 100644 index 0000000..b118bdc --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/edit/edit.go @@ -0,0 +1,83 @@ +// Package edit contains helpers for creating suggested fixes. +package edit + +import ( + "bytes" + "go/ast" + "go/format" + "go/token" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/pattern" +) + +// Ranger describes values that have a start and end position. +// In most cases these are either ast.Node or manually constructed ranges. +type Ranger interface { + Pos() token.Pos + End() token.Pos +} + +// Range implements the Ranger interface. +type Range [2]token.Pos + +func (r Range) Pos() token.Pos { return r[0] } +func (r Range) End() token.Pos { return r[1] } + +// ReplaceWithString replaces a range with a string. +func ReplaceWithString(old Ranger, new string) analysis.TextEdit { + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: []byte(new), + } +} + +// ReplaceWithNode replaces a range with an AST node. +func ReplaceWithNode(fset *token.FileSet, old Ranger, new ast.Node) analysis.TextEdit { + buf := &bytes.Buffer{} + if err := format.Node(buf, fset, new); err != nil { + panic("internal error: " + err.Error()) + } + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: buf.Bytes(), + } +} + +// ReplaceWithPattern replaces a range with the result of executing a pattern. +func ReplaceWithPattern(fset *token.FileSet, old Ranger, new pattern.Pattern, state pattern.State) analysis.TextEdit { + r := pattern.NodeToAST(new.Root, state) + buf := &bytes.Buffer{} + format.Node(buf, fset, r) + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: buf.Bytes(), + } +} + +// Delete deletes a range of code. +func Delete(old Ranger) analysis.TextEdit { + return analysis.TextEdit{ + Pos: old.Pos(), + End: old.End(), + NewText: nil, + } +} + +func Fix(msg string, edits ...analysis.TextEdit) analysis.SuggestedFix { + return analysis.SuggestedFix{ + Message: msg, + TextEdits: edits, + } +} + +// Selector creates a new selector expression. +func Selector(x, sel string) *ast.SelectorExpr { + return &ast.SelectorExpr{ + X: &ast.Ident{Name: x}, + Sel: &ast.Ident{Name: sel}, + } +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go b/vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go new file mode 100644 index 0000000..03c0621 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/deprecated/deprecated.go @@ -0,0 +1,154 @@ +package deprecated + +import ( + "go/ast" + "go/token" + "go/types" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" +) + +type IsDeprecated struct{ Msg string } + +func (*IsDeprecated) AFact() {} +func (d *IsDeprecated) String() string { return "Deprecated: " + d.Msg } + +type Result struct { + Objects map[types.Object]*IsDeprecated + Packages map[*types.Package]*IsDeprecated +} + +var Analyzer = &analysis.Analyzer{ + Name: "fact_deprecated", + Doc: "Mark deprecated objects", + Run: deprecated, + FactTypes: []analysis.Fact{(*IsDeprecated)(nil)}, + ResultType: reflect.TypeFor[Result](), +} + +func deprecated(pass *analysis.Pass) (any, error) { + var names []*ast.Ident + + extractDeprecatedMessage := func(docs []*ast.CommentGroup) string { + for _, doc := range docs { + if doc == nil { + continue + } + parts := strings.SplitSeq(doc.Text(), "\n\n") + for part := range parts { + if !strings.HasPrefix(part, "Deprecated: ") { + continue + } + alt := part[len("Deprecated: "):] + alt = strings.Replace(alt, "\n", " ", -1) + return alt + } + } + return "" + } + + doDocs := func(names []*ast.Ident, docs []*ast.CommentGroup) { + alt := extractDeprecatedMessage(docs) + if alt == "" { + return + } + + for _, name := range names { + obj := pass.TypesInfo.ObjectOf(name) + pass.ExportObjectFact(obj, &IsDeprecated{alt}) + } + } + + var docs []*ast.CommentGroup + for _, f := range pass.Files { + docs = append(docs, f.Doc) + } + if alt := extractDeprecatedMessage(docs); alt != "" { + // Don't mark package syscall as deprecated, even though + // it is. A lot of people still use it for simple + // constants like SIGKILL, and I am not comfortable + // telling them to use x/sys for that. + if pass.Pkg.Path() != "syscall" { + pass.ExportPackageFact(&IsDeprecated{alt}) + } + } + + docs = docs[:0] + for _, f := range pass.Files { + fn := func(node ast.Node) bool { + if node == nil { + return true + } + var ret bool + switch node := node.(type) { + case *ast.GenDecl: + switch node.Tok { + case token.TYPE, token.CONST, token.VAR: + docs = append(docs, node.Doc) + for i := range node.Specs { + switch n := node.Specs[i].(type) { + case *ast.ValueSpec: + names = append(names, n.Names...) + case *ast.TypeSpec: + names = append(names, n.Name) + } + } + ret = true + default: + return false + } + case *ast.FuncDecl: + docs = append(docs, node.Doc) + names = []*ast.Ident{node.Name} + ret = false + case *ast.TypeSpec: + docs = append(docs, node.Doc) + names = []*ast.Ident{node.Name} + ret = true + case *ast.ValueSpec: + docs = append(docs, node.Doc) + names = node.Names + ret = false + case *ast.File: + return true + case *ast.StructType: + for _, field := range node.Fields.List { + doDocs(field.Names, []*ast.CommentGroup{field.Doc}) + } + return false + case *ast.InterfaceType: + for _, field := range node.Methods.List { + doDocs(field.Names, []*ast.CommentGroup{field.Doc}) + } + return false + default: + return false + } + if len(names) == 0 || len(docs) == 0 { + return ret + } + doDocs(names, docs) + + docs = docs[:0] + names = nil + return ret + } + ast.Inspect(f, fn) + } + + out := Result{ + Objects: map[types.Object]*IsDeprecated{}, + Packages: map[*types.Package]*IsDeprecated{}, + } + + for _, fact := range pass.AllObjectFacts() { + out.Objects[fact.Object] = fact.Fact.(*IsDeprecated) + } + for _, fact := range pass.AllPackageFacts() { + out.Packages[fact.Package] = fact.Fact.(*IsDeprecated) + } + + return out, nil +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/directives/directives.go b/vendor/honnef.co/go/tools/analysis/facts/directives/directives.go new file mode 100644 index 0000000..467527b --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/directives/directives.go @@ -0,0 +1,20 @@ +package directives + +import ( + "reflect" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/lint" +) + +func directives(pass *analysis.Pass) (any, error) { + return lint.ParseDirectives(pass.Files, pass.Fset), nil +} + +var Analyzer = &analysis.Analyzer{ + Name: "directives", + Doc: "extracts linter directives", + Run: directives, + RunDespiteErrors: true, + ResultType: reflect.TypeFor[[]lint.Directive](), +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/generated/generated.go b/vendor/honnef.co/go/tools/analysis/facts/generated/generated.go new file mode 100644 index 0000000..301ad0e --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/generated/generated.go @@ -0,0 +1,97 @@ +package generated + +import ( + "bufio" + "bytes" + "io" + "os" + "reflect" + "strings" + + "golang.org/x/tools/go/analysis" +) + +type Generator int + +// A list of known generators we can detect +const ( + Unknown Generator = iota + Goyacc + Cgo + Stringer + ProtocGenGo +) + +var ( + // used by cgo before Go 1.11 + oldCgo = []byte("// Created by cgo - DO NOT EDIT") + prefix = []byte("// Code generated ") + suffix = []byte(" DO NOT EDIT.") + nl = []byte("\n") + crnl = []byte("\r\n") +) + +func isGenerated(path string) (Generator, bool) { + f, err := os.Open(path) + if err != nil { + return 0, false + } + defer f.Close() + br := bufio.NewReader(f) + for { + s, err := br.ReadBytes('\n') + if err != nil && err != io.EOF { + return 0, false + } + s = bytes.TrimSuffix(s, crnl) + s = bytes.TrimSuffix(s, nl) + if bytes.HasPrefix(s, prefix) && bytes.HasSuffix(s, suffix) { + if len(s)-len(suffix) < len(prefix) { + return Unknown, true + } + + text := string(s[len(prefix) : len(s)-len(suffix)]) + switch text { + case "by goyacc.": + return Goyacc, true + case "by cmd/cgo;": + return Cgo, true + case "by protoc-gen-go.": + return ProtocGenGo, true + } + if strings.HasPrefix(text, `by "stringer `) { + return Stringer, true + } + if strings.HasPrefix(text, `by goyacc `) { + return Goyacc, true + } + + return Unknown, true + } + if bytes.Equal(s, oldCgo) { + return Cgo, true + } + if err == io.EOF { + break + } + } + return 0, false +} + +var Analyzer = &analysis.Analyzer{ + Name: "isgenerated", + Doc: "annotate file names that have been code generated", + Run: func(pass *analysis.Pass) (any, error) { + m := map[string]Generator{} + for _, f := range pass.Files { + path := pass.Fset.PositionFor(f.Pos(), false).Filename + g, ok := isGenerated(path) + if ok { + m[path] = g + } + } + return m, nil + }, + RunDespiteErrors: true, + ResultType: reflect.TypeFor[map[string]Generator](), +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go b/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go new file mode 100644 index 0000000..25b315b --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/nilness/nilness.go @@ -0,0 +1,830 @@ +package nilness + +import ( + "fmt" + "go/constant" + "go/token" + "go/types" + "reflect" + "slices" + "strings" + + "honnef.co/go/tools/analysis/dfa" + "honnef.co/go/tools/analysis/dfa/dense" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/exp/typeparams" + "golang.org/x/tools/go/analysis" +) + +// TODO(dh): The analysis is currently entirely forward, which means that for +// +// x := s[:0] +// y := s[:1] +// z := s[:0] +// +// x will have MaybeNil at every program point and s will have MaybeNil before +// execution of y, even though executing y without panicing tells us that s has +// been non-nil for all 3 instructions. + +type nilnessFact struct { + Rets []ValueNilness +} + +func (*nilnessFact) AFact() {} +func (fact *nilnessFact) String() string { + return fmt.Sprintf("nilness: %v", fact.Rets) +} + +type ValueNilness struct { + // Undefined for non-interface values. + // For interface values, whether the stored value may be nil. + // Even when Outer == MaybeNil, Inner may still offer precise information + // for the cases when Outer is dynamically not nil. For example, {NeverNil, + // MaybeNil} states that the interface value might be nil, but if it isn't, + // it will definitely contain a non-nil value. + Inner Nilness + // For non-interface values, whether the value may be nil. + // For interface values, whether the interface value may be nil. + Outer Nilness +} + +type Result struct { + m map[*types.Func][]ValueNilness +} + +var Analysis = &analysis.Analyzer{ + Name: "nilness", + Doc: "Annotates return values with their nilness", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + FactTypes: []analysis.Fact{(*nilnessFact)(nil)}, + ResultType: reflect.TypeFor[*Result](), +} + +// Nilness returns nilness information for return value ret of fn. +func (r *Result) Nilness(fn *types.Func, ret int) ValueNilness { + typ := fn.Type().(*types.Signature).Results().At(ret).Type() + if !typeutil.IsPointerLike(typ) { + return ValueNilness{Outer: NeverNil} + } + if len(r.m[fn]) == 0 { + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} + } + + return normalize(r.m[fn][ret], typ) +} + +func normalize(v ValueNilness, typ types.Type) ValueNilness { + if v.Inner == 0 || !types.IsInterface(typ) { + v.Inner = MaybeNil + } + if v.Outer == 0 { + v.Outer = MaybeNil + } + return v +} + +func run(pass *analysis.Pass) (any, error) { + seen := map[*ir.Function]struct{}{} + out := &Result{ + m: map[*types.Func][]ValueNilness{}, + } + + // TODO(dh): instead of recursion and giving up on mutual recursion, we + // should compute the DFA over the call graph, at least until we have + // proper function summaries. + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + impl(pass, fn, seen) + } + + for _, fact := range pass.AllObjectFacts() { + out.m[fact.Object.(*types.Func)] = fact.Fact.(*nilnessFact).Rets + } + + return out, nil +} + +type Nilness uint8 + +const ( + // The value is never nil. + NeverNil Nilness = iota + 1 + // The value is always nil. + AlwaysNil + // The value might be nil, but only because of the value of a global + // variable. + MaybeNilGlobal + // The value might be nil. + MaybeNil +) + +func (n Nilness) String() string { + switch n { + case 0: + return "NoNilness" + case NeverNil: + return "NeverNil" + case AlwaysNil: + return "AlwaysNil" + case MaybeNilGlobal: + return "MaybeNilGlobal" + case MaybeNil: + return "MaybeNil" + default: + return "InvalidNilness" + } +} + +type state struct { + cloned bool + m []ValueNilness + n numbering +} + +func (s *state) get(v ir.Value) ValueNilness { + if !typeutil.IsPointerLike(v.Type()) { + // All non-pointer-like types are always {_ NeverNil}. + return ValueNilness{Outer: NeverNil} + } + num := s.n.number(v) + if num < len(s.m) { + return s.m[num] + } + + switch v.(type) { + case *ir.Parameter: + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} + case *ir.Builtin: + return ValueNilness{Outer: NeverNil} + case *ir.FreeVar: + return ValueNilness{Inner: MaybeNil, Outer: MaybeNil} + case *ir.Function: + return ValueNilness{Outer: NeverNil} + case *ir.Global: + // Globals are addresses, not the values stored in them. The addresses + // cannot be nil. + return ValueNilness{Outer: NeverNil} + } + + return lattice{}.Ident() +} + +func (s *state) set(key ir.Value, value ValueNilness) { + if !typeutil.IsPointerLike(key.Type()) { + // No point in recording state for non-pointer-like types. They're + // always {_ NeverNil}. + return + } + + if value == (lattice{}.Ident()) { + // No point in storing the default value. + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num] == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + } + s.m[num] = value +} + +func (s *state) setInner(key ir.Value, value Nilness) { + if !typeutil.IsPointerLike(key.Type()) { + return + } + if value == (lattice{}.Ident().Inner) { + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num].Inner == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + dflt := s.get(key) + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + s.m[num] = dflt + } + v := s.m[num] + v.Inner = value + s.m[num] = v +} + +func (s *state) setOuter(key ir.Value, value Nilness) { + if !typeutil.IsPointerLike(key.Type()) { + return + } + if value == (lattice{}).Ident().Outer { + return + } + num := s.n.number(key) + if !s.cloned { + if num < len(s.m) && s.m[num].Outer == value { + // Don't clone if the value already matches. + return + } + s.cloned = true + s.m = slices.Clone(s.m) + } + if num >= len(s.m) { + dflt := s.get(key) + s.m = append(s.m, make([]ValueNilness, num-len(s.m)+1)...) + s.m[num] = dflt + } + v := s.m[num] + v.Outer = value + s.m[num] = v +} + +func defaultNilnessForSignature(pass *analysis.Pass, typ *types.Signature) []ValueNilness { + n := typ.Results().Len() + if n == 0 { + return nil + } + out := make([]ValueNilness, n) + for i := range n { + out[i] = defaultNilness(pass, typ.Results().At(i).Type()) + } + return out +} + +func defaultNilness(pass *analysis.Pass, typ types.Type) ValueNilness { + if typeutil.IsPointerLike(typ) { + // IsPointerLike handles type parameters with type sets, too. + return ValueNilness{MaybeNil, MaybeNil} + } else { + return ValueNilness{NeverNil, NeverNil} + } +} + +func impl(pass *analysis.Pass, fn *ir.Function, seenFns map[*ir.Function]struct{}) []ValueNilness { + goto start +bailout: + return defaultNilnessForSignature(pass, fn.Signature) + +start: + if fn.Signature.Results().Len() == 0 { + return nil + } + if fn.Object() == nil { + // TODO(dh): support closures + goto bailout + } + if fact := new(nilnessFact); pass.ImportObjectFact(fn.Object(), fact) { + return fact.Rets + } + if fn.Pkg != pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg { + goto bailout + } + if fn.Blocks == nil { + goto bailout + } + if _, ok := seenFns[fn]; ok { + // break recursion + goto bailout + } + + seenFns[fn] = struct{}{} + + anyPointers := false + for ret := range fn.Signature.Results().Variables() { + if typeutil.IsPointerLike(ret.Type()) { + anyPointers = true + break + } + } + + if !anyPointers { + goto bailout + } + + n := numbering{} + + processBlock := func(from, to *ir.BasicBlock, s state) state { + handleReturnValue := func(v ir.Value, call *ir.Call, idx int) { + typ := call.Common().Signature().Results().At(idx).Type() + if !typeutil.IsPointerLike(typ) { + s.setOuter(v, NeverNil) + return + } + + if callee, ok := call.Call.Value.(*ir.Builtin); ok { + switch callee.Name() { + case "append": + // TODO(dh): if we knew that the varargs had non-zero + // length, we'd know that the resulting slice is non-nil. + switch an := s.get(call.Call.Args[0]).Outer; an { + case MaybeNil, MaybeNilGlobal, NeverNil: + s.setOuter(v, an) + case AlwaysNil: + s.setOuter(v, MaybeNil) + } + case "UnsafeSlice": + // If len is negative, or if ptr is nil and len is not + // zero, unsafe.Slice panics. This implies that a non-nil + // pointer cannot become nil, and vice versa. + s.set(v, s.get(call.Call.Args[0])) + case "UnsafeStringData": + // TODO(dh): if we had string length information we could + // return better information. + s.setOuter(v, MaybeNil) + case "UnsafeSliceData": + // When the slice is non-nil but has zero capacity, the + // returned pointer is still non-nil, so we don't have to + // worry about that. + s.set(v, s.get(call.Call.Args[0])) + case "UnsafeAdd": + // TODO(dh): a positive addend can never result in a nil pointer. + + // Pointer arithmetic can turn nil pointers into non-nil + // ones and vice versa. + s.setOuter(v, MaybeNil) + case "ssa:deferstack": + s.setOuter(v, NeverNil) + case "ssa:wrapnilchk": + s.setOuter(v, NeverNil) + case "recover": + s.setOuter(v, MaybeNil) + default: + panic(fmt.Sprintf("internal error: unhandled builtin %s", callee.Name())) + } + return + } + + callee := call.Common().StaticCallee() + if callee == nil { + // We don't know which function is being called. + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + return + } + calleeNilness := impl(pass, callee, seenFns) + if len(calleeNilness) > idx { + s.set(v, normalize(calleeNilness[idx], typ)) + } else { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + } + + for _, instr := range from.Instrs { + // It is tempting to return early when instr is an ir.Value that + // doesn't have pointer type. However, instructions like ir.Load + // tell us something about the value being operated on. + + switch v := instr.(type) { + case *ir.Convert: + s.set(v, s.get(v.X)) + case *ir.SliceToArrayPointer: + // Go does not currently allow (*T)(s) where T is a type + // parameter with a type set consisting of array types, but it + // does allow (T)(s) where T is a type parameter with a type + // set consisting of pointers to array types. + + allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool { + ptr := term.Type().Underlying().(*types.Pointer).Elem() + return typeutil.All(ptr, func(innerTerm *types.Term) bool { + return innerTerm.Type().Underlying().(*types.Array).Len() != 0 + }) + }) + + if allNonZero { + // converting a slice to an array pointer of length > 0 + // panics if the slice is nil + s.setOuter(v, NeverNil) + s.setOuter(v.X, NeverNil) + } else { + s.set(v, s.get(v.X)) + } + case *ir.SliceToArray: + // Pretty much the same logic as SliceToArrayPointer, minus the + // pointer. + + allNonZero := typeutil.All(v.Type(), func(term *types.Term) bool { + return term.Type().Underlying().(*types.Array).Len() != 0 + }) + + if allNonZero { + // converting a slice to an array of length > 0 + // panics if the slice is nil + s.setOuter(v.X, NeverNil) + } + case *ir.Slice: + if typeutil.All(v.X.Type(), typeutil.IsType[*types.Array]) { + // Slicing arrays never results in a nil slice. + s.setOuter(v, NeverNil) + continue + } + + // checkBound returns true if one of the bounds (low, high, + // capacity) has non-zero value. + checkBound := func(v ir.Value) bool { + if v == nil { + return false + } + // TODO(dh): this is where integration with constant + // propagation and value range analysis would be useful. + if k, ok := v.(*ir.Const); ok { + kv, ok := constant.Int64Val(k.Value) + return !ok || kv != 0 + } + return false + } + if checkBound(v.Low) || checkBound(v.High) || checkBound(v.Max) { + // One of the indices is non-zero, which means slicing can + // only succeed if the slicee is not nil. + s.setOuter(v, NeverNil) + s.setOuter(v.X, NeverNil) + } else { + // The new slice is as nilly as the slicee. + s.set(v, s.get(v.X)) + } + + case *ir.If: + cond := v.Cond + binop, ok := cond.(*ir.BinOp) + if !ok { + continue + } + isNil := func(v ir.Value) bool { + k, ok := v.(*ir.Const) + if !ok { + return false + } + return k.Value == nil + } + var target ir.Value + if isNil(binop.X) { + target = binop.Y + } else if isNil(binop.Y) { + target = binop.X + } else { + continue + } + op := binop.Op + if to != from.Succs[0] { + // we're in the false branch, negate op + switch op { + case token.EQL: + op = token.NEQ + case token.NEQ: + op = token.EQL + default: + panic(fmt.Sprintf("internal error: unhandled token %v", op)) + } + } + switch op { + case token.EQL: + s.set(target, ValueNilness{AlwaysNil, AlwaysNil}) + case token.NEQ: + s.setOuter(target, NeverNil) + default: + panic(fmt.Sprintf("internal error: unhandled token %v", op)) + } + + // TODO(dh): also handle comparison of two non-nil values. The + // true branch of neverNil == nilly makes the nilly value neverNil. + case *ir.ChangeType: + s.set(v, s.get(v.X)) + case *ir.MultiConvert: + s.set(v, s.get(v.X)) + case *ir.Load: + if _, ok := v.X.(*ir.Global); ok { + s.setOuter(v, MaybeNilGlobal) + } else { + s.setOuter(v, MaybeNil) + } + s.setOuter(v.X, NeverNil) + case *ir.FieldAddr: + s.setOuter(v.X, NeverNil) + s.setOuter(v, NeverNil) + case *ir.IndexAddr: + s.setOuter(v.X, NeverNil) + s.setOuter(v, NeverNil) + case *ir.Alloc, *ir.MakeMap, *ir.MakeSlice, *ir.MakeClosure, *ir.MakeChan: + s.setOuter(v.(ir.Value), NeverNil) + case *ir.MapUpdate: + s.setOuter(v.Map, NeverNil) + case *ir.Store: + s.setOuter(v.Addr, NeverNil) + case ir.CallInstruction: + // go/defer/calling a nil function fatals/panics + if !v.Common().IsInvoke() { + s.setOuter(v.Common().Value, NeverNil) + } + _, ok := v.(*ir.Call) + if !ok { + // Defer and Go don't produce values + continue + } + if v.Common().Signature().Results().Len() != 1 { + // If the called function doesn't return any values then we + // don't care about it. If it has more than one return + // value, they'll be handled by Extract. + continue + } + + handleReturnValue(v.(ir.Value), v.(*ir.Call), 0) + case *ir.Send: + s.setOuter(v.Chan, NeverNil) + case *ir.Recv: + s.setOuter(v.Chan, NeverNil) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + case *ir.MakeInterface: + s.set(v, ValueNilness{ + Inner: s.get(v.X).Outer, + Outer: NeverNil, + }) + case *ir.ChangeInterface: + s.set(v, s.get(v.X)) + case *ir.TypeAssert: + if !v.CommaOk { + // The interface value cannot have been nil, or the type + // assertion would have panicked. + s.setOuter(v.X, NeverNil) + + if types.IsInterface(v.Type()) && !typeparams.IsTypeParam(v.Type()) { + // Type asserting to another interface doesn't succeed + // if the assertee was nil. It also results in a new + // interface value. + s.setOuter(v, NeverNil) + s.setInner(v, s.get(v.X).Inner) + } else { + // We've extracted the interface value's inner value. + s.setOuter(v, s.get(v.X).Inner) + } + } else { + // In a comma-ok type assertion, the return type is a + // tuple. There'll be Extract instructions getting the + // individual values, to which we'll attach the nilness + // info. + } + case *ir.TypeSwitch: + // Handled in Extract + case *ir.MapLookup: + if s.get(v.X).Outer == AlwaysNil { + s.set(v, ValueNilness{AlwaysNil, AlwaysNil}) + } else { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + case *ir.Field: + s.set(v.X, ValueNilness{NeverNil, NeverNil}) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + case *ir.Index: + s.set(v.X, ValueNilness{NeverNil, NeverNil}) + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + + case *ir.Extract: + switch tuple := v.Tuple.(type) { + case *ir.TypeAssert: + // When we get here, the type assertion used the comma-ok + // form, and we don't yet know anything about the result of + // the type assertion. + if v.Index == 0 { + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + + // TODO(dh): We should set v's nilness in the true and + // false branches of checks on the ok value. However, ok can be + // used in arbitrary ways, and we're also not set up to handle + // relational facts (ok being true or false affects the value + // of the other Extract). + + case *ir.Call: + handleReturnValue(v, tuple, v.Index) + + case *ir.TypeSwitch: + if v.Index == 0 { + // Index 0 is an integer and not interesting. + continue + } + idx := v.Index - 1 + if idx >= len(tuple.Conds) { + // Default branch + + // If there is an untyped nil case, then being in the + // default branch tells us that the interface value + // isn't nil. + hasNil := slices.ContainsFunc(tuple.Conds, func(typ types.Type) bool { + if typ, ok := typ.(*types.Basic); ok && typ.Kind() == types.UntypedNil { + return true + } + return false + }) + if hasNil { + s.setOuter(tuple.Tag, NeverNil) + } else { + s.setOuter(tuple.Tag, MaybeNil) + } + s.setOuter(v, s.get(tuple.Tag).Inner) + } else { + // There is no Extract for the 'untyped nil' case, + // which means that executing any Extract from a type + // switch implies that the switched-over value wasn't a + // nil interface value. + s.setOuter(tuple.Tag, NeverNil) + typ := tuple.Conds[idx] + if types.IsInterface(typ) && !typeparams.IsTypeParam(typ) { + // Succesfully type asserting to an interface type + // always produces a non-nil interface value. + s.setInner(v, s.get(tuple.Tag).Inner) + s.setOuter(v, NeverNil) + } else { + s.setOuter(v, s.get(tuple.Tag).Inner) + } + } + default: + s.set(v, ValueNilness{MaybeNil, MaybeNil}) + } + case *ir.Select: + if v.Blocking && len(v.States) == 1 { + // If the select doesn't have a default branch and only has + // one state, that state's channel cannot have been nil if + // we finished execution the select. + s.setOuter(v.States[0].Chan, NeverNil) + } + case *ir.Jump, *ir.BlankStore, *ir.Phi, + *ir.Panic, *ir.Return, *ir.RunDefers, *ir.Unreachable, *ir.ConstantSwitch, + *ir.UnOp, *ir.BinOp, *ir.CompositeValue, *ir.Range, *ir.Next: + default: + posn := pass.Fset.PositionFor(v.Pos(), false) + panic(fmt.Sprintf("internal error: unhandled type %T at %s", v, posn)) + } + } + return s + } + + processPhis := func(b *ir.BasicBlock, i int, s state) state { + for _, instr := range b.Instrs { + if instr, ok := instr.(*ir.Phi); ok { + s.set(instr, s.get(instr.Edges[i])) + } else { + break + } + } + return s + } + + // Populate default state for non-instruction values we encounter. We + // cannot defer this logic to state.get because control flow merges use + // simple merges of lattice values and won't know about value-specific + // defaults. + entrys := state{cloned: true, n: n} + for _, param := range fn.Params { + if typeutil.IsPointerLike(param.Type()) { + entrys.set(param, ValueNilness{Inner: MaybeNil, Outer: MaybeNil}) + } else { + // We never track nilness for value types, so they don't have to be + // present in the entry state, either. + } + } + if strings.HasPrefix(fn.Synthetic, "bound method wrapper") { + // This is a bound method and the bound receiver might be nil + for _, fvar := range fn.FreeVars { + entrys.set(fvar, ValueNilness{Outer: MaybeNil}) + } + } else { + // This is a closure, and closed over variables are allocs, which + // cannot be nil. + for _, fvar := range fn.FreeVars { + entrys.set(fvar, ValueNilness{Outer: NeverNil}) + } + } + var ops []*ir.Value + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + ops = instr.Operands(ops[:0]) + for _, pop := range ops { + if op, ok := (*pop).(*ir.Const); ok && typeutil.IsPointerLike(op.Type()) { + // The only constant pointer-like is nil. + entrys.set(op, ValueNilness{Inner: AlwaysNil, Outer: AlwaysNil}) + } + } + } + } + res := dense.Forward[dfa.DenseMapLattice[ValueNilness, lattice]]( + fn, + map[int][]ValueNilness{0: entrys.m}, + func(fromID, toID int, in []ValueNilness) []ValueNilness { + from := fn.Blocks[fromID] + to := fn.Blocks[toID] + s := state{n: n, m: in} + s = processBlock(from, to, s) + i := slices.Index(to.Preds, from) + s = processPhis(to, i, s) + return s.m + }, + ) + + retNilness := make([]ValueNilness, fn.Signature.Results().Len()) + for b := range fn.Returns() { + ret := b.Control().(*ir.Return) + s := state{n: n, m: res.In(b.Index)} + s = processBlock(b, nil, s) + for i, res := range ret.Results { + retNilness[i] = lattice{}.Merge(retNilness[i], s.get(res)) + } + } + + interesting := false + for i := range retNilness { + typ := fn.Signature.Results().At(i).Type() + if !typeutil.IsPointerLike(typ) { + retNilness[i] = ValueNilness{NeverNil, NeverNil} + continue + } + retNilness[i] = normalize(retNilness[i], typ) + if retNilness[i] != (ValueNilness{MaybeNil, MaybeNil}) { + interesting = true + } + } + + if interesting { + pass.ExportObjectFact(fn.Object(), &nilnessFact{retNilness}) + } + + return retNilness +} + +type lattice struct{} + +var _ dfa.Semilattice[ValueNilness] = lattice{} + +// Equals implements [dfa.Semilattice]. +func (l lattice) Equals(a, b ValueNilness) bool { + return a == b +} + +// Ident implements [dfa.Semilattice]. +func (l lattice) Ident() ValueNilness { + return ValueNilness{} +} + +var latticeMerge = [5][5]Nilness{ + 0: { + 0: 0, + NeverNil: NeverNil, + AlwaysNil: AlwaysNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + NeverNil: { + 0: NeverNil, + NeverNil: NeverNil, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + AlwaysNil: { + 0: AlwaysNil, + NeverNil: MaybeNil, + AlwaysNil: AlwaysNil, + MaybeNilGlobal: MaybeNil, + MaybeNil: MaybeNil, + }, + MaybeNilGlobal: { + 0: MaybeNilGlobal, + NeverNil: MaybeNilGlobal, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNilGlobal, + MaybeNil: MaybeNil, + }, + MaybeNil: { + 0: MaybeNil, + NeverNil: MaybeNil, + AlwaysNil: MaybeNil, + MaybeNilGlobal: MaybeNil, + MaybeNil: MaybeNil, + }, +} + +// Merge implements [dfa.Semilattice]. +func (l lattice) Merge(a, b ValueNilness) ValueNilness { + return ValueNilness{ + Inner: latticeMerge[a.Inner][b.Inner], + Outer: latticeMerge[a.Outer][b.Outer], + } +} + +type numbering map[ir.Value]int + +func (n numbering) number(v ir.Value) int { + i, ok := n[v] + if !ok { + i = len(n) + n[v] = i + } + return i +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/purity/purity.go b/vendor/honnef.co/go/tools/analysis/facts/purity/purity.go new file mode 100644 index 0000000..92c7f28 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/purity/purity.go @@ -0,0 +1,264 @@ +package purity + +// TODO(dh): we should split this into two facts, one tracking actual purity, and one tracking side-effects. A function +// that returns a heap allocation isn't pure, but it may be free of side effects. + +import ( + "go/types" + "reflect" + + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +type IsPure struct{} + +func (*IsPure) AFact() {} +func (d *IsPure) String() string { return "is pure" } + +type Result map[*types.Func]*IsPure + +var Analyzer = &analysis.Analyzer{ + Name: "fact_purity", + Doc: "Mark pure functions", + Run: purity, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + FactTypes: []analysis.Fact{(*IsPure)(nil)}, + ResultType: reflect.TypeFor[Result](), +} + +var pureStdlib = map[string]struct{}{ + "errors.New": {}, + "fmt.Errorf": {}, + "fmt.Sprintf": {}, + "fmt.Sprint": {}, + "sort.Reverse": {}, + "strings.Map": {}, + "strings.Repeat": {}, + "strings.Replace": {}, + "strings.Title": {}, + "strings.ToLower": {}, + "strings.ToLowerSpecial": {}, + "strings.ToTitle": {}, + "strings.ToTitleSpecial": {}, + "strings.ToUpper": {}, + "strings.ToUpperSpecial": {}, + "strings.Trim": {}, + "strings.TrimFunc": {}, + "strings.TrimLeft": {}, + "strings.TrimLeftFunc": {}, + "strings.TrimPrefix": {}, + "strings.TrimRight": {}, + "strings.TrimRightFunc": {}, + "strings.TrimSpace": {}, + "strings.TrimSuffix": {}, + "(*net/http.Request).WithContext": {}, + "time.Now": {}, + "time.Parse": {}, + "time.ParseInLocation": {}, + "time.Unix": {}, + "time.UnixMicro": {}, + "time.UnixMilli": {}, + "(time.Time).Add": {}, + "(time.Time).AddDate": {}, + "(time.Time).After": {}, + "(time.Time).Before": {}, + "(time.Time).Clock": {}, + "(time.Time).Compare": {}, + "(time.Time).Date": {}, + "(time.Time).Day": {}, + "(time.Time).Equal": {}, + "(time.Time).Format": {}, + "(time.Time).GoString": {}, + "(time.Time).GobEncode": {}, + "(time.Time).Hour": {}, + "(time.Time).ISOWeek": {}, + "(time.Time).In": {}, + "(time.Time).IsDST": {}, + "(time.Time).IsZero": {}, + "(time.Time).Local": {}, + "(time.Time).Location": {}, + "(time.Time).MarshalBinary": {}, + "(time.Time).MarshalJSON": {}, + "(time.Time).MarshalText": {}, + "(time.Time).Minute": {}, + "(time.Time).Month": {}, + "(time.Time).Nanosecond": {}, + "(time.Time).Round": {}, + "(time.Time).Second": {}, + "(time.Time).String": {}, + "(time.Time).Sub": {}, + "(time.Time).Truncate": {}, + "(time.Time).UTC": {}, + "(time.Time).Unix": {}, + "(time.Time).UnixMicro": {}, + "(time.Time).UnixMilli": {}, + "(time.Time).UnixNano": {}, + "(time.Time).Weekday": {}, + "(time.Time).Year": {}, + "(time.Time).YearDay": {}, + "(time.Time).Zone": {}, + "(time.Time).ZoneBounds": {}, +} + +func purity(pass *analysis.Pass) (any, error) { + seen := map[*ir.Function]struct{}{} + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + var check func(fn *ir.Function) (ret bool) + check = func(fn *ir.Function) (ret bool) { + if fn.Object() == nil { + // TODO(dh): support closures + return false + } + if pass.ImportObjectFact(fn.Object(), new(IsPure)) { + return true + } + if fn.Pkg != irpkg { + // Function is in another package but wasn't marked as + // pure, ergo it isn't pure + return false + } + // Break recursion + if _, ok := seen[fn]; ok { + return false + } + + seen[fn] = struct{}{} + defer func() { + if ret { + pass.ExportObjectFact(fn.Object(), &IsPure{}) + } + }() + + if irutil.IsStub(fn) { + return false + } + + if _, ok := pureStdlib[fn.Object().(*types.Func).FullName()]; ok { + return true + } + + if fn.Signature.Results().Len() == 0 { + // A function with no return values is empty or is doing some + // work we cannot see (for example because of build tags); + // don't consider it pure. + return false + } + + var isBasic func(typ types.Type) bool + isBasic = func(typ types.Type) bool { + switch u := typ.Underlying().(type) { + case *types.Basic: + return true + case *types.Struct: + for field := range u.Fields() { + if !isBasic(field.Type()) { + return false + } + } + return true + default: + return false + } + } + + for _, param := range fn.Params { + // TODO(dh): this may not be strictly correct. pure code can, to an extent, operate on non-basic types. + if !isBasic(param.Type()) { + return false + } + } + + // Don't consider external functions pure. + if fn.Blocks == nil { + return false + } + checkCall := func(common *ir.CallCommon) bool { + if common.IsInvoke() { + return false + } + builtin, ok := common.Value.(*ir.Builtin) + if !ok { + if common.StaticCallee() != fn { + if common.StaticCallee() == nil { + return false + } + if !check(common.StaticCallee()) { + return false + } + } + } else { + switch builtin.Name() { + case "len", "cap": + default: + return false + } + } + return true + } + + var isStackAddr func(ir.Value) bool + isStackAddr = func(v ir.Value) bool { + switch v := v.(type) { + case *ir.Alloc: + return !v.Heap + case *ir.FieldAddr: + return isStackAddr(v.X) + default: + return false + } + } + for _, b := range fn.Blocks { + for _, ins := range b.Instrs { + switch ins := ins.(type) { + case *ir.Call: + if !checkCall(ins.Common()) { + return false + } + case *ir.Defer: + if !checkCall(&ins.Call) { + return false + } + case *ir.Select: + return false + case *ir.Send: + return false + case *ir.Go: + return false + case *ir.Panic: + return false + case *ir.Store: + if !isStackAddr(ins.Addr) { + return false + } + case *ir.FieldAddr: + if !isStackAddr(ins.X) { + return false + } + case *ir.Alloc: + // TODO(dh): make use of proper escape analysis + if ins.Heap { + return false + } + case *ir.Load: + if !isStackAddr(ins.X) { + return false + } + } + } + } + return true + } + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + check(fn) + } + + out := Result{} + for _, fact := range pass.AllObjectFacts() { + out[fact.Object.(*types.Func)] = fact.Fact.(*IsPure) + } + return out, nil +} diff --git a/vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go b/vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go new file mode 100644 index 0000000..3618c89 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/facts/tokenfile/token.go @@ -0,0 +1,24 @@ +package tokenfile + +import ( + "go/ast" + "go/token" + "reflect" + + "golang.org/x/tools/go/analysis" +) + +var Analyzer = &analysis.Analyzer{ + Name: "tokenfileanalyzer", + Doc: "creates a mapping of *token.File to *ast.File", + Run: func(pass *analysis.Pass) (any, error) { + m := map[*token.File]*ast.File{} + for _, af := range pass.Files { + tf := pass.Fset.File(af.Pos()) + m[tf] = af + } + return m, nil + }, + RunDespiteErrors: true, + ResultType: reflect.TypeFor[map[*token.File]*ast.File](), +} diff --git a/vendor/honnef.co/go/tools/analysis/lint/lint.go b/vendor/honnef.co/go/tools/analysis/lint/lint.go new file mode 100644 index 0000000..c82bc24 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/lint/lint.go @@ -0,0 +1,221 @@ +// Package lint provides abstractions on top of go/analysis. +// These abstractions add extra information to analyzes, such as structured documentation and severities. +package lint + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/facts/tokenfile" +) + +// Analyzer wraps a go/analysis.Analyzer and provides structured documentation. +type Analyzer struct { + // The analyzer's documentation. Unlike go/analysis.Analyzer.Doc, + // this field is structured, providing access to severity, options + // etc. + Doc *RawDocumentation + Analyzer *analysis.Analyzer +} + +func InitializeAnalyzer(a *Analyzer) *Analyzer { + a.Analyzer.Doc = a.Doc.Compile().String() + a.Analyzer.URL = "https://staticcheck.dev/docs/checks/#" + a.Analyzer.Name + a.Analyzer.Requires = append(a.Analyzer.Requires, tokenfile.Analyzer) + return a +} + +// Severity describes the severity of diagnostics reported by an analyzer. +type Severity int + +const ( + SeverityNone Severity = iota + SeverityError + SeverityDeprecated + SeverityWarning + SeverityInfo + SeverityHint +) + +// MergeStrategy sets how merge mode should behave for diagnostics of an analyzer. +type MergeStrategy int + +const ( + MergeIfAny MergeStrategy = iota + MergeIfAll +) + +type RawDocumentation struct { + Title string + Text string + Before string + After string + Since string + NonDefault bool + Options []string + Severity Severity + MergeIf MergeStrategy +} + +type Documentation struct { + Title string + Text string + + TitleMarkdown string + TextMarkdown string + + Before string + After string + Since string + NonDefault bool + Options []string + Severity Severity + MergeIf MergeStrategy +} + +func (doc RawDocumentation) Compile() *Documentation { + return &Documentation{ + Title: strings.TrimSpace(stripMarkdown(doc.Title)), + Text: strings.TrimSpace(stripMarkdown(doc.Text)), + + TitleMarkdown: strings.TrimSpace(toMarkdown(doc.Title)), + TextMarkdown: strings.TrimSpace(toMarkdown(doc.Text)), + + Before: strings.TrimSpace(doc.Before), + After: strings.TrimSpace(doc.After), + Since: doc.Since, + NonDefault: doc.NonDefault, + Options: doc.Options, + Severity: doc.Severity, + MergeIf: doc.MergeIf, + } +} + +func toMarkdown(s string) string { + return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s) +} + +func stripMarkdown(s string) string { + return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s) +} + +func (doc *Documentation) Format(metadata bool) string { + return doc.format(false, metadata) +} + +func (doc *Documentation) FormatMarkdown(metadata bool) string { + return doc.format(true, metadata) +} + +func (doc *Documentation) format(markdown bool, metadata bool) string { + b := &strings.Builder{} + if markdown { + fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown) + if doc.Text != "" { + fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown) + } + } else { + fmt.Fprintf(b, "%s\n\n", doc.Title) + if doc.Text != "" { + fmt.Fprintf(b, "%s\n\n", doc.Text) + } + } + + if doc.Before != "" { + fmt.Fprintln(b, "Before:") + fmt.Fprintln(b, "") + for line := range strings.SplitSeq(doc.Before, "\n") { + fmt.Fprint(b, " ", line, "\n") + } + fmt.Fprintln(b, "") + fmt.Fprintln(b, "After:") + fmt.Fprintln(b, "") + for line := range strings.SplitSeq(doc.After, "\n") { + fmt.Fprint(b, " ", line, "\n") + } + fmt.Fprintln(b, "") + } + + if metadata { + fmt.Fprint(b, "Available since\n ") + if doc.Since == "" { + fmt.Fprint(b, "unreleased") + } else { + fmt.Fprintf(b, "%s", doc.Since) + } + if doc.NonDefault { + fmt.Fprint(b, ", non-default") + } + fmt.Fprint(b, "\n") + if len(doc.Options) > 0 { + fmt.Fprintf(b, "\nOptions\n") + for _, opt := range doc.Options { + fmt.Fprintf(b, " %s", opt) + } + fmt.Fprint(b, "\n") + } + } + + return b.String() +} + +func (doc *Documentation) String() string { + return doc.Format(true) +} + +// ExhaustiveTypeSwitch panics when called. It can be used to ensure +// that type switches are exhaustive. +func ExhaustiveTypeSwitch(v any) { + panic(fmt.Sprintf("internal error: unhandled case %T", v)) +} + +// A directive is a comment of the form '//lint: +// [arguments...]'. It represents instructions to the static analysis +// tool. +type Directive struct { + Command string + Arguments []string + Directive *ast.Comment + Node ast.Node +} + +func parseDirective(s string) (cmd string, args []string) { + if !strings.HasPrefix(s, "//lint:") { + return "", nil + } + s = strings.TrimPrefix(s, "//lint:") + fields := strings.Split(s, " ") + return fields[0], fields[1:] +} + +// ParseDirectives extracts all directives from a list of Go files. +func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive { + var dirs []Directive + for _, f := range files { + // OPT(dh): in our old code, we skip all the comment map work if we + // couldn't find any directives, benchmark if that's actually + // worth doing + cm := ast.NewCommentMap(fset, f, f.Comments) + for node, cgs := range cm { + for _, cg := range cgs { + for _, c := range cg.List { + if !strings.HasPrefix(c.Text, "//lint:") { + continue + } + cmd, args := parseDirective(c.Text) + d := Directive{ + Command: cmd, + Arguments: args, + Directive: c, + Node: node, + } + dirs = append(dirs, d) + } + } + } + } + return dirs +} diff --git a/vendor/honnef.co/go/tools/analysis/report/report.go b/vendor/honnef.co/go/tools/analysis/report/report.go new file mode 100644 index 0000000..39919a8 --- /dev/null +++ b/vendor/honnef.co/go/tools/analysis/report/report.go @@ -0,0 +1,280 @@ +package report + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/token" + "go/version" + "path/filepath" + "strconv" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + + "golang.org/x/tools/go/analysis" +) + +type Options struct { + ShortRange bool + FilterGenerated bool + Fixes []analysis.SuggestedFix + Related []analysis.RelatedInformation + MinimumLanguageVersion string + MaximumLanguageVersion string + MinimumStdlibVersion string + MaximumStdlibVersion string +} + +type Option func(*Options) + +func ShortRange() Option { + return func(opts *Options) { + opts.ShortRange = true + } +} + +func FilterGenerated() Option { + return func(opts *Options) { + opts.FilterGenerated = true + } +} + +func Fixes(fixes ...analysis.SuggestedFix) Option { + return func(opts *Options) { + opts.Fixes = append(opts.Fixes, fixes...) + } +} + +func Related(node Positioner, message string) Option { + return func(opts *Options) { + pos, end, ok := getRange(node, opts.ShortRange) + if !ok { + return + } + r := analysis.RelatedInformation{ + Pos: pos, + End: end, + Message: message, + } + opts.Related = append(opts.Related, r) + } +} + +func MinimumLanguageVersion(vers string) Option { + return func(opts *Options) { opts.MinimumLanguageVersion = vers } +} +func MaximumLanguageVersion(vers string) Option { + return func(opts *Options) { opts.MinimumLanguageVersion = vers } +} +func MinimumStdlibVersion(vers string) Option { + return func(opts *Options) { opts.MinimumStdlibVersion = vers } +} +func MaximumStdlibVersion(vers string) Option { + return func(opts *Options) { opts.MaximumStdlibVersion = vers } +} + +type Positioner interface { + Pos() token.Pos +} + +type fullPositioner interface { + Pos() token.Pos + End() token.Pos +} + +type sourcer interface { + Source() ast.Node +} + +// shortRange returns the position and end of the main component of an +// AST node. For nodes that have no body, the short range is identical +// to the node's Pos and End. For nodes that do have a body, the short +// range excludes the body. +func shortRange(node ast.Node) (pos, end token.Pos) { + switch node := node.(type) { + case *ast.File: + return node.Pos(), node.Name.End() + case *ast.CaseClause: + return node.Pos(), node.Colon + 1 + case *ast.CommClause: + return node.Pos(), node.Colon + 1 + case *ast.DeferStmt: + return node.Pos(), node.Defer + token.Pos(len("defer")) + case *ast.ExprStmt: + return shortRange(node.X) + case *ast.ForStmt: + if node.Post != nil { + return node.For, node.Post.End() + } else if node.Cond != nil { + return node.For, node.Cond.End() + } else if node.Init != nil { + // +1 to catch the semicolon, for gofmt'ed code + return node.Pos(), node.Init.End() + 1 + } else { + return node.Pos(), node.For + token.Pos(len("for")) + } + case *ast.FuncDecl: + return node.Pos(), node.Type.End() + case *ast.FuncLit: + return node.Pos(), node.Type.End() + case *ast.GoStmt: + if _, ok := ast.Unparen(node.Call.Fun).(*ast.FuncLit); ok { + return node.Pos(), node.Go + token.Pos(len("go")) + } else { + return node.Pos(), node.End() + } + case *ast.IfStmt: + return node.Pos(), node.Cond.End() + case *ast.RangeStmt: + return node.Pos(), node.X.End() + case *ast.SelectStmt: + return node.Pos(), node.Pos() + token.Pos(len("select")) + case *ast.SwitchStmt: + if node.Tag != nil { + return node.Pos(), node.Tag.End() + } else if node.Init != nil { + // +1 to catch the semicolon, for gofmt'ed code + return node.Pos(), node.Init.End() + 1 + } else { + return node.Pos(), node.Pos() + token.Pos(len("switch")) + } + case *ast.TypeSwitchStmt: + return node.Pos(), node.Assign.End() + default: + return node.Pos(), node.End() + } +} + +func HasRange(node Positioner) bool { + // we don't know if getRange will be called with shortRange set to + // true, so make sure that both work. + _, _, ok := getRange(node, false) + if !ok { + return false + } + _, _, ok = getRange(node, true) + return ok +} + +func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) { + switch n := node.(type) { + case sourcer: + s := n.Source() + if s == nil { + return 0, 0, false + } + if short { + p, e := shortRange(s) + return p, e, true + } + return s.Pos(), s.End(), true + case fullPositioner: + if short { + p, e := shortRange(n) + return p, e, true + } + return n.Pos(), n.End(), true + default: + return n.Pos(), token.NoPos, true + } +} + +func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) { + cfg := &Options{} + for _, opt := range opts { + opt(cfg) + } + + langVersion := code.LanguageVersion(pass, node) + stdlibVersion := code.StdlibVersion(pass, node) + if n := cfg.MaximumLanguageVersion; n != "" && version.Compare(n, langVersion) == -1 { + return + } + if n := cfg.MaximumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == -1 { + return + } + if n := cfg.MinimumLanguageVersion; n != "" && version.Compare(n, langVersion) == 1 { + return + } + if n := cfg.MinimumStdlibVersion; n != "" && version.Compare(n, stdlibVersion) == 1 { + return + } + + file := DisplayPosition(pass.Fset, node.Pos()).Filename + if cfg.FilterGenerated { + m := pass.ResultOf[generated.Analyzer].(map[string]generated.Generator) + if _, ok := m[file]; ok { + return + } + } + + pos, end, ok := getRange(node, cfg.ShortRange) + if !ok { + panic(fmt.Sprintf("no valid position for reporting node %v", node)) + } + d := analysis.Diagnostic{ + Pos: pos, + End: end, + Message: message, + SuggestedFixes: cfg.Fixes, + Related: cfg.Related, + } + pass.Report(d) +} + +func Render(pass *analysis.Pass, x any) string { + var buf bytes.Buffer + if err := format.Node(&buf, pass.Fset, x); err != nil { + panic(err) + } + return buf.String() +} + +func RenderArgs(pass *analysis.Pass, args []ast.Expr) string { + var ss []string + for _, arg := range args { + ss = append(ss, Render(pass, arg)) + } + return strings.Join(ss, ", ") +} + +func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position { + if p == token.NoPos { + return token.Position{} + } + + // Only use the adjusted position if it points to another Go file. + // This means we'll point to the original file for cgo files, but + // we won't point to a YACC grammar file. + pos := fset.PositionFor(p, false) + adjPos := fset.PositionFor(p, true) + + if filepath.Ext(adjPos.Filename) == ".go" { + return adjPos + } + + return pos +} + +func Ordinal(n int) string { + suffix := "th" + if n < 10 || n > 20 { + switch n % 10 { + case 0: + suffix = "th" + case 1: + suffix = "st" + case 2: + suffix = "nd" + case 3: + suffix = "rd" + default: + suffix = "th" + } + } + + return strconv.Itoa(n) + suffix +} diff --git a/vendor/honnef.co/go/tools/cmd/staticcheck/README.md b/vendor/honnef.co/go/tools/cmd/staticcheck/README.md new file mode 100644 index 0000000..e5ee3aa --- /dev/null +++ b/vendor/honnef.co/go/tools/cmd/staticcheck/README.md @@ -0,0 +1,15 @@ +# staticcheck + +_staticcheck_ offers extensive analysis of Go code, covering a myriad +of categories. It will detect bugs, suggest code simplifications, +point out dead code, and more. + +## Installation + +See [the main README](https://github.com/dominikh/go-tools#installation) for installation instructions. + +## Documentation + +Detailed documentation can be found on +[staticcheck.dev](https://staticcheck.dev/docs/). + diff --git a/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go b/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go new file mode 100644 index 0000000..0561740 --- /dev/null +++ b/vendor/honnef.co/go/tools/cmd/staticcheck/staticcheck.go @@ -0,0 +1,45 @@ +// staticcheck analyses Go code and makes it better. +package main + +import ( + "log" + "os" + + "honnef.co/go/tools/lintcmd" + "honnef.co/go/tools/lintcmd/version" + "honnef.co/go/tools/quickfix" + "honnef.co/go/tools/simple" + "honnef.co/go/tools/staticcheck" + "honnef.co/go/tools/stylecheck" + "honnef.co/go/tools/unused" +) + +func main() { + cmd := lintcmd.NewCommand("staticcheck") + cmd.SetVersion(version.Version, version.MachineVersion) + + fs := cmd.FlagSet() + debug := fs.String("debug.unused-graph", "", "Write unused's object graph to `file`") + qf := fs.Bool("debug.run-quickfix-analyzers", false, "Run quickfix analyzers") + + cmd.ParseFlags(os.Args[1:]) + + cmd.AddAnalyzers(simple.Analyzers...) + cmd.AddAnalyzers(staticcheck.Analyzers...) + cmd.AddAnalyzers(stylecheck.Analyzers...) + cmd.AddAnalyzers(unused.Analyzer) + + if *qf { + cmd.AddAnalyzers(quickfix.Analyzers...) + } + + if *debug != "" { + f, err := os.OpenFile(*debug, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) + if err != nil { + log.Fatal(err) + } + unused.Debug = f + } + + cmd.Run() +} diff --git a/vendor/honnef.co/go/tools/config/config.go b/vendor/honnef.co/go/tools/config/config.go new file mode 100644 index 0000000..1c47f69 --- /dev/null +++ b/vendor/honnef.co/go/tools/config/config.go @@ -0,0 +1,278 @@ +package config + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "os" + "path/filepath" + "reflect" + "strings" + + "github.com/BurntSushi/toml" + "golang.org/x/tools/go/analysis" +) + +// Dir looks at a list of absolute file names, which should make up a +// single package, and returns the path of the directory that may +// contain a staticcheck.conf file. It returns the empty string if no +// such directory could be determined, for example because all files +// were located in Go's build cache. +func Dir(files []string) string { + if len(files) == 0 { + return "" + } + cache, err := os.UserCacheDir() + if err != nil { + cache = "" + } + var path string + for _, p := range files { + // FIXME(dh): using strings.HasPrefix isn't technically + // correct, but it should be good enough for now. + if cache != "" && strings.HasPrefix(p, cache) { + // File in the build cache of the standard Go build system + continue + } + path = p + break + } + + if path == "" { + // The package only consists of generated files. + return "" + } + + dir := filepath.Dir(path) + return dir +} + +func dirAST(files []*ast.File, fset *token.FileSet) string { + names := make([]string, len(files)) + for i, f := range files { + names[i] = fset.PositionFor(f.Pos(), true).Filename + } + return Dir(names) +} + +var Analyzer = &analysis.Analyzer{ + Name: "config", + Doc: "loads configuration for the current package tree", + Run: func(pass *analysis.Pass) (any, error) { + dir := dirAST(pass.Files, pass.Fset) + if dir == "" { + cfg := DefaultConfig + return &cfg, nil + } + cfg, err := Load(dir) + if err != nil { + return nil, fmt.Errorf("error loading staticcheck.conf: %s", err) + } + return &cfg, nil + }, + RunDespiteErrors: true, + ResultType: reflect.TypeFor[*Config](), +} + +func For(pass *analysis.Pass) *Config { + return pass.ResultOf[Analyzer].(*Config) +} + +func mergeLists(a, b []string) []string { + out := make([]string, 0, len(a)+len(b)) + for _, el := range b { + if el == "inherit" { + out = append(out, a...) + } else { + out = append(out, el) + } + } + + return out +} + +func normalizeList(list []string) []string { + if len(list) > 1 { + nlist := make([]string, 0, len(list)) + nlist = append(nlist, list[0]) + for i, el := range list[1:] { + if el != list[i] { + nlist = append(nlist, el) + } + } + list = nlist + } + + for _, el := range list { + if el == "inherit" { + // This should never happen, because the default config + // should not use "inherit" + panic(`unresolved "inherit"`) + } + } + + return list +} + +func (cfg Config) Merge(ocfg Config) Config { + if ocfg.Checks != nil { + cfg.Checks = mergeLists(cfg.Checks, ocfg.Checks) + } + if ocfg.Initialisms != nil { + cfg.Initialisms = mergeLists(cfg.Initialisms, ocfg.Initialisms) + } + if ocfg.DotImportWhitelist != nil { + cfg.DotImportWhitelist = mergeLists(cfg.DotImportWhitelist, ocfg.DotImportWhitelist) + } + if ocfg.HTTPStatusCodeWhitelist != nil { + cfg.HTTPStatusCodeWhitelist = mergeLists(cfg.HTTPStatusCodeWhitelist, ocfg.HTTPStatusCodeWhitelist) + } + return cfg +} + +type Config struct { + // TODO(dh): this implementation makes it impossible for external + // clients to add their own checkers with configuration. At the + // moment, we don't really care about that; we don't encourage + // that people use this package. In the future, we may. The + // obvious solution would be using map[string]interface{}, but + // that's obviously subpar. + + Checks []string `toml:"checks"` + Initialisms []string `toml:"initialisms"` + DotImportWhitelist []string `toml:"dot_import_whitelist"` + HTTPStatusCodeWhitelist []string `toml:"http_status_code_whitelist"` +} + +func (c Config) String() string { + buf := &bytes.Buffer{} + + fmt.Fprintf(buf, "Checks: %#v\n", c.Checks) + fmt.Fprintf(buf, "Initialisms: %#v\n", c.Initialisms) + fmt.Fprintf(buf, "DotImportWhitelist: %#v\n", c.DotImportWhitelist) + fmt.Fprintf(buf, "HTTPStatusCodeWhitelist: %#v", c.HTTPStatusCodeWhitelist) + + return buf.String() +} + +// DefaultConfig is the default configuration. +// Its initial value describes the majority of the default configuration, +// but the Checks field can be updated at runtime based on the analyzers being used, to disable non-default checks. +// For cmd/staticcheck, this is handled by (*lintcmd.Command).Run. +// +// Note that DefaultConfig shouldn't be modified while analyzers are executing. +var DefaultConfig = Config{ + Checks: []string{"all"}, + Initialisms: []string{ + "ACL", "API", "ASCII", "CPU", "CSS", "DNS", + "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", + "IP", "JSON", "QPS", "RAM", "RPC", "SLA", + "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", + "UDP", "UI", "GID", "UID", "UUID", "URI", + "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", + "XSS", "SIP", "RTP", "AMQP", "DB", "TS", + }, + DotImportWhitelist: []string{ + "simd/archsimd", + "github.com/mmcloughlin/avo/build", + "github.com/mmcloughlin/avo/operand", + "github.com/mmcloughlin/avo/reg", + }, + HTTPStatusCodeWhitelist: []string{"200", "400", "404", "500"}, +} + +const ConfigName = "staticcheck.conf" + +type ParseError struct { + Filename string + toml.ParseError +} + +func parseConfigs(dir string) ([]Config, error) { + var out []Config + + // TODO(dh): consider stopping at the GOPATH/module boundary + for dir != "" { + path := filepath.Join(dir, ConfigName) + fi, err := os.Stat(path) + if os.IsNotExist(err) || (err == nil && !fi.Mode().IsRegular()) { + // walk up + ndir := filepath.Dir(dir) + if ndir == dir { + break + } + dir = ndir + continue + } + if err != nil { + return nil, err + } + + // There is a small TOCTOU window here, but we're fine with reporting an + // error if the source tree is modified concurrently in weird ways while + // running Staticcheck. + f, err := os.Open(path) + if err != nil { + return nil, err + } + + var cfg Config + _, err = toml.NewDecoder(f).Decode(&cfg) + f.Close() + if err != nil { + if err, ok := err.(toml.ParseError); ok { + return nil, ParseError{ + Filename: filepath.Join(dir, ConfigName), + ParseError: err, + } + } + return nil, err + } + out = append(out, cfg) + ndir := filepath.Dir(dir) + if ndir == dir { + break + } + dir = ndir + } + out = append(out, DefaultConfig) + if len(out) < 2 { + return out, nil + } + for i := 0; i < len(out)/2; i++ { + out[i], out[len(out)-1-i] = out[len(out)-1-i], out[i] + } + return out, nil +} + +func mergeConfigs(confs []Config) Config { + if len(confs) == 0 { + // This shouldn't happen because we always have at least a + // default config. + panic("trying to merge zero configs") + } + if len(confs) == 1 { + return confs[0] + } + conf := confs[0] + for _, oconf := range confs[1:] { + conf = conf.Merge(oconf) + } + return conf +} + +func Load(dir string) (Config, error) { + confs, err := parseConfigs(dir) + if err != nil { + return Config{}, err + } + conf := mergeConfigs(confs) + + conf.Checks = normalizeList(conf.Checks) + conf.Initialisms = normalizeList(conf.Initialisms) + conf.DotImportWhitelist = normalizeList(conf.DotImportWhitelist) + conf.HTTPStatusCodeWhitelist = normalizeList(conf.HTTPStatusCodeWhitelist) + + return conf, nil +} diff --git a/vendor/honnef.co/go/tools/config/example.conf b/vendor/honnef.co/go/tools/config/example.conf new file mode 100644 index 0000000..acc9d69 --- /dev/null +++ b/vendor/honnef.co/go/tools/config/example.conf @@ -0,0 +1,15 @@ +checks = ["all", "-SA9003", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1023"] +initialisms = ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", + "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", + "IP", "JSON", "QPS", "RAM", "RPC", "SLA", + "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", + "UDP", "UI", "GID", "UID", "UUID", "URI", + "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", + "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] +dot_import_whitelist = [ + "simd/archsimd", + "github.com/mmcloughlin/avo/build", + "github.com/mmcloughlin/avo/operand", + "github.com/mmcloughlin/avo/reg", +] +http_status_code_whitelist = ["200", "400", "404", "500"] diff --git a/vendor/honnef.co/go/tools/go/ast/astutil/upstream.go b/vendor/honnef.co/go/tools/go/ast/astutil/upstream.go new file mode 100644 index 0000000..fc647c4 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ast/astutil/upstream.go @@ -0,0 +1,20 @@ +package astutil + +import ( + "go/ast" + "go/token" + _ "unsafe" + + "golang.org/x/tools/go/ast/astutil" +) + +type Cursor = astutil.Cursor +type ApplyFunc = astutil.ApplyFunc + +func Apply(root ast.Node, pre, post ApplyFunc) (result ast.Node) { + return astutil.Apply(root, pre, post) +} + +func PathEnclosingInterval(root *ast.File, start, end token.Pos) (path []ast.Node, exact bool) { + return astutil.PathEnclosingInterval(root, start, end) +} diff --git a/vendor/honnef.co/go/tools/go/ast/astutil/util.go b/vendor/honnef.co/go/tools/go/ast/astutil/util.go new file mode 100644 index 0000000..c23968f --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ast/astutil/util.go @@ -0,0 +1,462 @@ +package astutil + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" + "strings" + + "golang.org/x/tools/go/ast/astutil" +) + +func IsIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + return ok && id.Name == ident +} + +// isBlank returns whether id is the blank identifier "_". +// If id == nil, the answer is false. +func IsBlank(id ast.Expr) bool { + ident, _ := id.(*ast.Ident) + return ident != nil && ident.Name == "_" +} + +// Deprecated: use code.IsIntegerLiteral instead. +func IsIntLiteral(expr ast.Expr, literal string) bool { + lit, ok := expr.(*ast.BasicLit) + return ok && lit.Kind == token.INT && lit.Value == literal +} + +// Deprecated: use IsIntLiteral instead +func IsZero(expr ast.Expr) bool { + return IsIntLiteral(expr, "0") +} + +func Preamble(f *ast.File) string { + cutoff := f.Package + if f.Doc != nil { + cutoff = f.Doc.Pos() + } + var out []string + for _, cmt := range f.Comments { + if cmt.Pos() >= cutoff { + break + } + out = append(out, cmt.Text()) + } + return strings.Join(out, "\n") +} + +func GroupSpecs(fset *token.FileSet, specs []ast.Spec) [][]ast.Spec { + if len(specs) == 0 { + return nil + } + groups := make([][]ast.Spec, 1) + groups[0] = append(groups[0], specs[0]) + + for _, spec := range specs[1:] { + g := groups[len(groups)-1] + if fset.PositionFor(spec.Pos(), false).Line-1 != + fset.PositionFor(g[len(g)-1].End(), false).Line { + + groups = append(groups, nil) + } + + groups[len(groups)-1] = append(groups[len(groups)-1], spec) + } + + return groups +} + +// CopyExpr creates a deep copy of an expression. +// It doesn't support copying FuncLits and returns ok == false when encountering one. +func CopyExpr(node ast.Expr) (ast.Expr, bool) { + switch node := node.(type) { + case *ast.BasicLit: + cp := *node + return &cp, true + case *ast.BinaryExpr: + cp := *node + var ok1, ok2 bool + cp.X, ok1 = CopyExpr(cp.X) + cp.Y, ok2 = CopyExpr(cp.Y) + return &cp, ok1 && ok2 + case *ast.CallExpr: + var ok bool + cp := *node + cp.Fun, ok = CopyExpr(cp.Fun) + if !ok { + return nil, false + } + cp.Args = make([]ast.Expr, len(node.Args)) + for i, v := range node.Args { + cp.Args[i], ok = CopyExpr(v) + if !ok { + return nil, false + } + } + return &cp, true + case *ast.CompositeLit: + var ok bool + cp := *node + cp.Type, ok = CopyExpr(cp.Type) + if !ok { + return nil, false + } + cp.Elts = make([]ast.Expr, len(node.Elts)) + for i, v := range node.Elts { + cp.Elts[i], ok = CopyExpr(v) + if !ok { + return nil, false + } + } + return &cp, true + case *ast.Ident: + cp := *node + return &cp, true + case *ast.IndexExpr: + var ok1, ok2 bool + cp := *node + cp.X, ok1 = CopyExpr(cp.X) + cp.Index, ok2 = CopyExpr(cp.Index) + return &cp, ok1 && ok2 + case *ast.IndexListExpr: + var ok bool + cp := *node + cp.X, ok = CopyExpr(cp.X) + if !ok { + return nil, false + } + for i, v := range node.Indices { + cp.Indices[i], ok = CopyExpr(v) + if !ok { + return nil, false + } + } + return &cp, true + case *ast.KeyValueExpr: + var ok1, ok2 bool + cp := *node + cp.Key, ok1 = CopyExpr(cp.Key) + cp.Value, ok2 = CopyExpr(cp.Value) + return &cp, ok1 && ok2 + case *ast.ParenExpr: + var ok bool + cp := *node + cp.X, ok = CopyExpr(cp.X) + return &cp, ok + case *ast.SelectorExpr: + var ok bool + cp := *node + cp.X, ok = CopyExpr(cp.X) + if !ok { + return nil, false + } + sel, ok := CopyExpr(cp.Sel) + if !ok { + // this is impossible + return nil, false + } + cp.Sel = sel.(*ast.Ident) + return &cp, true + case *ast.SliceExpr: + var ok1, ok2, ok3, ok4 bool + cp := *node + cp.X, ok1 = CopyExpr(cp.X) + cp.Low, ok2 = CopyExpr(cp.Low) + cp.High, ok3 = CopyExpr(cp.High) + cp.Max, ok4 = CopyExpr(cp.Max) + return &cp, ok1 && ok2 && ok3 && ok4 + case *ast.StarExpr: + var ok bool + cp := *node + cp.X, ok = CopyExpr(cp.X) + return &cp, ok + case *ast.TypeAssertExpr: + var ok1, ok2 bool + cp := *node + cp.X, ok1 = CopyExpr(cp.X) + cp.Type, ok2 = CopyExpr(cp.Type) + return &cp, ok1 && ok2 + case *ast.UnaryExpr: + var ok bool + cp := *node + cp.X, ok = CopyExpr(cp.X) + return &cp, ok + case *ast.MapType: + var ok1, ok2 bool + cp := *node + cp.Key, ok1 = CopyExpr(cp.Key) + cp.Value, ok2 = CopyExpr(cp.Value) + return &cp, ok1 && ok2 + case *ast.ArrayType: + var ok1, ok2 bool + cp := *node + cp.Len, ok1 = CopyExpr(cp.Len) + cp.Elt, ok2 = CopyExpr(cp.Elt) + return &cp, ok1 && ok2 + case *ast.Ellipsis: + var ok bool + cp := *node + cp.Elt, ok = CopyExpr(cp.Elt) + return &cp, ok + case *ast.InterfaceType: + cp := *node + return &cp, true + case *ast.StructType: + cp := *node + return &cp, true + case *ast.FuncLit, *ast.FuncType: + // TODO(dh): implement copying of function literals and types. + return nil, false + case *ast.ChanType: + var ok bool + cp := *node + cp.Value, ok = CopyExpr(cp.Value) + return &cp, ok + case nil: + return nil, true + default: + panic(fmt.Sprintf("unreachable: %T", node)) + } +} + +func Equal(a, b ast.Node) bool { + if a == b { + return true + } + if a == nil || b == nil { + return false + } + if reflect.TypeOf(a) != reflect.TypeOf(b) { + return false + } + + switch a := a.(type) { + case *ast.BasicLit: + b := b.(*ast.BasicLit) + return a.Kind == b.Kind && a.Value == b.Value + case *ast.BinaryExpr: + b := b.(*ast.BinaryExpr) + return Equal(a.X, b.X) && a.Op == b.Op && Equal(a.Y, b.Y) + case *ast.CallExpr: + b := b.(*ast.CallExpr) + if len(a.Args) != len(b.Args) { + return false + } + for i, arg := range a.Args { + if !Equal(arg, b.Args[i]) { + return false + } + } + return Equal(a.Fun, b.Fun) && + (a.Ellipsis == token.NoPos && b.Ellipsis == token.NoPos || a.Ellipsis != token.NoPos && b.Ellipsis != token.NoPos) + case *ast.CompositeLit: + b := b.(*ast.CompositeLit) + if len(a.Elts) != len(b.Elts) { + return false + } + for i, elt := range b.Elts { + if !Equal(elt, b.Elts[i]) { + return false + } + } + return Equal(a.Type, b.Type) && a.Incomplete == b.Incomplete + case *ast.Ident: + b := b.(*ast.Ident) + return a.Name == b.Name + case *ast.IndexExpr: + b := b.(*ast.IndexExpr) + return Equal(a.X, b.X) && Equal(a.Index, b.Index) + case *ast.IndexListExpr: + b := b.(*ast.IndexListExpr) + if len(a.Indices) != len(b.Indices) { + return false + } + for i, v := range a.Indices { + if !Equal(v, b.Indices[i]) { + return false + } + } + return Equal(a.X, b.X) + case *ast.KeyValueExpr: + b := b.(*ast.KeyValueExpr) + return Equal(a.Key, b.Key) && Equal(a.Value, b.Value) + case *ast.ParenExpr: + b := b.(*ast.ParenExpr) + return Equal(a.X, b.X) + case *ast.SelectorExpr: + b := b.(*ast.SelectorExpr) + return Equal(a.X, b.X) && Equal(a.Sel, b.Sel) + case *ast.SliceExpr: + b := b.(*ast.SliceExpr) + return Equal(a.X, b.X) && Equal(a.Low, b.Low) && Equal(a.High, b.High) && Equal(a.Max, b.Max) && a.Slice3 == b.Slice3 + case *ast.StarExpr: + b := b.(*ast.StarExpr) + return Equal(a.X, b.X) + case *ast.TypeAssertExpr: + b := b.(*ast.TypeAssertExpr) + return Equal(a.X, b.X) && Equal(a.Type, b.Type) + case *ast.UnaryExpr: + b := b.(*ast.UnaryExpr) + return a.Op == b.Op && Equal(a.X, b.X) + case *ast.MapType: + b := b.(*ast.MapType) + return Equal(a.Key, b.Key) && Equal(a.Value, b.Value) + case *ast.ArrayType: + b := b.(*ast.ArrayType) + return Equal(a.Len, b.Len) && Equal(a.Elt, b.Elt) + case *ast.Ellipsis: + b := b.(*ast.Ellipsis) + return Equal(a.Elt, b.Elt) + case *ast.InterfaceType: + b := b.(*ast.InterfaceType) + return a.Incomplete == b.Incomplete && Equal(a.Methods, b.Methods) + case *ast.StructType: + b := b.(*ast.StructType) + return a.Incomplete == b.Incomplete && Equal(a.Fields, b.Fields) + case *ast.FuncLit: + // TODO(dh): support function literals + return false + case *ast.ChanType: + b := b.(*ast.ChanType) + return a.Dir == b.Dir && (a.Arrow == token.NoPos && b.Arrow == token.NoPos || a.Arrow != token.NoPos && b.Arrow != token.NoPos) + case *ast.FieldList: + b := b.(*ast.FieldList) + if len(a.List) != len(b.List) { + return false + } + for i, fieldA := range a.List { + if !Equal(fieldA, b.List[i]) { + return false + } + } + return true + case *ast.Field: + b := b.(*ast.Field) + if len(a.Names) != len(b.Names) { + return false + } + for j, name := range a.Names { + if !Equal(name, b.Names[j]) { + return false + } + } + if !Equal(a.Type, b.Type) || !Equal(a.Tag, b.Tag) { + return false + } + return true + default: + panic(fmt.Sprintf("unreachable: %T", a)) + } +} + +func NegateDeMorgan(expr ast.Expr, recursive bool) ast.Expr { + switch expr := expr.(type) { + case *ast.BinaryExpr: + var out ast.BinaryExpr + switch expr.Op { + case token.EQL: + out.X = expr.X + out.Op = token.NEQ + out.Y = expr.Y + case token.LSS: + out.X = expr.X + out.Op = token.GEQ + out.Y = expr.Y + case token.GTR: + out.X = expr.X + out.Op = token.LEQ + out.Y = expr.Y + case token.NEQ: + out.X = expr.X + out.Op = token.EQL + out.Y = expr.Y + case token.LEQ: + out.X = expr.X + out.Op = token.GTR + out.Y = expr.Y + case token.GEQ: + out.X = expr.X + out.Op = token.LSS + out.Y = expr.Y + + case token.LAND: + out.X = NegateDeMorgan(expr.X, recursive) + out.Op = token.LOR + out.Y = NegateDeMorgan(expr.Y, recursive) + case token.LOR: + out.X = NegateDeMorgan(expr.X, recursive) + out.Op = token.LAND + out.Y = NegateDeMorgan(expr.Y, recursive) + } + return &out + + case *ast.ParenExpr: + if recursive { + return &ast.ParenExpr{ + X: NegateDeMorgan(expr.X, recursive), + } + } else { + return &ast.UnaryExpr{ + Op: token.NOT, + X: expr, + } + } + + case *ast.UnaryExpr: + if expr.Op == token.NOT { + return expr.X + } else { + return &ast.UnaryExpr{ + Op: token.NOT, + X: expr, + } + } + + default: + return &ast.UnaryExpr{ + Op: token.NOT, + X: expr, + } + } +} + +func SimplifyParentheses(node ast.Expr) ast.Expr { + var changed bool + // XXX accept list of ops to operate on + // XXX copy AST node, don't modify in place + post := func(c *astutil.Cursor) bool { + out := c.Node() + if paren, ok := c.Node().(*ast.ParenExpr); ok { + out = paren.X + } + + if binop, ok := out.(*ast.BinaryExpr); ok { + if right, ok := binop.Y.(*ast.BinaryExpr); ok && binop.Op == right.Op { + // XXX also check that Op is associative + + root := binop + pivot := root.Y.(*ast.BinaryExpr) + root.Y = pivot.X + pivot.X = root + root = pivot + out = root + } + } + + if out != c.Node() { + changed = true + c.Replace(out) + } + return true + } + + for changed = true; changed; { + changed = false + node = astutil.Apply(node, nil, post).(ast.Expr) + } + + return node +} diff --git a/vendor/honnef.co/go/tools/go/buildid/UPSTREAM b/vendor/honnef.co/go/tools/go/buildid/UPSTREAM new file mode 100644 index 0000000..27d414e --- /dev/null +++ b/vendor/honnef.co/go/tools/go/buildid/UPSTREAM @@ -0,0 +1,5 @@ +This package extracts buildid.go and note.go from cmd/internal/buildid/. + +We have modified it to remove support for AIX big archive files, to cut down on our dependencies. + +The last upstream commit we've looked at was: d3ddc4854429185e6e06ca1f7628bb790404abb5 diff --git a/vendor/honnef.co/go/tools/go/buildid/buildid.go b/vendor/honnef.co/go/tools/go/buildid/buildid.go new file mode 100644 index 0000000..27e1c44 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/buildid/buildid.go @@ -0,0 +1,238 @@ +// Copyright 2017 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 buildid + +import ( + "bytes" + "debug/elf" + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +var errBuildIDMalformed = fmt.Errorf("malformed object file") + +var ( + bangArch = []byte("!") + pkgdef = []byte("__.PKGDEF") + goobject = []byte("go object ") + buildid = []byte("build id ") +) + +// ReadFile reads the build ID from an archive or executable file. +func ReadFile(name string) (id string, err error) { + f, err := os.Open(name) + if err != nil { + return "", err + } + defer f.Close() + + buf := make([]byte, 8) + if _, err := f.ReadAt(buf, 0); err != nil { + return "", err + } + if string(buf) != "!\n" { + if string(buf) == "\n" { + return "", errors.New("unsupported") + } + return readBinary(name, f) + } + + // Read just enough of the target to fetch the build ID. + // The archive is expected to look like: + // + // ! + // __.PKGDEF 0 0 0 644 7955 ` + // go object darwin amd64 devel X:none + // build id "b41e5c45250e25c9fd5e9f9a1de7857ea0d41224" + // + // The variable-sized strings are GOOS, GOARCH, and the experiment list (X:none). + // Reading the first 1024 bytes should be plenty. + data := make([]byte, 1024) + n, err := io.ReadFull(f, data) + if err != nil && n == 0 { + return "", err + } + + tryGccgo := func() (string, error) { + return readGccgoArchive(name, f) + } + + // Archive header. + for i := 0; ; i++ { // returns during i==3 + j := bytes.IndexByte(data, '\n') + if j < 0 { + return tryGccgo() + } + line := data[:j] + data = data[j+1:] + switch i { + case 0: + if !bytes.Equal(line, bangArch) { + return tryGccgo() + } + case 1: + if !bytes.HasPrefix(line, pkgdef) { + return tryGccgo() + } + case 2: + if !bytes.HasPrefix(line, goobject) { + return tryGccgo() + } + case 3: + if !bytes.HasPrefix(line, buildid) { + // Found the object header, just doesn't have a build id line. + // Treat as successful, with empty build id. + return "", nil + } + id, err := strconv.Unquote(string(line[len(buildid):])) + if err != nil { + return tryGccgo() + } + return id, nil + } + } +} + +// readGccgoArchive tries to parse the archive as a standard Unix +// archive file, and fetch the build ID from the _buildid.o entry. +// The _buildid.o entry is written by (*Builder).gccgoBuildIDELFFile +// in cmd/go/internal/work/exec.go. +func readGccgoArchive(name string, f *os.File) (string, error) { + bad := func() (string, error) { + return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} + } + + off := int64(8) + for { + if _, err := f.Seek(off, io.SeekStart); err != nil { + return "", err + } + + // TODO(iant): Make a debug/ar package, and use it + // here and in cmd/link. + var hdr [60]byte + if _, err := io.ReadFull(f, hdr[:]); err != nil { + if err == io.EOF { + // No more entries, no build ID. + return "", nil + } + return "", err + } + off += 60 + + sizeStr := strings.TrimSpace(string(hdr[48:58])) + size, err := strconv.ParseInt(sizeStr, 0, 64) + if err != nil { + return bad() + } + + name := strings.TrimSpace(string(hdr[:16])) + if name == "_buildid.o/" { + sr := io.NewSectionReader(f, off, size) + e, err := elf.NewFile(sr) + if err != nil { + return bad() + } + s := e.Section(".go.buildid") + if s == nil { + return bad() + } + data, err := s.Data() + if err != nil { + return bad() + } + return string(data), nil + } + + off += size + if off&1 != 0 { + off++ + } + } +} + +var ( + goBuildPrefix = []byte("\xff Go build ID: \"") + goBuildEnd = []byte("\"\n \xff") + + elfPrefix = []byte("\x7fELF") + + machoPrefixes = [][]byte{ + {0xfe, 0xed, 0xfa, 0xce}, + {0xfe, 0xed, 0xfa, 0xcf}, + {0xce, 0xfa, 0xed, 0xfe}, + {0xcf, 0xfa, 0xed, 0xfe}, + } +) + +var readSize = 32 * 1024 // changed for testing + +// readBinary reads the build ID from a binary. +// +// ELF binaries store the build ID in a proper PT_NOTE section. +// +// Other binary formats are not so flexible. For those, the linker +// stores the build ID as non-instruction bytes at the very beginning +// of the text segment, which should appear near the beginning +// of the file. This is clumsy but fairly portable. Custom locations +// can be added for other binary types as needed, like we did for ELF. +func readBinary(name string, f *os.File) (id string, err error) { + // Read the first 32 kB of the binary file. + // That should be enough to find the build ID. + // In ELF files, the build ID is in the leading headers, + // which are typically less than 4 kB, not to mention 32 kB. + // In Mach-O files, there's no limit, so we have to parse the file. + // On other systems, we're trying to read enough that + // we get the beginning of the text segment in the read. + // The offset where the text segment begins in a hello + // world compiled for each different object format today: + // + // Plan 9: 0x20 + // Windows: 0x600 + // + data := make([]byte, readSize) + _, err = io.ReadFull(f, data) + if err == io.ErrUnexpectedEOF { + err = nil + } + if err != nil { + return "", err + } + + if bytes.HasPrefix(data, elfPrefix) { + return readELF(name, f, data) + } + for _, m := range machoPrefixes { + if bytes.HasPrefix(data, m) { + return readMacho(name, f, data) + } + } + return readRaw(name, data) +} + +// readRaw finds the raw build ID stored in text segment data. +func readRaw(name string, data []byte) (id string, err error) { + i := bytes.Index(data, goBuildPrefix) + if i < 0 { + // Missing. Treat as successful but build ID empty. + return "", nil + } + + j := bytes.Index(data[i+len(goBuildPrefix):], goBuildEnd) + if j < 0 { + return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} + } + + quoted := data[i+len(goBuildPrefix)-1 : i+len(goBuildPrefix)+j+1] + id, err = strconv.Unquote(string(quoted)) + if err != nil { + return "", &os.PathError{Op: "parse", Path: name, Err: errBuildIDMalformed} + } + return id, nil +} diff --git a/vendor/honnef.co/go/tools/go/buildid/note.go b/vendor/honnef.co/go/tools/go/buildid/note.go new file mode 100644 index 0000000..7971e91 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/buildid/note.go @@ -0,0 +1,204 @@ +// Copyright 2015 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 buildid + +import ( + "bytes" + "debug/elf" + "debug/macho" + "encoding/binary" + "fmt" + "io" + "os" +) + +func readAligned4(r io.Reader, sz int32) ([]byte, error) { + full := (sz + 3) &^ 3 + data := make([]byte, full) + _, err := io.ReadFull(r, data) + if err != nil { + return nil, err + } + data = data[:sz] + return data, nil +} + +func ReadELFNote(filename, name string, typ int32) ([]byte, error) { + f, err := elf.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + for _, sect := range f.Sections { + if sect.Type != elf.SHT_NOTE { + continue + } + r := sect.Open() + for { + var namesize, descsize, noteType int32 + err = binary.Read(r, f.ByteOrder, &namesize) + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("read namesize failed: %v", err) + } + err = binary.Read(r, f.ByteOrder, &descsize) + if err != nil { + return nil, fmt.Errorf("read descsize failed: %v", err) + } + err = binary.Read(r, f.ByteOrder, ¬eType) + if err != nil { + return nil, fmt.Errorf("read type failed: %v", err) + } + noteName, err := readAligned4(r, namesize) + if err != nil { + return nil, fmt.Errorf("read name failed: %v", err) + } + desc, err := readAligned4(r, descsize) + if err != nil { + return nil, fmt.Errorf("read desc failed: %v", err) + } + if name == string(noteName) && typ == noteType { + return desc, nil + } + } + } + return nil, nil +} + +var elfGoNote = []byte("Go\x00\x00") +var elfGNUNote = []byte("GNU\x00") + +// The Go build ID is stored in a note described by an ELF PT_NOTE prog +// header. The caller has already opened filename, to get f, and read +// at least 4 kB out, in data. +func readELF(name string, f *os.File, data []byte) (buildid string, err error) { + // Assume the note content is in the data, already read. + // Rewrite the ELF header to set shnum to 0, so that we can pass + // the data to elf.NewFile and it will decode the Prog list but not + // try to read the section headers and the string table from disk. + // That's a waste of I/O when all we care about is the Prog list + // and the one ELF note. + switch elf.Class(data[elf.EI_CLASS]) { + case elf.ELFCLASS32: + data[48] = 0 + data[49] = 0 + case elf.ELFCLASS64: + data[60] = 0 + data[61] = 0 + } + + const elfGoBuildIDTag = 4 + const gnuBuildIDTag = 3 + + ef, err := elf.NewFile(bytes.NewReader(data)) + if err != nil { + return "", &os.PathError{Path: name, Op: "parse", Err: err} + } + var gnu string + for _, p := range ef.Progs { + if p.Type != elf.PT_NOTE || p.Filesz < 16 { + continue + } + + var note []byte + if p.Off+p.Filesz < uint64(len(data)) { + note = data[p.Off : p.Off+p.Filesz] + } else { + // For some linkers, such as the Solaris linker, + // the buildid may not be found in data (which + // likely contains the first 16kB of the file) + // or even the first few megabytes of the file + // due to differences in note segment placement; + // in that case, extract the note data manually. + _, err = f.Seek(int64(p.Off), io.SeekStart) + if err != nil { + return "", err + } + + note = make([]byte, p.Filesz) + _, err = io.ReadFull(f, note) + if err != nil { + return "", err + } + } + + filesz := p.Filesz + off := p.Off + for filesz >= 16 { + nameSize := ef.ByteOrder.Uint32(note) + valSize := ef.ByteOrder.Uint32(note[4:]) + tag := ef.ByteOrder.Uint32(note[8:]) + nname := note[12:16] + if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == elfGoBuildIDTag && bytes.Equal(nname, elfGoNote) { + return string(note[16 : 16+valSize]), nil + } + + if nameSize == 4 && 16+valSize <= uint32(len(note)) && tag == gnuBuildIDTag && bytes.Equal(nname, elfGNUNote) { + gnu = string(note[16 : 16+valSize]) + } + + nameSize = (nameSize + 3) &^ 3 + valSize = (valSize + 3) &^ 3 + notesz := uint64(12 + nameSize + valSize) + if filesz <= notesz { + break + } + off += notesz + align := p.Align + alignedOff := (off + align - 1) &^ (align - 1) + notesz += alignedOff - off + off = alignedOff + filesz -= notesz + note = note[notesz:] + } + } + + // If we didn't find a Go note, use a GNU note if available. + // This is what gccgo uses. + if gnu != "" { + return gnu, nil + } + + // No note. Treat as successful but build ID empty. + return "", nil +} + +// The Go build ID is stored at the beginning of the Mach-O __text segment. +// The caller has already opened filename, to get f, and read a few kB out, in data. +// Sadly, that's not guaranteed to hold the note, because there is an arbitrary amount +// of other junk placed in the file ahead of the main text. +func readMacho(name string, f *os.File, data []byte) (buildid string, err error) { + // If the data we want has already been read, don't worry about Mach-O parsing. + // This is both an optimization and a hedge against the Mach-O parsing failing + // in the future due to, for example, the name of the __text section changing. + if b, err := readRaw(name, data); b != "" && err == nil { + return b, err + } + + mf, err := macho.NewFile(f) + if err != nil { + return "", &os.PathError{Path: name, Op: "parse", Err: err} + } + + sect := mf.Section("__text") + if sect == nil { + // Every binary has a __text section. Something is wrong. + return "", &os.PathError{Path: name, Op: "parse", Err: fmt.Errorf("cannot find __text section")} + } + + // It should be in the first few bytes, but read a lot just in case, + // especially given our past problems on OS X with the build ID moving. + // There shouldn't be much difference between reading 4kB and 32kB: + // the hard part is getting to the data, not transferring it. + n := min(sect.Size, uint64(readSize)) + buf := make([]byte, n) + if _, err := f.ReadAt(buf, int64(sect.Offset)); err != nil { + return "", err + } + + return readRaw(name, buf) +} diff --git a/vendor/honnef.co/go/tools/go/ir/LICENSE b/vendor/honnef.co/go/tools/go/ir/LICENSE new file mode 100644 index 0000000..aee4804 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. +Copyright (c) 2016 Dominik Honnef. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/honnef.co/go/tools/go/ir/block.go b/vendor/honnef.co/go/tools/go/ir/block.go new file mode 100644 index 0000000..44399c7 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/block.go @@ -0,0 +1,133 @@ +// Copyright 2022 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 ir + +import ( + "fmt" + "go/ast" +) + +// This file implements the BasicBlock type. + +// addEdge adds a control-flow graph edge from from to to. +func addEdge(from, to *BasicBlock) { + from.Succs = append(from.Succs, to) + to.Preds = append(to.Preds, from) +} + +// Control returns the last instruction in the block. +func (b *BasicBlock) Control() Instruction { + if len(b.Instrs) == 0 { + return nil + } + return b.Instrs[len(b.Instrs)-1] +} + +// Parent returns the function that contains block b. +func (b *BasicBlock) Parent() *Function { return b.parent } + +func (b *BasicBlock) Reaches(o *BasicBlock) bool { + return b.SCC.reachable.Bit(o.SCC.Index) != 0 +} + +// String returns a human-readable label of this block. +// It is not guaranteed unique within the function. +func (b *BasicBlock) String() string { + return fmt.Sprintf("%d", b.Index) +} + +// emit appends an instruction to the current basic block. +// If the instruction defines a Value, it is returned. +func (b *BasicBlock) emit(i Instruction, source ast.Node) Value { + i.setSource(source) + i.setBlock(b) + b.Instrs = append(b.Instrs, i) + v, _ := i.(Value) + return v +} + +// predIndex returns the i such that b.Preds[i] == c or panics if +// there is none. +func (b *BasicBlock) predIndex(c *BasicBlock) int { + for i, pred := range b.Preds { + if pred == c { + return i + } + } + panic(fmt.Sprintf("no edge %s -> %s", c, b)) +} + +// hasPhi returns true if b.Instrs contains φ-nodes. +func (b *BasicBlock) hasPhi() bool { + _, ok := b.Instrs[0].(*Phi) + return ok +} + +func (b *BasicBlock) Phis() []Instruction { + return b.phis() +} + +// phis returns the prefix of b.Instrs containing all the block's φ-nodes. +func (b *BasicBlock) phis() []Instruction { + for i, instr := range b.Instrs { + if _, ok := instr.(*Phi); !ok { + return b.Instrs[:i] + } + } + return nil // unreachable in well-formed blocks +} + +// replacePred replaces all occurrences of p in b's predecessor list with q. +// Ordinarily there should be at most one. +func (b *BasicBlock) replacePred(p, q *BasicBlock) { + for i, pred := range b.Preds { + if pred == p { + b.Preds[i] = q + } + } +} + +// replaceSucc replaces all occurrences of p in b's successor list with q. +// Ordinarily there should be at most one. +func (b *BasicBlock) replaceSucc(p, q *BasicBlock) { + for i, succ := range b.Succs { + if succ == p { + b.Succs[i] = q + } + } +} + +// removePred removes all occurrences of p in b's +// predecessor list and φ-nodes. +// Ordinarily there should be at most one. +func (b *BasicBlock) removePred(p *BasicBlock) { + phis := b.phis() + + // We must preserve edge order for φ-nodes. + j := 0 + for i, pred := range b.Preds { + if pred != p { + b.Preds[j] = b.Preds[i] + // Strike out φ-edge too. + for _, instr := range phis { + phi := instr.(*Phi) + phi.Edges[j] = phi.Edges[i] + } + j++ + } + } + // Nil out b.Preds[j:] and φ-edges[j:] to aid GC. + for i := j; i < len(b.Preds); i++ { + b.Preds[i] = nil + for _, instr := range phis { + instr.(*Phi).Edges[i] = nil + } + } + b.Preds = b.Preds[:j] + for _, instr := range phis { + phi := instr.(*Phi) + phi.Edges = phi.Edges[:j] + } +} diff --git a/vendor/honnef.co/go/tools/go/ir/blockopt.go b/vendor/honnef.co/go/tools/go/ir/blockopt.go new file mode 100644 index 0000000..de6ac9a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/blockopt.go @@ -0,0 +1,194 @@ +// Copyright 2013 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 ir + +// Simple block optimizations to simplify the control flow graph. + +// TODO(adonovan): opt: instead of creating several "unreachable" blocks +// per function in the Builder, reuse a single one (e.g. at Blocks[1]) +// to reduce garbage. + +import ( + "fmt" + "os" +) + +// If true, perform sanity checking and show progress at each +// successive iteration of optimizeBlocks. Very verbose. +const debugBlockOpt = false + +// markReachable sets Index=-1 for all blocks reachable from b. +func markReachable(b *BasicBlock) { + b.gaps = -1 + for _, succ := range b.Succs { + if succ.gaps == 0 { + markReachable(succ) + } + } +} + +// deleteUnreachableBlocks marks all reachable blocks of f and +// eliminates (nils) all others, including possibly cyclic subgraphs. +func deleteUnreachableBlocks(f *Function) { + const white, black = 0, -1 + // We borrow b.gaps temporarily as the mark bit. + for _, b := range f.Blocks { + b.gaps = white + } + markReachable(f.Blocks[0]) + if f.Recover != nil { + markReachable(f.Recover) + } + for i, b := range f.Blocks { + if b.gaps == white { + for _, c := range b.Succs { + if c.gaps == black { + c.removePred(b) // delete white->black edge + } + } + if debugBlockOpt { + fmt.Fprintln(os.Stderr, "unreachable", b) + } + f.Blocks[i] = nil // delete b + } + } + f.removeNilBlocks() +} + +// jumpThreading attempts to apply simple jump-threading to block b, +// in which a->b->c become a->c if b is just a Jump. +// The result is true if the optimization was applied. +func jumpThreading(f *Function, b *BasicBlock) bool { + if b.Index == 0 { + return false // don't apply to entry block + } + if len(b.Instrs) == 0 { + return false + } + for _, pred := range b.Preds { + switch pred.Control().(type) { + case *ConstantSwitch: + // don't optimize away the head blocks of switch statements + return false + } + } + if _, ok := b.Instrs[0].(*Jump); !ok { + return false // not just a jump + } + c := b.Succs[0] + if c == b { + return false // don't apply to degenerate jump-to-self. + } + if c.hasPhi() { + return false // not sound without more effort + } + for j, a := range b.Preds { + a.replaceSucc(b, c) + + // If a now has two edges to c, replace its degenerate If by Jump. + if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c { + jump := new(Jump) + jump.setBlock(a) + a.Instrs[len(a.Instrs)-1] = jump + a.Succs = a.Succs[:1] + c.removePred(b) + } else { + if j == 0 { + c.replacePred(b, a) + } else { + c.Preds = append(c.Preds, a) + } + } + + if debugBlockOpt { + fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c) + } + } + f.Blocks[b.Index] = nil // delete b + return true +} + +// fuseBlocks attempts to apply the block fusion optimization to block +// a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1. +// The result is true if the optimization was applied. +func fuseBlocks(f *Function, a *BasicBlock) bool { + if len(a.Succs) != 1 { + return false + } + b := a.Succs[0] + if len(b.Preds) != 1 { + return false + } + if _, ok := a.Instrs[len(a.Instrs)-1].(*Panic); ok { + // panics aren't simple jumps, they have side effects. + return false + } + + // Degenerate &&/|| ops may result in a straight-line CFG + // containing φ-nodes. (Ideally we'd replace such them with + // their sole operand but that requires Referrers, built later.) + if b.hasPhi() { + return false // not sound without further effort + } + + // Eliminate jump at end of A, then copy all of B across. + a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...) + for _, instr := range b.Instrs { + instr.setBlock(a) + } + + // A inherits B's successors + a.Succs = append(a.succs2[:0], b.Succs...) + + // Fix up Preds links of all successors of B. + for _, c := range b.Succs { + c.replacePred(b, a) + } + + if debugBlockOpt { + fmt.Fprintln(os.Stderr, "fuseBlocks", a, b) + } + + f.Blocks[b.Index] = nil // delete b + return true +} + +// optimizeBlocks() performs some simple block optimizations on a +// completed function: dead block elimination, block fusion, jump +// threading. +func optimizeBlocks(f *Function) { + deleteUnreachableBlocks(f) + + // Loop until no further progress. + changed := true + for changed { + changed = false + + if debugBlockOpt { + f.WriteTo(os.Stderr) + mustSanityCheck(f, nil) + } + + for _, b := range f.Blocks { + // f.Blocks will temporarily contain nils to indicate + // deleted blocks; we remove them at the end. + if b == nil { + continue + } + + // Fuse blocks. b->c becomes bc. + if fuseBlocks(f, b) { + changed = true + } + + // a->b->c becomes a->c if b contains only a Jump. + if jumpThreading(f, b) { + changed = true + continue // (b was disconnected) + } + } + } + f.removeNilBlocks() +} diff --git a/vendor/honnef.co/go/tools/go/ir/builder.go b/vendor/honnef.co/go/tools/go/ir/builder.go new file mode 100644 index 0000000..d291830 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/builder.go @@ -0,0 +1,3595 @@ +// Copyright 2013 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 ir + +// This file defines the builder, which builds SSA-form IR for function bodies. +// +// SSA construction has two phases, "create" and "build". First, one +// or more packages are created in any order by a sequence of calls to +// CreatePackage, either from syntax or from mere type information. +// Each created package has a complete set of Members (const, var, +// type, func) that can be accessed through methods like +// Program.FuncValue. +// +// It is not necessary to call CreatePackage for all dependencies of +// each syntax package, only for its direct imports. (In future +// perhaps even this restriction may be lifted.) +// +// Second, packages created from syntax are built, by one or more +// calls to Package.Build, which may be concurrent; or by a call to +// Program.Build, which builds all packages in parallel. Building +// traverses the type-annotated syntax tree of each function body and +// creates SSA-form IR, a control-flow graph of instructions, +// populating fields such as Function.Body, .Params, and others. +// +// Building may create additional methods, including: +// - wrapper methods (e.g. for embedding, or implicit &recv) +// - bound method closures (e.g. for use(recv.f)) +// - thunks (e.g. for use(I.f) or use(T.f)) +// - generic instances (e.g. to produce f[int] from f[any]). +// As these methods are created, they are added to the build queue, +// and then processed in turn, until a fixed point is reached, +// Since these methods might belong to packages that were not +// created (by a call to CreatePackage), their Pkg field is unset. +// +// Instances of generic functions may be either instantiated (f[int] +// is a copy of f[T] with substitutions) or wrapped (f[int] delegates +// to f[T]), depending on the availability of generic syntax and the +// InstantiateGenerics mode flag. +// +// Each package has an initializer function named "init" that calls +// the initializer functions of each direct import, computes and +// assigns the initial value of each global variable, and calls each +// source-level function named "init". (These generate SSA functions +// named "init#1", "init#2", etc.) +// +// Runtime types +// +// Each MakeInterface operation is a conversion from a non-interface +// type to an interface type. The semantics of this operation requires +// a runtime type descriptor, which is the type portion of an +// interface, and the value abstracted by reflect.Type. +// +// The program accumulates all non-parameterized types that are +// encountered as MakeInterface operands, along with all types that +// may be derived from them using reflection. This set is available as +// Program.RuntimeTypes, and the methods of these types may be +// reachable via interface calls or reflection even if they are never +// referenced from the SSA IR. (In practice, algorithms such as RTA +// that compute reachability from package main perform their own +// tracking of runtime types at a finer grain, so this feature is not +// very useful.) +// +// Function literals +// +// Anonymous functions must be built as soon as they are encountered, +// as it may affect locals of the enclosing function, but they are not +// marked 'built' until the end of the outermost enclosing function. +// (Among other things, this causes them to be logged in top-down order.) +// +// The Function.build fields determines the algorithm for building the +// function body. It is cleared to mark that building is complete. + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "os" + "runtime" + "slices" + "sync" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/versions" + + "golang.org/x/exp/typeparams" +) + +var ( + varOk = newVar("ok", tBool) + varIndex = newVar("index", tInt) + + // Type constants. + tBool = types.Typ[types.Bool] + tByte = types.Typ[types.Byte] + tRune = types.Universe.Lookup("rune").Type() // prints as "rune" (Typ[Rune] is same as Int32) + tInt = types.Typ[types.Int] + tInvalid = types.Typ[types.Invalid] + tString = types.Typ[types.String] + tUntypedNil = types.Typ[types.UntypedNil] + tEface = types.NewInterfaceType(nil, nil).Complete() + tDeferStack = types.NewPointer(typeutil.NewDeferStack()) + + vOne = intConst(1, nil) + vTrue = NewConst(constant.MakeBool(true), tBool, nil) + vNoReturn = NewConst(constant.MakeString("noreturn"), tString, nil) + + jReady = intConst(0, nil) // range-over-func jump is READY + jBusy = intConst(-1, nil) // range-over-func jump is BUSY + jDone = intConst(-2, nil) // range-over-func jump is DONE + jDroppedPanic = stringConst("iterator call did not preserve panic", nil) + jLateYield = stringConst("yield function called after range loop exit", nil) + + vDeferStack = &Builtin{ + name: "ssa:deferstack", + sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false), + } +) + +// builder holds state associated with the package currently being built. +// Its methods contain all the logic for AST-to-IR conversion. +// +// All Functions belong to the same Program. +// +// builders are not thread-safe. +type builder struct { + fns []*Function // Functions that have finished their CREATE phases. + + finished int // finished is the length of the prefix of fns containing built functions. + + // The task of building shared functions within the builder. + // Shared functions are ones the builder may either create or lookup. + // These may be built by other builders in parallel. + // The task is done when the builder has finished iterating, and it + // waits for all shared functions to finish building. + // nil implies there are no hared functions to wait on. + buildshared *task +} + +// shared is done when the builder has built all of the +// enqueued functions to a fixed-point. +func (b *builder) shared() *task { + if b.buildshared == nil { // lazily-initialize + b.buildshared = &task{done: make(chan unit)} + } + return b.buildshared +} + +// enqueue fn to be built by the builder. +func (b *builder) enqueue(fn *Function) { + b.fns = append(b.fns, fn) +} + +// waitForSharedFunction indicates that the builder should wait until +// the potentially shared function fn has finished building. +// +// This should include any functions that may be built by other +// builders. +func (b *builder) waitForSharedFunction(fn *Function) { + if fn.buildshared != nil { // maybe need to wait? + s := b.shared() + s.addEdge(fn.buildshared) + } +} + +// cond emits to fn code to evaluate boolean condition e and jump +// to t or f depending on its value, performing various simplifications. +// +// Postcondition: fn.currentBlock is nil. +func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) *If { + switch e := e.(type) { + case *ast.ParenExpr: + return b.cond(fn, e.X, t, f) + + case *ast.BinaryExpr: + switch e.Op { + case token.LAND: + ltrue := fn.newBasicBlock("cond.true") + b.cond(fn, e.X, ltrue, f) + fn.currentBlock = ltrue + return b.cond(fn, e.Y, t, f) + + case token.LOR: + lfalse := fn.newBasicBlock("cond.false") + b.cond(fn, e.X, t, lfalse) + fn.currentBlock = lfalse + return b.cond(fn, e.Y, t, f) + } + + case *ast.UnaryExpr: + if e.Op == token.NOT { + return b.cond(fn, e.X, f, t) + } + } + + // A traditional compiler would simplify "if false" (etc) here + // but we do not, for better fidelity to the source code. + // + // The value of a constant condition may be platform-specific, + // and may cause blocks that are reachable in some configuration + // to be hidden from subsequent analyses such as bug-finding tools. + return emitIf(fn, b.expr(fn, e), t, f, e) +} + +// logicalBinop emits code to fn to evaluate e, a &&- or +// ||-expression whose reified boolean value is wanted. +// The value is returned. +func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value { + rhs := fn.newBasicBlock("binop.rhs") + done := fn.newBasicBlock("binop.done") + + // T(e) = T(e.X) = T(e.Y) after untyped constants have been + // eliminated. + // TODO(adonovan): not true; MyBool==MyBool yields UntypedBool. + t := fn.typeOf(e) + + var short Value // value of the short-circuit path + switch e.Op { + case token.LAND: + b.cond(fn, e.X, rhs, done) + short = NewConst(constant.MakeBool(false), t, e) + + case token.LOR: + b.cond(fn, e.X, done, rhs) + short = NewConst(constant.MakeBool(true), t, e) + } + + // Is rhs unreachable? + if rhs.Preds == nil { + // Simplify false&&y to false, true||y to true. + fn.currentBlock = done + return short + } + + // Is done unreachable? + if done.Preds == nil { + // Simplify true&&y (or false||y) to y. + fn.currentBlock = rhs + return b.expr(fn, e.Y) + } + + // All edges from e.X to done carry the short-circuit value. + var edges []Value + for range done.Preds { + edges = append(edges, short) + } + + // The edge from e.Y to done carries the value of e.Y. + fn.currentBlock = rhs + edges = append(edges, b.expr(fn, e.Y)) + emitJump(fn, done, e) + fn.currentBlock = done + + phi := &Phi{Edges: edges} + phi.typ = t + phi.comment = e.Op.String() + return done.emit(phi, e) +} + +// exprN lowers a multi-result expression e to IR form, emitting code +// to fn and returning a single Value whose type is a *types.Tuple. +// The caller must access the components via Extract. +// +// Multi-result expressions include CallExprs in a multi-value +// assignment or return statement, and "value,ok" uses of +// TypeAssertExpr, IndexExpr (when X is a map), and Recv. +func (b *builder) exprN(fn *Function, e ast.Expr) Value { + typ := fn.typeOf(e).(*types.Tuple) + switch e := e.(type) { + case *ast.ParenExpr: + return b.exprN(fn, e.X) + + case *ast.CallExpr: + // Currently, no built-in function nor type conversion + // has multiple results, so we can avoid some of the + // cases for single-valued CallExpr. + var c Call + b.setCall(fn, e, &c.Call) + c.typ = typ + return emitCall(fn, &c, e) + + case *ast.IndexExpr: + mapt := typeutil.CoreType(fn.typeOf(e.X)).(*types.Map) // ,ok must be a map. + lookup := &MapLookup{ + X: b.expr(fn, e.X), + Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e), + CommaOk: true, + } + lookup.setType(typ) + return fn.emit(lookup, e) + + case *ast.TypeAssertExpr: + return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e) + + case *ast.UnaryExpr: // must be receive <- + return emitRecv(fn, b.expr(fn, e.X), true, typ, e) + } + panic(fmt.Sprintf("exprN(%T) in %s", e, fn)) +} + +// builtin emits to fn IR instructions to implement a call to the +// built-in function obj with the specified arguments +// and return type. It returns the value defined by the result. +// +// The result is nil if no special handling was required; in this case +// the caller should treat this like an ordinary library function +// call. +func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value { + typ = fn.typ(typ) + switch obj.Name() { + case "make": + switch ct := typeutil.CoreType(typ).(type) { + case *types.Slice: + n := b.expr(fn, args[1]) + m := n + if len(args) == 3 { + m = b.expr(fn, args[2]) + } + if m, ok := m.(*Const); ok { + // treat make([]T, n, m) as new([m]T)[:n] + cap := m.Int64() + at := types.NewArray(ct.Elem(), cap) + v := &Slice{ + X: emitNew(fn, at, source, "makeslice"), + High: n, + } + v.setType(typ) + return fn.emit(v, source) + } + v := &MakeSlice{ + Len: n, + Cap: m, + } + v.setType(typ) + return fn.emit(v, source) + + case *types.Map: + var res Value + if len(args) == 2 { + res = b.expr(fn, args[1]) + } + v := &MakeMap{Reserve: res} + v.setType(typ) + return fn.emit(v, source) + + case *types.Chan: + var sz Value = intConst(0, source) + if len(args) == 2 { + sz = b.expr(fn, args[1]) + } + v := &MakeChan{Size: sz} + v.setType(typ) + return fn.emit(v, source) + + default: + lint.ExhaustiveTypeSwitch(typ.Underlying()) + } + + case "new": + alloc := emitNew(fn, deref(typ), source, "new") + if !fn.info.Types[args[0]].IsType() { + // new(expr), requires go1.26 + v := b.expr(fn, args[0]) + emitStore(fn, alloc, v, source) + } + return alloc + + case "len", "cap": + // Special case: len or cap of an array or *array is based on the type, not the value which may be nil. We must + // still evaluate the value, though. (If it was side-effect free, the whole call would have been + // constant-folded.) + // + // For example, for len(gen()), we need to evaluate gen() for its side-effects, but don't need the returned + // value to determine the length of the array, which is constant. + // + // Technically this shouldn't apply to type parameters because their length/capacity is never constant. We still + // choose to treat them as constant so that users of the IR get the practically constant length for free. + t := typeutil.CoreType(deref(fn.typeOf(args[0]))) + if at, ok := t.(*types.Array); ok { + b.expr(fn, args[0]) // for effects only + return intConst(at.Len(), args[0]) + } + // Otherwise treat as normal. + + case "panic": + fn.emit(&Panic{ + X: emitConv(fn, b.expr(fn, args[0]), tEface, source), + }, source) + fn.currentBlock = fn.newBasicBlock("unreachable") + return vTrue // any non-nil Value will do + } + return nil // treat all others as a regular function call +} + +// addr lowers a single-result addressable expression e to IR form, +// emitting code to fn and returning the location (an lvalue) defined +// by the expression. +// +// If escaping is true, addr marks the base variable of the +// addressable expression e as being a potentially escaping pointer +// value. For example, in this code: +// +// a := A{ +// b: [1]B{B{c: 1}} +// } +// return &a.b[0].c +// +// the application of & causes a.b[0].c to have its address taken, +// which means that ultimately the local variable a must be +// heap-allocated. This is a simple but very conservative escape +// analysis. +// +// Operations forming potentially escaping pointers include: +// - &x, including when implicit in method call or composite literals. +// - a[:] iff a is an array (not *array) +// - references to variables in lexically enclosing functions. +func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { + switch e := e.(type) { + case *ast.Ident: + if isBlankIdent(e) { + return blank{} + } + obj := fn.objectOf(e).(*types.Var) + var v Value + if g := fn.Prog.packageLevelMember(obj); g != nil { + v = g.(*Global) // var (address) + } else { + v = fn.lookup(obj, escaping) + } + return &address{addr: v, expr: e} + + case *ast.CompositeLit: + t := deref(fn.typeOf(e)) + var v *Alloc + if escaping { + v = emitNew(fn, t, e, "complit") + } else { + v = emitLocal(fn, t, e, "complit") + } + var sb storebuf + b.compLit(fn, v, e, true, &sb) + sb.emit(fn) + return &address{addr: v, expr: e} + + case *ast.ParenExpr: + return b.addr(fn, e.X, escaping) + + case *ast.SelectorExpr: + sel := fn.selection(e) + if sel == nil { + // qualified identifier + return b.addr(fn, e.Sel, escaping) + } + if sel.kind != types.FieldVal { + panic(sel) + } + wantAddr := true + v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) + index := sel.index[len(sel.index)-1] + fld := fieldOf(deref(v.Type()), index) // v is an addr. + + // Due to the two phases of resolving AssignStmt, a panic from x.f = p() + // when x is nil is required to come after the side-effects of + // evaluating x and p(). + emit := func(fn *Function) Value { + return emitFieldSelection(fn, v, index, true, e.Sel) + } + return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel} + + case *ast.IndexExpr: + xt := fn.typeOf(e.X) + elem, mode := indexType(xt) + var x Value + var et types.Type + switch mode { + case ixArrVar: // array, array|slice, array|*array, or array|*array|slice. + x = b.addr(fn, e.X, escaping).address(fn) + et = types.NewPointer(elem) + case ixVar: // *array, slice, *array|slice + x = b.expr(fn, e.X) + et = types.NewPointer(elem) + case ixMap: + mt := typeutil.CoreType(xt).(*types.Map) + return &element{ + m: b.expr(fn, e.X), + k: emitConv(fn, b.expr(fn, e.Index), mt.Key(), e.Index), + t: mt.Elem(), + } + default: + panic("unexpected container type in IndexExpr: " + xt.String()) + } + index := b.expr(fn, e.Index) + if isUntyped(index.Type()) { + index = emitConv(fn, index, tInt, e.Index) + } + + // Due to the two phases of resolving AssignStmt, a panic from x[i] = p() + // when x is nil or i is out-of-bounds is required to come after the + // side-effects of evaluating x, i and p(). + emit := func(fn *Function) Value { + v := &IndexAddr{ + X: x, + Index: index, + } + v.setType(et) + return fn.emit(v, e) + } + return &lazyAddress{addr: emit, t: deref(et), expr: e} + + case *ast.StarExpr: + return &address{addr: b.expr(fn, e.X), expr: e} + } + + panic(fmt.Sprintf("unexpected address expression: %T", e)) +} + +type store struct { + lhs lvalue + rhs Value + source ast.Node + + // if debugRef is set no other fields will be set + debugRef *debugRef +} + +type storebuf struct{ stores []store } + +func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) { + sb.stores = append(sb.stores, store{lhs, rhs, source, nil}) +} + +func (sb *storebuf) storeDebugRef(ref *debugRef) { + sb.stores = append(sb.stores, store{debugRef: ref}) +} + +func (sb *storebuf) emit(fn *Function) { + for _, s := range sb.stores { + if s.debugRef == nil { + s.lhs.store(fn, s.rhs, s.source) + } else { + fn.emit(s.debugRef, nil) + } + } +} + +// assign emits to fn code to initialize the lvalue loc with the value +// of expression e. If isZero is true, assign assumes that loc holds +// the zero value for its type. +// +// This is equivalent to loc.store(fn, b.expr(fn, e)), but may generate +// better code in some cases, e.g., for composite literals in an +// addressable location. +// +// If sb is not nil, assign generates code to evaluate expression e, but +// not to update loc. Instead, the necessary stores are appended to the +// storebuf sb so that they can be executed later. This allows correct +// in-place update of existing variables when the RHS is a composite +// literal that may reference parts of the LHS. +func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) { + // Can we initialize it in place? + if e, ok := ast.Unparen(e).(*ast.CompositeLit); ok { + // A CompositeLit never evaluates to a pointer, + // so if the type of the location is a pointer, + // an &-operation is implied. + if _, ok := loc.(blank); !ok { // avoid calling blank.typ() + if isPointerCore(loc.typ()) { + // Example input that hits this code: + // + // type S1 struct{ X int } + // x := []*S1{ + // {1}, // <-- & is implied + // } + // _ = x + ptr := b.addr(fn, e, true).address(fn) + // copy address + if sb != nil { + sb.store(loc, ptr, source) + } else { + loc.store(fn, ptr, source) + } + return + } + } + + if _, ok := loc.(*address); ok { + if types.IsInterface(loc.typ()) && !typeparams.IsTypeParam(loc.typ()) { + // e.g. var x interface{} = T{...} + // Can't in-place initialize an interface value. + // Fall back to copying. + } else { + // x = T{...} or x := T{...} + addr := loc.address(fn) + if sb != nil { + b.compLit(fn, addr, e, isZero, sb) + } else { + var sb storebuf + b.compLit(fn, addr, e, isZero, &sb) + sb.emit(fn) + } + + // Subtle: emit debug ref for aggregate types only; + // slice and map are handled by store ops in compLit. + switch typeutil.CoreType(loc.typ()).(type) { + case *types.Struct, *types.Array: + if sb != nil { + // Make sure we don't emit DebugRefs before the store has actually occurred + if ref := makeDebugRef(fn, e, addr, true); ref != nil { + sb.storeDebugRef(ref) + } + } else { + emitDebugRef(fn, e, addr, true) + } + } + + return + } + } + } + + // simple case: just copy + rhs := b.expr(fn, e) + if sb != nil { + sb.store(loc, rhs, source) + } else { + loc.store(fn, rhs, source) + } +} + +// expr lowers a single-result expression e to IR form, emitting code +// to fn and returning the Value defined by the expression. +func (b *builder) expr(fn *Function, e ast.Expr) Value { + e = ast.Unparen(e) + + tv := fn.info.Types[e] + + // Is expression a constant? + if tv.Value != nil { + return NewConst(tv.Value, fn.typ(tv.Type), e) + } + + var v Value + if tv.Addressable() { + // Prefer pointer arithmetic ({Index,Field}Addr) followed + // by Load over subelement extraction (e.g. Index, Field), + // to avoid large copies. + v = b.addr(fn, e, false).load(fn, e) + } else { + v = b.expr0(fn, e, tv) + } + if fn.debugInfo() { + emitDebugRef(fn, e, v, false) + } + return v +} + +func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { + switch e := e.(type) { + case *ast.BasicLit: + panic("non-constant BasicLit") // unreachable + + case *ast.FuncLit: + /* function literal */ + fn2 := &Function{ + name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)), + Signature: fn.typeOf(e.Type).(*types.Signature), + pos: e.Type.Func, + parent: fn, + anonIdx: int32(len(fn.AnonFuncs)), + Pkg: fn.Pkg, + Prog: fn.Prog, + syntax: e, + info: fn.info, + goversion: fn.goversion, + build: (*builder).buildFromSyntax, + topLevelOrigin: nil, // use anonIdx to lookup an anon instance's origin. + typeparams: fn.typeparams, // share the parent's type parameters. + typeargs: fn.typeargs, // share the parent's type arguments. + subst: fn.subst, // share the parent's type substitutions. + } + fn2.uniq = fn.uniq // start from parent's unique values + fn.AnonFuncs = append(fn.AnonFuncs, fn2) + // Build anon immediately, as it may cause fn's locals to escape. + // (It is not marked 'built' until the end of the enclosing FuncDecl.) + fn2.build(b, fn2) + fn.uniq = fn2.uniq // resume after anon's unique values + if fn2.FreeVars == nil { + return fn2 + } + v := &MakeClosure{Fn: fn2} + v.setType(fn.typ(tv.Type)) + for _, fv := range fn2.FreeVars { + v.Bindings = append(v.Bindings, fv.outer) + fv.outer = nil + } + return fn.emit(v, e) + + case *ast.TypeAssertExpr: // single-result form only + return emitTypeAssert(fn, b.expr(fn, e.X), fn.typ(tv.Type), e) + + case *ast.CallExpr: + if fn.info.Types[e.Fun].IsType() { + // Explicit type conversion, e.g. string(x) or big.Int(x) + x := b.expr(fn, e.Args[0]) + y := emitConv(fn, x, fn.typ(tv.Type), e) + return y + } + // Call to "intrinsic" built-ins, e.g. new, make, panic. + if id, ok := ast.Unparen(e.Fun).(*ast.Ident); ok { + if obj, ok := fn.info.Uses[id].(*types.Builtin); ok { + if v := b.builtin(fn, obj, e.Args, fn.typ(tv.Type), e); v != nil { + return v + } + } + } + // Regular function call. + var v Call + b.setCall(fn, e, &v.Call) + v.setType(fn.typ(tv.Type)) + return emitCall(fn, &v, e) + + case *ast.UnaryExpr: + switch e.Op { + case token.AND: // &X --- potentially escaping. + addr := b.addr(fn, e.X, true) + if _, ok := ast.Unparen(e.X).(*ast.StarExpr); ok { + // &*p must panic if p is nil (https://golang.org/s/go12nil). + // For simplicity, we'll just (suboptimally) rely + // on the side effects of a load. + // TODO(adonovan): emit dedicated nilcheck. + addr.load(fn, e) + } + return addr.address(fn) + case token.ADD: + return b.expr(fn, e.X) + case token.NOT, token.SUB, token.XOR: // ! <- - ^ + v := &UnOp{ + Op: e.Op, + X: b.expr(fn, e.X), + } + v.setType(fn.typ(tv.Type)) + return fn.emit(v, e) + case token.ARROW: + return emitRecv(fn, b.expr(fn, e.X), false, fn.typ(tv.Type), e) + default: + panic(e.Op) + } + + case *ast.BinaryExpr: + switch e.Op { + case token.LAND, token.LOR: + return b.logicalBinop(fn, e) + case token.SHL, token.SHR: + fallthrough + case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: + return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), fn.typ(tv.Type), e) + + case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ: + cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e) + // The type of x==y may be UntypedBool. + return emitConv(fn, cmp, types.Default(fn.typ(tv.Type)), e) + default: + panic("illegal op in BinaryExpr: " + e.Op.String()) + } + + case *ast.SliceExpr: + var low, high, max Value + var x Value + xtyp := fn.typeOf(e.X) + switch typeutil.CoreType(xtyp).(type) { + case *types.Array: + // Potentially escaping. + x = b.addr(fn, e.X, true).address(fn) + case *types.Basic, *types.Slice, *types.Pointer: // *array + x = b.expr(fn, e.X) + default: + // core type exception? + if isBytestring(xtyp) { + x = b.expr(fn, e.X) // bytestring is handled as string and []byte. + } else { + panic("unexpected sequence type in SliceExpr") + } + } + if e.Low != nil { + low = b.expr(fn, e.Low) + } + if e.High != nil { + high = b.expr(fn, e.High) + } + if e.Slice3 { + max = b.expr(fn, e.Max) + } + v := &Slice{ + X: x, + Low: low, + High: high, + Max: max, + } + v.setType(fn.typ(tv.Type)) + return fn.emit(v, e) + + case *ast.Ident: + obj := fn.info.Uses[e] + // Universal built-in or nil? + switch obj := obj.(type) { + case *types.Builtin: + return &Builtin{name: obj.Name(), sig: fn.instanceType(e).(*types.Signature)} + case *types.Nil: + return zeroConst(fn.instanceType(e), e) + } + + // Package-level func or var? + // (obj must belong to same package or a direct import.) + if v := fn.Prog.packageLevelMember(obj); v != nil { + if g, ok := v.(*Global); ok { + return emitLoad(fn, g, e) // var (address) + } + callee := v.(*Function) // (func) + if callee.typeparams.Len() > 0 { + targs := fn.subtargs(e) + callee = callee.instance(nil, targs, b) + } + return callee + } + // Local var. + return emitLoad(fn, fn.lookup(obj.(*types.Var), false), e) // var (address) + + case *ast.SelectorExpr: + sel := fn.selection(e) + if sel == nil { + // builtin unsafe.{Add,Slice} + if obj, ok := fn.info.Uses[e.Sel].(*types.Builtin); ok { + return &Builtin{name: "Unsafe" + obj.Name(), sig: fn.typ(tv.Type).(*types.Signature)} + } + // qualified identifier + return b.expr(fn, e.Sel) + } + switch sel.kind { + case types.MethodExpr: + // (*T).f or T.f, the method f from the method-set of type T. + // The result is a "thunk". + targs := fn.subtargs(e.Sel) + thunk := createThunk(fn.Prog, sel, targs) + b.enqueue(thunk) + return thunk + + case types.MethodVal: + // e.f where e is an expression and f is a method. + // The result is a "bound". + m := sel.obj.(*types.Func) + rt := fn.typ(recvType(m)) + wantAddr := isPointer(rt) + escaping := true + v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) + + if types.IsInterface(rt) { + // If v may be an interface type I (after instantiating), + // we must emit a check that v is non-nil. + if recv, ok := types.Unalias(sel.recv).(*types.TypeParam); ok { + // Emit a nil check if any possible instantiation of the + // type parameter is an interface type. + if !typeSetIsEmpty(recv) { + // recv has a concrete term its typeset. + // So it cannot be instantiated as an interface. + // + // Example: + // func _[T interface{~int; Foo()}] () { + // var v T + // _ = v.Foo // <-- MethodVal + // } + } else { + // rt may be instantiated as an interface. + // Emit nil check: typeassert (any(v)).(any). + emitTypeAssert(fn, emitConv(fn, v, tEface, nil), tEface, nil) + } + } else { + // non-type param interface + // Emit nil check: typeassert v.(I). + emitTypeAssert(fn, v, rt, e.Sel) + } + } + + if rtargs := fn.subrtargs(m); len(rtargs) > 0 { + m = fn.Prog.canon.instantiateMethod(m, rtargs, fn.Prog.ctxt) + } + + targs := fn.subtargs(e.Sel) + bound := createBound(fn.Prog, m, targs) + b.enqueue(bound) + + // The assignment may widen a type parameter to its + // interface bound (case #3 of go.dev/issue.78110). + v = emitConv(fn, v, bound.FreeVars[0].Type(), nil) + + c := &MakeClosure{ + Fn: bound, + Bindings: []Value{v}, + } + c.source = e.Sel + c.setType(bound.Signature) + return fn.emit(c, e.Sel) + + case types.FieldVal: + indices := sel.index + last := len(indices) - 1 + v := b.expr(fn, e.X) + v = emitImplicitSelections(fn, v, indices[:last], e) + v = emitFieldSelection(fn, v, indices[last], false, e.Sel) + return v + } + + panic("unexpected expression-relative selector") + + case *ast.IndexListExpr: + // f[X, Y] must be a generic function + if !instance(fn.info, e.X) { + panic("unexpected expression-could not match index list to instantiation") + } + return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases. + + case *ast.IndexExpr: + if instance(fn.info, e.X) { + return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases. + } + // not a generic instantiation. + xt := fn.typeOf(e.X) + switch et, mode := indexType(xt); mode { + case ixVar: + // Addressable slice/array; use IndexAddr and Load. + return b.addr(fn, e, false).load(fn, e) + + case ixArrVar, ixValue: + // An array in a register, a string or a combined type that contains + // either an [_]array (ixArrVar) or string (ixValue). + + // Note: for ixArrVar and CoreType(xt)==nil can be IndexAddr and Load. + index := b.expr(fn, e.Index) + if isUntyped(index.Type()) { + index = emitConv(fn, index, tInt, e.Index) + } + v := &Index{ + X: b.expr(fn, e.X), + Index: index, + } + v.setType(et) + return fn.emit(v, e) + + case ixMap: + ct := typeutil.CoreType(xt).(*types.Map) + v := &MapLookup{ + X: b.expr(fn, e.X), + Index: emitConv(fn, b.expr(fn, e.Index), ct.Key(), e.Index), + } + v.setType(ct.Elem()) + return fn.emit(v, e) + default: + panic("unexpected container type in IndexExpr: " + xt.String()) + } + + case *ast.CompositeLit, *ast.StarExpr: + // Addressable types (lvalues) + return b.addr(fn, e, false).load(fn, e) + } + + panic(fmt.Sprintf("unexpected expr: %T", e)) +} + +// stmtList emits to fn code for all statements in list. +func (b *builder) stmtList(fn *Function, list []ast.Stmt) { + for _, s := range list { + b.stmt(fn, s) + } +} + +// receiver emits to fn code for expression e in the "receiver" +// position of selection e.f (where f may be a field or a method) and +// returns the effective receiver after applying the implicit field +// selections of sel. +// +// wantAddr requests that the result is an address. If +// !sel.indirect, this may require that e be built in addr() mode; it +// must thus be addressable. +// +// escaping is defined as per builder.addr(). +func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *selection, source ast.Node) Value { + var v Value + if wantAddr && !sel.indirect && !isPointerCore(fn.typeOf(e)) { + v = b.addr(fn, e, escaping).address(fn) + } else { + v = b.expr(fn, e) + } + + last := len(sel.index) - 1 + v = emitImplicitSelections(fn, v, sel.index[:last], source) + if types.IsInterface(v.Type()) { + // When v is an interface, sel.Kind()==MethodValue and v.f is invoked. + // So v is not loaded, even if v has a pointer core type. + } else if !wantAddr && isPointerCore(v.Type()) { + v = emitLoad(fn, v, e) + } + return v +} + +// setCallFunc populates the function parts of a CallCommon structure +// (Func, Method, Recv, Args[0]) based on the kind of invocation +// occurring in e. +func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) { + // Is this a (possibly generic) method call? + m := ast.Unparen(e.Fun) + switch e := m.(type) { + case *ast.IndexExpr: + m = e.X + case *ast.IndexListExpr: + m = e.X + } + if selector, ok := m.(*ast.SelectorExpr); ok { + sel := fn.selection(selector) + if sel != nil && sel.kind == types.MethodVal { + obj := sel.obj.(*types.Func) + recv := recvType(obj) + + wantAddr := isPointer(recv) + escaping := true + v := b.receiver(fn, selector.X, wantAddr, escaping, sel, selector) + if types.IsInterface(recv) { + // Invoke-mode call. + c.Value = v // possibly type param + c.Method = obj + } else { + // "Call"-mode call. + targs := fn.subtargs(selector.Sel) + c.Value = fn.Prog.objectMethod(obj, targs, b) + c.Args = append(c.Args, v) + } + return + } + + // sel.kind==MethodExpr indicates T.f() or (*T).f(): + // a statically dispatched call to the method f in the + // method-set of T or *T. T may be an interface. + // + // e.Fun would evaluate to a concrete method, interface + // wrapper function, or promotion wrapper. + // + // For now, we evaluate it in the usual way. + // + // TODO(adonovan): opt: inline expr() here, to make the + // call static and to avoid generation of wrappers. + // It's somewhat tricky as it may consume the first + // actual parameter if the call is "invoke" mode. + // + // Examples: + // type T struct{}; func (T) f() {} // "call" mode + // type T interface { f() } // "invoke" mode + // + // type S struct{ T } + // + // var s S + // S.f(s) + // (*S).f(&s) + // + // Suggested approach: + // - consume the first actual parameter expression + // and build it with b.expr(). + // - apply implicit field selections. + // - use MethodVal logic to populate fields of c. + } + + // Evaluate the function operand in the usual way. + c.Value = b.expr(fn, e.Fun) +} + +// emitCallArgs emits to f code for the actual parameters of call e to +// a (possibly built-in) function of effective type sig. +// The argument values are appended to args, which is then returned. +func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallExpr, args []Value) []Value { + // f(x, y, z...): pass slice z straight through. + if e.Ellipsis != 0 { + for i, arg := range e.Args { + v := emitConv(fn, b.expr(fn, arg), sig.Params().At(i).Type(), arg) + args = append(args, v) + } + return args + } + + offset := len(args) // 1 if call has receiver, 0 otherwise + + // Evaluate actual parameter expressions. + // + // If this is a chained call of the form f(g()) where g has + // multiple return values (MRV), they are flattened out into + // args; a suffix of them may end up in a varargs slice. + for _, arg := range e.Args { + v := b.expr(fn, arg) + if ttuple, ok := v.Type().(*types.Tuple); ok { // MRV chain + for i, n := 0, ttuple.Len(); i < n; i++ { + args = append(args, emitExtract(fn, v, i, arg)) + } + } else { + args = append(args, v) + } + } + + // Actual->formal assignability conversions for normal parameters. + np := sig.Params().Len() // number of normal parameters + if sig.Variadic() { + np-- + } + for i := 0; i < np; i++ { + args[offset+i] = emitConv(fn, args[offset+i], sig.Params().At(i).Type(), args[offset+i].Source()) + } + + // Actual->formal assignability conversions for variadic parameter, + // and construction of slice. + if sig.Variadic() { + varargs := args[offset+np:] + st := sig.Params().At(np).Type().(*types.Slice) + vt := st.Elem() + if len(varargs) == 0 { + args = append(args, zeroConst(st, nil)) + } else { + // Replace a suffix of args with a slice containing it. + at := types.NewArray(vt, int64(len(varargs))) + a := emitNew(fn, at, e, "varargs") + for i, arg := range varargs { + iaddr := &IndexAddr{ + X: a, + Index: intConst(int64(i), nil), + } + iaddr.setType(types.NewPointer(vt)) + fn.emit(iaddr, e) + emitStore(fn, iaddr, arg, arg.Source()) + } + s := &Slice{X: a} + s.setType(st) + args[offset+np] = fn.emit(s, args[offset+np].Source()) + args = args[:offset+np+1] + } + } + return args +} + +// setCall emits to fn code to evaluate all the parameters of a function +// call e, and populates *c with those values. +func (b *builder) setCall(fn *Function, e *ast.CallExpr, c *CallCommon) { + // First deal with the f(...) part and optional receiver. + b.setCallFunc(fn, e, c) + + // Then append the other actual parameters. + sig, _ := typeutil.CoreType(fn.typeOf(e.Fun)).(*types.Signature) + if sig == nil { + panic(fmt.Sprintf("no signature for call of %s", e.Fun)) + } + c.Args = b.emitCallArgs(fn, sig, e, c.Args) +} + +// assignOp emits to fn code to perform loc = val. +func (b *builder) assignOp(fn *Function, loc lvalue, val Value, op token.Token, source ast.Node) { + loc.store(fn, emitArith(fn, op, loc.load(fn, source), val, loc.typ(), source), source) +} + +// localValueSpec emits to fn code to define all of the vars in the +// function-local ValueSpec, spec. +func (b *builder) localValueSpec(fn *Function, spec *ast.ValueSpec) { + switch { + case len(spec.Values) == len(spec.Names): + // e.g. var x, y = 0, 1 + // 1:1 assignment + for i, id := range spec.Names { + if !isBlankIdent(id) { + emitLocalVar(fn, identVar(fn, id), id) + } + lval := b.addr(fn, id, false) // non-escaping + b.assign(fn, lval, spec.Values[i], true, nil, spec) + } + + case len(spec.Values) == 0: + // e.g. var x, y int + // Locals are implicitly zero-initialized. + for _, id := range spec.Names { + if !isBlankIdent(id) { + lhs := emitLocalVar(fn, identVar(fn, id), id) + if fn.debugInfo() { + emitDebugRef(fn, id, lhs, true) + } + } + } + + default: + // e.g. var x, y = pos() + tuple := b.exprN(fn, spec.Values[0]) + for i, id := range spec.Names { + if !isBlankIdent(id) { + emitLocalVar(fn, identVar(fn, id), id) + lhs := b.addr(fn, id, false) // non-escaping + lhs.store(fn, emitExtract(fn, tuple, i, id), id) + } + } + } +} + +// assignStmt emits code to fn for a parallel assignment of rhss to lhss. +// isDef is true if this is a short variable declaration (:=). +// +// Note the similarity with localValueSpec. +func (b *builder) assignStmt(fn *Function, lhss, rhss []ast.Expr, isDef bool, source ast.Node) { + // Side effects of all LHSs and RHSs must occur in left-to-right order. + lvals := make([]lvalue, len(lhss)) + isZero := make([]bool, len(lhss)) + for i, lhs := range lhss { + var lval lvalue = blank{} + if !isBlankIdent(lhs) { + if isDef { + if obj, ok := fn.info.Defs[lhs.(*ast.Ident)].(*types.Var); ok { + emitLocalVar(fn, obj, lhs) + isZero[i] = true + } + } + lval = b.addr(fn, lhs, false) // non-escaping + } + lvals[i] = lval + } + if len(lhss) == len(rhss) { + // Simple assignment: x = f() (!isDef) + // Parallel assignment: x, y = f(), g() (!isDef) + // or short var decl: x, y := f(), g() (isDef) + // + // In all cases, the RHSs may refer to the LHSs, + // so we need a storebuf. + var sb storebuf + for i := range rhss { + b.assign(fn, lvals[i], rhss[i], isZero[i], &sb, source) + } + sb.emit(fn) + } else { + // e.g. x, y = pos() + tuple := b.exprN(fn, rhss[0]) + emitDebugRef(fn, rhss[0], tuple, false) + for i, lval := range lvals { + lval.store(fn, emitExtract(fn, tuple, i, source), source) + } + } +} + +// arrayLen returns the length of the array whose composite literal elements are elts. +func (b *builder) arrayLen(fn *Function, elts []ast.Expr) int64 { + var max int64 = -1 + var i int64 = -1 + for _, e := range elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + i = b.expr(fn, kv.Key).(*Const).Int64() + } else { + i++ + } + if i > max { + max = i + } + } + return max + 1 +} + +// compLit emits to fn code to initialize a composite literal e at +// address addr with type typ. +// +// Nested composite literals are recursively initialized in place +// where possible. If isZero is true, compLit assumes that addr +// holds the zero value for typ. +// +// Because the elements of a composite literal may refer to the +// variables being updated, as in the second line below, +// +// x := T{a: 1} +// x = T{a: x.a} +// +// all the reads must occur before all the writes. Thus all stores to +// loc are emitted to the storebuf sb for later execution. +// +// A CompositeLit may have pointer type only in the recursive (nested) +// case when the type name is implicit. e.g. in []*T{{}}, the inner +// literal has type *T behaves like &T{}. +// In that case, addr must hold a T, not a *T. +func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) { + typ := deref(fn.typeOf(e)) // retain the named/alias/param type, if any + switch t := typeutil.CoreType(typ).(type) { + case *types.Struct: + lvalue := &address{addr: addr, expr: e} + if len(e.Elts) == 0 { + if !isZero { + sb.store(lvalue, zeroConst(deref(addr.Type()), e), e) + } + } else { + v := &CompositeValue{ + Values: make([]Value, t.NumFields()), + } + for i := range t.NumFields() { + v.Values[i] = zeroConst(t.Field(i).Type(), e) + } + v.setType(typ) + + type chainElement struct { + children []chainElement + cv *CompositeValue + } + + chain := chainElement{ + cv: v, + } + + for i, e := range e.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + fname := kv.Key.(*ast.Ident).Name + _, index, _ := types.LookupFieldOrMethod(t, true, fn.declaredPackage().Pkg, fname) + + var parent *chainElement + chain := &chain + chainCoreType := t + + // Ensure that the entire chain of embedded fields has + // corresponding CompositeValues + for _, idx := range index[:len(index)-1] { + if idx >= len(chain.children) { + n := make([]chainElement, idx+1) + copy(n, chain.children) + chain.children = n + } + + parent = chain + chain = &chain.children[idx] + treeType := chainCoreType.Field(idx).Type() + chainCoreType = typeutil.CoreType(treeType).(*types.Struct) + if chain.cv == nil { + ncv := &CompositeValue{ + Values: make([]Value, chainCoreType.NumFields()), + } + for i := range chainCoreType.NumFields() { + ncv.Values[i] = zeroConst(chainCoreType.Field(i).Type(), kv) + } + ncv.setType(treeType) + chain.cv = ncv + ce := &compositeElement{ + cv: parent.cv, + idx: idx, + t: ncv.Type(), + } + parent.cv.Bitmap.SetBit(&parent.cv.Bitmap, idx, 1) + parent.cv.NumSet++ + sb.store(ce, chain.cv, kv) + } + } + + ce := &compositeElement{ + cv: chain.cv, + idx: index[len(index)-1], + t: chainCoreType.Field(index[len(index)-1]).Type(), + expr: kv.Value, + } + // We use b.assign for its handling of implicit & (which, + // albeit not needed for structs now, may be needed in the + // future), but no store buffer because 1) it's not needed 2) + // implicit conversions have to be emitted before we emit the + // CompositeValue. + b.assign(fn, ce, kv.Value, isZero, nil, kv) + chain.cv.Bitmap.SetBit(&chain.cv.Bitmap, index[len(index)-1], 1) + chain.cv.NumSet++ + } else { + ce := &compositeElement{ + cv: v, + idx: i, + t: t.Field(i).Type(), + expr: e, + } + b.assign(fn, ce, e, isZero, nil, e) + v.Bitmap.SetBit(&v.Bitmap, i, 1) + v.NumSet++ + } + } + + var dfs func(t chainElement) + dfs = func(t chainElement) { + for _, tt := range t.children { + dfs(tt) + } + + // XXX better e + if t.cv != nil { + fn.emit(t.cv, e) + } + } + dfs(chain) + sb.store(lvalue, v, e) + } + + case *types.Array, *types.Slice: + var at *types.Array + var array Value + switch t := t.(type) { + case *types.Slice: + at = types.NewArray(t.Elem(), b.arrayLen(fn, e.Elts)) + array = emitNew(fn, at, e, "slicelit") + case *types.Array: + at = t + array = addr + } + + var final Value + if len(e.Elts) == 0 { + if !isZero { + zc := zeroConst(at, e) + final = zc + } + } else { + if at.Len() == int64(len(e.Elts)) { + // The literal specifies all elements, so we can use a composite value + v := &CompositeValue{ + Values: make([]Value, at.Len()), + } + zc := zeroConst(at.Elem(), e) + for i := range v.Values { + v.Values[i] = zc + } + v.setType(at) + + var idx *Const + for _, e := range e.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + idx = b.expr(fn, kv.Key).(*Const) + e = kv.Value + } else { + var idxval int64 + if idx != nil { + idxval = idx.Int64() + 1 + } + idx = intConst(idxval, e) + } + + iaddr := &compositeElement{ + cv: v, + idx: int(idx.Int64()), + t: at.Elem(), + expr: e, + } + + // We use b.assign for its handling of implicit &, but no + // store buffer because 1) it's not needed 2) implicit + // conversions have to be emitted before we emit the + // CompositeValue. + b.assign(fn, iaddr, e, true, nil, e) + v.Bitmap.SetBit(&v.Bitmap, int(idx.Int64()), 1) + v.NumSet++ + } + final = v + fn.emit(v, e) + } else { + // Not all elements are specified. Populate the array with a series of stores, to guard against literals + // like []int{1<<62: 1}. + if !isZero { + // memclear + sb.store(&address{array, nil}, zeroConst(deref(array.Type()), e), e) + } + + var idx *Const + for _, e := range e.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok { + idx = b.expr(fn, kv.Key).(*Const) + e = kv.Value + } else { + var idxval int64 + if idx != nil { + idxval = idx.Int64() + 1 + } + idx = intConst(idxval, e) + } + iaddr := &IndexAddr{ + X: array, + Index: idx, + } + iaddr.setType(types.NewPointer(at.Elem())) + fn.emit(iaddr, e) + if t != at { // slice + // backing array is unaliased => storebuf not needed. + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, nil, e) + } else { + b.assign(fn, &address{addr: iaddr, expr: e}, e, true, sb, e) + } + } + } + } + if t != at { // slice + if final != nil { + sb.store(&address{addr: array}, final, e) + } + s := &Slice{X: array} + s.setType(typ) + sb.store(&address{addr: addr, expr: e}, fn.emit(s, e), e) + } else if final != nil { + sb.store(&address{addr: array, expr: e}, final, e) + } + + case *types.Map: + m := &MakeMap{Reserve: intConst(int64(len(e.Elts)), e)} + m.setType(typ) + fn.emit(m, e) + for _, e := range e.Elts { + e := e.(*ast.KeyValueExpr) + + // If a key expression in a map literal is itself a + // composite literal, the type may be omitted. + // For example: + // map[*struct{}]bool{{}: true} + // An &-operation may be implied: + // map[*struct{}]bool{&struct{}{}: true} + wantAddr := false + if _, ok := ast.Unparen(e.Key).(*ast.CompositeLit); ok { + wantAddr = isPointerCore(t.Key()) + } + + var key Value + if wantAddr { + // A CompositeLit never evaluates to a pointer, + // so if the type of the location is a pointer, + // an &-operation is implied. + key = b.addr(fn, e.Key, true).address(fn) + } else { + key = b.expr(fn, e.Key) + } + + loc := element{ + m: m, + k: emitConv(fn, key, t.Key(), e), + t: t.Elem(), + } + + // We call assign() only because it takes care + // of any &-operation required in the recursive + // case, e.g., + // map[int]*struct{}{0: {}} implies &struct{}{}. + // In-place update is of course impossible, + // and no storebuf is needed. + b.assign(fn, &loc, e.Value, true, nil, e) + } + sb.store(&address{addr: addr, expr: e}, m, e) + + default: + panic("unexpected CompositeLit type: " + typ.String()) + } +} + +func (b *builder) switchStmt(fn *Function, s *ast.SwitchStmt, label *lblock) { + if s.Tag == nil { + b.switchStmtDynamic(fn, s, label) + return + } + dynamic := false + for _, iclause := range s.Body.List { + clause := iclause.(*ast.CaseClause) + for _, cond := range clause.List { + if fn.info.Types[ast.Unparen(cond)].Value == nil { + dynamic = true + break + } + } + } + + if dynamic { + b.switchStmtDynamic(fn, s, label) + return + } + + if s.Init != nil { + b.stmt(fn, s.Init) + } + + entry := fn.currentBlock + tag := b.expr(fn, s.Tag) + + heads := make([]*BasicBlock, 0, len(s.Body.List)) + bodies := make([]*BasicBlock, len(s.Body.List)) + conds := make([]Value, 0, len(s.Body.List)) + + hasDefault := false + done := fn.newBasicBlock("switch.done") + if label != nil { + label._break = done + } + for i, stmt := range s.Body.List { + body := fn.newBasicBlock(fmt.Sprintf("switch.body.%d", i)) + bodies[i] = body + cas := stmt.(*ast.CaseClause) + if cas.List == nil { + // default branch + hasDefault = true + head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d", i)) + conds = append(conds, nil) + heads = append(heads, head) + fn.currentBlock = head + emitJump(fn, body, cas) + } + for j, cond := range stmt.(*ast.CaseClause).List { + fn.currentBlock = entry + head := fn.newBasicBlock(fmt.Sprintf("switch.head.%d.%d", i, j)) + conds = append(conds, b.expr(fn, cond)) + heads = append(heads, head) + fn.currentBlock = head + emitJump(fn, body, cond) + } + } + + for i, stmt := range s.Body.List { + clause := stmt.(*ast.CaseClause) + body := bodies[i] + fn.currentBlock = body + fallthru := done + if i+1 < len(bodies) { + fallthru = bodies[i+1] + } + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _fallthrough: fallthru, + } + b.stmtList(fn, clause.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, stmt) + } + + if !hasDefault { + head := fn.newBasicBlock("switch.head.implicit-default") + body := fn.newBasicBlock("switch.body.implicit-default") + fn.currentBlock = head + emitJump(fn, body, s) + fn.currentBlock = body + emitJump(fn, done, s) + heads = append(heads, head) + conds = append(conds, nil) + } + + if len(heads) != len(conds) { + panic(fmt.Sprintf("internal error: %d heads for %d conds", len(heads), len(conds))) + } + for _, head := range heads { + addEdge(entry, head) + } + fn.currentBlock = entry + entry.emit(&ConstantSwitch{ + Tag: tag, + Conds: conds, + }, s) + fn.currentBlock = done +} + +// switchStmt emits to fn code for the switch statement s, optionally +// labelled by label. +func (b *builder) switchStmtDynamic(fn *Function, s *ast.SwitchStmt, label *lblock) { + // We treat SwitchStmt like a sequential if-else chain. + // Multiway dispatch can be recovered later by irutil.Switches() + // to those cases that are free of side effects. + if s.Init != nil { + b.stmt(fn, s.Init) + } + var tag Value = vTrue + + if s.Tag != nil { + tag = b.expr(fn, s.Tag) + } + + done := fn.newBasicBlock("switch.done") + if label != nil { + label._break = done + } + // We pull the default case (if present) down to the end. + // But each fallthrough label must point to the next + // body block in source order, so we preallocate a + // body block (fallthru) for the next case. + // Unfortunately this makes for a confusing block order. + var dfltBody *[]ast.Stmt + var dfltFallthrough *BasicBlock + var fallthru, dfltBlock *BasicBlock + ncases := len(s.Body.List) + for i, clause := range s.Body.List { + body := fallthru + if body == nil { + body = fn.newBasicBlock("switch.body") // first case only + } + + // Preallocate body block for the next case. + fallthru = done + if i+1 < ncases { + fallthru = fn.newBasicBlock("switch.body") + } + + cc := clause.(*ast.CaseClause) + if cc.List == nil { + // Default case. + dfltBody = &cc.Body + dfltFallthrough = fallthru + dfltBlock = body + continue + } + + var nextCond *BasicBlock + for _, cond := range cc.List { + nextCond = fn.newBasicBlock("switch.next") + // For boolean switches, emit short-circuit control flow, + // just like an if/else-chain. + if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) { + b.cond(fn, cond, body, nextCond) + } else { + cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond) + emitIf(fn, cond, body, nextCond, cond.Source()) + } + + fn.currentBlock = nextCond + } + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _fallthrough: fallthru, + } + b.stmtList(fn, cc.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, s) + fn.currentBlock = nextCond + } + if dfltBlock != nil { + // The lack of a Source for the jump doesn't matter, block + // fusing will get rid of the jump later. + + emitJump(fn, dfltBlock, s) + fn.currentBlock = dfltBlock + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _fallthrough: dfltFallthrough, + } + b.stmtList(fn, *dfltBody) + fn.targets = fn.targets.tail + } + emitJump(fn, done, s) + fn.currentBlock = done +} + +// typeSwitchStmt emits to fn code for the type switch statement s, optionally +// labelled by label. +func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lblock) { + if s.Init != nil { + b.stmt(fn, s.Init) + } + + var tag Value + switch e := s.Assign.(type) { + case *ast.ExprStmt: // x.(type) + tag = b.expr(fn, ast.Unparen(e.X).(*ast.TypeAssertExpr).X) + case *ast.AssignStmt: // y := x.(type) + tag = b.expr(fn, ast.Unparen(e.Rhs[0]).(*ast.TypeAssertExpr).X) + default: + panic("unreachable") + } + + // +1 in case there's no explicit default case + heads := make([]*BasicBlock, 0, len(s.Body.List)+1) + + entry := fn.currentBlock + done := fn.newBasicBlock("typeswitch.done") + if label != nil { + label._break = done + } + + // set up type switch and constant switch, populate their conditions + tswtch := &TypeSwitch{ + Tag: tag, + Conds: make([]types.Type, 0, len(s.Body.List)+1), + } + cswtch := &ConstantSwitch{ + Conds: make([]Value, 0, len(s.Body.List)+1), + } + + rets := make([]types.Type, 0, len(s.Body.List)+1) + index := 0 + var default_ *ast.CaseClause + for _, clause := range s.Body.List { + cc := clause.(*ast.CaseClause) + if obj, ok := fn.info.Implicits[cc].(*types.Var); ok { + emitLocalVar(fn, obj, cc) + } + if cc.List == nil { + // default case + default_ = cc + } else { + for _, expr := range cc.List { + tswtch.Conds = append(tswtch.Conds, fn.typeOf(expr)) + cswtch.Conds = append(cswtch.Conds, intConst(int64(index), expr)) + index++ + } + if len(cc.List) == 1 { + rets = append(rets, fn.typeOf(cc.List[0])) + } else { + for range cc.List { + rets = append(rets, tag.Type()) + } + } + } + } + + // default branch + rets = append(rets, tag.Type()) + + var vars []*types.Var + vars = append(vars, varIndex) + for _, typ := range rets { + vars = append(vars, anonVar(typ)) + } + tswtch.setType(types.NewTuple(vars...)) + // default branch + fn.currentBlock = entry + fn.emit(tswtch, s) + cswtch.Conds = append(cswtch.Conds, intConst(int64(-1), nil)) + cswtch.Tag = emitExtract(fn, tswtch, 0, s) + fn.emit(cswtch, s) + + // build heads and bodies + index = 0 + for _, clause := range s.Body.List { + cc := clause.(*ast.CaseClause) + if cc.List == nil { + continue + } + + body := fn.newBasicBlock("typeswitch.body") + for _, expr := range cc.List { + head := fn.newBasicBlock("typeswitch.head") + heads = append(heads, head) + fn.currentBlock = head + + if obj, ok := fn.info.Implicits[cc].(*types.Var); ok { + // In a switch y := x.(type), each case clause + // implicitly declares a distinct object y. + // In a single-type case, y has that type. + // In multi-type cases, 'case nil' and default, + // y has the same type as the interface operand. + + l := fn.vars[obj] + if rets[index] == tUntypedNil { + emitStore(fn, l, nilConst(tswtch.Tag.Type(), nil), s.Assign) + } else { + x := emitExtract(fn, tswtch, index+1, s.Assign) + emitStore(fn, l, x, nil) + } + } + + emitJump(fn, body, expr) + index++ + } + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + b.stmtList(fn, cc.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, clause) + } + + if default_ == nil { + // implicit default + heads = append(heads, done) + } else { + body := fn.newBasicBlock("typeswitch.default") + heads = append(heads, body) + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + if obj, ok := fn.info.Implicits[default_].(*types.Var); ok { + l := fn.vars[obj] + x := emitExtract(fn, tswtch, index+1, s.Assign) + emitStore(fn, l, x, s) + } + b.stmtList(fn, default_.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, s) + } + + fn.currentBlock = entry + for _, head := range heads { + addEdge(entry, head) + } + fn.currentBlock = done +} + +// selectStmt emits to fn code for the select statement s, optionally +// labelled by label. +func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) (noreturn bool) { + if len(s.Body.List) == 0 { + instr := &Select{Blocking: true} + instr.setType(types.NewTuple(varIndex, varOk)) + fn.emit(instr, s) + fn.emit(new(Unreachable), s) + return true + } + + // A blocking select of a single case degenerates to a + // simple send or receive. + // TODO(adonovan): opt: is this optimization worth its weight? + if len(s.Body.List) == 1 { + clause := s.Body.List[0].(*ast.CommClause) + if clause.Comm != nil { + b.stmt(fn, clause.Comm) + done := fn.newBasicBlock("select.done") + if label != nil { + label._break = done + } + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + b.stmtList(fn, clause.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, clause) + fn.currentBlock = done + return false + } + } + + // First evaluate all channels in all cases, and find + // the directions of each state. + var states []*SelectState + blocking := true + debugInfo := fn.debugInfo() + for _, clause := range s.Body.List { + var st *SelectState + switch comm := clause.(*ast.CommClause).Comm.(type) { + case nil: // default case + blocking = false + continue + + case *ast.SendStmt: // ch<- i + ch := b.expr(fn, comm.Chan) + st = &SelectState{ + Dir: types.SendOnly, + Chan: ch, + Send: emitConv(fn, b.expr(fn, comm.Value), + typeutil.CoreType(fn.typ(ch.Type())).(*types.Chan).Elem(), comm), + Pos: comm.Arrow, + } + if debugInfo { + st.DebugNode = comm + } + + case *ast.AssignStmt: // x := <-ch + recv := ast.Unparen(comm.Rhs[0]).(*ast.UnaryExpr) + st = &SelectState{ + Dir: types.RecvOnly, + Chan: b.expr(fn, recv.X), + Pos: recv.OpPos, + } + if debugInfo { + st.DebugNode = recv + } + + case *ast.ExprStmt: // <-ch + recv := ast.Unparen(comm.X).(*ast.UnaryExpr) + st = &SelectState{ + Dir: types.RecvOnly, + Chan: b.expr(fn, recv.X), + Pos: recv.OpPos, + } + if debugInfo { + st.DebugNode = recv + } + } + states = append(states, st) + } + + // We dispatch on the (fair) result of Select using a + // switch on the returned index. + sel := &Select{ + States: states, + Blocking: blocking, + } + sel.source = s + var vars []*types.Var + vars = append(vars, varIndex, varOk) + for _, st := range states { + if st.Dir == types.RecvOnly { + tElem := typeutil.CoreType(fn.typ(st.Chan.Type())).(*types.Chan).Elem() + vars = append(vars, anonVar(tElem)) + } + } + sel.setType(types.NewTuple(vars...)) + fn.emit(sel, s) + idx := emitExtract(fn, sel, 0, s) + + done := fn.newBasicBlock("select.done") + if label != nil { + label._break = done + } + + entry := fn.currentBlock + swtch := &ConstantSwitch{ + Tag: idx, + // one condition per case + Conds: make([]Value, 0, len(s.Body.List)+1), + } + // note that we don't need heads; a select case can only have a single condition + var bodies []*BasicBlock + + state := 0 + r := 2 // index in 'sel' tuple of value; increments if st.Dir==RECV + for _, cc := range s.Body.List { + clause := cc.(*ast.CommClause) + if clause.Comm == nil { + body := fn.newBasicBlock("select.default") + fn.currentBlock = body + bodies = append(bodies, body) + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + b.stmtList(fn, clause.Body) + emitJump(fn, done, s) + fn.targets = fn.targets.tail + swtch.Conds = append(swtch.Conds, intConst(-1, nil)) + continue + } + swtch.Conds = append(swtch.Conds, intConst(int64(state), nil)) + body := fn.newBasicBlock("select.body") + fn.currentBlock = body + bodies = append(bodies, body) + fn.targets = &targets{ + tail: fn.targets, + _break: done, + } + switch comm := clause.Comm.(type) { + case *ast.ExprStmt: // <-ch + if debugInfo { + v := emitExtract(fn, sel, r, comm) + emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false) + } + r++ + + case *ast.AssignStmt: // x := <-states[state].Chan + if comm.Tok == token.DEFINE { + id := comm.Lhs[0].(*ast.Ident) + emitLocalVar(fn, identVar(fn, id), id) + } + x := b.addr(fn, comm.Lhs[0], false) // non-escaping + v := emitExtract(fn, sel, r, comm) + if debugInfo { + emitDebugRef(fn, states[state].DebugNode.(ast.Expr), v, false) + } + x.store(fn, v, comm) + + if len(comm.Lhs) == 2 { // x, ok := ... + if comm.Tok == token.DEFINE { + id := comm.Lhs[1].(*ast.Ident) + emitLocalVar(fn, identVar(fn, id), id) + } + ok := b.addr(fn, comm.Lhs[1], false) // non-escaping + ok.store(fn, emitExtract(fn, sel, 1, comm), comm) + } + r++ + } + b.stmtList(fn, clause.Body) + fn.targets = fn.targets.tail + emitJump(fn, done, s) + state++ + } + fn.currentBlock = entry + fn.emit(swtch, s) + for _, body := range bodies { + addEdge(entry, body) + } + fn.currentBlock = done + return false +} + +// forStmt emits to fn code for the for statement s, optionally +// labelled by label. +func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) { + // Use forStmtGo122 instead if it applies. + if s.Init != nil { + if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE { + if versions.AtLeast(fn.goversion, versions.Go1_22) { + b.forStmtGo122(fn, s, label) + return + } + } + } + + // ...init... + // jump loop + // loop: + // if cond goto body else done + // body: + // ...body... + // jump post + // post: (target of continue) + // ...post... + // jump loop + // done: (target of break) + if s.Init != nil { + b.stmt(fn, s.Init) + } + body := fn.newBasicBlock("for.body") + done := fn.newBasicBlock("for.done") // target of 'break' + loop := body // target of back-edge + if s.Cond != nil { + loop = fn.newBasicBlock("for.loop") + } + cont := loop // target of 'continue' + if s.Post != nil { + cont = fn.newBasicBlock("for.post") + } + if label != nil { + label._break = done + label._continue = cont + } + emitJump(fn, loop, s) + fn.currentBlock = loop + if loop != body { + b.cond(fn, s.Cond, body, done) + fn.currentBlock = body + } + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _continue: cont, + } + b.stmt(fn, s.Body) + fn.targets = fn.targets.tail + emitJump(fn, cont, s) + + if s.Post != nil { + fn.currentBlock = cont + b.stmt(fn, s.Post) + emitJump(fn, loop, s) // back-edge + } + fn.currentBlock = done +} + +// forStmtGo122 emits to fn code for the for statement s, optionally +// labelled by label. s must define its variables. +// +// This allocates once per loop iteration. This is only correct in +// GoVersions >= go1.22. +func (b *builder) forStmtGo122(fn *Function, s *ast.ForStmt, label *lblock) { + // i_outer = alloc[T] + // *i_outer = ...init... // under objects[i] = i_outer + // jump loop + // loop: + // i = phi [head: i_outer, loop: i_next] + // ...cond... // under objects[i] = i + // if cond goto body else done + // body: + // ...body... // under objects[i] = i (same as loop) + // jump post + // post: + // tmp = *i + // i_next = alloc[T] + // *i_next = tmp + // ...post... // under objects[i] = i_next + // goto loop + // done: + + init := s.Init.(*ast.AssignStmt) + startingBlocks := len(fn.Blocks) + + pre := fn.currentBlock // current block before starting + loop := fn.newBasicBlock("for.loop") // target of back-edge + body := fn.newBasicBlock("for.body") + post := fn.newBasicBlock("for.post") // target of 'continue' + done := fn.newBasicBlock("for.done") // target of 'break' + + // For each of the n loop variables, we create five SSA values, + // outer, phi, next, load, and store in pre, loop, and post. + // There is no limit on n. + type loopVar struct { + obj *types.Var + outer *Alloc + phi *Phi + load *Load + next *Alloc + store *Store + } + vars := make([]loopVar, len(init.Lhs)) + for i, lhs := range init.Lhs { + v := identVar(fn, lhs.(*ast.Ident)) + typ := fn.typ(v.Type()) + + fn.currentBlock = pre + outer := emitLocal(fn, typ, lhs, v.Name()) + + fn.currentBlock = loop + phi := &Phi{} + phi.comment = v.Name() + phi.typ = outer.Type() + fn.emit(phi, lhs) + + fn.currentBlock = post + // If next is local, it reuses the address and zeroes the old value so + // load before allocating next. + load := emitLoad(fn, phi, init) + next := emitLocal(fn, typ, lhs, v.Name()) + store := emitStore(fn, next, load, s) + + phi.Edges = []Value{outer, next} // pre edge is emitted before post edge. + vars[i] = loopVar{v, outer, phi, load, next, store} + } + + // ...init... under fn.objects[v] = i_outer + fn.currentBlock = pre + for _, v := range vars { + fn.vars[v.obj] = v.outer + } + const isDef = false // assign to already-allocated outers + b.assignStmt(fn, init.Lhs, init.Rhs, isDef, init) + if label != nil { + label._break = done + label._continue = post + } + emitJump(fn, loop, s) + + // ...cond... under fn.objects[v] = i + fn.currentBlock = loop + for _, v := range vars { + fn.vars[v.obj] = v.phi + } + if s.Cond != nil { + b.cond(fn, s.Cond, body, done) + } else { + emitJump(fn, body, s) + } + + // ...body... under fn.objects[v] = i + fn.currentBlock = body + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _continue: post, + } + b.stmt(fn, s.Body) + fn.targets = fn.targets.tail + emitJump(fn, post, s) + + // ...post... under fn.objects[v] = i_next + for _, v := range vars { + fn.vars[v.obj] = v.next + } + fn.currentBlock = post + if s.Post != nil { + b.stmt(fn, s.Post) + } + emitJump(fn, loop, s) // back-edge + fn.currentBlock = done + + // For each loop variable that does not escape, + // (the common case), fuse its next cells into its + // (local) outer cell as they have disjoint live ranges. + // + // It is sufficient to test whether i_next escapes, + // because its Heap flag will be marked true if either + // the cond or post expression causes i to escape + // (because escape distributes over phi). + var nlocals int + for _, v := range vars { + if !v.next.Heap { + nlocals++ + } + } + if nlocals > 0 { + replace := make(map[Value]Value, 2*nlocals) + dead := make(map[Instruction]bool, 4*nlocals) + for _, v := range vars { + if !v.next.Heap { + replace[v.next] = v.outer + replace[v.phi] = v.outer + dead[v.phi], dead[v.next], dead[v.load], dead[v.store] = true, true, true, true + } + } + + // Replace all uses of i_next and phi with i_outer. + // Referrers have not been built for fn yet so only update Instruction operands. + // We need only look within the blocks added by the loop. + var operands []*Value // recycle storage + for _, b := range fn.Blocks[startingBlocks:] { + for _, instr := range b.Instrs { + operands = instr.Operands(operands[:0]) + for _, ptr := range operands { + k := *ptr + if v := replace[k]; v != nil { + *ptr = v + } + } + } + } + + // Remove instructions for phi, load, and store. + // lift() will remove the unused i_next *Alloc. + isDead := func(i Instruction) bool { return dead[i] } + loop.Instrs = slices.DeleteFunc(loop.Instrs, isDead) + post.Instrs = slices.DeleteFunc(post.Instrs, isDead) + } +} + +// rangeIndexed emits to fn the header for an integer-indexed loop +// over array, *array or slice value x. +// The v result is defined only if tv is non-nil. +// forPos is the position of the "for" token. +func (b *builder) rangeIndexed(fn *Function, x Value, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) { + // + // length = len(x) + // index = -1 + // loop: (target of continue) + // index++ + // if index < length goto body else done + // body: + // k = index + // v = x[index] + // ...body... + // jump loop + // done: (target of break) + + // Determine number of iterations. + var length Value + dt := deref(x.Type()) + if arr, ok := typeutil.CoreType(dt).(*types.Array); ok { + // For array or *array, the number of iterations is + // known statically thanks to the type. We avoid a + // data dependence upon x, permitting later dead-code + // elimination if x is pure, static unrolling, etc. + // Ranging over a nil *array may have >0 iterations. + // We still generate code for x, in case it has effects. + length = intConst(arr.Len(), nil) + } else { + // length = len(x). + var c Call + c.Call.Value = makeLen(x.Type()) + c.Call.Args = []Value{x} + c.setType(tInt) + length = fn.emit(&c, source) + } + + index := emitLocal(fn, tInt, source, "rangeindex") + emitStore(fn, index, intConst(-1, nil), source) + + loop = fn.newBasicBlock("rangeindex.loop") + emitJump(fn, loop, source) + fn.currentBlock = loop + + incr := &BinOp{ + Op: token.ADD, + X: emitLoad(fn, index, source), + Y: vOne, + } + incr.setType(tInt) + emitStore(fn, index, fn.emit(incr, source), source) + + body := fn.newBasicBlock("rangeindex.body") + done = fn.newBasicBlock("rangeindex.done") + emitIf(fn, emitCompare(fn, token.LSS, incr, length, source), body, done, source) + fn.currentBlock = body + + k = emitLoad(fn, index, source) + if tv != nil { + switch t := typeutil.CoreType(x.Type()).(type) { + case *types.Array: + instr := &Index{ + X: x, + Index: k, + } + instr.setType(t.Elem()) + v = fn.emit(instr, source) + + case *types.Pointer: // *array + instr := &IndexAddr{ + X: x, + Index: k, + } + instr.setType(types.NewPointer(t.Elem().Underlying().(*types.Array).Elem())) + v = emitLoad(fn, fn.emit(instr, source), source) + + case *types.Slice: + instr := &IndexAddr{ + X: x, + Index: k, + } + instr.setType(types.NewPointer(t.Elem())) + v = emitLoad(fn, fn.emit(instr, source), source) + + default: + panic("rangeIndexed x:" + t.String()) + } + } + return +} + +// rangeIter emits to fn the header for a loop using +// Range/Next/Extract to iterate over map or string value x. +// tk and tv are the types of the key/value results k and v, or nil +// if the respective component is not wanted. +func (b *builder) rangeIter(fn *Function, x Value, tk, tv types.Type, source ast.Node) (k, v Value, loop, done *BasicBlock) { + // + // it = range x + // loop: (target of continue) + // okv = next it (ok, key, value) + // ok = extract okv #0 + // if ok goto body else done + // body: + // k = extract okv #1 + // v = extract okv #2 + // ...body... + // jump loop + // done: (target of break) + // + + var ak, av types.Type + isString := false + if m, ok := typeutil.CoreType(x.Type()).(*types.Map); ok { + ak, av = m.Key(), m.Elem() + } else { + isString = true + ak, av = tInt, tRune + } + if tk == nil { + ak = tInvalid + } + if tv == nil { + av = tInvalid + } + + rng := &Range{X: x} + rng.setType(typeutil.NewIterator(types.NewTuple( + varOk, + newVar("k", ak), + newVar("v", av), + ))) + it := fn.emit(rng, source) + + loop = fn.newBasicBlock("rangeiter.loop") + emitJump(fn, loop, source) + fn.currentBlock = loop + + okv := &Next{ + Iter: it, + IsString: isString, + } + okv.setType(rng.typ.(*typeutil.Iterator).Elem()) + fn.emit(okv, source) + + body := fn.newBasicBlock("rangeiter.body") + done = fn.newBasicBlock("rangeiter.done") + emitIf(fn, emitExtract(fn, okv, 0, source), body, done, source) + fn.currentBlock = body + + // The assignment may widen a map or string + // key/value to a variable's interface type + // (cases #1 and #2 of go.dev/issue/78110). + if tk != nil { + k = emitConv(fn, emitExtract(fn, okv, 1, source), tk, source) + } + if tv != nil { + v = emitConv(fn, emitExtract(fn, okv, 2, source), tv, source) + } + return +} + +// rangeChan emits to fn the header for a loop that receives from +// channel x until it fails. +// tk is the channel's element type, or nil if the k result is +// not wanted +// pos is the position of the '=' or ':=' token. +func (b *builder) rangeChan(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) { + // + // loop: (target of continue) + // ko = <-x (key, ok) + // ok = extract ko #1 + // if ok goto body else done + // body: + // k = extract ko #0 + // ... + // goto loop + // done: (target of break) + + loop = fn.newBasicBlock("rangechan.loop") + emitJump(fn, loop, source) + fn.currentBlock = loop + + retv := emitRecv(fn, x, true, types.NewTuple(newVar("k", typeutil.CoreType(x.Type()).(*types.Chan).Elem()), varOk), source) + + body := fn.newBasicBlock("rangechan.body") + done = fn.newBasicBlock("rangechan.done") + emitIf(fn, emitExtract(fn, retv, 1, source), body, done, source) + fn.currentBlock = body + if tk != nil { + k = emitExtract(fn, retv, 0, source) + } + return +} + +// rangeInt emits to fn the header for a range loop with an integer operand. +// tk is the key value's type, or nil if the k result is not wanted. +// pos is the position of the "for" token. +func (b *builder) rangeInt(fn *Function, x Value, tk types.Type, source ast.Node) (k Value, loop, done *BasicBlock) { + // + // iter = 0 + // if 0 < x goto body else done + // loop: (target of continue) + // iter++ + // if iter < x goto body else done + // body: + // k = x + // ...body... + // jump loop + // done: (target of break) + + if b, ok := x.Type().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { + x = emitConv(fn, x, tInt, source) + } + + T := x.Type() + iter := emitLocal(fn, T, source, "rangeint.iter") + // x may be unsigned. Avoid initializing x to -1. + + body := fn.newBasicBlock("rangeint.body") + done = fn.newBasicBlock("rangeint.done") + emitIf(fn, emitCompare(fn, token.LSS, zeroConst(T, source), x, source), body, done, source) + + loop = fn.newBasicBlock("rangeint.loop") + fn.currentBlock = loop + + incr := &BinOp{ + Op: token.ADD, + X: emitLoad(fn, iter, source), + Y: emitConv(fn, intConst(1, source), T, source), + } + incr.setType(T) + emitStore(fn, iter, fn.emit(incr, source), source) + emitIf(fn, emitCompare(fn, token.LSS, incr, x, source), body, done, source) + fn.currentBlock = body + + if tk != nil { + // Integer types (int, uint8, etc.) are named and + // we know that k is assignable to x when tk != nil. + // This implies tk and T are identical so no conversion is needed. + k = emitLoad(fn, iter, source) + } + + return +} + +// rangeStmt emits to fn code for the range statement s, optionally +// labelled by label. +func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock, source ast.Node) { + var tk, tv types.Type + if s.Key != nil && !isBlankIdent(s.Key) { + tk = fn.typeOf(s.Key) + } + if s.Value != nil && !isBlankIdent(s.Value) { + tv = fn.typeOf(s.Value) + } + + // create locals for s.Key and s.Value + createVars := func() { + // Unlike a short variable declaration, a RangeStmt + // using := never redeclares an existing variable; it + // always creates a new one. + if tk != nil { + id := s.Key.(*ast.Ident) + emitLocalVar(fn, identVar(fn, id), id) + } + if tv != nil { + id := s.Value.(*ast.Ident) + emitLocalVar(fn, identVar(fn, id), id) + } + } + + afterGo122 := versions.AtLeast(fn.goversion, versions.Go1_22) + if s.Tok == token.DEFINE && !afterGo122 { + // pre-go1.22: If iteration variables are defined (:=), this + // occurs once outside the loop. + createVars() + } + + x := b.expr(fn, s.X) + + var k, v Value + var loop, done *BasicBlock + switch rt := typeutil.CoreType(x.Type()).(type) { + case *types.Slice, *types.Array, *types.Pointer: // *array + k, v, loop, done = b.rangeIndexed(fn, x, tv, source) + + case *types.Chan: + k, loop, done = b.rangeChan(fn, x, tk, source) + + case *types.Map: + k, v, loop, done = b.rangeIter(fn, x, tk, tv, source) + + case *types.Basic: + switch { + case rt.Info()&types.IsString != 0: + k, v, loop, done = b.rangeIter(fn, x, tk, tv, source) + + case rt.Info()&types.IsInteger != 0: + k, loop, done = b.rangeInt(fn, x, tk, source) + + default: + panic("Cannot range over basic type: " + rt.String()) + } + + case *types.Signature: + // Special case rewrite (fn.goversion >= go1.23): + // for x := range f { ... } + // into + // f(func(x T) bool { ... }) + b.rangeFunc(fn, x, s, label) + return + + default: + panic("Cannot range over: " + rt.String()) + } + + if s.Tok == token.DEFINE && afterGo122 { + // go1.22: If iteration variables are defined (:=), this occurs inside the loop. + createVars() + } + + // Evaluate both LHS expressions before we update either. + var kl, vl lvalue + if tk != nil { + kl = b.addr(fn, s.Key, false) // non-escaping + } + if tv != nil { + vl = b.addr(fn, s.Value, false) // non-escaping + } + if tk != nil { + kl.store(fn, k, s) + } + if tv != nil { + vl.store(fn, v, s) + } + + if label != nil { + label._break = done + label._continue = loop + } + + fn.targets = &targets{ + tail: fn.targets, + _break: done, + _continue: loop, + } + b.stmt(fn, s.Body) + fn.targets = fn.targets.tail + emitJump(fn, loop, source) // back-edge + fn.currentBlock = done +} + +// rangeFunc emits to fn code for the range-over-func rng.Body of the iterator +// function x, optionally labelled by label. It creates a new anonymous function +// yield for rng and builds the function. +func (b *builder) rangeFunc(fn *Function, x Value, rng *ast.RangeStmt, label *lblock) { + // Consider the SSA code for the outermost range-over-func in fn: + // + // func fn(...) (ret R) { + // ... + // for k, v = range x { + // ... + // } + // ... + // } + // + // The code emitted into fn will look something like this. + // + // loop: + // jump := READY + // y := make closure yield [ret, deferstack, jump, k, v] + // x(y) + // switch jump { + // [see resuming execution] + // } + // goto done + // done: + // ... + // + // where yield is a new synthetic yield function: + // + // func yield(_k tk, _v tv) bool + // free variables: [ret, stack, jump, k, v] + // { + // entry: + // if jump != READY then goto invalid else valid + // invalid: + // panic("iterator called when it is not in a ready state") + // valid: + // jump = BUSY + // k = _k + // v = _v + // ... + // cont: + // jump = READY + // return true + // } + // + // Yield state: + // + // Each range loop has an associated jump variable that records + // the state of the iterator. A yield function is initially + // in a READY (0) and callable state. If the yield function is called + // and is not in READY state, it panics. When it is called in a callable + // state, it becomes BUSY. When execution reaches the end of the body + // of the loop (or a continue statement targeting the loop is executed), + // the yield function returns true and resumes being in a READY state. + // After the iterator function x(y) returns, then if the yield function + // is in a READY state, the yield enters the DONE state. + // + // Each lowered control statement (break X, continue X, goto Z, or return) + // that exits the loop sets the variable to a unique positive EXIT value, + // before returning false from the yield function. + // + // If the yield function returns abruptly due to a panic or GoExit, + // it remains in a BUSY state. The generated code asserts that, after + // the iterator call x(y) returns normally, the jump variable state + // is DONE. + // + // Resuming execution: + // + // The code generated for the range statement checks the jump + // variable to determine how to resume execution. + // + // switch jump { + // case BUSY: panic("...") + // case DONE: goto done + // case READY: state = DONE; goto done + // case 123: ... // action for exit 123. + // case 456: ... // action for exit 456. + // ... + // } + // + // Forward goto statements within a yield are jumps to labels that + // have not yet been traversed in fn. They may be in the Body of the + // function. What we emit for these is: + // + // goto target + // target: + // ... + // + // We leave an unresolved exit in yield.exits to check at the end + // of building yield if it encountered target in the body. If it + // encountered target, no additional work is required. Otherwise, + // the yield emits a new early exit in the basic block for target. + // We expect that blockopt will fuse the early exit into the case + // block later. The unresolved exit is then added to yield.parent.exits. + + loop := fn.newBasicBlock("rangefunc.loop") + done := fn.newBasicBlock("rangefunc.done") + + // These are targets within y. + fn.targets = &targets{ + tail: fn.targets, + _break: done, + // _continue is within y. + } + if label != nil { + label._break = done + // _continue is within y + } + + emitJump(fn, loop, nil) + fn.currentBlock = loop + + // loop: + // jump := READY + + anonIdx := len(fn.AnonFuncs) + + jump := newVar(fmt.Sprintf("jump$%d", anonIdx+1), tInt) + emitLocalVar(fn, jump, nil) // zero value is READY + + xsig := typeutil.CoreType(x.Type()).(*types.Signature) + ysig := typeutil.CoreType(xsig.Params().At(0).Type()).(*types.Signature) + + /* synthetic yield function for body of range-over-func loop */ + y := &Function{ + name: fmt.Sprintf("%s$%d", fn.Name(), anonIdx+1), + Signature: ysig, + Synthetic: "range-over-func yield", + pos: rng.Range, + parent: fn, + anonIdx: int32(len(fn.AnonFuncs)), + Pkg: fn.Pkg, + Prog: fn.Prog, + syntax: rng, + info: fn.info, + build: (*builder).buildYieldFunc, + topLevelOrigin: nil, + typeparams: fn.typeparams, + typeargs: fn.typeargs, + subst: fn.subst, + } + y.goversion = fn.goversion + y.jump = jump + y.deferstack = fn.deferstack + y.returnVars = fn.returnVars // use the parent's return variables + y.uniq = fn.uniq // start from parent's unique values + + // If the RangeStmt has a label, this is how it is passed to buildYieldFunc. + if label != nil { + y.lblocks = map[*types.Label]*lblock{label.label: nil} + } + fn.AnonFuncs = append(fn.AnonFuncs, y) + + // Build y immediately. It may: + // * cause fn's locals to escape, and + // * create new exit nodes in exits. + // (y is not marked 'built' until the end of the enclosing FuncDecl.) + unresolved := len(fn.exits) + y.build(b, y) + fn.uniq = y.uniq // resume after y's unique values + + // Emit the call of y. + // c := MakeClosure y + // x(c) + c := &MakeClosure{Fn: y} + c.setType(ysig) + c.comment = "yield" + for _, fv := range y.FreeVars { + c.Bindings = append(c.Bindings, fv.outer) + fv.outer = nil + } + fn.emit(c, nil) + call := Call{ + Call: CallCommon{ + Value: x, + Args: []Value{c}, + }, + } + call.setType(xsig.Results()) + fn.emit(&call, nil) + + exits := fn.exits[unresolved:] + b.buildYieldResume(fn, jump, exits, done) + + fn.currentBlock = done + // pop the stack for the range-over-func + fn.targets = fn.targets.tail +} + +// buildYieldResume emits to fn code for how to resume execution once a call to +// the iterator function over the yield function returns x(y). It does this by building +// a switch over the value of jump for when it is READY, BUSY, or EXIT(id). +func (b *builder) buildYieldResume(fn *Function, jump *types.Var, exits []*exit, done *BasicBlock) { + // v := *jump + // switch v { + // case BUSY: panic("...") + // case READY: jump = DONE; goto done + // case EXIT(a): ... + // case EXIT(b): ... + // ... + // } + v := emitLoad(fn, fn.lookup(jump, false), nil) + + entry := fn.currentBlock + bodies := make([]*BasicBlock, 2, 2+len(exits)) + bodies[0] = fn.newBasicBlock("rangefunc.resume.busy") + bodies[1] = fn.newBasicBlock("rangefunc.resume.ready") + + conds := make([]Value, 2, 2+len(exits)) + conds[0] = jBusy + conds[1] = jReady + + fn.currentBlock = bodies[0] + fn.emit( + &Panic{ + X: emitConv(fn, jDroppedPanic, tEface, nil), + }, + nil, + ) + + fn.currentBlock = bodies[1] + storeVar(fn, jump, jDone, nil) + emitJump(fn, done, nil) + + for _, e := range exits { + body := fn.newBasicBlock(fmt.Sprintf("rangefunc.resume.exit.%d", e.id)) + bodies = append(bodies, body) + id := intConst(e.id, nil) + conds = append(conds, id) + + fn.currentBlock = body + switch { + case e.label != nil: // forward goto? + // case EXIT(id): goto lb // label + lb := fn.lblockOf(e.label) + // Do not mark lb as resolved. + // If fn does not contain label, lb remains unresolved and + // fn must itself be a range-over-func function. lb will be: + // lb: + // fn.jump = id + // return false + emitJump(fn, lb._goto, e.source) + + case e.to != fn: // e jumps to an ancestor of fn? + // case EXIT(id): { fn.jump = id; return false } + // fn is a range-over-func function. + + storeVar(fn, fn.jump, id, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) + + case e.block == nil && e.label == nil: // return from fn? + // case EXIT(id): { return ... } + fn.emit(new(RunDefers), e.source) + results := make([]Value, len(fn.results)) + for i, r := range fn.results { + results[i] = emitLoad(fn, r, e.source) + } + fn.emit(&Return{Results: results}, e.source) + + case e.block != nil: + // case EXIT(id): goto block + emitJump(fn, e.block, e.source) + + default: + panic("unreachable") + } + + } + + fn.currentBlock = entry + // Note that this switch does not have an implicit default case. This wouldn't be + // valid for a user-provided switch statement, but for range-over-func we know all + // possible values and we can avoid the impossible branch. + swtch := &ConstantSwitch{ + Tag: v, + Conds: conds, + } + fn.emit(swtch, nil) + for _, body := range bodies { + addEdge(entry, body) + } +} + +// stmt lowers statement s to IR form, emitting code to fn. +func (b *builder) stmt(fn *Function, _s ast.Stmt) { + // The label of the current statement. If non-nil, its _goto + // target is always set; its _break and _continue are set only + // within the body of switch/typeswitch/select/for/range. + // It is effectively an additional default-nil parameter of stmt(). + var label *lblock +start: + switch s := _s.(type) { + case *ast.EmptyStmt: + // ignore. (Usually removed by gofmt.) + + case *ast.DeclStmt: // Con, Var or Typ + d := s.Decl.(*ast.GenDecl) + if d.Tok == token.VAR { + for _, spec := range d.Specs { + if vs, ok := spec.(*ast.ValueSpec); ok { + b.localValueSpec(fn, vs) + } + } + } + + case *ast.LabeledStmt: + if s.Label.Name == "_" { + // Blank labels can't be the target of a goto, break, + // or continue statement, so we don't need a new block. + _s = s.Stmt + goto start + } + label = fn.lblockOf(fn.label(s.Label)) + label.resolved = true + emitJump(fn, label._goto, s) + fn.currentBlock = label._goto + _s = s.Stmt + goto start // effectively: tailcall stmt(fn, s.Stmt, label) + + case *ast.ExprStmt: + b.expr(fn, s.X) + + case *ast.SendStmt: + instr := &Send{ + Chan: b.expr(fn, s.Chan), + X: emitConv(fn, b.expr(fn, s.Value), + typeutil.CoreType(fn.typeOf(s.Chan)).(*types.Chan).Elem(), s), + } + fn.emit(instr, s) + + case *ast.IncDecStmt: + op := token.ADD + if s.Tok == token.DEC { + op = token.SUB + } + loc := b.addr(fn, s.X, false) + b.assignOp(fn, loc, NewConst(constant.MakeInt64(1), loc.typ(), s), op, s) + + case *ast.AssignStmt: + switch s.Tok { + case token.ASSIGN, token.DEFINE: + b.assignStmt(fn, s.Lhs, s.Rhs, s.Tok == token.DEFINE, _s) + + default: // +=, etc. + op := s.Tok + token.ADD - token.ADD_ASSIGN + b.assignOp(fn, b.addr(fn, s.Lhs[0], false), b.expr(fn, s.Rhs[0]), op, s) + } + + case *ast.GoStmt: + // The "intrinsics" new/make/len/cap are forbidden here. + // panic is treated like an ordinary function call. + v := Go{} + b.setCall(fn, s.Call, &v.Call) + fn.emit(&v, s) + + case *ast.DeferStmt: + // The "intrinsics" new/make/len/cap are forbidden here. + // panic is treated like an ordinary function call. + deferstack := emitLoad(fn, fn.lookup(fn.deferstack, false), s) + v := Defer{DeferStack: deferstack} + b.setCall(fn, s.Call, &v.Call) + fn.emit(&v, s) + + // A deferred call can cause recovery from panic, + // and control resumes at the Recover block. + createRecoverBlock(fn.source) + + case *ast.ReturnStmt: + b.returnStmt(fn, s) + + case *ast.BranchStmt: + b.branchStmt(fn, s) + + case *ast.BlockStmt: + b.stmtList(fn, s.List) + + case *ast.IfStmt: + if s.Init != nil { + b.stmt(fn, s.Init) + } + then := fn.newBasicBlock("if.then") + done := fn.newBasicBlock("if.done") + els := done + if s.Else != nil { + els = fn.newBasicBlock("if.else") + } + instr := b.cond(fn, s.Cond, then, els) + instr.source = s + fn.currentBlock = then + b.stmt(fn, s.Body) + emitJump(fn, done, s) + + if s.Else != nil { + fn.currentBlock = els + b.stmt(fn, s.Else) + emitJump(fn, done, s) + } + + fn.currentBlock = done + + case *ast.SwitchStmt: + b.switchStmt(fn, s, label) + + case *ast.TypeSwitchStmt: + b.typeSwitchStmt(fn, s, label) + + case *ast.SelectStmt: + if b.selectStmt(fn, s, label) { + // the select has no cases, it blocks forever + fn.currentBlock = fn.newBasicBlock("unreachable") + } + + case *ast.ForStmt: + b.forStmt(fn, s, label) + + case *ast.RangeStmt: + b.rangeStmt(fn, s, label, s) + + default: + panic(fmt.Sprintf("unexpected statement kind: %T", s)) + } +} + +func (b *builder) branchStmt(fn *Function, s *ast.BranchStmt) { + var block *BasicBlock + if s.Label == nil { + block = targetedBlock(fn, s.Tok) + } else { + target := fn.label(s.Label) + block = labelledBlock(fn, target, s.Tok) + if block == nil { // forward goto + lb := fn.lblockOf(target) + block = lb._goto // jump to lb._goto + if fn.jump != nil { + // fn is a range-over-func and the goto may exit fn. + // Create an exit and resolve it at the end of + // builder.buildYieldFunc. + labelExit(fn, target, s) + } + } + } + to := block.parent + + if to == fn { + emitJump(fn, block, s) + } else { // break outside of fn. + // fn must be a range-over-func + e := blockExit(fn, block, s) + id := intConst(e.id, s) + storeVar(fn, fn.jump, id, s) + vFalse := NewConst(constant.MakeBool(false), tBool, s) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) + } + fn.currentBlock = fn.newBasicBlock("unreachable") +} + +func (b *builder) returnStmt(fn *Function, s *ast.ReturnStmt) { + // TODO(dh): we could emit tighter position information by + // using the ith returned expression + + var results []Value + + sig := fn.source.Signature // signature of the enclosing source function + + // Convert return operands to result type. + if len(s.Results) == 1 && sig.Results().Len() > 1 { + // Return of one expression in a multi-valued function. + tuple := b.exprN(fn, s.Results[0]) + ttuple := tuple.Type().(*types.Tuple) + for i, n := 0, ttuple.Len(); i < n; i++ { + results = append(results, + emitConv(fn, emitExtract(fn, tuple, i, s), + sig.Results().At(i).Type(), s)) + } + } else { + // 1:1 return, or no-arg return in non-void function. + for i, r := range s.Results { + v := emitConv(fn, b.expr(fn, r), sig.Results().At(i).Type(), s) + results = append(results, v) + } + } + + // Store the results. + for i, r := range results { + var result Value // fn.sourceFn.result[i] conceptually + if fn == fn.source { + result = fn.results[i] + } else { // lookup needed? + result = fn.lookup(fn.returnVars[i], false) + } + emitStore(fn, result, r, s) + } + + if fn.jump != nil { + // Return from body of a range-over-func. + // The return statement is syntactically within the loop, + // but the generated code is in the 'switch jump {...}' after it. + e := returnExit(fn, s) + id := intConst(e.id, e.source) + storeVar(fn, fn.jump, id, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) + fn.currentBlock = fn.newBasicBlock("unreachable") + return + } + + // Run function calls deferred in this + // function when explicitly returning from it. + fn.emit(new(RunDefers), s) + // Reload (potentially) named result variables to form the result tuple. + results = results[:0] + for _, nr := range fn.results { + results = append(results, emitLoad(fn, nr, s)) + } + + fn.emit(&Return{Results: results}, s) + fn.currentBlock = fn.newBasicBlock("unreachable") +} + +// A buildFunc is a strategy for building the SSA body for a function. +type buildFunc = func(*builder, *Function) + +// iterate causes all created but unbuilt functions to be built. As +// this may create new methods, the process is iterated until it +// converges. +// +// Waits for any dependencies to finish building. +func (b *builder) iterate() { + for ; b.finished < len(b.fns); b.finished++ { + fn := b.fns[b.finished] + b.buildFunction(fn) + } + + b.buildshared.markDone() + b.buildshared.wait() +} + +// buildFunction builds IR code for the body of function fn. Idempotent. +func (b *builder) buildFunction(fn *Function) { + if fn.build != nil { + assert(fn.parent == nil, "anonymous functions should not be built by buildFunction()") + + if fn.Prog.mode&LogSource != 0 { + defer logStack("build %s @ %s", fn, fn.Prog.Fset.Position(fn.pos))() + } + fn.build(b, fn) + fn.done() + } +} + +// buildParamsOnly builds fn.Params from fn.Signature, but does not build fn.Body. +func (b *builder) buildParamsOnly(fn *Function) { + // For external (C, asm) functions or functions loaded from + // export data, we must set fn.Params even though there is no + // body code to reference them. + if recv := fn.Signature.Recv(); recv != nil { + // TODO(dh): should we synthesize a node so we have position info? + fn.addParamVar(recv, nil) + } + params := fn.Signature.Params() + for i, n := 0, params.Len(); i < n; i++ { + // TODO(dh): should we synthesize a node so we have position info? + fn.addParamVar(params.At(i), nil) + } + + // clear out other function state (keep consistent with finishBody) + fn.subst = nil +} + +// buildFromSyntax builds fn.Body from fn.syntax, which must be non-nil. +func (b *builder) buildFromSyntax(fn *Function) { + var ( + recvField *ast.FieldList + body *ast.BlockStmt + functype *ast.FuncType + ) + switch syntax := fn.syntax.(type) { + case *ast.FuncDecl: + functype = syntax.Type + recvField = syntax.Recv + body = syntax.Body + if body == nil { + b.buildParamsOnly(fn) // no body (non-Go function) + return + } + case *ast.FuncLit: + functype = syntax.Type + body = syntax.Body + case nil: + panic("no syntax") + default: + panic(syntax) // unexpected syntax + } + fn.source = fn + fn.startBody() + fn.createSyntacticParams(recvField, functype) + fn.createDeferStack() + b.stmt(fn, body) + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { + // Control fell off the end of the function's body block. + // + // Block optimizations eliminate the current block, if + // unreachable. It is a builder invariant that + // if this no-arg return is ill-typed for + // fn.Signature.Results, this block must be + // unreachable. The sanity checker checks this. + fn.emit(new(RunDefers), nil) + fn.emit(new(Return), nil) + } + fn.finishBody() +} + +// buildYieldFunc builds the body of the yield function created +// from a range-over-func *ast.RangeStmt. +func (b *builder) buildYieldFunc(fn *Function) { + // See builder.rangeFunc for detailed documentation on how fn is set up. + // + // In pseudo-Go this roughly builds: + // func yield(_k tk, _v tv) bool { + // if jump != READY { panic("yield function called after range loop exit") } + // jump = BUSY + // k, v = _k, _v // assign the iterator variable (if needed) + // ... // rng.Body + // continue: + // jump = READY + // return true + // } + s := fn.syntax.(*ast.RangeStmt) + fn.source = fn.parent.source + fn.startBody() + params := fn.Signature.Params() + for v := range params.Variables() { + fn.addParamVar(v, nil) + } + + // Initial targets + ycont := fn.newBasicBlock("yield-continue") + // lblocks is either {} or is {label: nil} where label is the label of syntax. + for label := range fn.lblocks { + fn.lblocks[label] = &lblock{ + label: label, + resolved: true, + _goto: ycont, + _continue: ycont, + // `break label` statement targets fn.parent.targets._break + } + } + fn.targets = &targets{ + tail: fn.targets, + _continue: ycont, + // `break` statement targets fn.parent.targets._break. + } + + // continue: + // jump = READY + // return true + saved := fn.currentBlock + fn.currentBlock = ycont + storeVar(fn, fn.jump, jReady, s.Body) + // A yield function's own deferstack is always empty, so rundefers is not needed. + fn.emit(&Return{Results: []Value{vTrue}}, nil) + + // Emit header: + // + // if jump != READY { panic("yield iterator accessed after exit") } + // jump = BUSY + // k, v = _k, _v + fn.currentBlock = saved + yloop := fn.newBasicBlock("yield-loop") + invalid := fn.newBasicBlock("yield-invalid") + + jumpVal := emitLoad(fn, fn.lookup(fn.jump, true), nil) + emitIf(fn, emitCompare(fn, token.EQL, jumpVal, jReady, nil), yloop, invalid, nil) + fn.currentBlock = invalid + fn.emit( + &Panic{ + X: emitConv(fn, jLateYield, tEface, nil), + }, + nil, + ) + + fn.currentBlock = yloop + storeVar(fn, fn.jump, jBusy, s.Body) + + // Initialize k and v from params. + var tk, tv types.Type + if s.Key != nil && !isBlankIdent(s.Key) { + tk = fn.typeOf(s.Key) // fn.parent.typeOf is identical + } + if s.Value != nil && !isBlankIdent(s.Value) { + tv = fn.typeOf(s.Value) + } + if s.Tok == token.DEFINE { + if tk != nil { + emitLocalVar(fn, identVar(fn, s.Key.(*ast.Ident)), s.Key) + } + if tv != nil { + emitLocalVar(fn, identVar(fn, s.Value.(*ast.Ident)), s.Value) + } + } + var k, v Value + if len(fn.Params) > 0 { + k = fn.Params[0] + } + if len(fn.Params) > 1 { + v = fn.Params[1] + } + var kl, vl lvalue + if tk != nil { + kl = b.addr(fn, s.Key, false) // non-escaping + } + if tv != nil { + vl = b.addr(fn, s.Value, false) // non-escaping + } + if tk != nil { + kl.store(fn, k, s.Key) + } + if tv != nil { + vl.store(fn, v, s.Value) + } + + // Build the body of the range loop. + b.stmt(fn, s.Body) + if cb := fn.currentBlock; cb != nil && (cb == fn.Blocks[0] || cb == fn.Recover || cb.Preds != nil) { + // Control fell off the end of the function's body block. + // Block optimizations eliminate the current block, if + // unreachable. + emitJump(fn, ycont, nil) + } + // pop the stack for the yield function + fn.targets = fn.targets.tail + + // Clean up exits and promote any unresolved exits to fn.parent. + for _, e := range fn.exits { + if e.label != nil { + lb := fn.lblocks[e.label] + if lb.resolved { + // label was resolved. Do not turn lb into an exit. + // e does not need to be handled by the parent. + continue + } + + // _goto becomes an exit. + // _goto: + // jump = id + // return false + fn.currentBlock = lb._goto + id := intConst(e.id, e.source) + storeVar(fn, fn.jump, id, e.source) + vFalse := NewConst(constant.MakeBool(false), tBool, e.source) + fn.emit(&Return{Results: []Value{vFalse}}, e.source) + } + + if e.to != fn { // e needs to be handled by the parent too. + fn.parent.exits = append(fn.parent.exits, e) + } + } + + fn.finishBody() +} + +// addMakeInterfaceType records non-interface type t as the type of +// the operand a MakeInterface operation, for [Program.RuntimeTypes]. +// +// Acquires prog.makeInterfaceTypesMu. +func addMakeInterfaceType(prog *Program, t types.Type) { + prog.makeInterfaceTypesMu.Lock() + defer prog.makeInterfaceTypesMu.Unlock() + if prog.makeInterfaceTypes == nil { + prog.makeInterfaceTypes = make(map[types.Type]unit) + } + prog.makeInterfaceTypes[t] = unit{} +} + +// Build calls Package.Build for each package in prog. +// Building occurs in parallel unless the BuildSerially mode flag was set. +// +// Build is intended for whole-program analysis; a typical compiler +// need only build a single package. +// +// Build is idempotent and thread-safe. +func (prog *Program) Build() { + var wg sync.WaitGroup + for _, p := range prog.packages { + if prog.mode&BuildSerially != 0 { + p.Build() + } else { + wg.Add(1) + cpuLimit <- unit{} // acquire a token + go func(p *Package) { + p.Build() + wg.Done() + <-cpuLimit // release a token + }(p) + } + } + wg.Wait() +} + +// cpuLimit is a counting semaphore to limit CPU parallelism. +var cpuLimit = make(chan unit, runtime.GOMAXPROCS(0)) + +// Build builds IR code for all functions and vars in package p. +// +// CreatePackage must have been called for all of p's direct imports +// (and hence its direct imports must have been error-free). It is not +// necessary to call CreatePackage for indirect dependencies. +// Functions will be created for all necessary methods in those +// packages on demand. +// +// Build is idempotent and thread-safe. +func (p *Package) Build() { p.buildOnce.Do(p.build) } + +func (p *Package) build() { + if p.info == nil { + return // synthetic package, e.g. "testmain" + } + if p.Prog.mode&LogSource != 0 { + defer logStack("build %s", p)() + } + + b := builder{fns: p.created} + b.iterate() + + // We no longer need transient information: ASTs or go/types deductions. + p.info = nil + p.created = nil + p.files = nil + p.initVersion = nil + + if p.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckPackage(p) + } +} + +// buildPackageInit builds fn.Body for the synthetic package initializer. +func (b *builder) buildPackageInit(init *Function) { + p := init.Pkg + init.startBody() + + var done *BasicBlock + + if p.Prog.mode&BareInits == 0 { + // Make init() skip if package is already initialized. + initguard := p.Var("init$guard") + doinit := init.newBasicBlock("init.start") + done = init.newBasicBlock("init.done") + emitIf(init, emitLoad(init, initguard, nil), done, doinit, nil) + init.currentBlock = doinit + emitStore(init, initguard, vTrue, nil) + + // Call the init() function of each package we import. + for _, pkg := range p.Pkg.Imports() { + prereq := p.Prog.packages[pkg] + if prereq == nil { + panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Pkg.Path(), pkg.Path())) + } + var v Call + v.Call.Value = prereq.init + v.setType(types.NewTuple()) + init.emit(&v, nil) + } + } + + // Initialize package-level vars in correct order. + if len(p.info.InitOrder) > 0 && len(p.files) == 0 { + panic("no source files provided for package. cannot initialize globals") + } + + for _, varinit := range p.info.InitOrder { + if init.Prog.mode&LogSource != 0 { + fmt.Fprintf(os.Stderr, "build global initializer %v @ %s\n", + varinit.Lhs, p.Prog.Fset.Position(varinit.Rhs.Pos())) + } + // Initializers for global vars are evaluated in dependency + // order, but may come from arbitrary files of the package + // with different versions, so we transiently update + // init.goversion for each one. (Since init is a synthetic + // function it has no syntax of its own that needs a version.) + init.goversion = p.initVersion[varinit.Rhs] + if len(varinit.Lhs) == 1 { + // 1:1 initialization: var x, y = a(), b() + var lval lvalue + if v := varinit.Lhs[0]; v.Name() != "_" { + lval = &address{addr: p.values[v].(*Global)} + } else { + lval = blank{} + } + // TODO(dh): do emit position information + b.assign(init, lval, varinit.Rhs, true, nil, nil) + } else { + // n:1 initialization: var x, y := f() + tuple := b.exprN(init, varinit.Rhs) + for i, v := range varinit.Lhs { + if v.Name() == "_" { + continue + } + emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i, nil), nil) + } + } + } + + // The rest of the init function is synthetic: + // no syntax, info, goversion. + init.info = nil + init.goversion = "" + + // Call all of the declared init() functions in source order. + for _, file := range p.files { + for _, decl := range file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok { + id := decl.Name + if !isBlankIdent(id) && id.Name == "init" && decl.Recv == nil { + declaredInit := p.values[p.info.Defs[id]].(*Function) + var v Call + v.Call.Value = declaredInit + v.setType(types.NewTuple()) + p.init.emit(&v, nil) + } + } + } + } + + // Finish up init(). + if p.Prog.mode&BareInits == 0 { + emitJump(init, done, nil) + init.currentBlock = done + } + init.emit(new(Return), nil) + init.finishBody() +} diff --git a/vendor/honnef.co/go/tools/go/ir/const.go b/vendor/honnef.co/go/tools/go/ir/const.go new file mode 100644 index 0000000..399eb7a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/const.go @@ -0,0 +1,241 @@ +// Copyright 2013 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 ir + +// This file defines the Const SSA value type. + +import ( + "bytes" + "fmt" + "go/ast" + "go/constant" + "go/types" + "strconv" + + "golang.org/x/exp/typeparams" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" +) + +// soleTypeKind returns a BasicInfo for which constant.Value can +// represent all zero values for the types in the type set. +// +// types.IsBoolean for false is a representative. +// types.IsInteger for 0 +// types.IsString for "" +// 0 otherwise. +func soleTypeKind(typ types.Type) types.BasicInfo { + // State records the set of possible zero values (false, 0, ""). + // Candidates (perhaps all) are eliminated during the type-set + // iteration, which executes at least once. + state := types.IsBoolean | types.IsInteger | types.IsString + underIs(typ, func(ut types.Type) bool { + var c types.BasicInfo + if t, ok := ut.(*types.Basic); ok { + c = t.Info() + } + if c&types.IsNumeric != 0 { // int/float/complex + c = types.IsInteger + } + state = state & c + return state != 0 + }) + return state +} + +// NewConst returns a new constant of the specified value and type. +// val must be valid according to the specification of Const.Value. +func NewConst(val constant.Value, typ types.Type, source ast.Node) *Const { + if val == nil { + switch soleTypeKind(typ) { + case types.IsBoolean: + val = constant.MakeBool(false) + case types.IsInteger: + val = constant.MakeInt64(0) + case types.IsString: + val = constant.MakeString("") + } + } + c := &Const{ + typ: typ, + Value: val, + } + c.setSource(source) + return c +} + +// intConst returns an 'int' constant that evaluates to i. +// (i is an int64 in case the host is narrower than the target.) +func intConst(i int64, source ast.Node) *Const { + return NewConst(constant.MakeInt64(i), tInt, source) +} + +// nilConst returns a nil constant of the specified type, which may +// be any reference type, including interfaces. +func nilConst(typ types.Type, source ast.Node) *Const { + return NewConst(nil, typ, source) +} + +// stringConst returns a 'string' constant that evaluates to s. +func stringConst(s string, source ast.Node) *Const { + return NewConst(constant.MakeString(s), tString, source) +} + +// zeroConst returns a new "zero" constant of the specified type. +func zeroConst(t types.Type, source ast.Node) Constant { + return NewConst(nil, t, source) +} + +func (c *Const) RelString(from *types.Package) string { + var p string + if c.Value == nil { + switch c.typ.(type) { + case *types.Array, *types.Struct: + p = "{}" + case *types.Alias: + switch c.typ.Underlying().(type) { + case *types.Array, *types.Struct: + p = "{}" + default: + p, _ = typesinternal.ZeroString(types.Unalias(c.typ), types.RelativeTo(from)) + } + default: + p, _ = typesinternal.ZeroString(c.typ, types.RelativeTo(from)) + } + } else if c.Value.Kind() == constant.String { + v := constant.StringVal(c.Value) + const max = 20 + // TODO(adonovan): don't cut a rune in half. + if len(v) > max { + v = v[:max-3] + "..." // abbreviate + } + p = strconv.Quote(v) + } else { + p = c.Value.String() + } + return p + ":" + relType(c.Type(), from) +} + +func (c *Const) Name() string { + return c.RelString(nil) +} + +func (c *Const) String() string { + return c.RelString(nil) +} + +func (c *Const) Type() types.Type { + return c.typ +} + +func (c *Const) Referrers() *[]Instruction { + return nil +} + +func (c *Const) Parent() *Function { return nil } + +func (v *AggregateConst) RelString(pkg *types.Package) string { + var b bytes.Buffer + fmt.Fprint(&b, "const {") + for i, vv := range v.Values { + if i > 0 { + fmt.Fprint(&b, ", ") + } + fmt.Fprint(&b, relName(vv, v)) + } + fmt.Fprint(&b, "}") + return b.String() +} + +func (v *AggregateConst) Name() string { + return v.RelString(nil) +} + +func (v *AggregateConst) String() string { + return v.RelString(nil) +} + +func (v *AggregateConst) Type() types.Type { + return v.typ +} + +func (v *AggregateConst) Referrers() *[]Instruction { + return nil +} + +func (v *AggregateConst) Parent() *Function { return nil } + +// IsNil returns true if this constant represents a typed or untyped nil value. +func (c *Const) IsNil() bool { + return c.Value == nil && nillable(c.typ) +} + +// nillable reports whether *new(T) == nil is legal for type T. +func nillable(t types.Type) bool { + if typeparams.IsTypeParam(t) { + return underIs(t, func(u types.Type) bool { + // empty type set (u==nil) => any underlying types => not nillable + return u != nil && nillable(u) + }) + } + switch t.Underlying().(type) { + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return true + case *types.Interface: + return true // basic interface. + default: + return false + } +} + +// TODO(adonovan): move everything below into honnef.co/go/tools/go/ir/interp. + +// Int64 returns the numeric value of this constant truncated to fit +// a signed 64-bit integer. +func (c *Const) Int64() int64 { + switch x := constant.ToInt(c.Value); x.Kind() { + case constant.Int: + if i, ok := constant.Int64Val(x); ok { + return i + } + return 0 + case constant.Float: + f, _ := constant.Float64Val(x) + return int64(f) + } + panic(fmt.Sprintf("unexpected constant value: %T", c.Value)) +} + +// Uint64 returns the numeric value of this constant truncated to fit +// an unsigned 64-bit integer. +func (c *Const) Uint64() uint64 { + switch x := constant.ToInt(c.Value); x.Kind() { + case constant.Int: + if u, ok := constant.Uint64Val(x); ok { + return u + } + return 0 + case constant.Float: + f, _ := constant.Float64Val(x) + return uint64(f) + } + panic(fmt.Sprintf("unexpected constant value: %T", c.Value)) +} + +// Float64 returns the numeric value of this constant truncated to fit +// a float64. +func (c *Const) Float64() float64 { + x := constant.ToFloat(c.Value) // (c.Value == nil) => x.Kind() == Unknown + f, _ := constant.Float64Val(x) + return f +} + +// Complex128 returns the complex value of this constant truncated to +// fit a complex128. +func (c *Const) Complex128() complex128 { + x := constant.ToComplex(c.Value) // (c.Value == nil) => x.Kind() == Unknown + re, _ := constant.Float64Val(constant.Real(x)) + im, _ := constant.Float64Val(constant.Imag(x)) + return complex(re, im) +} diff --git a/vendor/honnef.co/go/tools/go/ir/create.go b/vendor/honnef.co/go/tools/go/ir/create.go new file mode 100644 index 0000000..f5656e2 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/create.go @@ -0,0 +1,319 @@ +// Copyright 2013 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 ir + +// This file implements the CREATE phase of IR construction. +// See builder.go for explanation. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "os" + "sync" + + "honnef.co/go/tools/internal/xtools-internal/versions" +) + +// NewProgram returns a new IR Program. +// +// mode controls diagnostics and checking during IR construction. +// +// To construct an SSA program: +// +// - Call NewProgram to create an empty Program. +// - Call CreatePackage providing typed syntax for each package +// you want to build, and call it with types but not +// syntax for each of those package's direct dependencies. +// - Call [Package.Build] on each syntax package you wish to build, +// or [Program.Build] to build all of them. +// +// See the Example tests for simple examples. +func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { + return &Program{ + Fset: fset, + imported: make(map[string]*Package), + packages: make(map[*types.Package]*Package), + mode: mode, + canon: newCanonizer(), + ctxt: types.NewContext(), + } +} + +// memberFromObject populates package pkg with a member for the +// typechecker object obj. +// +// For objects from Go source code, syntax is the associated syntax tree +// (for funcs and vars only) and goversion defines the appropriate +// interpretation; they will be used during the build phase. +func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node, goversion string) { + name := obj.Name() + switch obj := obj.(type) { + case *types.Builtin: + if pkg.Pkg != types.Unsafe { + panic("unexpected builtin object: " + obj.String()) + } + + case *types.TypeName: + if name != "_" { + pkg.Members[name] = &Type{ + object: obj, + pkg: pkg, + } + } + + case *types.Const: + c := &NamedConst{ + object: obj, + Value: NewConst(obj.Val(), obj.Type(), syntax), + pkg: pkg, + } + pkg.values[obj] = c + if name != "_" { + pkg.Members[name] = c + } + + case *types.Var: + g := &Global{ + Pkg: pkg, + name: name, + object: obj, + typ: types.NewPointer(obj.Type()), // address + } + g.source = syntax + pkg.values[obj] = g + if name != "_" { + pkg.Members[name] = g + } + + case *types.Func: + sig := obj.Type().(*types.Signature) + if sig.Recv() == nil && name == "init" { + pkg.ninit++ + name = fmt.Sprintf("init#%d", pkg.ninit) + } + fn := createFunction(pkg.Prog, obj, name, syntax, pkg.info, goversion) + fn.Pkg = pkg + pkg.created = append(pkg.created, fn) + pkg.values[obj] = fn + pkg.Functions = append(pkg.Functions, fn) + if name != "_" && sig.Recv() == nil { + pkg.Members[name] = fn // package-level function + } + + default: // (incl. *types.Package) + panic("unexpected Object type: " + obj.String()) + } +} + +// createFunction creates a function or method. It supports both +// CreatePackage (with or without syntax) and the on-demand creation +// of methods in non-created packages based on their types.Func. +func createFunction(prog *Program, obj *types.Func, name string, syntax ast.Node, info *types.Info, goversion string) *Function { + sig := obj.Type().(*types.Signature) + + /* declared function/method (from syntax or export data) */ + fn := &Function{ + name: name, + object: obj, + Signature: sig, + build: (*builder).buildFromSyntax, + info: info, + goversion: goversion, + pos: obj.Pos(), + syntax: syntax, + Pkg: nil, // may be set by caller + Prog: prog, + recvtypeparams: sig.RecvTypeParams(), + typeparams: sig.TypeParams(), + } + if syntax == nil { + fn.Synthetic = "from type information" + fn.build = (*builder).buildParamsOnly + } + if fn.hasTypeParams() { + fn.generic = new(generic) + } + return fn +} + +// membersFromDecl populates package pkg with members for each +// typechecker object (var, func, const or type) associated with the +// specified decl. +func membersFromDecl(pkg *Package, decl ast.Decl, goversion string) { + switch decl := decl.(type) { + case *ast.GenDecl: // import, const, type or var + switch decl.Tok { + case token.CONST: + for _, spec := range decl.Specs { + for _, id := range spec.(*ast.ValueSpec).Names { + memberFromObject(pkg, pkg.info.Defs[id], nil, "") + } + } + + case token.VAR: + for _, spec := range decl.Specs { + for _, rhs := range spec.(*ast.ValueSpec).Values { + pkg.initVersion[rhs] = goversion + } + for _, id := range spec.(*ast.ValueSpec).Names { + memberFromObject(pkg, pkg.info.Defs[id], spec, goversion) + } + } + + case token.TYPE: + for _, spec := range decl.Specs { + id := spec.(*ast.TypeSpec).Name + memberFromObject(pkg, pkg.info.Defs[id], nil, "") + } + } + + case *ast.FuncDecl: + id := decl.Name + memberFromObject(pkg, pkg.info.Defs[id], decl, goversion) + } +} + +// CreatePackage creates and returns an IR Package from the +// specified type-checked, error-free file ASTs, and populates its +// Members mapping. +// +// importable determines whether this package should be returned by a +// subsequent call to ImportedPackage(pkg.Path()). +// +// The real work of building IR form for each function is not done +// until a subsequent call to Package.Build. +func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { + if pkg == nil { + panic("nil pkg") // otherwise pkg.Scope below returns types.Universe! + } + p := &Package{ + Prog: prog, + Members: make(map[string]Member), + values: make(map[types.Object]Member), + Pkg: pkg, + syntax: info != nil, + // transient values (cleared after Package.Build) + info: info, + files: files, + initVersion: make(map[ast.Expr]string), + } + + /* synthesized package initializer */ + p.init = &Function{ + name: "init", + Signature: new(types.Signature), + Synthetic: "package initializer", + Pkg: p, + Prog: prog, + build: (*builder).buildPackageInit, + info: p.info, + goversion: "", // See Package.build for details. + } + p.Members[p.init.name] = p.init + p.Functions = append(p.Functions, p.init) + p.created = append(p.created, p.init) + + // Allocate all package members: vars, funcs, consts and types. + if len(files) > 0 { + // Go source package. + for _, file := range files { + goversion := versions.Lang(versions.FileVersion(p.info, file)) + for _, decl := range file.Decls { + membersFromDecl(p, decl, goversion) + } + } + } else { + // GC-compiled binary package (or "unsafe") + // No code. + // No position information. + scope := p.Pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + memberFromObject(p, obj, nil, "") + if obj, ok := obj.(*types.TypeName); ok { + // No Unalias: aliases should not duplicate methods. + if named, ok := obj.Type().(*types.Named); ok { + for i, n := 0, named.NumMethods(); i < n; i++ { + memberFromObject(p, named.Method(i), nil, "") + } + } + } + } + } + + if prog.mode&BareInits == 0 { + // Add initializer guard variable. + initguard := &Global{ + Pkg: p, + name: "init$guard", + typ: types.NewPointer(tBool), + } + p.Members[initguard.Name()] = initguard + } + + if prog.mode&GlobalDebug != 0 { + p.SetDebugMode(true) + } + + if prog.mode&PrintPackages != 0 { + printMu.Lock() + p.WriteTo(os.Stdout) + printMu.Unlock() + } + + if importable { + prog.imported[p.Pkg.Path()] = p + } + prog.packages[p.Pkg] = p + + return p +} + +// printMu serializes printing of Packages/Functions to stdout. +var printMu sync.Mutex + +// AllPackages returns a new slice containing all packages created by +// prog.CreatePackage in unspecified order. +func (prog *Program) AllPackages() []*Package { + pkgs := make([]*Package, 0, len(prog.packages)) + for _, pkg := range prog.packages { + pkgs = append(pkgs, pkg) + } + return pkgs +} + +// ImportedPackage returns the importable Package whose PkgPath +// is path, or nil if no such Package has been created. +// +// A parameter to CreatePackage determines whether a package should be +// considered importable. For example, no import declaration can resolve +// to the ad-hoc main package created by 'go build foo.go'. +// +// TODO(adonovan): rethink this function and the "importable" concept; +// most packages are importable. This function assumes that all +// types.Package.Path values are unique within the ir.Program, which is +// false---yet this function remains very convenient. +// Clients should use (*Program).Package instead where possible. +// IR doesn't really need a string-keyed map of packages. +// +// Furthermore, the graph of packages may contain multiple variants +// (e.g. "p" vs "p as compiled for q.test"), and each has a different +// view of its dependencies. +func (prog *Program) ImportedPackage(path string) *Package { + return prog.imported[path] +} + +// SetNoReturn sets the predicate used when building the ir.Program +// prog that reports whether a given function cannot return. +// This may be used to prune spurious control flow edges +// after (e.g.) log.Fatal, improving the precision of analyses. +// +// A typical implementation is the [ctrlflow.CFGs.NoReturn] method from +// [golang.org/x/tools/go/analysis/passes/ctrlflow]. +func (prog *Program) SetNoReturn(fn func(*types.Func) bool) { + prog.noReturn = fn +} diff --git a/vendor/honnef.co/go/tools/go/ir/doc.go b/vendor/honnef.co/go/tools/go/ir/doc.go new file mode 100644 index 0000000..13e4749 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/doc.go @@ -0,0 +1,123 @@ +// Copyright 2013 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 ir defines a representation of the elements of Go programs +// (packages, types, functions, variables and constants) using a +// static single-assignment (SSA) form intermediate representation +// (IR) for the bodies of functions. +// +// For an introduction to SSA form, see +// http://en.wikipedia.org/wiki/Static_single_assignment_form. +// This page provides a broader reading list: +// http://www.dcs.gla.ac.uk/~jsinger/ssa.html. +// +// The level of abstraction of the IR form is intentionally close to +// the source language to facilitate construction of source analysis +// tools. It is not intended for machine code generation. +// +// The simplest way to create the IR of a package is +// to load typed syntax trees using [golang.org/x/tools/go/packages], then +// invoke the [honnef.co/go/tools/go/ir/irutil.Packages] helper function. +// (See the package-level Examples named LoadPackages and LoadWholeProgram.) +// The resulting [ir.Program] contains all the packages and their +// members, but IR code is not created for function bodies until a +// subsequent call to [Package.Build] or [Program.Build]. +// +// The builder initially builds a naive IR form in which all local +// variables are addresses of stack locations with explicit loads and +// stores. Registerisation of eligible locals and φ-node insertion +// using dominance and dataflow are then performed as a second pass +// called "lifting" to improve the accuracy and performance of +// subsequent analyses; this pass can be skipped by setting the +// NaiveForm builder flag. +// +// The primary interfaces of this package are: +// +// - [Member]: a named member of a Go package. +// - [Value]: an expression that yields a value. +// - [Instruction]: a statement that consumes values and performs computation. +// - [Node]: a [Value] or [Instruction] (emphasizing its membership in the IR value graph) +// +// A computation that yields a result implements both the [Value] and +// [Instruction] interfaces. The following table shows for each +// concrete type which of these interfaces it implements. +// +// Value? Instruction? Member? +// *Alloc ✔ ✔ +// *BinOp ✔ ✔ +// *BlankStore ✔ +// *Builtin ✔ +// *Call ✔ ✔ +// *ChangeInterface ✔ ✔ +// *ChangeType ✔ ✔ +// *Const ✔ ✔ +// *Convert ✔ ✔ +// *DebugRef ✔ +// *Defer ✔ +// *Extract ✔ ✔ +// *Field ✔ ✔ +// *FieldAddr ✔ ✔ +// *FreeVar ✔ +// *Function ✔ ✔ (func) +// *Global ✔ ✔ (var) +// *Go ✔ +// *If ✔ +// *Index ✔ ✔ +// *IndexAddr ✔ ✔ +// *Jump ✔ +// *Load ✔ ✔ +// *MakeChan ✔ ✔ +// *MakeClosure ✔ ✔ +// *MakeInterface ✔ ✔ +// *MakeMap ✔ ✔ +// *MakeSlice ✔ ✔ +// *MapLookup ✔ ✔ +// *MapUpdate ✔ +// *MultiConvert ✔ ✔ +// *NamedConst ✔ (const) +// *Next ✔ ✔ +// *Panic ✔ +// *Parameter ✔ ✔ +// *Phi ✔ ✔ +// *Range ✔ ✔ +// *Recv ✔ ✔ +// *Return ✔ +// *RunDefers ✔ +// *Select ✔ ✔ +// *Send ✔ +// *Slice ✔ ✔ +// *SliceToArrayPointer ✔ ✔ +// *SliceToArray ✔ ✔ +// *Store ✔ +// *StringLookup ✔ ✔ +// *Type ✔ (type) +// *TypeAssert ✔ ✔ +// *UnOp ✔ ✔ +// *Unreachable ✔ +// +// Other key types in this package include: [Program], [Package], [Function] +// and [BasicBlock]. +// +// The program representation constructed by this package is fully +// resolved internally, i.e. it does not rely on the names of Values, +// Packages, Functions, Types or BasicBlocks for the correct +// interpretation of the program. Only the identities of objects and +// the topology of the IR and type graphs are semantically +// significant. (There is one exception: [types.Id] values, which identify field +// and method names, contain strings.) Avoidance of name-based +// operations simplifies the implementation of subsequent passes and +// can make them very efficient. Many objects are nonetheless named +// to aid in debugging, but it is not essential that the names be +// either accurate or unambiguous. The public API exposes a number of +// name-based maps for client convenience. +// +// The [honnef.co/go/tools/go/ir/irutil] package provides various +// helper functions, for example to simplify loading a Go program into +// SSA form. +// +// TODO(adonovan): write a how-to document for all the various cases +// of trying to determine corresponding elements across the four +// domains of source locations, ast.Nodes, types.Objects, +// ssa.Values/Instructions. +package ir // import "honnef.co/go/tools/go/ir" diff --git a/vendor/honnef.co/go/tools/go/ir/dom.go b/vendor/honnef.co/go/tools/go/ir/dom.go new file mode 100644 index 0000000..d733c95 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/dom.go @@ -0,0 +1,345 @@ +// Copyright 2013 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 ir + +// This file defines algorithms related to dominance. + +// Dominator tree construction ---------------------------------------- +// +// We use the algorithm described in Lengauer & Tarjan. 1979. A fast +// algorithm for finding dominators in a flowgraph. +// https://doi.acm.org/10.1145/357062.357071 +// +// We also apply the optimizations to SLT described in Georgiadis et +// al, Finding Dominators in Practice, JGAA 2006, +// https://jgaa.info/accepted/2006/GeorgiadisTarjanWerneck2006.10.1.pdf +// to avoid the need for buckets of size > 1. + +import ( + "bytes" + "fmt" + "io" + "math/big" + "os" + "slices" + "sort" +) + +// Idom returns the block that immediately dominates b: +// its parent in the dominator tree, if any. +// Neither the entry node (b.Index==0) nor recover node +// (b==b.Parent().Recover()) have a parent. +func (b *BasicBlock) Idom() *BasicBlock { return b.dom.idom } + +// Dominees returns the list of blocks that b immediately dominates: +// its children in the dominator tree. +func (b *BasicBlock) Dominees() []*BasicBlock { return b.dom.children } + +// Dominates reports whether b dominates c. +func (b *BasicBlock) Dominates(c *BasicBlock) bool { + return b.dom.pre <= c.dom.pre && c.dom.post <= b.dom.post +} + +// DomPreorder returns a new slice containing the blocks of f +// in a preorder traversal of the dominator tree. +func (f *Function) DomPreorder() []*BasicBlock { + slice := slices.Clone(f.Blocks) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.pre < slice[j].dom.pre + }) + return slice +} + +// DomPostorder returns a new slice containing the blocks of f +// in a postorder traversal of the dominator tree. +// (This is not the same as a postdominance order.) +func (f *Function) DomPostorder() []*BasicBlock { + slice := slices.Clone(f.Blocks) + sort.Slice(slice, func(i, j int) bool { + return slice[i].dom.post < slice[j].dom.post + }) + return slice +} + +// domInfo contains a BasicBlock's dominance information. +type domInfo struct { + idom *BasicBlock // immediate dominator (parent in domtree) + children []*BasicBlock // nodes immediately dominated by this one + pre, post int32 // pre- and post-order numbering within domtree +} + +// ltState holds the working state for Lengauer-Tarjan algorithm +// (during which domInfo.pre is repurposed for CFG DFS preorder number). +type ltState struct { + // Each slice is indexed by b.Index. + sdom []*BasicBlock // b's semidominator + parent []*BasicBlock // b's parent in DFS traversal of CFG + ancestor []*BasicBlock // b's ancestor with least sdom +} + +// dfs implements the depth-first search part of the LT algorithm. +func (lt *ltState) dfs(v *BasicBlock, i int32, preorder []*BasicBlock) int32 { + preorder[i] = v + v.dom.pre = i // For now: DFS preorder of spanning tree of CFG + i++ + lt.sdom[v.Index] = v + lt.link(nil, v) + for _, w := range v.Succs { + if lt.sdom[w.Index] == nil { + lt.parent[w.Index] = v + i = lt.dfs(w, i, preorder) + } + } + return i +} + +// eval implements the EVAL part of the LT algorithm. +func (lt *ltState) eval(v *BasicBlock) *BasicBlock { + // TODO(adonovan): opt: do path compression per simple LT. + u := v + for ; lt.ancestor[v.Index] != nil; v = lt.ancestor[v.Index] { + if lt.sdom[v.Index].dom.pre < lt.sdom[u.Index].dom.pre { + u = v + } + } + return u +} + +// link implements the LINK part of the LT algorithm. +func (lt *ltState) link(v, w *BasicBlock) { + lt.ancestor[w.Index] = v +} + +// buildDomTree computes the dominator tree of f using the LT algorithm. +// Precondition: all blocks are reachable (e.g. optimizeBlocks has been run). +func buildDomTree(fn *Function) { + // The step numbers refer to the original LT paper; the + // reordering is due to Georgiadis. + + // Clear any previous domInfo. + for _, b := range fn.Blocks { + b.dom = domInfo{} + } + + n := len(fn.Blocks) + // Allocate space for 5 contiguous [n]*BasicBlock arrays: + // sdom, parent, ancestor, preorder, buckets. + space := make([]*BasicBlock, 5*n) + lt := ltState{ + sdom: space[0:n], + parent: space[n : 2*n], + ancestor: space[2*n : 3*n], + } + + // Step 1. Number vertices by depth-first preorder. + preorder := space[3*n : 4*n] + root := fn.Blocks[0] + prenum := lt.dfs(root, 0, preorder) + recover := fn.Recover + if recover != nil { + lt.dfs(recover, prenum, preorder) + } + + buckets := space[4*n : 5*n] + copy(buckets, preorder) + + // In reverse preorder... + for i := int32(n) - 1; i > 0; i-- { + w := preorder[i] + + // Step 3. Implicitly define the immediate dominator of each node. + for v := buckets[i]; v != w; v = buckets[v.dom.pre] { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < i { + v.dom.idom = u + } else { + v.dom.idom = w + } + } + + // Step 2. Compute the semidominators of all nodes. + lt.sdom[w.Index] = lt.parent[w.Index] + for _, v := range w.Preds { + u := lt.eval(v) + if lt.sdom[u.Index].dom.pre < lt.sdom[w.Index].dom.pre { + lt.sdom[w.Index] = lt.sdom[u.Index] + } + } + + lt.link(lt.parent[w.Index], w) + + if lt.parent[w.Index] == lt.sdom[w.Index] { + w.dom.idom = lt.parent[w.Index] + } else { + buckets[i] = buckets[lt.sdom[w.Index].dom.pre] + buckets[lt.sdom[w.Index].dom.pre] = w + } + } + + // The final 'Step 3' is now outside the loop. + for v := buckets[0]; v != root; v = buckets[v.dom.pre] { + v.dom.idom = root + } + + // Step 4. Explicitly define the immediate dominator of each + // node, in preorder. + for _, w := range preorder[1:] { + if w == root || w == recover { + w.dom.idom = nil + } else { + if w.dom.idom != lt.sdom[w.Index] { + w.dom.idom = w.dom.idom.dom.idom + } + // Calculate Children relation as inverse of Idom. + w.dom.idom.dom.children = append(w.dom.idom.dom.children, w) + } + } + + pre, post := numberDomTree(root, 0, 0) + if recover != nil { + numberDomTree(recover, pre, post) + } + + // printDomTreeDot(os.Stderr, f) // debugging + // printDomTreeText(os.Stderr, root, 0) // debugging + + if fn.Prog.mode&SanityCheckFunctions != 0 { + sanityCheckDomTree(fn) + } +} + +// numberDomTree sets the pre- and post-order numbers of a depth-first +// traversal of the dominator tree rooted at v. These are used to +// answer dominance queries in constant time. +func numberDomTree(v *BasicBlock, pre, post int32) (int32, int32) { + v.dom.pre = pre + pre++ + for _, child := range v.dom.children { + pre, post = numberDomTree(child, pre, post) + } + v.dom.post = post + post++ + return pre, post +} + +// Testing utilities ---------------------------------------- + +// sanityCheckDomTree checks the correctness of the dominator tree +// computed by the LT algorithm by comparing against the dominance +// relation computed by a naive Kildall-style forward dataflow +// analysis (Algorithm 10.16 from the "Dragon" book). +func sanityCheckDomTree(f *Function) { + n := len(f.Blocks) + + // D[i] is the set of blocks that dominate f.Blocks[i], + // represented as a bit-set of block indices. + D := make([]big.Int, n) + + one := big.NewInt(1) + + // all is the set of all blocks; constant. + var all big.Int + all.Set(one).Lsh(&all, uint(n)).Sub(&all, one) + + // Initialization. + for i, b := range f.Blocks { + if i == 0 || b == f.Recover { + // A root is dominated only by itself. + D[i].SetBit(&D[0], 0, 1) + } else { + // All other blocks are (initially) dominated + // by every block. + D[i].Set(&all) + } + } + + // Iteration until fixed point. + for changed := true; changed; { + changed = false + for i, b := range f.Blocks { + if i == 0 || b == f.Recover { + continue + } + // Compute intersection across predecessors. + var x big.Int + x.Set(&all) + for _, pred := range b.Preds { + x.And(&x, &D[pred.Index]) + } + x.SetBit(&x, i, 1) // a block always dominates itself. + if D[i].Cmp(&x) != 0 { + D[i].Set(&x) + changed = true + } + } + } + + // Check the entire relation. O(n^2). + // The Recover block (if any) must be treated specially so we skip it. + ok := true + for i := range n { + for j := range n { + b, c := f.Blocks[i], f.Blocks[j] + if c == f.Recover { + continue + } + actual := b.Dominates(c) + expected := D[j].Bit(i) == 1 + if actual != expected { + fmt.Fprintf(os.Stderr, "dominates(%s, %s)==%t, want %t\n", b, c, actual, expected) + ok = false + } + } + } + + preorder := f.DomPreorder() + for _, b := range f.Blocks { + if got := preorder[b.dom.pre]; got != b { + fmt.Fprintf(os.Stderr, "preorder[%d]==%s, want %s\n", b.dom.pre, got, b) + ok = false + } + } + + if !ok { + panic("sanityCheckDomTree failed for " + f.String()) + } +} + +// Printing functions ---------------------------------------- + +// printDomTreeText prints the dominator tree as text, using indentation. +// +//lint:ignore U1000 used during debugging +func printDomTreeText(buf *bytes.Buffer, v *BasicBlock, indent int) { + fmt.Fprintf(buf, "%*s%s\n", 4*indent, "", v) + for _, child := range v.dom.children { + printDomTreeText(buf, child, indent+1) + } +} + +// printDomTreeDot prints the dominator tree of f in AT&T GraphViz +// (.dot) format. +// +//lint:ignore U1000 used during debugging +func printDomTreeDot(buf io.Writer, f *Function) { + fmt.Fprintln(buf, "//", f) + fmt.Fprintln(buf, "digraph domtree {") + for i, b := range f.Blocks { + v := b.dom + fmt.Fprintf(buf, "\tn%d [label=\"%s (%d, %d)\",shape=\"rectangle\"];\n", v.pre, b, v.pre, v.post) + // TODO(adonovan): improve appearance of edges + // belonging to both dominator tree and CFG. + + // Dominator tree edge. + if i != 0 { + fmt.Fprintf(buf, "\tn%d -> n%d [style=\"solid\",weight=100];\n", v.idom.dom.pre, v.pre) + } + // CFG edges. + for _, pred := range b.Preds { + fmt.Fprintf(buf, "\tn%d -> n%d [style=\"dotted\",weight=0];\n", pred.dom.pre, v.pre) + } + } + fmt.Fprintln(buf, "}") +} diff --git a/vendor/honnef.co/go/tools/go/ir/emit.go b/vendor/honnef.co/go/tools/go/ir/emit.go new file mode 100644 index 0000000..bd9a5bc --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/emit.go @@ -0,0 +1,645 @@ +// Copyright 2013 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 ir + +// Helpers for emitting IR instructions. + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + + "golang.org/x/exp/typeparams" +) + +// emitAlloc emits to f a new Alloc instruction allocating a variable +// of type typ. +// +// The caller must set Alloc.Heap=true (for a heap-allocated variable) +// or add the Alloc to f.Locals (for a frame-allocated variable). +// +// During building, a variable in f.Locals may have its Heap flag +// set when it is discovered that its address is taken. +// These Allocs are removed from f.Locals at the end. +// +// The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead. +func emitAlloc(f *Function, typ types.Type, source ast.Node, comment string) *Alloc { + v := &Alloc{} + v.comment = comment + v.setType(types.NewPointer(typ)) + f.emit(v, source) + return v +} + +// emitNew emits to f a new Alloc instruction heap-allocating a +// variable of type typ. +func emitNew(f *Function, typ types.Type, source ast.Node, comment string) *Alloc { + alloc := emitAlloc(f, typ, source, comment) + alloc.Heap = true + return alloc +} + +// emitLocal creates a local var for (t, source, comment) and +// emits an Alloc instruction for it. +// +// (Use this function or emitNew for synthetic variables; +// for source-level variables in the same function, use emitLocalVar.) +func emitLocal(f *Function, t types.Type, source ast.Node, comment string) *Alloc { + local := emitAlloc(f, t, source, comment) + f.Locals = append(f.Locals, local) + return local +} + +// emitLocalVar creates a local var for v and emits an Alloc instruction for it. +// Subsequent calls to f.lookup(v) return it. +// It applies the appropriate generic instantiation to the type. +func emitLocalVar(f *Function, v *types.Var, source ast.Node) *Alloc { + alloc := emitLocal(f, f.typ(v.Type()), source, v.Name()) + f.vars[v] = alloc + return alloc +} + +// emitLoad emits to f an instruction to load the address addr into a +// new temporary, and returns the value so defined. +func emitLoad(f *Function, addr Value, source ast.Node) *Load { + v := &Load{X: addr} + v.setType(deref(addr.Type())) + f.emit(v, source) + return v +} + +func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value { + recv := &Recv{ + Chan: ch, + CommaOk: commaOk, + } + recv.setType(typ) + return f.emit(recv, source) +} + +// emitDebugRef emits to f a DebugRef pseudo-instruction associating +// expression e with value v. +func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) { + ref := makeDebugRef(f, e, v, isAddr) + if ref == nil { + return + } + f.emit(ref, nil) +} + +func makeDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) *debugRef { + if !f.debugInfo() { + return nil // debugging not enabled + } + if v == nil || e == nil { + panic("nil") + } + var obj types.Object + e = ast.Unparen(e) + if id, ok := e.(*ast.Ident); ok { + if isBlankIdent(id) { + return nil + } + obj = f.objectOf(id) + switch obj.(type) { + case *types.Nil, *types.Const, *types.Builtin: + return nil + } + } + return &debugRef{ + X: v, + Expr: e, + IsAddr: isAddr, + object: obj, + } +} + +// emitArith emits to f code to compute the binary operation op(x, y) +// where op is an eager shift, logical or arithmetic operation. +// (Use emitCompare() for comparisons and Builder.logicalBinop() for +// non-eager operations.) +func emitArith(f *Function, op token.Token, x, y Value, t types.Type, source ast.Node) Value { + switch op { + case token.SHL, token.SHR: + x = emitConv(f, x, t, source) + // y may be signed or an 'untyped' constant. + // There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0 + // and converting to an unsigned value (like the compiler) leave y as is. + if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { + // Untyped conversion: + // Spec https://go.dev/ref/spec#Operators: + // The right operand in a shift expression must have integer type or be an untyped constant + // representable by a value of type uint. + y = emitConv(f, y, types.Typ[types.Uint], source) + } + + case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: + x = emitConv(f, x, t, source) + y = emitConv(f, y, t, source) + + default: + panic("illegal op in emitArith: " + op.String()) + + } + v := &BinOp{ + Op: op, + X: x, + Y: y, + } + v.setType(t) + return f.emit(v, source) +} + +// emitCompare emits to f code compute the boolean result of +// comparison 'x op y'. +func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) Value { + xt := x.Type().Underlying() + yt := y.Type().Underlying() + + // Special case to optimise a tagless SwitchStmt so that + // these are equivalent + // switch { case e: ...} + // switch true { case e: ... } + // if e==true { ... } + // even in the case when e's type is an interface. + // TODO(adonovan): opt: generalise to x==true, false!=y, etc. + if x, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) { + if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 { + return y + } + } + + if types.Identical(xt, yt) { + // no conversion necessary + } else if _, ok := xt.(*types.Interface); ok && !typeparams.IsTypeParam(x.Type()) { + y = emitConv(f, y, x.Type(), source) + } else if _, ok := yt.(*types.Interface); ok && !typeparams.IsTypeParam(y.Type()) { + x = emitConv(f, x, y.Type(), source) + } else if _, ok := x.(*Const); ok { + x = emitConv(f, x, y.Type(), source) + } else if _, ok := y.(*Const); ok { + y = emitConv(f, y, x.Type(), source) + //lint:ignore SA9003 no-op + } else { + // other cases, e.g. channels. No-op. + } + + v := &BinOp{ + Op: op, + X: x, + Y: y, + } + v.setType(tBool) + return f.emit(v, source) +} + +// isValuePreserving returns true if a conversion from ut_src to +// ut_dst is value-preserving, i.e. just a change of type. +// Precondition: neither argument is a named or alias type. +func isValuePreserving(ut_src, ut_dst types.Type) bool { + // Identical underlying types? + if types.IdenticalIgnoreTags(ut_dst, ut_src) { + return true + } + + switch ut_dst.(type) { + case *types.Chan: + // Conversion between channel types? + _, ok := ut_src.(*types.Chan) + return ok + + case *types.Pointer: + // Conversion between pointers with identical base types? + _, ok := ut_src.(*types.Pointer) + return ok + } + return false +} + +// emitConv emits to f code to convert Value val to exactly type typ, +// and returns the converted value. Implicit conversions are required +// by language assignability rules in assignments, parameter passing, +// etc. +func emitConv(f *Function, val Value, t_dst types.Type, source ast.Node) Value { + t_src := val.Type() + + // Identical types? Conversion is a no-op. + if types.Identical(t_src, t_dst) { + return val + } + + ut_dst := t_dst.Underlying() + ut_src := t_src.Underlying() + + // Conversion to, or construction of a value of, an interface type? + if isNonTypeParamInterface(t_dst) { + // Interface name change? + if isValuePreserving(ut_src, ut_dst) { + c := &ChangeType{X: val} + c.setType(t_dst) + return f.emit(c, source) + } + + // Assignment from one interface type to another? + if isNonTypeParamInterface(t_src) { + c := &ChangeInterface{X: val} + c.setType(t_dst) + return f.emit(c, source) + } + + // Untyped nil constant? Return interface-typed nil constant. + if ut_src == tUntypedNil { + return zeroConst(t_dst, source) + } + + // Convert (non-nil) "untyped" literals to their default type. + if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 { + val = emitConv(f, val, types.Default(ut_src), source) + } + + // Record the types of operands to MakeInterface, if + // non-parameterized, as they are the set of runtime types. + t := val.Type() + if !f.Prog.isParameterized(t) { + addMakeInterfaceType(f.Prog, t) + } + + mi := &MakeInterface{X: val} + mi.setType(t_dst) + return f.emit(mi, source) + } + + // conversionCase describes an instruction pattern that maybe emitted to + // model d <- s for d in dst_terms and s in src_terms. + // Multiple conversions can match the same pattern. + type conversionCase uint8 + const ( + changeType conversionCase = 1 << iota + sliceToArray + sliceToArrayPtr + sliceTo0Array + sliceTo0ArrayPtr + convert + ) + // classify the conversion case of a source type us to a destination type ud. + // us and ud are underlying types (not *Named or *Alias) + classify := func(s, d types.Type) conversionCase { + // Just a change of type, but not value or representation? + if isValuePreserving(s, d) { + return changeType + } + + // Conversion from slice to array or slice to array pointer? + if slice, ok := s.(*types.Slice); ok { + var arr *types.Array + var ptr bool + // Conversion from slice to array pointer? + switch d := d.(type) { + case *types.Array: + arr = d + case *types.Pointer: + arr, _ = d.Elem().Underlying().(*types.Array) + ptr = true + } + if arr != nil && types.Identical(slice.Elem(), arr.Elem()) { + if arr.Len() == 0 { + if ptr { + return sliceTo0ArrayPtr + } else { + return sliceTo0Array + } + } + if ptr { + return sliceToArrayPtr + } else { + return sliceToArray + } + } + } + + // The only remaining case in well-typed code is a representation- + // changing conversion of basic types (possibly with []byte/[]rune). + if !isBasic(s) && !isBasic(d) { + panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), s, t_dst, d)) + } + return convert + } + + var classifications conversionCase + underIs(ut_src, func(us types.Type) bool { + return underIs(ut_dst, func(ud types.Type) bool { + if us != nil && ud != nil { + classifications |= classify(us, ud) + } + return classifications != 0 + }) + }) + if classifications == 0 { + panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), t_dst)) + } + + // Conversion of a compile-time constant value? + if c, ok := val.(*Const); ok { + // Conversion to a basic type? + if isBasic(ut_dst) { + // Conversion of a compile-time constant to + // another constant type results in a new + // constant of the destination type and + // (initially) the same abstract value. + // We don't truncate the value yet. + return NewConst(c.Value, t_dst, source) + } + // Can we always convert from zero value without panicking? + const mayPanic = sliceToArray | sliceToArrayPtr + if c.Value == nil && classifications&mayPanic == 0 { + return NewConst(nil, t_dst, source) + } + + // We're converting from constant to non-constant type, + // e.g. string -> []byte/[]rune. + } + + switch classifications { + case changeType: // representation-preserving change + c := &ChangeType{X: val} + c.setType(t_dst) + return f.emit(c, source) + + case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer + c := &SliceToArrayPointer{X: val} + c.setType(t_dst) + return f.emit(c, source) + + case sliceToArray: // slice to arrays (not zero-length) + p := &SliceToArray{X: val} + p.setType(t_dst) + return f.emit(p, source) + + case sliceTo0Array: // slice to zero-length arrays (constant) + return zeroConst(t_dst, source) + + case convert: // representation-changing conversion + c := &Convert{X: val} + c.setType(t_dst) + return f.emit(c, source) + + default: // The conversion represents a cross product. + c := &MultiConvert{X: val, from: t_src, to: t_dst} + c.setType(t_dst) + return f.emit(c, source) + } +} + +// emitTypeCoercion emits to f code to coerce the type of a +// Value v to exactly type typ, and returns the coerced value. +// +// Requires that coercing v.Typ() to typ is a value preserving change. +// +// Currently used only when v.Type() is a type instance of typ or vice versa. +// A type v is a type instance of a type t if there exists a +// type parameter substitution σ s.t. σ(v) == t. Example: +// +// σ(func(T) T) == func(int) int for σ == [T ↦ int] +// +// This happens in instantiation wrappers for conversion +// from an instantiation to a parameterized type (and vice versa) +// with σ substituting f.typeparams by f.typeargs. +func emitTypeCoercion(f *Function, v Value, typ types.Type, source ast.Node) Value { + if types.Identical(v.Type(), typ) { + return v // no coercion needed + } + // TODO(taking): for instances should we record which side is the instance? + c := &ChangeType{ + X: v, + } + c.setType(typ) + f.emit(c, source) + return c +} + +// emitStore emits to f an instruction to store value val at location +// addr, applying implicit conversions as required by assignability rules. +func emitStore(f *Function, addr, val Value, source ast.Node) *Store { + typ := deref(addr.Type()) + s := &Store{ + Addr: addr, + Val: emitConv(f, val, typ, source), + } + f.emit(s, source) + return s +} + +// emitJump emits to f a jump to target, and updates the control-flow graph. +// Postcondition: f.currentBlock is nil. +func emitJump(f *Function, target *BasicBlock, source ast.Node) *Jump { + b := f.currentBlock + j := new(Jump) + b.emit(j, source) + addEdge(b, target) + f.currentBlock = nil + return j +} + +// emitIf emits to f a conditional jump to tblock or fblock based on +// cond, and updates the control-flow graph. +// Postcondition: f.currentBlock is nil. +func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock, source ast.Node) *If { + b := f.currentBlock + stmt := &If{Cond: cond} + b.emit(stmt, source) + addEdge(b, tblock) + addEdge(b, fblock) + f.currentBlock = nil + return stmt +} + +// emitExtract emits to f an instruction to extract the index'th +// component of tuple. It returns the extracted value. +func emitExtract(f *Function, tuple Value, index int, source ast.Node) Value { + e := &Extract{Tuple: tuple, Index: index} + e.setType(tuple.Type().(*types.Tuple).At(index).Type()) + return f.emit(e, source) +} + +// emitTypeAssert emits to f a type assertion value := x.(t) and +// returns the value. x.Type() must be an interface. +func emitTypeAssert(f *Function, x Value, t types.Type, source ast.Node) Value { + a := &TypeAssert{X: x, AssertedType: t} + a.setType(t) + return f.emit(a, source) +} + +// emitTypeTest emits to f a type test value,ok := x.(t) and returns +// a (value, ok) tuple. x.Type() must be an interface. +func emitTypeTest(f *Function, x Value, t types.Type, source ast.Node) Value { + a := &TypeAssert{ + X: x, + AssertedType: t, + CommaOk: true, + } + a.setType(types.NewTuple( + newVar("value", t), + varOk, + )) + return f.emit(a, source) +} + +// emitTailCall emits to f a function call in tail position. The +// caller is responsible for all fields of 'call' except its type. +// Intended for wrapper methods. +// Precondition: f does/will not use deferred procedure calls. +// Postcondition: f.currentBlock is nil. +func emitTailCall(f *Function, call *Call, source ast.Node) { + tresults := f.Signature.Results() + nr := tresults.Len() + if nr == 1 { + call.typ = tresults.At(0).Type() + } else { + call.typ = tresults + } + tuple := emitCall(f, call, source) + var ret Return + switch nr { + case 0: + // no-op + case 1: + ret.Results = []Value{tuple} + default: + for i := range nr { + v := emitExtract(f, tuple, i, source) + // TODO(adonovan): in principle, this is required: + // v = emitConv(f, o.Type, f.Signature.Results[i].Type) + // but in practice emitTailCall is only used when + // the types exactly match. + ret.Results = append(ret.Results, v) + } + } + + f.emit(&ret, source) + f.currentBlock = nil +} + +// emitCall emits a call instruction. If the callee is "no return", +// it also emits a panic to eliminate infeasible CFG edges. +func emitCall(fn *Function, call *Call, source ast.Node) Value { + res := fn.emit(call, source) + + callee := call.Call.StaticCallee() + if callee != nil && + callee.object != nil && + fn.Prog.noReturn != nil && + fn.Prog.noReturn(callee.object) { + // Call doesn't return normally. Either it doesn't return at all + // (infinitely blocked or exitting the process), or it unwinds the stack + // (panic, runtime.Goexit). Model this as a panic. + fn.emit(&Panic{ + X: emitConv(fn, vNoReturn, tEface, source), + }, source) + fn.currentBlock = fn.newBasicBlock("unreachable") + } + + return res +} + +// emitImplicitSelections emits to f code to apply the sequence of +// implicit field selections specified by indices to base value v, and +// returns the selected value. +// +// If v is the address of a struct, the result will be the address of +// a field; if it is the value of a struct, the result will be the +// value of a field. +func emitImplicitSelections(f *Function, v Value, indices []int, source ast.Node) Value { + for _, index := range indices { + // We may have a generic type containing a pointer, or a pointer to a generic type containing a struct. A + // pointer to a generic containing a pointer to a struct shouldn't be possible because the outer pointer gets + // dereferenced implicitly before we get here. + if isPointerCore(v.Type()) { + fld := fieldOf(deref(v.Type()), index) + instr := &FieldAddr{ + X: v, + Field: index, + } + instr.setType(types.NewPointer(fld.Type())) + v = f.emit(instr, source) + // Load the field's value iff indirectly embedded. + if isPointerCore(fld.Type()) { + v = emitLoad(f, v, source) + } + } else { + fld := fieldOf(v.Type(), index) + instr := &Field{ + X: v, + Field: index, + } + instr.setType(fld.Type()) + v = f.emit(instr, source) + } + } + return v +} + +// emitFieldSelection emits to f code to select the index'th field of v. +// +// If wantAddr, the input must be a pointer-to-struct and the result +// will be the field's address; otherwise the result will be the +// field's value. +// Ident id is used for position and debug info. +func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value { + if isPointerCore(v.Type()) { + fld := fieldOf(deref(v.Type()), index) + instr := &FieldAddr{ + X: v, + Field: index, + } + instr.setSource(id) + instr.setType(types.NewPointer(fld.Type())) + v = f.emit(instr, id) + // Load the field's value iff we don't want its address. + if !wantAddr { + v = emitLoad(f, v, id) + } + } else { + fld := fieldOf(v.Type(), index) + instr := &Field{ + X: v, + Field: index, + } + instr.setSource(id) + instr.setType(fld.Type()) + v = f.emit(instr, id) + } + emitDebugRef(f, id, v, wantAddr) + return v +} + +// createRecoverBlock emits to f a block of code to return after a +// recovered panic, and sets f.Recover to it. +// +// If f's result parameters are named, the code loads and returns +// their current values, otherwise it returns the zero values of their +// type. +// +// Idempotent. +func createRecoverBlock(f *Function) { + if f.Recover != nil { + return // already created + } + saved := f.currentBlock + + f.Recover = f.newBasicBlock("recover") + f.currentBlock = f.Recover + + var results []Value + // Reload NRPs to form value tuple. + for _, nr := range f.results { + results = append(results, emitLoad(f, nr, nr.source)) + } + + f.emit(&Return{Results: results}, nil) + + f.currentBlock = saved +} diff --git a/vendor/honnef.co/go/tools/go/ir/func.go b/vendor/honnef.co/go/tools/go/ir/func.go new file mode 100644 index 0000000..5b0c338 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/func.go @@ -0,0 +1,936 @@ +// Copyright 2013 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 ir + +// This file implements the Function type. + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "go/types" + "io" + "iter" + "os" + "strings" +) + +// Like ObjectOf, but panics instead of returning nil. +// Only valid during f's create and build phases. +func (f *Function) objectOf(id *ast.Ident) types.Object { + if o := f.info.ObjectOf(id); o != nil { + return o + } + panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s", + id.Name, f.Prog.Fset.Position(id.Pos()))) +} + +// instrs returns an iterator that returns each reachable instruction of the SSA function. +func (f *Function) instrs() iter.Seq[Instruction] { + return func(yield func(i Instruction) bool) { + for _, block := range f.Blocks { + for _, instr := range block.Instrs { + if !yield(instr) { + return + } + } + } + } +} + +// Like TypeOf, but panics instead of returning nil. +// Only valid during f's create and build phases. +func (f *Function) typeOf(e ast.Expr) types.Type { + if T := f.info.TypeOf(e); T != nil { + return f.typ(T) + } + panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos()))) +} + +// typ is the locally instantiated type of T. +// If f is not an instantiation, then f.typ(T)==T. +func (f *Function) typ(T types.Type) types.Type { + return f.subst.typ(T) +} + +// If id is an Instance, returns info.Instances[id].Type. +// Otherwise returns f.typeOf(id). +func (f *Function) instanceType(id *ast.Ident) types.Type { + if t, ok := f.info.Instances[id]; ok { + return t.Type + } + return f.typeOf(id) +} + +// selection returns a *selection corresponding to f.info.Selections[selector] +// with potential updates for type substitution. +func (f *Function) selection(selector *ast.SelectorExpr) *selection { + sel := f.info.Selections[selector] + if sel == nil { + return nil + } + + switch sel.Kind() { + case types.MethodExpr, types.MethodVal: + if recv := f.typ(sel.Recv()); recv != sel.Recv() { + // recv changed during type substitution. + pkg := f.declaredPackage().Pkg + obj, index, indirect := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name()) + + // sig replaces sel.Type(). See (types.Selection).Typ() for details. + sig := obj.Type().(*types.Signature) + sig = changeRecv(sig, newVar(sig.Recv().Name(), recv)) + if sel.Kind() == types.MethodExpr { + sig = recvAsFirstArg(sig) + } + return &selection{ + kind: sel.Kind(), + recv: recv, + typ: sig, + obj: obj, + index: index, + indirect: indirect, + } + } + } + return toSelection(sel) +} + +// Destinations associated with unlabelled for/switch/select stmts. +// We push/pop one of these as we enter/leave each construct and for +// each BranchStmt we scan for the innermost target of the right type. +type targets struct { + tail *targets // rest of stack + _break *BasicBlock + _continue *BasicBlock + _fallthrough *BasicBlock +} + +// Destinations associated with a labelled block. +// We populate these as labels are encountered in forward gotos or +// labelled statements. +// Forward gotos are resolved once it is known which statement they +// are associated with inside the Function. +type lblock struct { + label *types.Label // Label targeted by the blocks. + resolved bool // _goto block encountered (back jump or resolved fwd jump) + _goto *BasicBlock + _break *BasicBlock + _continue *BasicBlock +} + +// label returns the symbol denoted by a label identifier. +// +// label should be a non-blank identifier (label.Name != "_"). +func (f *Function) label(label *ast.Ident) *types.Label { + return f.objectOf(label).(*types.Label) +} + +// lblockOf returns the branch target associated with the +// specified label, creating it if needed. +func (f *Function) lblockOf(label *types.Label) *lblock { + lb := f.lblocks[label] + if lb == nil { + lb = &lblock{ + label: label, + _goto: f.newBasicBlock(label.Name()), + } + if f.lblocks == nil { + f.lblocks = make(map[*types.Label]*lblock) + } + f.lblocks[label] = lb + } + return lb +} + +// labelledBlock searches f for the block of the specified label. +// +// If f is a yield function, it additionally searches ancestor Functions +// corresponding to enclosing range-over-func statements within the +// same source function, so the returned block may belong to a different Function. +func labelledBlock(f *Function, label *types.Label, tok token.Token) *BasicBlock { + if lb := f.lblocks[label]; lb != nil { + var block *BasicBlock + switch tok { + case token.BREAK: + block = lb._break + case token.CONTINUE: + block = lb._continue + case token.GOTO: + block = lb._goto + } + if block != nil { + return block + } + } + // Search ancestors if this is a yield function. + if f.jump != nil { + return labelledBlock(f.parent, label, tok) + } + return nil +} + +// targetedBlock looks for the nearest block in f.targets +// (and f's ancestors) that matches tok's type, and returns +// the block and function it was found in. +func targetedBlock(f *Function, tok token.Token) *BasicBlock { + if f == nil { + return nil + } + for t := f.targets; t != nil; t = t.tail { + var block *BasicBlock + switch tok { + case token.BREAK: + block = t._break + case token.CONTINUE: + block = t._continue + case token.FALLTHROUGH: + block = t._fallthrough + } + if block != nil { + return block + } + } + // Search f's ancestors (in case f is a yield function). + return targetedBlock(f.parent, tok) +} + +// addResultVar adds a result for a variable v to f.results and v to f.returnVars. +func (f *Function) addResultVar(v *types.Var, source ast.Node) { + name := v.Name() + if name == "" { + name = fmt.Sprintf("res.%d", len(f.results)) + } + result := emitLocalVar(f, v, source) + result.comment = name + f.results = append(f.results, result) + f.returnVars = append(f.returnVars, v) +} + +// addParamVar adds a parameter to f.Params. +func (f *Function) addParamVar(v *types.Var, source ast.Node) *Parameter { + name := v.Name() + if name == "" { + name = fmt.Sprintf("arg%d", len(f.Params)) + } + param := &Parameter{name: name, typ: f.typ(v.Type()), parent: f} + param.setSource(source) + param.object = v + f.Params = append(f.Params, param) + return param +} + +// addSpilledParam declares a parameter that is pre-spilled to the +// stack; the function body will load/store the spilled location. +// Subsequent lifting will eliminate spills where possible. +func (f *Function) addSpilledParam(obj *types.Var, source ast.Node) { + param := f.addParamVar(obj, source) + spill := emitLocalVar(f, obj, source) + emitStore(f, spill, param, source) +} + +// startBody initializes the function prior to generating IR code for its body. +// Precondition: f.Type() already set. +func (f *Function) startBody() { + entry := f.newBasicBlock("entry") + f.currentBlock = entry + f.vars = make(map[*types.Var]Value) // needed for some synthetics, e.g. init +} + +func (f *Function) blockset(i int) *BlockSet { + bs := &f.blocksets[i] + if len(bs.values) != len(f.Blocks) { + if cap(bs.values) >= len(f.Blocks) { + bs.values = bs.values[:len(f.Blocks)] + bs.Clear() + } else { + bs.values = make([]bool, len(f.Blocks)) + } + } else { + bs.Clear() + } + return bs +} + +// createSyntacticParams populates f.Params and generates code (spills +// and named result locals) for all the parameters declared in the +// syntax. In addition it populates the f.objects mapping. +// +// Preconditions: +// f.startBody() was called. f.info != nil. +// Postcondition: +// len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0) +func (f *Function) createSyntacticParams(recv *ast.FieldList, functype *ast.FuncType) { + // Receiver (at most one inner iteration). + if recv != nil { + for _, field := range recv.List { + for _, n := range field.Names { + f.addSpilledParam(identVar(f, n), n) + } + // Anonymous receiver? No need to spill. + if field.Names == nil { + f.addParamVar(f.Signature.Recv(), field) + } + } + } + + // Parameters. + if functype.Params != nil { + n := len(f.Params) // 1 if has recv, 0 otherwise + for _, field := range functype.Params.List { + for _, n := range field.Names { + f.addSpilledParam(identVar(f, n), n) + } + // Anonymous parameter? No need to spill. + if field.Names == nil { + f.addParamVar(f.Signature.Params().At(len(f.Params)-n), field) + } + } + } + + // Results. + if functype.Results != nil { + for _, field := range functype.Results.List { + // Implicit "var" decl of locals for named results. + for _, n := range field.Names { + v := identVar(f, n) + f.addResultVar(v, n) + } + // Implicit "var" decl of local for an unnamed result. + if field.Names == nil { + v := f.Signature.Results().At(len(f.results)) + f.addResultVar(v, field.Type) + } + } + } +} + +// createDeferStack initializes fn.deferstack to a local variable +// initialized to a ssa:deferstack() call. +func (fn *Function) createDeferStack() { + // Each syntactic function makes a call to ssa:deferstack, + // which is spilled to a local. Unused ones are later removed. + fn.deferstack = newVar("defer$stack", tDeferStack) + call := &Call{Call: CallCommon{Value: vDeferStack}} + call.setType(tDeferStack) + deferstack := fn.emit(call, nil) + spill := emitLocalVar(fn, fn.deferstack, nil) + emitStore(fn, spill, deferstack, nil) +} + +func numberNodes(f *Function) { + var base ID + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + if instr == nil { + continue + } + base++ + instr.setID(base) + } + } +} + +func updateOperandsReferrers(instr Instruction, ops []*Value) { + for _, op := range ops { + if r := *op; r != nil { + if refs := (*op).Referrers(); refs != nil { + if len(*refs) == 0 { + // per median, each value has two referrers, so we can avoid one call into growslice + // + // Note: we experimented with allocating + // sequential scratch space, but we + // couldn't find a value that gave better + // performance than making many individual + // allocations + *refs = make([]Instruction, 1, 2) + (*refs)[0] = instr + } else { + *refs = append(*refs, instr) + } + } + } + } +} + +// buildReferrers populates the def/use information in all non-nil +// Value.Referrers slice. +// Precondition: all such slices are initially empty. +func buildReferrers(f *Function) { + var rands []*Value + + for _, b := range f.Blocks { + for _, instr := range b.Instrs { + rands = instr.Operands(rands[:0]) // recycle storage + updateOperandsReferrers(instr, rands) + } + } +} + +// finishBody() finalizes the contents of the function after IR code generation of its body. +// +// The function is not done being built until done() is called. +func (f *Function) finishBody() { + f.currentBlock = nil + f.lblocks = nil + f.returnVars = nil + f.jump = nil + f.source = nil + f.exits = nil + + // Remove from f.Locals any Allocs that escape to the heap. + j := 0 + for _, l := range f.Locals { + if !l.Heap { + f.Locals[j] = l + j++ + } + } + // Nil out f.Locals[j:] to aid GC. + for i := j; i < len(f.Locals); i++ { + f.Locals[i] = nil + } + f.Locals = f.Locals[:j] + + optimizeBlocks(f) + buildReferrers(f) + buildDomTree(f) + buildSCCs(f) + + if f.Prog.mode&NaiveForm == 0 { + for lift(f) { + } + if doSimplifyConstantCompositeValues { + for simplifyConstantCompositeValues(f) { + } + } + } + + if f.debugInfo() { + // Turn debug references into a map + f.exprToValue = map[ast.Expr]struct { + v Value + isAddr bool + }{} + for _, b := range f.Blocks { + newInstrs := b.Instrs[:0] + for _, instr := range b.Instrs { + if instr, ok := instr.(*debugRef); ok { + if refs := instr.X.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + if _, ok := f.exprToValue[instr.Expr]; !ok { + f.exprToValue[instr.Expr] = struct { + v Value + isAddr bool + }{instr.X, instr.IsAddr} + } + continue + } + newInstrs = append(newInstrs, instr) + } + b.Instrs = newInstrs + } + } + + // clear remaining builder state + f.results = nil // (used by lifting) + f.deferstack = nil // (used by lifting) + f.vars = nil // (used by lifting) + + numberNodes(f) + + // clear out other function state (keep consistent with buildParamsOnly) + f.subst = nil + + numberNodes(f) +} + +// done marks the building of f's SSA body complete, +// along with any nested functions, and optionally prints them. +func (f *Function) done() { + assert(f.parent == nil, "done called on an anonymous function") + + var visit func(*Function) + visit = func(f *Function) { + for _, anon := range f.AnonFuncs { + visit(anon) // anon is done building before f. + } + + f.uniq = 0 // done with uniq + f.build = nil // function is built + + if f.Prog.mode&PrintFunctions != 0 { + printMu.Lock() + f.WriteTo(os.Stdout) + printMu.Unlock() + } + + if f.Prog.mode&SanityCheckFunctions != 0 { + mustSanityCheck(f, nil) + } + } + visit(f) +} + +func isUselessPhi(phi *Phi) (Value, bool) { + var v0 Value + if len(phi.Edges) == 0 { + panic("φ with no edges") + } + for _, e := range phi.Edges { + if e == nil { + panic(fmt.Sprintf("φ has nil edge: %v", phi)) + } + if e == phi { + continue + } + if v0 == nil { + v0 = e + } + if v0 != e { + if v0, ok := v0.(*Const); ok { + if e, ok := e.(*Const); ok { + if v0.typ == e.typ && v0.Value == e.Value { + continue + } + } + } + return nil, false + } + } + if v0 == nil { + panic("trying to replace φ with nil") + } + return v0, true +} + +// removeNilBlocks eliminates nils from f.Blocks and updates each +// BasicBlock.Index. Use this after any pass that may delete blocks. +func (f *Function) removeNilBlocks() { + j := 0 + for _, b := range f.Blocks { + if b != nil { + b.Index = j + f.Blocks[j] = b + j++ + } + } + // Nil out f.Blocks[j:] to aid GC. + for i := j; i < len(f.Blocks); i++ { + f.Blocks[i] = nil + } + f.Blocks = f.Blocks[:j] +} + +// SetDebugMode sets the debug mode for package pkg. If true, all its +// functions will include full debug info. This greatly increases the +// size of the instruction stream, and causes Functions to depend upon +// the ASTs, potentially keeping them live in memory for longer. +func (pkg *Package) SetDebugMode(debug bool) { + pkg.debug = debug +} + +// debugInfo reports whether debug info is wanted for this function. +func (f *Function) debugInfo() bool { + // debug info for instantiations follows the debug info of their origin. + p := f.declaredPackage() + return p != nil && p.debug +} + +// lookup returns the address of the named variable identified by obj +// that is local to function f or one of its enclosing functions. +// If escaping, the reference comes from a potentially escaping pointer +// expression and the referent must be heap-allocated. +// We assume the referent is a *Alloc or *Phi. +// (The only Phis at this stage are those created directly by go1.22 "for" loops.) +func (f *Function) lookup(obj *types.Var, escaping bool) Value { + if v, ok := f.vars[obj]; ok { + if escaping { + switch v := v.(type) { + case *Alloc: + v.Heap = true + case *Phi: + for _, edge := range v.Edges { + if alloc, ok := edge.(*Alloc); ok { + alloc.Heap = true + } + } + } + } + return v // function-local var (address) + } + + // Definition must be in an enclosing function; + // plumb it through intervening closures. + if f.parent == nil { + panic("no ir.Value for " + obj.String()) + } + outer := f.parent.lookup(obj, true) // escaping + v := &FreeVar{ + name: obj.Name(), + typ: outer.Type(), + outer: outer, + parent: f, + } + f.vars[obj] = v + f.FreeVars = append(f.FreeVars, v) + return v +} + +// emit emits the specified instruction to function f. +func (f *Function) emit(instr Instruction, source ast.Node) Value { + return f.currentBlock.emit(instr, source) +} + +// RelString returns the full name of this function, qualified by +// package name, receiver type, etc. +// +// The specific formatting rules are not guaranteed and may change. +// +// Examples: +// +// "math.IsNaN" // a package-level function +// "(*bytes.Buffer).Bytes" // a declared method or a wrapper +// "(*bytes.Buffer).Bytes$thunk" // thunk (func wrapping method; receiver is param 0) +// "(*bytes.Buffer).Bytes$bound" // bound (func wrapping method; receiver supplied by closure) +// "main.main$1" // an anonymous function in main +// "main.init#1" // a declared init function +// "main.init" // the synthesized package initializer +// +// When these functions are referred to from within the same package +// (i.e. from == f.Pkg.Object), they are rendered without the package path. +// For example: "IsNaN", "(*Buffer).Bytes", etc. +// +// All non-synthetic functions have distinct package-qualified names. +// (But two methods may have the same name "(T).f" if one is a synthetic +// wrapper promoting a non-exported method "f" from another package; in +// that case, the strings are equal but the identifiers "f" are distinct.) +func (f *Function) RelString(from *types.Package) string { + // Anonymous? + if f.parent != nil { + // An anonymous function's Name() looks like "parentName$1", + // but its String() should include the type/package/etc. + parent := f.parent.RelString(from) + for i, anon := range f.parent.AnonFuncs { + if anon == f { + return fmt.Sprintf("%s$%d", parent, 1+i) + } + } + + return f.name // should never happen + } + + // Method (declared or wrapper)? + if recv := f.Signature.Recv(); recv != nil { + return f.relMethod(from, recv.Type()) + } + + // Thunk? + if f.method != nil { + return f.relMethod(from, f.method.recv) + } + + // Bound? + if len(f.FreeVars) == 1 && strings.HasSuffix(f.name, "$bound") { + return f.relMethod(from, f.FreeVars[0].Type()) + } + + // Package-level function? + // Prefix with package name for cross-package references only. + if p := f.relPkg(); p != nil && p != from { + return fmt.Sprintf("%s.%s", p.Path(), f.name) + } + + // Unknown. + return f.name +} + +func (f *Function) relMethod(from *types.Package, recv types.Type) string { + return fmt.Sprintf("(%s).%s", relType(recv, from), f.name) +} + +// writeSignature writes to buf the signature sig in declaration syntax. +func writeSignature(buf *bytes.Buffer, from *types.Package, name string, sig *types.Signature) { + buf.WriteString("func ") + if recv := sig.Recv(); recv != nil { + buf.WriteString("(") + if name := recv.Name(); name != "" { + buf.WriteString(name) + buf.WriteString(" ") + } + types.WriteType(buf, recv.Type(), types.RelativeTo(from)) + buf.WriteString(") ") + } + buf.WriteString(name) + types.WriteSignature(buf, sig, types.RelativeTo(from)) +} + +// declaredPackage returns the package fn is declared in or nil if the +// function is not declared in a package. +func (fn *Function) declaredPackage() *Package { + switch { + case fn.Pkg != nil: + return fn.Pkg // non-generic function (does that follow??) + case fn.topLevelOrigin != nil: + return fn.topLevelOrigin.Pkg // instance of a named generic function + case fn.parent != nil: + return fn.parent.declaredPackage() // instance of an anonymous [generic] function + default: + return nil // function is not declared in a package, e.g. a wrapper. + } +} + +// relPkg returns types.Package fn is printed in relationship to. +func (fn *Function) relPkg() *types.Package { + if p := fn.declaredPackage(); p != nil { + return p.Pkg + } + return nil +} + +var _ io.WriterTo = (*Function)(nil) // *Function implements io.Writer + +func (f *Function) WriteTo(w io.Writer) (int64, error) { + var buf bytes.Buffer + WriteFunction(&buf, f) + n, err := w.Write(buf.Bytes()) + return int64(n), err +} + +// WriteFunction writes to buf a human-readable "disassembly" of f. +func WriteFunction(buf *bytes.Buffer, f *Function) { + fmt.Fprintf(buf, "# Name: %s\n", f.String()) + if f.Pkg != nil { + fmt.Fprintf(buf, "# Package: %s\n", f.Pkg.Pkg.Path()) + } + if syn := f.Synthetic; syn != "" { + fmt.Fprintln(buf, "# Synthetic:", syn) + } + if pos := f.Pos(); pos.IsValid() { + fmt.Fprintf(buf, "# Location: %s\n", f.Prog.Fset.Position(pos)) + } + + if f.parent != nil { + fmt.Fprintf(buf, "# Parent: %s\n", f.parent.Name()) + } + + from := f.relPkg() + + // NB. column calculations are confused by non-ASCII + // characters + const punchcard = 80 // for old time's sake. + const padding = " " + + if f.FreeVars != nil { + buf.WriteString("# Free variables:\n") + for i, fv := range f.FreeVars { + fmt.Fprintf(buf, "# % 3d:%s%s %s\n", i, padding, fv.Name(), relType(fv.Type(), from)) + } + } + + if len(f.Locals) > 0 { + buf.WriteString("# Locals:\n") + for i, l := range f.Locals { + fmt.Fprintf(buf, "# % 3d:%s%s %s\n", i, padding, l.Name(), relType(deref(l.Type()), from)) + } + } + writeSignature(buf, from, f.Name(), f.Signature) + buf.WriteString(":\n") + + if f.Blocks == nil { + buf.WriteString(padding) + buf.WriteString("(external)\n") + } + + for _, b := range f.Blocks { + if b == nil { + // Corrupt CFG. + fmt.Fprintf(buf, ".nil:\n") + continue + } + n, _ := fmt.Fprintf(buf, "%d:", b.Index) + // (|predecessors|, |successors|, immediate dominator) + bmsg := fmt.Sprintf("%s P:%d S:%d", b.Comment, len(b.Preds), len(b.Succs)) + if b.Idom() != nil { + bmsg = fmt.Sprintf("%s idom:%d", bmsg, b.Idom().Index) + } + fmt.Fprintf(buf, "%*s%s\n", punchcard-1-n-len(bmsg), "", bmsg) + + if false { // CFG debugging + fmt.Fprintf(buf, "%s# CFG: %s --> %s --> %s\n", padding, b.Preds, b, b.Succs) + } + for _, instr := range b.Instrs { + buf.WriteString(padding) + switch v := instr.(type) { + case Value: + l := punchcard - len(padding) + // Left-align the instruction. + if name := v.Name(); name != "" { + n, _ := fmt.Fprintf(buf, "%s = ", name) + l -= n + } + n, _ := buf.WriteString(instr.String()) + l -= n + // Right-align the type if there's space. + if t := v.Type(); t != nil { + buf.WriteByte(' ') + ts := relType(t, from) + l -= len(ts) + len(" ") // (spaces before and after type) + if l > 0 { + fmt.Fprintf(buf, "%*s", l, "") + } + buf.WriteString(ts) + } + case nil: + // Be robust against bad transforms. + buf.WriteString("") + default: + buf.WriteString(instr.String()) + } + // -mode=S: show line numbers + if f.Prog.mode&LogSource != 0 { + if pos := instr.Pos(); pos.IsValid() { + fmt.Fprintf(buf, " L%d", f.Prog.Fset.Position(pos).Line) + } + } + buf.WriteString("\n") + } + } + buf.WriteString("\n") +} + +// newBasicBlock adds to f a new basic block and returns it. It does +// not automatically become the current block for subsequent calls to emit. +// comment is an optional string for more readable debugging output. +func (f *Function) newBasicBlock(comment string) *BasicBlock { + b := &BasicBlock{ + Index: len(f.Blocks), + Comment: comment, + parent: f, + } + b.Succs = b.succs2[:0] + f.Blocks = append(f.Blocks, b) + return b +} + +// NewFunction returns a new synthetic Function instance belonging to +// prog, with its name and signature fields set as specified. +// +// The caller is responsible for initializing the remaining fields of +// the function object, e.g. Pkg, Params, Blocks. +// +// It is practically impossible for clients to construct well-formed +// IR functions/packages/programs directly, so we assume this is the +// job of the Builder alone. NewFunction exists to provide clients a +// little flexibility. For example, analysis tools may wish to +// construct fake Functions for the root of the callgraph, a fake +// "reflect" package, etc. +// +// TODO(adonovan): think harder about the API here. +func (prog *Program) NewFunction(name string, sig *types.Signature, provenance string) *Function { + return &Function{Prog: prog, name: name, Signature: sig, Synthetic: provenance} +} + +//lint:ignore U1000 we may make use of this for functions loaded from export data +type extentNode [2]token.Pos + +func (n extentNode) Pos() token.Pos { return n[0] } +func (n extentNode) End() token.Pos { return n[1] } + +func killInstruction(instr Instruction) { + ops := instr.Operands(nil) + for _, op := range ops { + if refs := (*op).Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + } +} + +// Syntax returns the function's syntax (*ast.Func{Decl,Lit}) +// if it was produced from syntax or an *ast.RangeStmt if +// it is a range-over-func yield function. +func (f *Function) Syntax() ast.Node { return f.syntax } + +// identVar returns the variable defined by id. +func identVar(fn *Function, id *ast.Ident) *types.Var { + return fn.info.Defs[id].(*types.Var) +} + +// unique returns a unique positive int within the source tree of f. +// The source tree of f includes all of f's ancestors by parent and all +// of the AnonFuncs contained within these. +func unique(f *Function) int64 { + f.uniq++ + return f.uniq +} + +// exit is a change of control flow going from a range-over-func +// yield function to an ancestor function caused by a break, continue, +// goto, or return statement. +// +// There are 3 types of exits: +// * return from the source function (from ReturnStmt), +// * jump to a block (from break and continue statements [labelled/unlabelled]), +// * go to a label (from goto statements). +// +// As the builder does one pass over the ast, it is unclear whether +// a forward goto statement will leave a range-over-func body. +// The function being exited to is unresolved until the end +// of building the range-over-func body. +type exit struct { + id int64 // unique value for exit within from and to + from *Function // the function the exit starts from + to *Function // the function being exited to (nil if unresolved) + source ast.Node + + block *BasicBlock // basic block within to being jumped to. + label *types.Label // forward label being jumped to via goto. + // block == nil && label == nil => return +} + +// storeVar emits to function f code to store a value v to a *types.Var x. +func storeVar(f *Function, x *types.Var, v Value, source ast.Node) { + emitStore(f, f.lookup(x, true), v, source) +} + +// labelExit creates a new exit to a yield fn to exit the function using a label. +func labelExit(fn *Function, label *types.Label, source ast.Node) *exit { + e := &exit{ + id: unique(fn), + from: fn, + to: nil, + source: source, + label: label, + } + fn.exits = append(fn.exits, e) + return e +} + +// blockExit creates a new exit to a yield fn that jumps to a basic block. +func blockExit(fn *Function, block *BasicBlock, source ast.Node) *exit { + e := &exit{ + id: unique(fn), + from: fn, + to: block.parent, + source: source, + block: block, + } + fn.exits = append(fn.exits, e) + return e +} + +// returnExit creates a new exit to a yield fn that returns to the source function. +func returnExit(fn *Function, source ast.Node) *exit { + e := &exit{ + id: unique(fn), + from: fn, + to: fn.source, + source: source, + } + fn.exits = append(fn.exits, e) + return e +} diff --git a/vendor/honnef.co/go/tools/go/ir/instantiate.go b/vendor/honnef.co/go/tools/go/ir/instantiate.go new file mode 100644 index 0000000..bcb6d9a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/instantiate.go @@ -0,0 +1,161 @@ +// Copyright 2022 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 ir + +import ( + "fmt" + "go/types" + "slices" + "sync" +) + +// A generic records information about a generic origin function, +// including a cache of existing instantiations. +type generic struct { + instancesMu sync.Mutex + instances map[*typeList]*Function // canonical type arguments to an instance. +} + +// instance returns a Function that is the instantiation of generic +// origin function fn with the type arguments rtargs and targs. +// +// Any created instance is added to cr. +// +// Acquires fn.generic.instancesMu. +func (fn *Function) instance(rtargs, targs []types.Type, b *builder) *Function { + key := fn.Prog.canon.List(slices.Concat(rtargs, targs)) + + gen := fn.generic + + gen.instancesMu.Lock() + defer gen.instancesMu.Unlock() + inst, ok := gen.instances[key] + if !ok { + inst = createInstance(fn, rtargs, targs) + inst.buildshared = b.shared() + b.enqueue(inst) + + if gen.instances == nil { + gen.instances = make(map[*typeList]*Function) + } + gen.instances[key] = inst + } else { + b.waitForSharedFunction(inst) + } + return inst +} + +// createInstance returns the instantiation of generic function fn using targs. +// If fn is a method on a generic type, fn's receiver type will be instantiated +// using rtargs. +// +// Requires fn.generic.instancesMu. +func createInstance(fn *Function, rtargs, targs []types.Type) *Function { + prog := fn.Prog + + // Compute signature. + var sig *types.Signature + var obj *types.Func + if recv := fn.Signature.Recv(); recv != nil { + // method, len(rtargs) > 0 || len(targs) > 0 + if len(rtargs) > 0 { + // possibly generic method on generic type + obj = prog.canon.instantiateMethod(fn.object, rtargs, prog.ctxt) + } else { + // generic method on non-generic type + obj = fn.object // instantiation does not exist yet + } + if len(targs) > 0 { + // generic method + instSig, err := types.Instantiate(prog.ctxt, obj.Signature(), targs, false) + if err != nil { + panic(err) + } + instance, ok := instSig.(*types.Signature) + if !ok { + panic("Instantiate of a Signature returned a non-signature") + } + // Do not canonicalize generic methods, because the receiver is not + // part of a Signature's type identity. For example, + // (*G[int]).m[int] and (G[int]).n[int] may be identical types even + // though their receiver types differ. + sig = instance + } else { + // non-generic method on generic type + sig = obj.Signature() + } + } else { + // function, len(rtargs) == 0 && len(targs) > 0 + instSig, err := types.Instantiate(prog.ctxt, fn.Signature, targs, false) + if err != nil { + panic(err) + } + instance, ok := instSig.(*types.Signature) + if !ok { + panic("Instantiate of a Signature returned a non-signature") + } + obj = fn.object // instantiation does not exist yet + sig = prog.canon.Type(instance).(*types.Signature) + } + + // Choose strategy (instance or wrapper). + var ( + synthetic string + subst *subster + build buildFunc + ) + if prog.mode&InstantiateGenerics != 0 && !prog.isParameterized(slices.Concat(rtargs, targs)...) { + synthetic = fmt.Sprintf("instance of %s", fn.Name()) + if fn.syntax != nil { + subst = makeSubster(prog.ctxt, obj, fn.recvtypeparams, rtargs, fn.typeparams, targs) + build = (*builder).buildFromSyntax + } else { + build = (*builder).buildParamsOnly + } + } else { + synthetic = fmt.Sprintf("instantiation wrapper of %s", fn.Name()) + build = (*builder).buildInstantiationWrapper + } + + name := fn.Name() + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) // may not be unique + } + + /* generic instance or instantiation wrapper */ + ret := &Function{ + name: name, + object: obj, + Signature: sig, + Synthetic: synthetic, + syntax: fn.syntax, // \ + info: fn.info, // } empty for non-created packages + goversion: fn.goversion, // / + build: build, + topLevelOrigin: fn, + pos: obj.Pos(), + Pkg: nil, + Prog: fn.Prog, + recvtypeparams: fn.recvtypeparams, // share with origin + recvtypeargs: rtargs, + typeparams: fn.typeparams, // share with origin + typeargs: targs, + subst: subst, + } + return ret +} + +// isParameterized reports whether any of the specified types contains +// a free type parameter. It is safe to call concurrently. +func (prog *Program) isParameterized(ts ...types.Type) bool { + prog.hasParamsMu.Lock() + defer prog.hasParamsMu.Unlock() + + // TODO(adonovan): profile. If this operation is expensive, + // handle the most common but shallow cases such as T, pkg.T, + // *T without consulting the cache under the lock. + + return slices.ContainsFunc(ts, prog.hasParams.Has) +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/load.go b/vendor/honnef.co/go/tools/go/ir/irutil/load.go new file mode 100644 index 0000000..49eaa72 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/load.go @@ -0,0 +1,189 @@ +// Copyright 2015 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 irutil + +// This file defines utility functions for constructing programs in IR form. + +import ( + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/go/ir" + + "golang.org/x/tools/go/packages" +) + +// Packages creates an IR program for a set of packages. +// +// The packages must have been loaded from source syntax using the +// golang.org/x/tools/go/packages.Load function in LoadSyntax or +// LoadAllSyntax mode. +// +// Packages creates an IR package for each well-typed package in the +// initial list, plus all their dependencies. The resulting list of +// packages corresponds to the list of initial packages, and may contain +// a nil if IR code could not be constructed for the corresponding initial +// package due to type errors. +// +// Code for bodies of functions is not built until Build is called on +// the resulting Program. IR code is constructed only for the initial +// packages with well-typed syntax trees. +// +// The mode parameter controls diagnostics and checking during IR construction. +func Packages(initial []*packages.Package, mode ir.BuilderMode) (*ir.Program, []*ir.Package) { + // TODO(adonovan): opt: this calls CreatePackage far more than + // necessary: for all dependencies, not just the (non-initial) + // direct dependencies of the initial packages. + // + // But can it reasonably be changed without breaking the + // spirit and/or letter of the law above? Clients may notice + // if we call CreatePackage less, as methods like + // Program.FuncValue will return nil. Or must we provide a new + // function (and perhaps deprecate this one)? Is it worth it? + // + // Tim King makes the interesting point that it would be + // possible to entirely alleviate the client from the burden + // of calling CreatePackage for non-syntax packages, if we + // were to treat vars and funcs lazily in the same way we now + // treat methods. (In essence, try to move away from the + // notion of ir.Packages, and make the Program answer + // all reasonable questions about any types.Object.) + + return doPackages(initial, mode, false) +} + +// AllPackages creates an IR program for a set of packages plus all +// their dependencies. +// +// The packages must have been loaded from source syntax using the +// golang.org/x/tools/go/packages.Load function in LoadAllSyntax mode. +// +// AllPackages creates an IR package for each well-typed package in the +// initial list, plus all their dependencies. The resulting list of +// packages corresponds to the list of initial packages, and may contain +// a nil if IR code could not be constructed for the corresponding +// initial package due to type errors. +// +// Code for bodies of functions is not built until Build is called on +// the resulting Program. IR code is constructed for all packages with +// well-typed syntax trees. +// +// The mode parameter controls diagnostics and checking during IR construction. +func AllPackages(initial []*packages.Package, mode ir.BuilderMode) (*ir.Program, []*ir.Package) { + return doPackages(initial, mode, true) +} + +func doPackages(initial []*packages.Package, mode ir.BuilderMode, deps bool) (*ir.Program, []*ir.Package) { + var fset *token.FileSet + if len(initial) > 0 { + fset = initial[0].Fset + } + + prog := ir.NewProgram(fset, mode) + + isInitial := make(map[*packages.Package]bool, len(initial)) + for _, p := range initial { + isInitial[p] = true + } + + irmap := make(map[*packages.Package]*ir.Package) + packages.Visit(initial, nil, func(p *packages.Package) { + if p.Types != nil && !p.IllTyped { + var files []*ast.File + var info *types.Info + if deps || isInitial[p] { + files = p.Syntax + info = p.TypesInfo + } + irmap[p] = prog.CreatePackage(p.Types, files, info, true) + } + }) + + var irpkgs []*ir.Package + for _, p := range initial { + irpkgs = append(irpkgs, irmap[p]) // may be nil + } + return prog, irpkgs +} + +// BuildPackage builds an IR program with IR intermediate +// representation (IR) for all functions of a single package. +// +// It populates pkg by type-checking the specified file ASTs. All +// dependencies are loaded using the importer specified by tc, which +// typically loads compiler export data; IR code cannot be built for +// those packages. BuildPackage then constructs an ir.Program with all +// dependency packages created, and builds and returns the IR package +// corresponding to pkg. +// +// The caller must have set pkg.Path() to the import path. +// +// The operation fails if there were any type-checking or import errors. +// +// See ../ir/example_test.go for an example. +func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ir.BuilderMode) (*ir.Package, *types.Info, error) { + if fset == nil { + panic("no token.FileSet") + } + if pkg.Path() == "" { + panic("package has no import path") + } + + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Instances: make(map[*ast.Ident]types.Instance), + FileVersions: make(map[*ast.File]string), + } + if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { + return nil, nil, err + } + + prog := ir.NewProgram(fset, mode) + + // Create IR packages for all imports. + // Order is not significant. + created := make(map[*types.Package]bool) + var createAll func(pkgs []*types.Package) + createAll = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !created[p] { + created[p] = true + prog.CreatePackage(p, nil, nil, true) + createAll(p.Imports()) + } + } + } + createAll(pkg.Imports()) + + // TODO(adonovan): we could replace createAll with just: + // + // // Create IR packages for all imports. + // for _, p := range pkg.Imports() { + // prog.CreatePackage(p, nil, nil, true) + // } + // + // (with minor changes to changes to ../builder_test.go as + // shown in CL 511715 PS 10.) But this would strictly violate + // the letter of the doc comment above, which says "all + // dependencies created". + // + // Tim makes the good point with some extra work we could + // remove the need for any CreatePackage calls except the + // ones with syntax (i.e. primary packages). Of course + // You wouldn't have ir.Packages and Members for as + // many things but no-one really uses that anyway. + // I wish I had done this from the outset. + + // Create and build the primary package. + irpkg := prog.CreatePackage(pkg, files, info, false) + irpkg.Build() + return irpkg, info, nil +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/loops.go b/vendor/honnef.co/go/tools/go/ir/irutil/loops.go new file mode 100644 index 0000000..751cc68 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/loops.go @@ -0,0 +1,54 @@ +package irutil + +import "honnef.co/go/tools/go/ir" + +type Loop struct{ *ir.BlockSet } + +func FindLoops(fn *ir.Function) []Loop { + if fn.Blocks == nil { + return nil + } + tree := fn.DomPreorder() + var sets []Loop + for _, h := range tree { + for _, n := range h.Preds { + if !h.Dominates(n) { + continue + } + // n is a back-edge to h + // h is the loop header + if n == h { + set := Loop{ir.NewBlockSet(len(fn.Blocks))} + set.Add(n) + sets = append(sets, set) + continue + } + set := Loop{ir.NewBlockSet(len(fn.Blocks))} + set.Add(h) + set.Add(n) + for _, b := range allPredsBut(n, h, nil) { + set.Add(b) + } + sets = append(sets, set) + } + } + return sets +} + +func allPredsBut(b, but *ir.BasicBlock, list []*ir.BasicBlock) []*ir.BasicBlock { +outer: + for _, pred := range b.Preds { + if pred == but { + continue + } + for _, p := range list { + // TODO improve big-o complexity of this function + if pred == p { + continue outer + } + } + list = append(list, pred) + list = allPredsBut(pred, but, list) + } + return list +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/stub.go b/vendor/honnef.co/go/tools/go/ir/irutil/stub.go new file mode 100644 index 0000000..a686ede --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/stub.go @@ -0,0 +1,97 @@ +package irutil + +import ( + "honnef.co/go/tools/go/ir" +) + +// IsStub reports whether a function is a stub. A function is +// considered a stub if it has no instructions or if all it does is +// return a fixed value (either an actual constant or a new allocation). +func IsStub(fn *ir.Function) bool { + if fn.Source() == nil { + // External functions have to be assumed to not be stubs. + return false + } + + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.Panic: + // panic is a stub if it only uses constants + case *ir.Return: + // return is a stub if it only uses constants + case *ir.Jump: + // if there are no disallowed instructions, then we're + // only jumping to the exit block (or possibly + // somewhere else that's stubby?) + case *ir.MakeInterface: + // this can only be wrapping Const and HeapAlloc, which is fine. + case *ir.Alloc: + // allocating is fine as long as all we do with the allocation + // is return it (possibly in an interface value) + default: + // all other instructions are assumed to do actual work + return false + } + } + } + return true +} + +// IsTrivial reports whether a function is trivial. A function is +// considered trivial if it is a stub, only returns a global variable, or calls +// another trivial function. +func IsTrivial(fn *ir.Function) bool { + return isTrivial(fn, nil) +} + +func isTrivial(fn *ir.Function, seen map[*ir.Function]struct{}) bool { + if fn.Source() == nil { + // External functions have to be assumed to be nontrivial. + return false + } + + if _, ok := seen[fn]; ok { + // Mutual recursion is not trivial + return false + } + + // We delay adding to seen until a call, to avoid creating garbage in the + // common case. + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + switch instr := instr.(type) { + case *ir.Panic: + // panic is a stub if it only uses constants + case *ir.Return: + // return is a stub if it only uses constants + case *ir.Jump: + // if there are no disallowed instructions, then we're + // only jumping to the exit block (or possibly + // somewhere else that's stubby?) + case *ir.MakeInterface: + // this can only be wrapping allowed instructions + case *ir.Alloc: + // allocating is fine as long as all we do with the allocation + // is return it (possibly in an interface value) + case *ir.Load: + if _, ok := instr.X.(*ir.Global); !ok { + return false + } + case *ir.Call: + if seen == nil { + seen = make(map[*ir.Function]struct{}) + } + seen[fn] = struct{}{} + callee := instr.Call.StaticCallee() + if callee == nil || !isTrivial(callee, seen) { + return false + } + default: + // all other instructions are assumed to do actual work + return false + } + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/switch.go b/vendor/honnef.co/go/tools/go/ir/irutil/switch.go new file mode 100644 index 0000000..cf7c54e --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/switch.go @@ -0,0 +1,250 @@ +// Copyright 2013 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 irutil + +// This file implements discovery of switch and type-switch constructs +// from low-level control flow. +// +// Many techniques exist for compiling a high-level switch with +// constant cases to efficient machine code. The optimal choice will +// depend on the data type, the specific case values, the code in the +// body of each case, and the hardware. +// Some examples: +// - a lookup table (for a switch that maps constants to constants) +// - a computed goto +// - a binary tree +// - a perfect hash +// - a two-level switch (to partition constant strings by their first byte). + +import ( + "bytes" + "fmt" + "go/token" + "go/types" + + "honnef.co/go/tools/go/ir" +) + +// A ConstCase represents a single constant comparison. +// It is part of a Switch. +type ConstCase struct { + Block *ir.BasicBlock // block performing the comparison + Body *ir.BasicBlock // body of the case + Value *ir.Const // case comparand +} + +// A TypeCase represents a single type assertion. +// It is part of a Switch. +type TypeCase struct { + Block *ir.BasicBlock // block performing the type assert + Body *ir.BasicBlock // body of the case + Type types.Type // case type + Binding ir.Value // value bound by this case +} + +// A Switch is a logical high-level control flow operation +// (a multiway branch) discovered by analysis of a CFG containing +// only if/else chains. It is not part of the ir.Instruction set. +// +// One of ConstCases and TypeCases has length >= 2; +// the other is nil. +// +// In a value switch, the list of cases may contain duplicate constants. +// A type switch may contain duplicate types, or types assignable +// to an interface type also in the list. +// TODO(adonovan): eliminate such duplicates. +type Switch struct { + Start *ir.BasicBlock // block containing start of if/else chain + X ir.Value // the switch operand + ConstCases []ConstCase // ordered list of constant comparisons + TypeCases []TypeCase // ordered list of type assertions + Default *ir.BasicBlock // successor if all comparisons fail +} + +func (sw *Switch) String() string { + // We represent each block by the String() of its + // first Instruction, e.g. "print(42:int)". + var buf bytes.Buffer + if sw.ConstCases != nil { + fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) + for _, c := range sw.ConstCases { + fmt.Fprintf(&buf, "case %s: %s\n", c.Value.Name(), c.Body.Instrs[0]) + } + } else { + fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) + for _, c := range sw.TypeCases { + fmt.Fprintf(&buf, "case %s %s: %s\n", + c.Binding.Name(), c.Type, c.Body.Instrs[0]) + } + } + if sw.Default != nil { + fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) + } + fmt.Fprintf(&buf, "}") + return buf.String() +} + +// Switches examines the control-flow graph of fn and returns the +// set of inferred value and type switches. A value switch tests an +// ir.Value for equality against two or more compile-time constant +// values. Switches involving link-time constants (addresses) are +// ignored. A type switch type-asserts an ir.Value against two or +// more types. +// +// The switches are returned in dominance order. +// +// The resulting switches do not necessarily correspond to uses of the +// 'switch' keyword in the source: for example, a single source-level +// switch statement with non-constant cases may result in zero, one or +// many Switches, one per plural sequence of constant cases. +// Switches may even be inferred from if/else- or goto-based control flow. +// (In general, the control flow constructs of the source program +// cannot be faithfully reproduced from the IR.) +func Switches(fn *ir.Function) []Switch { + // Traverse the CFG in dominance order, so we don't + // enter an if/else-chain in the middle. + var switches []Switch + seen := make(map[*ir.BasicBlock]bool) // TODO(adonovan): opt: use ir.blockSet + for _, b := range fn.DomPreorder() { + if x, k := isComparisonBlock(b); x != nil { + // Block b starts a switch. + sw := Switch{Start: b, X: x} + valueSwitch(&sw, k, seen) + if len(sw.ConstCases) > 1 { + switches = append(switches, sw) + } + } + + if y, x, T := isTypeAssertBlock(b); y != nil { + // Block b starts a type switch. + sw := Switch{Start: b, X: x} + typeSwitch(&sw, y, T, seen) + if len(sw.TypeCases) > 1 { + switches = append(switches, sw) + } + } + } + return switches +} + +func valueSwitch(sw *Switch, k *ir.Const, seen map[*ir.BasicBlock]bool) { + b := sw.Start + x := sw.X + for sw.X == x { + if seen[b] { + break + } + seen[b] = true + + sw.ConstCases = append(sw.ConstCases, ConstCase{ + Block: b, + Body: b.Succs[0], + Value: k, + }) + b = b.Succs[1] + n := 0 + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.If, *ir.BinOp: + n++ + case *ir.Phi: + default: + n += 1000 + } + } + if n != 2 { + // Block b contains not just 'if x == k' and ϕ nodes, + // so it may have side effects that + // make it unsafe to elide. + break + } + if len(b.Preds) != 1 { + // Block b has multiple predecessors, + // so it cannot be treated as a case. + break + } + x, k = isComparisonBlock(b) + } + sw.Default = b +} + +func typeSwitch(sw *Switch, y ir.Value, T types.Type, seen map[*ir.BasicBlock]bool) { + b := sw.Start + x := sw.X + for sw.X == x { + if seen[b] { + break + } + seen[b] = true + + sw.TypeCases = append(sw.TypeCases, TypeCase{ + Block: b, + Body: b.Succs[0], + Type: T, + Binding: y, + }) + b = b.Succs[1] + n := 0 + for _, instr := range b.Instrs { + switch instr.(type) { + case *ir.TypeAssert, *ir.Extract, *ir.If: + n++ + case *ir.Phi: + default: + n += 1000 + } + } + if n != 4 { + // Block b contains not just + // {TypeAssert; Extract #0; Extract #1; If} + // so it may have side effects that + // make it unsafe to elide. + break + } + if len(b.Preds) != 1 { + // Block b has multiple predecessors, + // so it cannot be treated as a case. + break + } + y, x, T = isTypeAssertBlock(b) + } + sw.Default = b +} + +// isComparisonBlock returns the operands (v, k) if a block ends with +// a comparison v==k, where k is a compile-time constant. +func isComparisonBlock(b *ir.BasicBlock) (v ir.Value, k *ir.Const) { + if n := len(b.Instrs); n >= 2 { + if i, ok := b.Instrs[n-1].(*ir.If); ok { + if binop, ok := i.Cond.(*ir.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { + if k, ok := binop.Y.(*ir.Const); ok { + return binop.X, k + } + if k, ok := binop.X.(*ir.Const); ok { + return binop.Y, k + } + } + } + } + return +} + +// isTypeAssertBlock returns the operands (y, x, T) if a block ends with +// a type assertion "if y, ok := x.(T); ok {". +func isTypeAssertBlock(b *ir.BasicBlock) (y, x ir.Value, T types.Type) { + if n := len(b.Instrs); n >= 4 { + if i, ok := b.Instrs[n-1].(*ir.If); ok { + if ext1, ok := i.Cond.(*ir.Extract); ok && ext1.Block() == b && ext1.Index == 1 { + if ta, ok := ext1.Tuple.(*ir.TypeAssert); ok && ta.Block() == b { + // hack: relies upon instruction ordering. + if ext0, ok := b.Instrs[n-3].(*ir.Extract); ok { + return ext0, ta.X, ta.AssertedType + } + } + } + } + } + return +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/terminates.go b/vendor/honnef.co/go/tools/go/ir/irutil/terminates.go new file mode 100644 index 0000000..84e7503 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/terminates.go @@ -0,0 +1,70 @@ +package irutil + +import ( + "go/types" + + "honnef.co/go/tools/go/ir" +) + +// Terminates reports whether fn is supposed to return, that is if it +// has at least one theoretic path that returns from the function. +// Explicit panics do not count as terminating. +func Terminates(fn *ir.Function) bool { + if fn.Blocks == nil { + // assuming that a function terminates is the conservative + // choice + return true + } + + for _, block := range fn.Blocks { + if _, ok := block.Control().(*ir.Return); ok { + if len(block.Preds) == 0 { + return true + } + for _, pred := range block.Preds { + switch ctrl := pred.Control().(type) { + case *ir.Panic: + // explicit panics do not count as terminating + case *ir.If: + // Check if we got here by receiving from a closed + // time.Tick channel – this cannot happen at + // runtime and thus doesn't constitute termination + iff := ctrl + if !ok { + return true + } + ex, ok := iff.Cond.(*ir.Extract) + if !ok { + return true + } + if ex.Index != 1 { + return true + } + recv, ok := ex.Tuple.(*ir.Recv) + if !ok { + return true + } + call, ok := recv.Chan.(*ir.Call) + if !ok { + return true + } + fn, ok := call.Common().Value.(*ir.Function) + if !ok { + return true + } + fn2, ok := fn.Object().(*types.Func) + if !ok { + return true + } + if fn2.FullName() != "time.Tick" { + return true + } + default: + // we've reached the exit block + return true + } + } + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/util.go b/vendor/honnef.co/go/tools/go/ir/irutil/util.go new file mode 100644 index 0000000..c8b980b --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/util.go @@ -0,0 +1,151 @@ +package irutil + +import ( + "go/types" + "slices" + "strings" + + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" +) + +func Reachable(from, to *ir.BasicBlock) bool { + if from == to { + return true + } + if from.Dominates(to) { + return true + } + + found := false + Walk(from, func(b *ir.BasicBlock) bool { + if b == to { + found = true + return false + } + return true + }) + return found +} + +func Walk(b *ir.BasicBlock, fn func(*ir.BasicBlock) bool) { + seen := map[*ir.BasicBlock]bool{} + wl := []*ir.BasicBlock{b} + for len(wl) > 0 { + b := wl[len(wl)-1] + wl = wl[:len(wl)-1] + if seen[b] { + continue + } + seen[b] = true + if !fn(b) { + continue + } + wl = append(wl, b.Succs...) + } +} + +func Vararg(x *ir.Slice) ([]ir.Value, bool) { + var out []ir.Value + alloc, ok := x.X.(*ir.Alloc) + if !ok { + return nil, false + } + for _, ref := range *alloc.Referrers() { + if ref == x { + continue + } + if ref.Block() != x.Block() { + return nil, false + } + switch ref := ref.(type) { + case *ir.IndexAddr: + idx := ref + if len(*idx.Referrers()) != 1 { + return nil, false + } + store, ok := (*idx.Referrers())[0].(*ir.Store) + if !ok { + return nil, false + } + out = append(out, store.Val) + default: + return nil, false + } + } + return out, true +} + +func CallName(call *ir.CallCommon) string { + if call.IsInvoke() { + return "" + } + switch v := call.Value.(type) { + case *ir.Function: + fn, ok := v.Object().(*types.Func) + if !ok { + return "" + } + return typeutil.FuncName(fn) + case *ir.Builtin: + return v.Name() + } + return "" +} + +func IsCallTo(call *ir.CallCommon, name string) bool { return CallName(call) == name } + +func IsCallToAny(call *ir.CallCommon, names ...string) bool { + q := CallName(call) + return slices.Contains(names, q) +} + +func IsExample(fn *ir.Function) bool { + if !strings.HasPrefix(fn.Name(), "Example") { + return false + } + f := fn.Prog.Fset.File(fn.Pos()) + if f == nil { + return false + } + return strings.HasSuffix(f.Name(), "_test.go") +} + +// Flatten recursively returns the underlying value of an +// ir.Phi node. If all edges in an ir.Phi node are the same (after +// flattening), the flattened edge will get returned. If flattening is +// not possible, nil is returned. +func Flatten(v ir.Value) ir.Value { + failed := false + seen := map[ir.Value]struct{}{} + var out ir.Value + var dfs func(v ir.Value) + dfs = func(v ir.Value) { + if failed { + return + } + if _, ok := seen[v]; ok { + return + } + seen[v] = struct{}{} + + switch v := v.(type) { + case *ir.Phi: + for _, e := range v.Edges { + dfs(e) + } + default: + if out == nil { + out = v + } else if out != v { + failed = true + } + } + } + dfs(v) + + if failed { + return nil + } + return out +} diff --git a/vendor/honnef.co/go/tools/go/ir/irutil/visit.go b/vendor/honnef.co/go/tools/go/ir/irutil/visit.go new file mode 100644 index 0000000..ef438a9 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/irutil/visit.go @@ -0,0 +1,166 @@ +// Copyright 2013 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 irutil // import "honnef.co/go/tools/go/ir/irutil" + +import ( + "go/ast" + "go/types" + + _ "unsafe" // for linkname hack + + "honnef.co/go/tools/go/ir" +) + +// This file defines utilities for visiting the IR of +// a Program. +// +// TODO(adonovan): test coverage. + +// AllFunctions finds and returns the set of functions potentially +// needed by program prog, as determined by a simple linker-style +// reachability algorithm starting from the members and method-sets of +// each package. The result may include anonymous functions and +// synthetic wrappers. +// +// Precondition: all packages are built. +// +// TODO(adonovan): this function is underspecified. It doesn't +// actually work like a linker, which computes reachability from main +// using something like go/callgraph/cha (without materializing the +// call graph). In fact, it treats all public functions and all +// methods of public non-parameterized types as roots, even though +// they may be unreachable--but only in packages created from syntax. +// +// I think we should deprecate AllFunctions function in favor of two +// clearly defined ones: +// +// 1. The first would efficiently compute CHA reachability from a set +// of main packages, making it suitable for a whole-program +// analysis context with InstantiateGenerics, in conjunction with +// Program.Build. +// +// 2. The second would return only the set of functions corresponding +// to source Func{Decl,Lit} syntax, like SrcFunctions in +// go/analysis/passes/buildssa; this is suitable for +// package-at-a-time (or handful of packages) context. +// ir.Package could easily expose it as a field. +// +// We could add them unexported for now and use them via the linkname hack. +func AllFunctions(prog *ir.Program) map[*ir.Function]bool { + seen := make(map[*ir.Function]bool) + + var function func(fn *ir.Function) + function = func(fn *ir.Function) { + if !seen[fn] { + seen[fn] = true + var buf [10]*ir.Value // avoid alloc in common case + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + for _, op := range instr.Operands(buf[:0]) { + if fn, ok := (*op).(*ir.Function); ok { + function(fn) + } + } + } + } + } + } + + // TODO(adonovan): opt: provide a way to share a builder + // across a sequence of MethodValue calls. + + methodsOf := func(T types.Type) { + if !types.IsInterface(T) { + mset := prog.MethodSets.MethodSet(T) + for sel := range mset.Methods() { + // Skip generic methods. + if sel.Obj().(*types.Func).Signature().TypeParams() == nil { + function(prog.MethodValue(sel)) + } + } + } + } + + // Historically, Program.RuntimeTypes used to include the type + // of any exported member of a package loaded from syntax that + // has a non-parameterized type, plus all types + // reachable from that type using reflection, even though + // these runtime types may not be required for them. + // + // Rather than break existing programs that rely on + // AllFunctions visiting extra methods that are unreferenced + // by IR and unreachable via reflection, we moved the logic + // here, unprincipled though it is. + // (See doc comment for better ideas.) + // + // Nonetheless, after the move, we no longer visit every + // method of any type recursively reachable from T, only the + // methods of T and *T themselves, and we only apply this to + // named types T, and not to the type of every exported + // package member. + exportedTypeHack := func(t *ir.Type) { + if isSyntactic(t.Package()) && + ast.IsExported(t.Name()) && + !types.IsInterface(t.Type()) { + // Consider only named types. + // (Ignore aliases and unsafe.Pointer.) + if named, ok := t.Type().(*types.Named); ok { + // Skip generic types. + if named.TypeParams() == nil { + methodsOf(named) // T + methodsOf(types.NewPointer(named)) // *T + } + } + } + } + + for _, pkg := range prog.AllPackages() { + for _, mem := range pkg.Members { + switch mem := mem.(type) { + case *ir.Function: + // Visit all package-level declared functions. + // + // (This may include generic functions, which is + // inconsistent with the treatment of methods: + // we skip both generic methods, + // and methods of generic types.) + function(mem) + + case *ir.Type: + exportedTypeHack(mem) + } + } + } + + // Visit all methods of types for which runtime types were + // materialized, as they are reachable through reflection. + for _, T := range prog.RuntimeTypes() { + methodsOf(T) + } + + return seen +} + +// MainPackages returns the subset of the specified packages +// named "main" that define a main function. +// The result may include synthetic "testmain" packages. +func MainPackages(pkgs []*ir.Package) []*ir.Package { + var mains []*ir.Package + for _, pkg := range pkgs { + if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { + mains = append(mains, pkg) + } + } + return mains +} + +// TODO(adonovan): propose a principled API for this. One possibility +// is a new field, Package.SrcFunctions []*Function, which would +// contain the list of SrcFunctions described in point 2 of the +// AllFunctions doc comment, or nil if the package is not from syntax. +// But perhaps overloading nil vs empty slice is too subtle. +// +//go:linkname isSyntactic honnef.co/go/tools/go/ir.isSyntactic +func isSyntactic(pkg *ir.Package) bool diff --git a/vendor/honnef.co/go/tools/go/ir/lift.go b/vendor/honnef.co/go/tools/go/ir/lift.go new file mode 100644 index 0000000..61324b0 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/lift.go @@ -0,0 +1,1261 @@ +// Copyright 2013 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 ir + +// This file defines the lifting pass which tries to "lift" Alloc +// cells (new/local variables) into SSA registers, replacing loads +// with the dominating stored value, eliminating loads and stores, and +// inserting φ-nodes as needed. + +// Cited papers and resources: +// +// Ron Cytron et al. 1991. Efficiently computing SSA form... +// https://doi.acm.org/10.1145/115372.115320 +// +// Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm. +// Software Practice and Experience 2001, 4:1-10. +// https://www.hipersoft.rice.edu/grads/publications/dom14.pdf +// +// Daniel Berlin, llvmdev mailing list, 2012. +// https://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html +// (Be sure to expand the whole thread.) +// +// C. Scott Ananian. 1997. The static single information form. +// +// Jeremy Singer. 2006. Static program analysis based on virtual register renaming. + +// TODO(adonovan): opt: there are many optimizations worth evaluating, and +// the conventional wisdom for SSA construction is that a simple +// algorithm well engineered often beats those of better asymptotic +// complexity on all but the most egregious inputs. +// +// Danny Berlin suggests that the Cooper et al. algorithm for +// computing the dominance frontier is superior to Cytron et al. +// Furthermore he recommends that rather than computing the DF for the +// whole function then renaming all alloc cells, it may be cheaper to +// compute the DF for each alloc cell separately and throw it away. +// +// Consider exploiting liveness information to avoid creating dead +// φ-nodes which we then immediately remove. +// +// Also see many other "TODO: opt" suggestions in the code. + +import ( + "fmt" + "os" + "slices" +) + +// If true, show diagnostic information at each step of lifting. +// Very verbose. +const debugLifting = false + +// domFrontier maps each block to the set of blocks in its dominance +// frontier. The outer slice is conceptually a map keyed by +// Block.Index. The inner slice is conceptually a set, possibly +// containing duplicates. +// +// TODO(adonovan): opt: measure impact of dups; consider a packed bit +// representation, e.g. big.Int, and bitwise parallel operations for +// the union step in the Children loop. +// +// domFrontier's methods mutate the slice's elements but not its +// length, so their receivers needn't be pointers. +type domFrontier BlockMap[[]*BasicBlock] + +func (df domFrontier) add(u, v *BasicBlock) { + df[u.Index] = append(df[u.Index], v) +} + +// build builds the dominance frontier df for the dominator tree of +// fn, using the algorithm found in A Simple, Fast Dominance +// Algorithm, Figure 5. +// +// TODO(adonovan): opt: consider Berlin approach, computing pruned SSA +// by pruning the entire IDF computation, rather than merely pruning +// the DF -> IDF step. +func (df domFrontier) build(fn *Function) { + for _, b := range fn.Blocks { + if len(b.Preds) >= 2 { + for _, p := range b.Preds { + runner := p + for runner != b.dom.idom { + df.add(runner, b) + runner = runner.dom.idom + } + } + } + } +} + +func buildDomFrontier(fn *Function) domFrontier { + df := make(domFrontier, len(fn.Blocks)) + df.build(fn) + return df +} + +func removeInstr(refs []Instruction, instr Instruction) []Instruction { + s := refs + i := -1 + for j := range s { + if instr == s[j] { + i = j + break + } + } + if i == -1 { + return s + } + // Don't start copying elements until we find one to delete. + for j := i + 1; j < len(s); j++ { + if v := s[j]; v != instr { + s[i] = v + i++ + } + } + clear(s[i:]) // zero/nil out the obsolete elements, for GC + return s[:i] +} + +func clearInstrs(instrs []Instruction) { + for i := range instrs { + instrs[i] = nil + } +} + +func numberNodesPerBlock(f *Function) { + for _, b := range f.Blocks { + var base ID + for _, instr := range b.Instrs { + if instr == nil { + continue + } + instr.setID(base) + base++ + } + } +} + +// lift replaces local and new Allocs accessed only with +// load/store by IR registers, inserting φ-nodes where necessary. +// The result is a program in pruned SSA form. +// +// Preconditions: +// - fn has no dead blocks (blockopt has run). +// - Def/use info (Operands and Referrers) is up-to-date. +// - The dominator tree is up-to-date. +func lift(fn *Function) bool { + // TODO(adonovan): opt: lots of little optimizations may be + // worthwhile here, especially if they cause us to avoid + // buildDomFrontier. For example: + // + // - Alloc never loaded? Eliminate. + // - Alloc never stored? Replace all loads with a zero constant. + // - Alloc stored once? Replace loads with dominating store; + // don't forget that an Alloc is itself an effective store + // of zero. + // - Alloc used only within a single block? + // Use degenerate algorithm avoiding φ-nodes. + // - Consider synergy with scalar replacement of aggregates (SRA). + // e.g. *(&x.f) where x is an Alloc. + // Perhaps we'd get better results if we generated this as x.f + // i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)). + // Unclear. + // + // But we will start with the simplest correct code. + var df domFrontier + var newPhis BlockMap[[]newPhi] + + // During this pass we will replace some BasicBlock.Instrs + // (allocs, loads and stores) with nil, keeping a count in + // BasicBlock.gaps. At the end we will reset Instrs to the + // concatenation of all non-dead newPhis and non-nil Instrs + // for the block, reusing the original array if space permits. + + // While we're here, we also eliminate 'rundefers' + // instructions and ssa:deferstack() in functions that contain no + // 'defer' instructions. Eliminate ssa:deferstack() if it does not + // escape. + usesDefer := false + deferstackAlloc, deferstackCall := deferstackPreamble(fn) + eliminateDeferStack := deferstackAlloc != nil && !deferstackAlloc.Heap + + // Determine which allocs we can lift and number them densely. + // The renaming phase uses this numbering for compact maps. + numAllocs := 0 + + instructions := make(BlockMap[liftInstructions], len(fn.Blocks)) + for i := range instructions { + instructions[i].insertInstructions = map[Instruction][]Instruction{} + } + + // Number nodes, for liftable + numberNodesPerBlock(fn) + + heads := make(BlockMap[int], len(fn.Blocks)) + for i := range heads { + heads[i] = -1 + } + for _, b := range fn.Blocks { + for i, instr := range b.Instrs { + if _, ok := instr.(*Phi); !ok { + heads[b.Index] = i + break + } + } + } + + for _, b := range fn.Blocks { + b.gaps = 0 + b.rundefers = 0 + + for _, instr := range b.Instrs { + switch instr := instr.(type) { + case *Alloc: + if !liftable(instr, instructions, heads) { + instr.index = -1 + continue + } + + if numAllocs == 0 { + df = buildDomFrontier(fn) + newPhis = make(BlockMap[[]newPhi], len(fn.Blocks)) + + if debugLifting { + title := false + for i, blocks := range df { + if blocks != nil { + if !title { + fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn) + title = true + } + fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks) + } + } + } + } + instr.index = numAllocs + numAllocs++ + case *Defer: + usesDefer = true + if eliminateDeferStack { + // Clear _DeferStack and remove references to loads + if instr.DeferStack != nil { + if refs := instr.DeferStack.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + instr.DeferStack = nil + } + } + case *RunDefers: + b.rundefers++ + } + } + } + + if numAllocs > 0 { + for _, b := range fn.Blocks { + work := instructions[b.Index] + for _, rename := range work.renameAllocs { + for _, instr_ := range b.Instrs[rename.startingAt:] { + replace(instr_, rename.from, rename.to) + } + } + } + + for _, b := range fn.Blocks { + work := instructions[b.Index] + if len(work.insertInstructions) != 0 { + newInstrs := make([]Instruction, 0, len(fn.Blocks)+len(work.insertInstructions)*3) + for _, instr := range b.Instrs { + if add, ok := work.insertInstructions[instr]; ok { + newInstrs = append(newInstrs, add...) + } + newInstrs = append(newInstrs, instr) + } + b.Instrs = newInstrs + } + } + + // TODO(dh): remove inserted allocs that end up unused after lifting. + + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + if instr, ok := instr.(*Alloc); ok && instr.index >= 0 { + liftAlloc(df, instr, newPhis) + } + } + } + + // renaming maps an alloc (keyed by index) to its replacement + // value. Initially the renaming contains nil, signifying the + // zero constant of the appropriate type; we construct the + // Const lazily at most once on each path through the domtree. + // TODO(adonovan): opt: cache per-function not per subtree. + renaming := make([]Value, numAllocs) + + // Renaming. + rename(fn.Blocks[0], renaming, newPhis) + + simplifyPhis(newPhis) + + // Eliminate dead φ-nodes. + markLiveNodes(fn.Blocks, newPhis) + + // Eliminate ssa:deferstack() call. + if eliminateDeferStack { + b := deferstackCall.block + for i, instr := range b.Instrs { + if instr == deferstackCall { + b.Instrs[i] = nil + b.gaps++ + break + } + } + } + } + + // Prepend remaining live φ-nodes to each block and possibly kill rundefers. + for _, b := range fn.Blocks { + var head []Instruction + if numAllocs > 0 { + nps := newPhis[b.Index] + head = make([]Instruction, 0, len(nps)) + for _, np := range nps { + if np.phi.live { + head = append(head, np.phi) + } else { + for _, edge := range np.phi.Edges { + if refs := edge.Referrers(); refs != nil { + *refs = removeInstr(*refs, np.phi) + } + } + np.phi.block = nil + } + } + } + + rundefersToKill := b.rundefers + if usesDefer { + rundefersToKill = 0 + } + + j := len(head) + if j+b.gaps+rundefersToKill == 0 { + continue // fast path: no new phis or gaps + } + + // We could do straight copies instead of element-wise copies + // when both b.gaps and rundefersToKill are zero. However, + // that seems to only be the case ~1% of the time, which + // doesn't seem worth the extra branch. + + // Remove dead instructions, add phis + ns := len(b.Instrs) + j - b.gaps - rundefersToKill + if ns <= cap(b.Instrs) { + // b.Instrs has enough capacity to store all instructions + + // OPT(dh): check cap vs the actually required space; if + // there is a big enough difference, it may be worth + // allocating a new slice, to avoid pinning memory. + dst := b.Instrs[:cap(b.Instrs)] + i := len(dst) - 1 + for n := len(b.Instrs) - 1; n >= 0; n-- { + instr := dst[n] + if instr == nil { + continue + } + if !usesDefer { + if _, ok := instr.(*RunDefers); ok { + continue + } + } + dst[i] = instr + i-- + } + off := i + 1 - len(head) + // aid GC + clearInstrs(dst[:off]) + dst = dst[off:] + copy(dst, head) + b.Instrs = dst + } else { + // not enough space, so allocate a new slice and copy + // over. + dst := make([]Instruction, ns) + copy(dst, head) + + for _, instr := range b.Instrs { + if instr == nil { + continue + } + if !usesDefer { + if _, ok := instr.(*RunDefers); ok { + continue + } + } + dst[j] = instr + j++ + } + b.Instrs = dst + } + } + + // Remove any fn.Locals that were lifted. + j := 0 + for _, l := range fn.Locals { + if l.index < 0 { + fn.Locals[j] = l + j++ + } + } + // Nil out fn.Locals[j:] to aid GC. + for i := j; i < len(fn.Locals); i++ { + fn.Locals[i] = nil + } + fn.Locals = fn.Locals[:j] + + return numAllocs > 0 +} + +func hasDirectReferrer(instr Instruction) bool { + for _, instr := range *instr.Referrers() { + if _, ok := instr.(*Phi); !ok { + return true + } + } + return false +} + +func markLiveNodes(blocks []*BasicBlock, newPhis BlockMap[[]newPhi]) { + // Phis may become dead due to optimization passes. + + // Phi nodes are considered live if a non-phi + // node uses them. Once we find a node that is live, we mark all + // of its operands as used, too. + for _, npList := range newPhis { + for _, np := range npList { + phi := np.phi + if !phi.live && hasDirectReferrer(phi) { + markLivePhi(phi) + } + } + } + // Existing φ-nodes due to && and || operators + // are all considered live (see Go issue 19622). + for _, b := range blocks { + for _, phi := range b.phis() { + markLivePhi(phi.(*Phi)) + } + } +} + +func markLivePhi(phi *Phi) { + phi.live = true + for _, rand := range phi.Edges { + if rand, ok := rand.(*Phi); ok { + if !rand.live { + markLivePhi(rand) + } + } + } +} + +// simplifyPhis replaces trivial phis with non-phi alternatives. Phi +// nodes where all edges are identical, or consist of only the phi +// itself and one other value, may be replaced with the value. +func simplifyPhis(newPhis BlockMap[[]newPhi]) { + // find all phis that are trivial and can be replaced with a + // non-phi value. run until we reach a fixpoint, because replacing + // a phi may make other phis trivial. + for changed := true; changed; { + changed = false + for _, npList := range newPhis { + for _, np := range npList { + if np.phi.live { + // we're reusing 'live' to mean 'dead' in the context of simplifyPhis + continue + } + if r, ok := isUselessPhi(np.phi); ok { + // useless phi, replace its uses with the + // replacement value. the dead phi pass will clean + // up the phi afterwards. + replaceAll(np.phi, r) + np.phi.live = true + changed = true + } + } + } + } +} + +type BlockSet struct { + idx int + values []bool + count int +} + +func NewBlockSet(size int) *BlockSet { + return &BlockSet{values: make([]bool, size)} +} + +func (s *BlockSet) Set(s2 *BlockSet) { + copy(s.values, s2.values) + s.count = 0 + for _, v := range s.values { + if v { + s.count++ + } + } +} + +func (s *BlockSet) Num() int { + return s.count +} + +func (s *BlockSet) Has(b *BasicBlock) bool { + if b.Index >= len(s.values) { + return false + } + return s.values[b.Index] +} + +// add adds b to the set and returns true if the set changed. +func (s *BlockSet) Add(b *BasicBlock) bool { + if s.values[b.Index] { + return false + } + s.count++ + s.values[b.Index] = true + s.idx = b.Index + + return true +} + +func (s *BlockSet) Clear() { + for j := range s.values { + s.values[j] = false + } + s.count = 0 +} + +// take removes an arbitrary element from a set s and +// returns its index, or returns -1 if empty. +func (s *BlockSet) Take() int { + // [i, end] + for i := s.idx; i < len(s.values); i++ { + if s.values[i] { + s.values[i] = false + s.idx = i + s.count-- + return i + } + } + + // [start, i) + for i := 0; i < s.idx; i++ { + if s.values[i] { + s.values[i] = false + s.idx = i + s.count-- + return i + } + } + + return -1 +} + +// newPhi is a pair of a newly introduced φ-node and the lifted Alloc +// it replaces. +type newPhi struct { + phi *Phi + alloc *Alloc +} + +type liftInstructions struct { + insertInstructions map[Instruction][]Instruction + renameAllocs []struct { + from *Alloc + to *Alloc + startingAt int + } +} + +type liftableBlockDesc struct { + // is the block (partially) unliftable, because it contains unliftable + // instructions or is reachable by an unliftable block + isUnliftable bool + hasLiftableLoad bool + hasLiftableOther bool + // we need to emit stores in predecessors because the unliftable use is in + // a phi + storeInPreds bool + + lastLiftable int + firstUnliftable int +} + +// liftable determines if alloc can be lifted, and records instructions to split partially liftable allocs. +// +// In the trivial case, all uses of the alloc can be lifted. This is the case when it is only used for storing into and +// loading from. In that case, no instructions are recorded. +// +// In the more complex case, the alloc is used for storing into and loading from, but it is also used as a value, for +// example because it gets passed to a function, e.g. fn(&x). In this case, uses of the alloc fall into one of two +// categories: those that can be lifted and those that can't. A boundary forms between these two categories in the +// function's control flow: Once an unliftable use is encountered, the alloc is no longer liftable for the remainder of +// the basic block the use is in, nor in any blocks reachable from it. +// +// We record instructions that split the alloc into two allocs: one that is used in liftable uses, and one that is used +// in unliftable uses. Whenever we encounter a boundary between liftable and unliftable uses or blocks, we emit a pair +// of Load and Store that copy the value from the liftable alloc into the unliftable alloc. Taking these instructions +// into account, the normal lifting machinery will completely lift the liftable alloc, store the correct lifted values +// into the unliftable alloc, and will not at all lift the unliftable alloc. +// +// In Go syntax, the transformation looks somewhat like this: +// +// func foo() { +// x := 32 +// if cond { +// println(x) +// escape(&x) +// println(x) +// } else { +// println(x) +// } +// println(x) +// } +// +// transforms into +// +// func fooSplitAlloc() { +// x := 32 +// var x_ int +// if cond { +// println(x) +// x_ = x +// escape(&x_) +// println(x_) +// } else { +// println(x) +// x_ = x +// } +// println(x_) +// } +func liftable(alloc *Alloc, instructions BlockMap[liftInstructions], heads BlockMap[int]) bool { + fn := alloc.block.parent + + // Don't lift result values in functions that defer + // calls that may recover from panic. + if fn.Recover != nil { + if slices.Contains(fn.results, alloc) { + return false + } + } + + blocks := fn.liftableBlockMap + if len(blocks) != len(fn.Blocks) { + blocks = make(BlockMap[liftableBlockDesc], len(fn.Blocks)) + fn.liftableBlockMap = blocks + } else { + clear(blocks) + } + for _, b := range fn.Blocks { + blocks[b.Index].lastLiftable = -1 + blocks[b.Index].firstUnliftable = len(b.Instrs) + 1 + } + + // Look at all uses of the alloc and deduce which blocks have liftable or unliftable instructions. + for _, instr := range alloc.referrers { + // Find the first unliftable use + + desc := &blocks[instr.Block().Index] + hasUnliftable := false + inHead := false + switch instr := instr.(type) { + case *Store: + if instr.Val == alloc { + hasUnliftable = true + } + case *Load: + case *debugRef: + case *Phi: + inHead = true + hasUnliftable = true + default: + hasUnliftable = true + } + + if hasUnliftable { + desc.isUnliftable = true + if int(instr.ID()) < desc.firstUnliftable { + desc.firstUnliftable = int(instr.ID()) + } + if inHead { + desc.storeInPreds = true + desc.firstUnliftable = 0 + } + } + } + + for _, instr := range alloc.referrers { + // Find the last liftable use, taking the previously calculated firstUnliftable into consideration + + desc := &blocks[instr.Block().Index] + if int(instr.ID()) >= desc.firstUnliftable { + continue + } + hasLiftable := false + switch instr := instr.(type) { + case *Store: + if instr.Val != alloc { + desc.hasLiftableOther = true + hasLiftable = true + } + case *Load: + desc.hasLiftableLoad = true + hasLiftable = true + case *debugRef: + desc.hasLiftableOther = true + } + if hasLiftable { + if int(instr.ID()) > desc.lastLiftable { + desc.lastLiftable = int(instr.ID()) + } + } + } + + for i := range blocks { + // Update firstUnliftable to be one after lastLiftable. We do this to include the unliftable's preceding + // DebugRefs in the renaming. + if blocks[i].lastLiftable == -1 && !blocks[i].storeInPreds { + // There are no liftable instructions (for this alloc) in this block. Set firstUnliftable to the + // first non-head instruction to avoid inserting the store before phi instructions, which would + // fail validation. + first := heads[i] + blocks[i].firstUnliftable = first + } else { + blocks[i].firstUnliftable = blocks[i].lastLiftable + 1 + } + } + + // If a block is reachable by a (partially) unliftable block, then the entirety of the block is unliftable. In that + // case, stores have to be inserted in the predecessors. + // + // TODO(dh): this isn't always necessary. If the block is reachable by itself, i.e. part of a loop, then if the + // Alloc instruction is itself part of that loop, then there is a subset of instructions in the loop that can be + // lifted. For example: + // + // for { + // x := 42 + // println(x) + // escape(&x) + // } + // + // The x that escapes in one iteration of the loop isn't the same x that we read from on the next iteration. + seen := make(BlockMap[bool], len(fn.Blocks)) + var dfs func(b *BasicBlock) + dfs = func(b *BasicBlock) { + if seen[b.Index] { + return + } + seen[b.Index] = true + desc := &blocks[b.Index] + desc.hasLiftableLoad = false + desc.hasLiftableOther = false + desc.isUnliftable = true + desc.firstUnliftable = 0 + desc.storeInPreds = true + for _, succ := range b.Succs { + dfs(succ) + } + } + for _, b := range fn.Blocks { + if blocks[b.Index].isUnliftable { + for _, succ := range b.Succs { + dfs(succ) + } + } + } + + hasLiftableLoad := false + hasLiftableOther := false + hasUnliftable := false + for _, b := range fn.Blocks { + desc := &blocks[b.Index] + hasLiftableLoad = hasLiftableLoad || desc.hasLiftableLoad + hasLiftableOther = hasLiftableOther || desc.hasLiftableOther + if desc.isUnliftable { + hasUnliftable = true + } + } + if !hasLiftableLoad && !hasLiftableOther { + // There are no liftable uses + return false + } else if !hasUnliftable { + // The alloc is entirely liftable without splitting + return true + } else if !hasLiftableLoad { + // The alloc is not entirely liftable, and the only liftable uses are stores. While some of those stores could + // get lifted away, it would also lead to an infinite loop when lifting to a fixpoint, because the newly created + // allocs also get stored into repeatable and that's their only liftable uses. + return false + } + + // We need to insert stores for the new alloc. If a (partially) unliftable block has no unliftable + // predecessors and the use isn't in a phi node, then the store can be inserted right before the unliftable use. + // Otherwise, stores have to be inserted at the end of all liftable predecessors. + + newAlloc := &Alloc{Heap: true} + newAlloc.setBlock(alloc.block) + newAlloc.setType(alloc.typ) + newAlloc.setSource(alloc.source) + newAlloc.index = -1 + newAlloc.comment = "split alloc" + + { + work := instructions[alloc.block.Index] + work.insertInstructions[alloc] = append(work.insertInstructions[alloc], newAlloc) + } + + predHasStore := make(BlockMap[bool], len(fn.Blocks)) + for _, b := range fn.Blocks { + desc := &blocks[b.Index] + bWork := &instructions[b.Index] + + if desc.isUnliftable { + bWork.renameAllocs = append(bWork.renameAllocs, struct { + from *Alloc + to *Alloc + startingAt int + }{ + alloc, newAlloc, int(desc.firstUnliftable), + }) + } + + if !desc.isUnliftable { + continue + } + + propagate := func(in *BasicBlock, before Instruction) { + load := &Load{ + X: alloc, + } + store := &Store{ + Addr: newAlloc, + Val: load, + } + load.setType(deref(alloc.typ)) + load.setBlock(in) + load.comment = "split alloc" + store.setBlock(in) + updateOperandReferrers(load) + updateOperandReferrers(store) + store.comment = "split alloc" + + entry := &instructions[in.Index] + entry.insertInstructions[before] = append(entry.insertInstructions[before], load, store) + } + + if desc.storeInPreds { + // emit stores at the end of liftable preds + for _, pred := range b.Preds { + if blocks[pred.Index].isUnliftable { + continue + } + + if !alloc.block.Dominates(pred) { + // Consider this cfg: + // + // 1 + // /| + // / | + // ↙ ↓ + // 2--→3 + // + // with an Alloc in block 2. It doesn't make sense to insert a store in block 1 for the jump to + // block 3, because 1 can never see the Alloc in the first place. + // + // Ignoring phi nodes, an Alloc always dominates all of its uses, and phi nodes don't matter here, + // because for the incoming edges that do matter, we do emit the stores. + + continue + } + + if predHasStore[pred.Index] { + // Don't generate redundant propagations. Not only is it unnecessary, it can lead to infinite loops + // when trying to lift to a fix point, because redundant stores are liftable. + continue + } + + predHasStore[pred.Index] = true + + before := pred.Instrs[len(pred.Instrs)-1] + propagate(pred, before) + } + } else { + // emit store before the first unliftable use + before := b.Instrs[desc.firstUnliftable] + propagate(b, before) + } + } + + return true +} + +// liftAlloc lifts alloc into registers and populates newPhis with all the φ-nodes it may require. +func liftAlloc(df domFrontier, alloc *Alloc, newPhis BlockMap[[]newPhi]) { + fn := alloc.Parent() + + defblocks := fn.blockset(0) + Aphi := fn.blockset(2) + W := fn.blockset(3) + + // Compute defblocks, the set of blocks containing a + // definition of the alloc cell. + for _, instr := range *alloc.Referrers() { + switch instr := instr.(type) { + case *Store: + defblocks.Add(instr.Block()) + } + } + // The Alloc itself counts as a (zero) definition of the cell. + defblocks.Add(alloc.Block()) + + if debugLifting { + fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name()) + } + + // Φ-insertion. + // + // What follows is the body of the main loop of the insert-φ + // function described by Cytron et al, but instead of using + // counter tricks, we just reset the 'hasAlready' and 'work' + // sets each iteration. These are bitmaps so it's pretty cheap. + + // Initialize W and work to defblocks. + + for change := true; change; { + change = false + { + // Traverse iterated dominance frontier, inserting φ-nodes. + W.Set(defblocks) + + for i := W.Take(); i != -1; i = W.Take() { + n := fn.Blocks[i] + for _, y := range df[n.Index] { + if Aphi.Add(y) { + if len(*alloc.Referrers()) == 0 { + continue + } + live := false + for _, ref := range *alloc.Referrers() { + if _, ok := ref.(*Load); ok { + if y.Reaches(ref.Block()) { + live = true + break + } + } + } + if !live { + continue + } + + // Create φ-node. + // It will be prepended to v.Instrs later, if needed. + phi := &Phi{ + Edges: make([]Value, len(y.Preds)), + } + phi.comment = alloc.comment + phi.source = alloc.source + phi.setType(deref(alloc.Type())) + phi.block = y + if debugLifting { + fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, y) + } + newPhis[y.Index] = append(newPhis[y.Index], newPhi{phi, alloc}) + + change = true + if defblocks.Add(y) { + W.Add(y) + } + } + } + } + } + } +} + +// replaceAll replaces all intraprocedural uses of x with y, +// updating x.Referrers and y.Referrers. +// Precondition: x.Referrers() != nil, i.e. x must be local to some function. +func replaceAll(x, y Value) { + var rands []*Value + pxrefs := x.Referrers() + pyrefs := y.Referrers() + for _, instr := range *pxrefs { + switch instr := instr.(type) { + case *CompositeValue: + // Special case CompositeValue because it might have very large lists of operands + // + // OPT(dh): this loop is still expensive for large composite values + for i, rand := range instr.Values { + if rand == x { + instr.Values[i] = y + } + } + default: + rands = instr.Operands(rands[:0]) // recycle storage + for _, rand := range rands { + if *rand != nil { + if *rand == x { + *rand = y + } + } + } + } + if pyrefs != nil { + *pyrefs = append(*pyrefs, instr) // dups ok + } + } + *pxrefs = nil // x is now unreferenced +} + +func replace(instr Instruction, x, y Value) { + args := instr.Operands(nil) + matched := false + for _, arg := range args { + if *arg == x { + *arg = y + matched = true + } + } + if matched { + yrefs := y.Referrers() + if yrefs != nil { + *yrefs = append(*yrefs, instr) + } + + xrefs := x.Referrers() + if xrefs != nil { + *xrefs = removeInstr(*xrefs, instr) + } + } +} + +// renamed returns the value to which alloc is being renamed, +// constructing it lazily if it's the implicit zero initialization. +func renamed(fn *Function, renaming []Value, alloc *Alloc) Value { + v := renaming[alloc.index] + if v == nil { + v = zeroConst(deref(alloc.Type()), alloc.source) + renaming[alloc.index] = v + } + return v +} + +// rename implements the Cytron et al-based SSA renaming algorithm, a +// preorder traversal of the dominator tree replacing all loads of +// Alloc cells with the value stored to that cell by the dominating +// store instruction. +// +// renaming is a map from *Alloc (keyed by index number) to its +// dominating stored value; newPhis[x] is the set of new φ-nodes to be +// prepended to block x. +func rename(u_ *BasicBlock, renaming_ []Value, newPhis BlockMap[[]newPhi]) { + type worklistEntry struct { + u *BasicBlock + renaming []Value + } + + worklist := []worklistEntry{{u_, renaming_}} + var freelist [][]Value + + for len(worklist) > 0 { + entry := worklist[len(worklist)-1] + worklist = worklist[:len(worklist)-1] + u, renaming := entry.u, entry.renaming + + // Each φ-node becomes the new name for its associated Alloc. + for _, np := range newPhis[u.Index] { + phi := np.phi + alloc := np.alloc + renaming[alloc.index] = phi + } + + // Rename loads and stores of allocs. + for i, instr := range u.Instrs { + switch instr := instr.(type) { + case *Alloc: + if instr.index >= 0 { // store of zero to Alloc cell + // Replace dominated loads by the zero value. + renaming[instr.index] = nil + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) + } + // Delete the Alloc. + u.Instrs[i] = nil + u.gaps++ + } + + case *Store: + if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell + // Replace dominated loads by the stored value. + renaming[alloc.index] = instr.Val + if debugLifting { + fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", + instr, instr.Val.Name()) + } + if refs := instr.Addr.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + if refs := instr.Val.Referrers(); refs != nil { + *refs = removeInstr(*refs, instr) + } + // Delete the Store. + u.Instrs[i] = nil + u.gaps++ + } + + case *Load: + if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell + newval := renamed(u.Parent(), renaming, alloc) + if debugLifting { + fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", + instr.Name(), instr, newval) + } + // Replace all references to the loaded value by the dominating + // stored value. + replaceAll(instr, newval) + u.Instrs[i] = nil + u.gaps++ + } + + case *debugRef: + if x, ok := instr.X.(*Alloc); ok && x.index >= 0 { + if instr.IsAddr { + instr.X = renamed(u.Parent(), renaming, x) + instr.IsAddr = false + + // Add DebugRef to instr.X's referrers. + if refs := instr.X.Referrers(); refs != nil { + *refs = append(*refs, instr) + } + } else { + // A source expression denotes the address + // of an Alloc that was optimized away. + instr.X = nil + + // Delete the DebugRef. + u.Instrs[i] = nil + u.gaps++ + } + } + } + } + + // For each φ-node in a CFG successor, rename the edge. + for _, v := range u.Succs { + phis := newPhis[v.Index] + if len(phis) == 0 { + continue + } + i := v.predIndex(u) + for _, np := range phis { + phi := np.phi + alloc := np.alloc + newval := renamed(u.Parent(), renaming, alloc) + if debugLifting { + fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", + phi.Name(), u, v, i, alloc.Name(), newval.Name()) + } + phi.Edges[i] = newval + if prefs := newval.Referrers(); prefs != nil { + *prefs = append(*prefs, phi) + } + } + } + + // Continue depth-first recursion over domtree, pushing a + // fresh copy of the renaming map for each subtree. + for _, v := range slices.Backward(u.dom.children) { + var r []Value + if len(freelist) == 0 { + r = make([]Value, len(renaming)) + } else { + r = freelist[len(freelist)-1] + freelist = freelist[:len(freelist)-1] + } + copy(r, renaming) + worklist = append(worklist, worklistEntry{v, r}) + } + freelist = append(freelist, renaming) + } +} + +func simplifyConstantCompositeValues(fn *Function) bool { + changed := false + + for _, b := range fn.Blocks { + n := 0 + for _, instr := range b.Instrs { + replaced := false + + if cv, ok := instr.(*CompositeValue); ok { + ac := &AggregateConst{} + ac.typ = cv.typ + replaced = true + for _, v := range cv.Values { + if c, ok := v.(Constant); ok { + ac.Values = append(ac.Values, c) + } else { + replaced = false + break + } + } + if replaced { + replaceAll(cv, ac) + killInstruction(cv) + } + + } + + if replaced { + changed = true + } else { + b.Instrs[n] = instr + n++ + } + } + + clearInstrs(b.Instrs[n:]) + b.Instrs = b.Instrs[:n] + } + + return changed +} + +func updateOperandReferrers(instr Instruction) { + for _, op := range instr.Operands(nil) { + refs := (*op).Referrers() + if refs != nil { + *refs = append(*refs, instr) + } + } +} + +// deferstackPreamble returns the *Alloc and ssa:deferstack() call for fn.deferstack. +func deferstackPreamble(fn *Function) (*Alloc, *Call) { + if alloc, _ := fn.vars[fn.deferstack].(*Alloc); alloc != nil { + for _, ref := range *alloc.Referrers() { + if ref, _ := ref.(*Store); ref != nil && ref.Addr == alloc { + if call, _ := ref.Val.(*Call); call != nil { + return alloc, call + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/go/ir/lvalue.go b/vendor/honnef.co/go/tools/go/ir/lvalue.go new file mode 100644 index 0000000..502293c --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/lvalue.go @@ -0,0 +1,175 @@ +// Copyright 2013 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 ir + +// lvalues are the union of addressable expressions and map-index +// expressions. + +import ( + "go/ast" + "go/types" +) + +// An lvalue represents an assignable location that may appear on the +// left-hand side of an assignment. This is a generalization of a +// pointer to permit updates to elements of maps. +type lvalue interface { + store(fn *Function, v Value, source ast.Node) // stores v into the location + load(fn *Function, source ast.Node) Value // loads the contents of the location + address(fn *Function) Value // address of the location + typ() types.Type // returns the type of the location +} + +// An address is an lvalue represented by a true pointer. +type address struct { + addr Value // must have a pointer core type. + expr ast.Expr // source syntax of the value (not address) [debug mode] +} + +func (a *address) load(fn *Function, source ast.Node) Value { + return emitLoad(fn, a.addr, source) +} + +func (a *address) store(fn *Function, v Value, source ast.Node) { + store := emitStore(fn, a.addr, v, source) + if a.expr != nil { + // store.Val is v, converted for assignability. + emitDebugRef(fn, a.expr, store.Val, false) + } +} + +func (a *address) address(fn *Function) Value { + if a.expr != nil { + emitDebugRef(fn, a.expr, a.addr, true) + } + return a.addr +} + +func (a *address) typ() types.Type { + return deref(a.addr.Type()) +} + +type compositeElement struct { + cv *CompositeValue + idx int + t types.Type + expr ast.Expr +} + +func (ce *compositeElement) load(fn *Function, source ast.Node) Value { + panic("not implemented") +} + +func (ce *compositeElement) store(fn *Function, v Value, source ast.Node) { + v = emitConv(fn, v, ce.t, source) + ce.cv.Values[ce.idx] = v + if ce.expr != nil { + // store.Val is v, converted for assignability. + emitDebugRef(fn, ce.expr, v, false) + } +} + +func (ce *compositeElement) address(fn *Function) Value { + panic("not implemented") +} + +func (ce *compositeElement) typ() types.Type { + return ce.t +} + +// An element is an lvalue represented by m[k], the location of an +// element of a map. These locations are not addressable +// since pointers cannot be formed from them, but they do support +// load() and store(). +type element struct { + m, k Value // map + t types.Type // map element type +} + +func (e *element) load(fn *Function, source ast.Node) Value { + l := &MapLookup{ + X: e.m, + Index: e.k, + } + l.setType(e.t) + return fn.emit(l, source) +} + +func (e *element) store(fn *Function, v Value, source ast.Node) { + up := &MapUpdate{ + Map: e.m, + Key: e.k, + Value: emitConv(fn, v, e.t, source), + } + fn.emit(up, source) +} + +func (e *element) address(fn *Function) Value { + panic("map elements are not addressable") +} + +func (e *element) typ() types.Type { + return e.t +} + +// A lazyAddress is an lvalue whose address is the result of an instruction. +// These work like an *address except a new address.address() Value +// is created on each load, store and address call. +// A lazyAddress can be used to control when a side effect (nil pointer +// dereference, index out of bounds) of using a location happens. +type lazyAddress struct { + addr func(fn *Function) Value // emit to fn the computation of the address + t types.Type // type of the location + expr ast.Expr // source syntax of the value (not address) [debug mode] +} + +func (l *lazyAddress) load(fn *Function, source ast.Node) Value { + load := emitLoad(fn, l.addr(fn), source) + return load +} + +func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) { + store := emitStore(fn, l.addr(fn), v, source) + if l.expr != nil { + // store.Val is v, converted for assignability. + emitDebugRef(fn, l.expr, store.Val, false) + } +} + +func (l *lazyAddress) address(fn *Function) Value { + addr := l.addr(fn) + if l.expr != nil { + emitDebugRef(fn, l.expr, addr, true) + } + return addr +} + +func (l *lazyAddress) typ() types.Type { return l.t } + +// A blank is a dummy variable whose name is "_". +// It is not reified: loads are illegal and stores are ignored. +type blank struct{} + +func (bl blank) load(fn *Function, source ast.Node) Value { + panic("blank.load is illegal") +} + +func (bl blank) store(fn *Function, v Value, source ast.Node) { + s := &BlankStore{ + Val: v, + } + fn.emit(s, source) +} + +func (bl blank) address(fn *Function) Value { + panic("blank var is not addressable") +} + +func (bl blank) typ() types.Type { + // This should be the type of the blank Ident; the typechecker + // doesn't provide this yet, but fortunately, we don't need it + // yet either. + panic("blank.typ is unimplemented") +} diff --git a/vendor/honnef.co/go/tools/go/ir/methods.go b/vendor/honnef.co/go/tools/go/ir/methods.go new file mode 100644 index 0000000..9ad8e41 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/methods.go @@ -0,0 +1,177 @@ +// Copyright 2013 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 ir + +// This file defines utilities for population of method sets. + +import ( + "fmt" + "go/types" + + "golang.org/x/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" +) + +// MethodValue returns the Function implementing method sel, building +// wrapper methods on demand. It returns nil if sel denotes an +// interface or generic method. +// +// Precondition: sel.Kind() == MethodVal. +// +// Thread-safe. +// +// Acquires prog.methodsMu. +func (prog *Program) MethodValue(sel *types.Selection) *Function { + if sel.Kind() != types.MethodVal { + panic(fmt.Sprintf("MethodValue(%s) kind != MethodVal", sel)) + } + T := sel.Recv() + if types.IsInterface(T) { + return nil // interface method or type parameter + } + + if prog.isParameterized(T, sel.Type()) { + return nil // method on generic type or generic method + } + + if prog.mode&LogSource != 0 { + defer logStack("MethodValue %s %v", T, sel)() + } + + var b builder + + m := func() *Function { + prog.methodsMu.Lock() + defer prog.methodsMu.Unlock() + + // Get or create SSA method set. + mset, ok := prog.methodSets.At(T) + if !ok { + mset = &methodSet{mapping: make(map[string]*Function)} + prog.methodSets.Set(T, mset) + } + + // Get or create SSA method. + id := sel.Obj().Id() + fn, ok := mset.mapping[id] + if !ok { + obj := sel.Obj().(*types.Func) + needsPromotion := len(sel.Index()) > 1 + needsIndirection := !isPointer(recvType(obj)) && isPointer(T) + if needsPromotion || needsIndirection { + fn = createWrapper(prog, toSelection(sel), nil) + fn.buildshared = b.shared() + b.enqueue(fn) + } else { + fn = prog.objectMethod(obj, nil, &b) + } + if fn.Signature.Recv() == nil { + panic(fn) + } + mset.mapping[id] = fn + } else { + b.waitForSharedFunction(fn) + } + + return fn + }() + + b.iterate() + + return m +} + +// objectMethod returns the Function for a given method symbol. +// The symbol may be an instance of a generic function. It need not +// belong to an existing SSA package created by a call to +// prog.CreatePackage. +// +// objectMethod panics if the function is not a method. +// +// Acquires prog.objectMethodsMu. +func (prog *Program) objectMethod(obj *types.Func, targs []types.Type, b *builder) *Function { + sig := obj.Type().(*types.Signature) + if sig.Recv() == nil { + panic("not a method: " + obj.String()) + } + + // Instantiation of generic? + if orig := obj.Origin(); orig != obj || len(targs) > 0 { + return prog.objectMethod(orig, nil, b).instance(receiverTypeArgs(obj), targs, b) + } + + // Belongs to a created package? + if fn := prog.FuncValue(obj); fn != nil { + return fn + } + + // Consult/update cache of methods created from types.Func. + prog.objectMethodsMu.Lock() + defer prog.objectMethodsMu.Unlock() + fn, ok := prog.objectMethods[obj] + if !ok { + fn = createFunction(prog, obj, obj.Name(), nil, nil, "") + fn.Synthetic = "from type information (on demand)" + fn.buildshared = b.shared() + b.enqueue(fn) + + if prog.objectMethods == nil { + prog.objectMethods = make(map[*types.Func]*Function) + } + prog.objectMethods[obj] = fn + } else { + b.waitForSharedFunction(fn) + } + return fn +} + +// LookupMethod returns the implementation of the method of type T +// identified by (pkg, name). It returns nil if the method exists but +// is an interface method or generic method, and panics if T has no such method. +func (prog *Program) LookupMethod(T types.Type, pkg *types.Package, name string) *Function { + sel := prog.MethodSets.MethodSet(T).Lookup(pkg, name) + if sel == nil { + panic(fmt.Sprintf("%s has no method %s", T, types.Id(pkg, name))) + } + return prog.MethodValue(sel) +} + +// methodSet contains the (concrete) methods of a concrete type (non-interface, non-parameterized). +type methodSet struct { + mapping map[string]*Function // populated lazily +} + +// RuntimeTypes returns a new unordered slice containing all types in +// the program for which a runtime type is required. +// +// A runtime type is required for any non-parameterized, non-interface +// type that is converted to an interface, or for any type (including +// interface types) derivable from one through reflection. +// +// The methods of such types may be reachable through reflection or +// interface calls even if they are never called directly. +// +// Thread-safe. +// +// Acquires prog.makeInterfaceTypesMu. +func (prog *Program) RuntimeTypes() []types.Type { + prog.makeInterfaceTypesMu.Lock() + defer prog.makeInterfaceTypesMu.Unlock() + + // Compute the derived types on demand, since many SSA clients + // never call RuntimeTypes, and those that do typically call + // it once (often within irutil.AllFunctions, which will + // eventually not use it; see Go issue #69291.) This + // eliminates the need to eagerly compute all the element + // types during SSA building. + var runtimeTypes []types.Type + add := func(t types.Type) { runtimeTypes = append(runtimeTypes, t) } + var set typeutil.Map // for de-duping identical types + for t := range prog.makeInterfaceTypes { + typesinternal.ForEachElement(&set, &prog.MethodSets, t, add) + } + + return runtimeTypes +} diff --git a/vendor/honnef.co/go/tools/go/ir/mode.go b/vendor/honnef.co/go/tools/go/ir/mode.go new file mode 100644 index 0000000..8f55bce --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/mode.go @@ -0,0 +1,118 @@ +// Copyright 2015 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 ir + +// This file defines the BuilderMode type and its command-line flag. + +import ( + "bytes" + "fmt" +) + +// BuilderMode is a bitmask of options for diagnostics and checking. +// +// *BuilderMode satisfies the flag.Value interface. Example: +// +// var mode = ir.BuilderMode(0) +// func init() { flag.Var(&mode, "build", ir.BuilderModeDoc) } +type BuilderMode uint + +const ( + PrintPackages BuilderMode = 1 << iota // Print package inventory to stdout + PrintFunctions // Print function IR code to stdout + PrintSource // Print source code when printing function IR + LogSource // Log source locations as IR builder progresses + SanityCheckFunctions // Perform sanity checking of function bodies + NaiveForm // Build naïve IR form: don't replace local loads/stores with registers + BuildSerially // Build packages serially, not in parallel. + GlobalDebug // Enable debug info for all packages + BareInits // Build init functions without guards or calls to dependent inits + InstantiateGenerics // Instantiate generics functions (monomorphize) while building +) + +const BuilderModeDoc = `Options controlling the IR builder. +The value is a sequence of zero or more of these letters: +C perform sanity [C]hecking of the IR form. +D include [D]ebug info for every function. +P print [P]ackage inventory. +F print [F]unction IR code. +A print [A]ST nodes responsible for IR instructions +S log [S]ource locations as IR builder progresses. +L build distinct packages seria[L]ly instead of in parallel. +N build [N]aive IR form: don't replace local loads/stores with registers. +I build bare [I]nit functions: no init guards or calls to dependent inits. +G instantiate [G]eneric function bodies via monomorphization +` + +func (m BuilderMode) String() string { + var buf bytes.Buffer + if m&GlobalDebug != 0 { + buf.WriteByte('D') + } + if m&PrintPackages != 0 { + buf.WriteByte('P') + } + if m&PrintFunctions != 0 { + buf.WriteByte('F') + } + if m&PrintSource != 0 { + buf.WriteByte('A') + } + if m&LogSource != 0 { + buf.WriteByte('S') + } + if m&SanityCheckFunctions != 0 { + buf.WriteByte('C') + } + if m&NaiveForm != 0 { + buf.WriteByte('N') + } + if m&BuildSerially != 0 { + buf.WriteByte('L') + } + if m&BareInits != 0 { + buf.WriteByte('I') + } + if m&InstantiateGenerics != 0 { + buf.WriteByte('G') + } + return buf.String() +} + +// Set parses the flag characters in s and updates *m. +func (m *BuilderMode) Set(s string) error { + var mode BuilderMode + for _, c := range s { + switch c { + case 'D': + mode |= GlobalDebug + case 'P': + mode |= PrintPackages + case 'F': + mode |= PrintFunctions + case 'A': + mode |= PrintSource + case 'S': + mode |= LogSource | BuildSerially + case 'C': + mode |= SanityCheckFunctions + case 'N': + mode |= NaiveForm + case 'L': + mode |= BuildSerially + case 'I': + mode |= BareInits + case 'G': + mode |= InstantiateGenerics + default: + return fmt.Errorf("unknown BuilderMode option: %q", c) + } + } + *m = mode + return nil +} + +// Get returns m. +func (m BuilderMode) Get() any { return m } diff --git a/vendor/honnef.co/go/tools/go/ir/print.go b/vendor/honnef.co/go/tools/go/ir/print.go new file mode 100644 index 0000000..c71518a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/print.go @@ -0,0 +1,554 @@ +// Copyright 2013 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 ir + +// This file implements the String() methods for all Value and +// Instruction types. + +import ( + "bytes" + "fmt" + "go/types" + "io" + "reflect" + "sort" + "strings" + + "honnef.co/go/tools/go/types/typeutil" +) + +// relName returns the name of v relative to i. +// In most cases, this is identical to v.Name(), but references to +// Functions (including methods) and Globals use RelString and +// all types are displayed with relType, so that only cross-package +// references are package-qualified. +func relName(v Value, i Node) string { + if v == nil { + return "" + } + var from *types.Package + if i != nil { + from = i.Parent().relPkg() + } + switch v := v.(type) { + case Member: // *Function or *Global + return v.RelString(from) + case *Const: + return v.RelString(from) + } + return v.Name() +} + +func relType(t types.Type, from *types.Package) string { + return types.TypeString(t, types.RelativeTo(from)) +} + +func relTerm(term *types.Term, from *types.Package) string { + s := relType(term.Type(), from) + if term.Tilde() { + return "~" + s + } + return s +} + +func relString(m Member, from *types.Package) string { + // NB: not all globals have an Object (e.g. init$guard), + // so use Package().Object not Object.Package(). + if pkg := m.Package().Pkg; pkg != nil && pkg != from { + return fmt.Sprintf("%s.%s", pkg.Path(), m.Name()) + } + return m.Name() +} + +// Value.String() +// +// This method is provided only for debugging. +// It never appears in disassembly, which uses Value.Name(). + +func (v *Parameter) String() string { + from := v.Parent().relPkg() + return fmt.Sprintf("parameter %s : %s", v.Object().Name(), relType(v.Type(), from)) +} + +func (v *FreeVar) String() string { + from := v.Parent().relPkg() + return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from)) +} + +func (v *Builtin) String() string { + return fmt.Sprintf("builtin %s", v.Name()) +} + +// Instruction.String() + +func (v *Alloc) String() string { + op := "local" + if v.Heap { + op = "new" + } + from := v.Parent().relPkg() + return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment()) +} + +func (v *Phi) String() string { + var b bytes.Buffer + b.WriteString("phi [") + for i, edge := range v.Edges { + if i > 0 { + b.WriteString(", ") + } + // Be robust against malformed CFG. + if v.block == nil { + b.WriteString("??") + continue + } + block := -1 + if i < len(v.block.Preds) { + block = v.block.Preds[i].Index + } + fmt.Fprintf(&b, "%d: ", block) + edgeVal := "" // be robust + if edge != nil { + edgeVal = relName(edge, v) + } + b.WriteString(edgeVal) + } + b.WriteString("]") + if v.Comment() != "" { + b.WriteString(" #") + b.WriteString(v.Comment()) + } + return b.String() +} + +func printCall(v *CallCommon, prefix string, instr Instruction) string { + var b bytes.Buffer + b.WriteString(prefix) + if !v.IsInvoke() { + b.WriteString(relName(v.Value, instr)) + } else { + fmt.Fprintf(&b, "invoke %s.%s", relName(v.Value, instr), v.Method.Name()) + } + b.WriteString("(") + for i, arg := range v.Args { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(relName(arg, instr)) + } + if v.Signature().Variadic() { + b.WriteString("...") + } + b.WriteString(")") + return b.String() +} + +func (c *CallCommon) String() string { + return printCall(c, "", nil) +} + +func (v *Call) String() string { + return printCall(&v.Call, "", v) +} + +func (v *BinOp) String() string { + return fmt.Sprintf("%s %s %s", relName(v.X, v), v.Op.String(), relName(v.Y, v)) +} + +func (v *UnOp) String() string { + return fmt.Sprintf("%s%s", v.Op, relName(v.X, v)) +} + +func (v *Load) String() string { + return fmt.Sprintf("*%s", relName(v.X, v)) +} + +func printConv(prefix string, v, x Value) string { + from := v.Parent().relPkg() + return fmt.Sprintf("%s %s <- %s (%s)", + prefix, + relType(v.Type(), from), + relType(x.Type(), from), + relName(x, v.(Instruction))) +} + +func (v *ChangeType) String() string { return printConv("changetype", v, v.X) } +func (v *Convert) String() string { return printConv("convert", v, v.X) } +func (v *ChangeInterface) String() string { return printConv("change interface", v, v.X) } +func (v *SliceToArrayPointer) String() string { return printConv("slice to array pointer", v, v.X) } +func (v *SliceToArray) String() string { return printConv("SliceToArray", v, v.X) } +func (v *MakeInterface) String() string { return printConv("make", v, v.X) } + +func (v *MultiConvert) String() string { + from := v.Parent().relPkg() + + var b strings.Builder + b.WriteString(printConv("multiconvert", v, v.X)) + b.WriteString(" [") + for i, s := range termListOf(v.from) { + for j, d := range termListOf(v.to) { + if i != 0 || j != 0 { + b.WriteString(" | ") + } + fmt.Fprintf(&b, "%s <- %s", relTerm(d, from), relTerm(s, from)) + } + } + b.WriteString("]") + return b.String() +} + +func (v *MakeClosure) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "make closure %s", relName(v.Fn, v)) + if v.Bindings != nil { + b.WriteString(" [") + for i, c := range v.Bindings { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(relName(c, v)) + } + b.WriteString("]") + } + return b.String() +} + +func (v *MakeSlice) String() string { + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s %s", + relType(v.Type(), from), + relName(v.Len, v), + relName(v.Cap, v)) +} + +func (v *Slice) String() string { + var b bytes.Buffer + b.WriteString("slice ") + b.WriteString(relName(v.X, v)) + b.WriteString("[") + if v.Low != nil { + b.WriteString(relName(v.Low, v)) + } + b.WriteString(":") + if v.High != nil { + b.WriteString(relName(v.High, v)) + } + if v.Max != nil { + b.WriteString(":") + b.WriteString(relName(v.Max, v)) + } + b.WriteString("]") + return b.String() +} + +func (v *MakeMap) String() string { + res := "" + if v.Reserve != nil { + res = relName(v.Reserve, v) + } + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s", relType(v.Type(), from), res) +} + +func (v *MakeChan) String() string { + from := v.Parent().relPkg() + return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v)) +} + +func (v *FieldAddr) String() string { + // Be robust against a bad index. + name := "?" + if fld := fieldOf(deref(v.X.Type()), v.Field); fld != nil { + name = fld.Name() + } + return fmt.Sprintf("&%s.%s [#%d]", relName(v.X, v), name, v.Field) +} + +func (v *Field) String() string { + // Be robust against a bad index. + name := "?" + if fld := fieldOf(v.X.Type(), v.Field); fld != nil { + name = fld.Name() + } + return fmt.Sprintf("%s.%s [#%d]", relName(v.X, v), name, v.Field) +} + +func (v *IndexAddr) String() string { + return fmt.Sprintf("&%s[%s]", relName(v.X, v), relName(v.Index, v)) +} + +func (v *Index) String() string { + return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v)) +} + +func (v *MapLookup) String() string { + return fmt.Sprintf("%s[%s]%s", relName(v.X, v), relName(v.Index, v), commaOk(v.CommaOk)) +} + +func (v *StringLookup) String() string { + return fmt.Sprintf("%s[%s]", relName(v.X, v), relName(v.Index, v)) +} + +func (v *Range) String() string { + return "range " + relName(v.X, v) +} + +func (v *Next) String() string { + return "next " + relName(v.Iter, v) +} + +func (v *TypeAssert) String() string { + from := v.Parent().relPkg() + return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from)) +} + +func (v *Extract) String() string { + name := v.Tuple.Type().(*types.Tuple).At(v.Index).Name() + if name == "" { + return fmt.Sprintf("extract %s #%d", relName(v.Tuple, v), v.Index) + } else { + return fmt.Sprintf("extract %s #%d (%s)", relName(v.Tuple, v), v.Index, name) + } +} + +func (s *Jump) String() string { + // Be robust against malformed CFG. + block := -1 + if s.block != nil && len(s.block.Succs) == 1 { + block = s.block.Succs[0].Index + } + str := fmt.Sprintf("jump %d", block) + if s.Comment() != "" { + str = fmt.Sprintf("%s # %s", str, s.Comment()) + } + return str +} + +func (s *Unreachable) String() string { + return "Unreachable" +} + +func (s *If) String() string { + // Be robust against malformed CFG. + tblock, fblock := -1, -1 + if s.block != nil && len(s.block.Succs) == 2 { + tblock = s.block.Succs[0].Index + fblock = s.block.Succs[1].Index + } + return fmt.Sprintf("if %s goto %d else %d", relName(s.Cond, s), tblock, fblock) +} + +func (s *ConstantSwitch) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "ConstantSwitch %s", relName(s.Tag, s)) + for _, cond := range s.Conds { + fmt.Fprintf(&b, " %s", relName(cond, s)) + } + fmt.Fprint(&b, " →") + for _, succ := range s.block.Succs { + fmt.Fprintf(&b, " b%d", succ.Index) + } + return b.String() +} + +func (v *CompositeValue) String() string { + var b bytes.Buffer + fmt.Fprint(&b, "{") + first := true + + var printValue func(idx int, vv Value) + switch typ := typeutil.CoreType(v.typ).(type) { + case *types.Struct: + printValue = func(idx int, vv Value) { + fieldName := typ.Field(idx).Name() + fmt.Fprintf(&b, "%s: %s", fieldName, relName(vv, v)) + } + case *types.Array: + if v.NumSet >= len(v.Values) { + printValue = func(idx int, vv Value) { + fmt.Fprint(&b, relName(vv, v)) + } + } else { + // This is currently unreachable as we don't use CompositeValue for + // incomplete arrays. + printValue = func(idx int, vv Value) { + fmt.Fprintf(&b, "%d: %s", idx, relName(vv, v)) + } + } + default: + panic(fmt.Sprintf("internal error: unexpected type %T (%v)", typ, typ)) + } + for i, vv := range v.Values { + if !first { + fmt.Fprint(&b, ", ") + } + first = false + printValue(i, vv) + } + fmt.Fprint(&b, "}") + return b.String() +} + +func (s *TypeSwitch) String() string { + var b bytes.Buffer + fmt.Fprintf(&b, "switch %s.(type)", relName(s.Tag, s)) + for _, cond := range s.Conds { + fmt.Fprintf(&b, " %s", relType(cond, s.block.parent.relPkg())) + } + return b.String() +} + +func (s *Go) String() string { + return printCall(&s.Call, "go ", s) +} + +func (s *Panic) String() string { + return "panic " + relName(s.X, s) +} + +func (s *Return) String() string { + var b bytes.Buffer + b.WriteString("return") + for i, r := range s.Results { + if i == 0 { + b.WriteString(" ") + } else { + b.WriteString(", ") + } + b.WriteString(relName(r, s)) + } + return b.String() +} + +func (*RunDefers) String() string { + return "rundefers" +} + +func (s *Send) String() string { + return fmt.Sprintf("send %s <- %s", relName(s.Chan, s), relName(s.X, s)) +} + +func (recv *Recv) String() string { + return fmt.Sprintf("<-%s", relName(recv.Chan, recv)) +} + +func (s *Defer) String() string { + prefix := "defer " + if s.DeferStack != nil { + prefix += "[" + relName(s.DeferStack, s) + "] " + } + c := printCall(&s.Call, prefix, s) + return c +} + +func (s *Select) String() string { + var b bytes.Buffer + for i, st := range s.States { + if i > 0 { + b.WriteString(", ") + } + if st.Dir == types.RecvOnly { + b.WriteString("<-") + b.WriteString(relName(st.Chan, s)) + } else { + b.WriteString(relName(st.Chan, s)) + b.WriteString("<-") + b.WriteString(relName(st.Send, s)) + } + } + non := "" + if !s.Blocking { + non = "non" + } + return fmt.Sprintf("select %sblocking [%s]", non, b.String()) +} + +func (s *Store) String() string { + return fmt.Sprintf("*%s = %s", relName(s.Addr, s), relName(s.Val, s)) +} + +func (s *BlankStore) String() string { + return fmt.Sprintf("_ = %s", relName(s.Val, s)) +} + +func (s *MapUpdate) String() string { + return fmt.Sprintf("%s[%s] = %s", relName(s.Map, s), relName(s.Key, s), relName(s.Value, s)) +} + +func (s *debugRef) String() string { + p := s.Parent().Prog.Fset.Position(s.Pos()) + var descr any + if s.object != nil { + descr = s.object // e.g. "var x int" + } else { + descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr" + } + var addr string + if s.IsAddr { + addr = "address of " + } + return fmt.Sprintf("; %s%s @ %d:%d is %s", addr, descr, p.Line, p.Column, s.X.Name()) +} + +func (p *Package) String() string { + return "package " + p.Pkg.Path() +} + +var _ io.WriterTo = (*Package)(nil) // *Package implements io.Writer + +func (p *Package) WriteTo(w io.Writer) (int64, error) { + var buf bytes.Buffer + WritePackage(&buf, p) + n, err := w.Write(buf.Bytes()) + return int64(n), err +} + +// WritePackage writes to buf a human-readable summary of p. +func WritePackage(buf *bytes.Buffer, p *Package) { + fmt.Fprintf(buf, "%s:\n", p) + + var names []string + maxname := 0 + for name := range p.Members { + if l := len(name); l > maxname { + maxname = l + } + names = append(names, name) + } + + from := p.Pkg + sort.Strings(names) + for _, name := range names { + switch mem := p.Members[name].(type) { + case *NamedConst: + fmt.Fprintf(buf, " const %-*s %s = %s\n", + maxname, name, mem.Name(), mem.Value.RelString(from)) + + case *Function: + fmt.Fprintf(buf, " func %-*s %s\n", + maxname, name, relType(mem.Type(), from)) + + case *Type: + fmt.Fprintf(buf, " type %-*s %s\n", + maxname, name, relType(mem.Type().Underlying(), from)) + for _, meth := range typeutil.IntuitiveMethodSet(mem.Type(), &p.Prog.MethodSets) { + fmt.Fprintf(buf, " %s\n", types.SelectionString(meth, types.RelativeTo(from))) + } + + case *Global: + fmt.Fprintf(buf, " var %-*s %s\n", + maxname, name, relType(deref(mem.Type()), from)) + } + } + + fmt.Fprintf(buf, "\n") +} + +func commaOk(x bool) string { + if x { + return ",ok" + } + return "" +} diff --git a/vendor/honnef.co/go/tools/go/ir/sanity.go b/vendor/honnef.co/go/tools/go/ir/sanity.go new file mode 100644 index 0000000..ab379ed --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/sanity.go @@ -0,0 +1,689 @@ +// Copyright 2013 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 ir + +// An optional pass for sanity-checking invariants of the IR representation. +// Currently it checks CFG invariants but little at the instruction level. + +import ( + "bytes" + "fmt" + "go/ast" + "go/types" + "io" + "os" + "slices" + "strings" + + "honnef.co/go/tools/go/types/typeutil" +) + +type sanity struct { + reporter io.Writer + fn *Function + block *BasicBlock + instrs map[Instruction]struct{} + insane bool +} + +// sanityCheck performs integrity checking of the IR representation +// of the function fn and returns true if it was valid. Diagnostics +// are written to reporter if non-nil, os.Stderr otherwise. Some +// diagnostics are only warnings and do not imply a negative result. +// +// Sanity-checking is intended to facilitate the debugging of code +// transformation passes. +func sanityCheck(fn *Function, reporter io.Writer) bool { + if reporter == nil { + reporter = os.Stderr + } + return (&sanity{reporter: reporter}).checkFunction(fn) +} + +// mustSanityCheck is like sanityCheck but panics instead of returning +// a negative result. +func mustSanityCheck(fn *Function, reporter io.Writer) { + if !sanityCheck(fn, reporter) { + fn.WriteTo(os.Stderr) + panic("SanityCheck failed") + } +} + +func (s *sanity) diagnostic(prefix, format string, args ...any) { + fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn) + if s.block != nil { + fmt.Fprintf(s.reporter, ", block %s", s.block) + } + io.WriteString(s.reporter, ": ") + fmt.Fprintf(s.reporter, format, args...) + io.WriteString(s.reporter, "\n") +} + +func (s *sanity) errorf(format string, args ...any) { + s.insane = true + s.diagnostic("Error", format, args...) +} + +func (s *sanity) warnf(format string, args ...any) { + s.diagnostic("Warning", format, args...) +} + +// findDuplicate returns an arbitrary basic block that appeared more +// than once in blocks, or nil if all were unique. +func findDuplicate(blocks []*BasicBlock) *BasicBlock { + if len(blocks) < 2 { + return nil + } + if blocks[0] == blocks[1] { + return blocks[0] + } + // Slow path: + m := make(map[*BasicBlock]bool) + for _, b := range blocks { + if m[b] { + return b + } + m[b] = true + } + return nil +} + +func (s *sanity) checkInstr(idx int, instr Instruction) { + switch instr := instr.(type) { + case *If, *Jump, *Return, *Panic, *Unreachable, *ConstantSwitch: + s.errorf("control flow instruction not at end of block") + case *Phi: + if idx == 0 { + // It suffices to apply this check to just the first phi node. + if dup := findDuplicate(s.block.Preds); dup != nil { + s.errorf("phi node in block with duplicate predecessor %s", dup) + } + } else { + prev := s.block.Instrs[idx-1] + switch prev.(type) { + case *Phi: + default: + s.errorf("Phi instruction follows a non-Phi: %T", prev) + } + } + if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { + s.errorf("phi node has %d edges but %d predecessors", ne, np) + } else { + for i, e := range instr.Edges { + if e == nil { + s.errorf("phi node '%s' has no value for edge #%d from %s", instr.Comment(), i, s.block.Preds[i]) + } else if !types.Identical(instr.typ, e.Type()) { + s.errorf("phi node '%s' has a different type (%s) for edge #%d from %s (%s)", + instr.Comment(), instr.Type(), i, s.block.Preds[i], e.Type()) + } + } + } + + case *Alloc: + if !instr.Heap && !slices.Contains(s.fn.Locals, instr) { + s.errorf("local alloc %s = %s does not appear in Function.Locals", instr.Name(), instr) + } + + case *BinOp: + case *Call: + if common := instr.Call; common.IsInvoke() { + if !types.IsInterface(common.Value.Type()) { + s.errorf("invoke on %s (%s) which is not an interface type (or type param)", common.Value, common.Value.Type()) + } + } + case *ChangeInterface: + case *ChangeType: + case *SliceToArrayPointer: + case *SliceToArray: + case *Convert: + if from := instr.X.Type(); !isBasicConvTypes(from) { + if to := instr.Type(); !isBasicConvTypes(to) { + s.errorf("convert %s -> %s: at least one type must be basic (or all basic, []byte, or []rune)", from, to) + } + } + case *MultiConvert: + case *Defer: + case *Extract: + case *Field: + case *FieldAddr: + case *Go: + case *Index: + case *IndexAddr: + case *MapLookup: + case *StringLookup: + case *MakeChan: + case *MakeClosure: + numFree := len(instr.Fn.(*Function).FreeVars) + numBind := len(instr.Bindings) + if numFree != numBind { + s.errorf("MakeClosure has %d Bindings for function %s with %d free vars", + numBind, instr.Fn, numFree) + } else { + for i, fv := range instr.Fn.(*Function).FreeVars { + if !types.Identical(instr.Bindings[i].Type(), fv.Type()) { + s.errorf("MakeClosure binding %d for %s has type %s, expected %s", + i, fv.Name(), instr.Bindings[i].Type(), fv.Type()) + } + } + } + if recv := instr.Type().(*types.Signature).Recv(); recv != nil { + s.errorf("MakeClosure's type includes receiver %s", recv.Type()) + } + + case *MakeInterface: + case *MakeMap: + case *MakeSlice: + case *MapUpdate: + case *Next: + rng, ok := instr.Iter.(*Range) + if !ok { + s.errorf("Next: Iter is %T, not *Range", instr.Iter) + } + var ek, ev types.Type + switch xt := typeutil.CoreType(rng.X.Type()).(type) { + case *types.Basic: + if types.Default(xt) != tString { + s.errorf("Next: basic operand of Next.Iter (Range) is %s, want string or untyped string", xt) + } + ek, ev = tInt, tRune + case *types.Map: + ek, ev = xt.Key(), xt.Elem() + } + + res := instr.Type().(*types.Tuple) // (ok bool, k K, v V), but K or V may be invalid if unused + if !types.Identical(res.At(1).Type(), ek) && res.At(1).Type() != tInvalid { + s.errorf("Next: key type %s does not match map key type %s", res.At(1).Type(), ek) + } + if !types.Identical(res.At(2).Type(), ev) && res.At(2).Type() != tInvalid { + s.errorf("Next: value type %s does not match map value type %s", res.At(2).Type(), ev) + } + + case *Range: + case *RunDefers: + case *Select: + case *Send: + case *Slice: + case *Store: + if !types.Identical(instr.Val.Type(), typeutil.CoreType(instr.Addr.Type()).(*types.Pointer).Elem()) { + s.errorf("Store: value type %s does not match address type %s", + instr.Val.Type(), instr.Addr.Type()) + } + case *TypeAssert: + case *UnOp: + case *debugRef: + case *BlankStore: + case *Load: + case *Recv: + case *TypeSwitch: + case *CompositeValue: + default: + panic(fmt.Sprintf("Unknown instruction type: %T", instr)) + } + + if call, ok := instr.(CallInstruction); ok { + if call.Common().Signature() == nil { + s.errorf("nil signature: %s", call) + } + } + + // Check that value-defining instructions have valid types + // and a valid referrer list. + if v, ok := instr.(Value); ok { + t := v.Type() + if t == nil { + s.errorf("no type: %s = %s", v.Name(), v) + } else if t == tDeferStack { + // not a proper type; ignore. + } else if b, ok := t.Underlying().(*types.Basic); ok && b.Info()&types.IsUntyped != 0 { + if _, ok := v.(*Const); !ok { + s.errorf("instruction has 'untyped' result: %s = %s : %s", v.Name(), v, t) + } + } + s.checkReferrerList(v) + } + + // Untyped constants are legal as instruction Operands(), + // for example: + // _ = "foo"[0] + // or: + // if wordsize==64 {...} + + // All other non-Instruction Values can be found via their + // enclosing Function or Package. +} + +func (s *sanity) checkFinalInstr(instr Instruction) { + switch instr := instr.(type) { + case *If: + if nsuccs := len(s.block.Succs); nsuccs != 2 { + s.errorf("If-terminated block has %d successors; expected 2", nsuccs) + return + } + if s.block.Succs[0] == s.block.Succs[1] { + s.errorf("If-instruction has same True, False target blocks: %s", s.block.Succs[0]) + return + } + + case *Jump: + if nsuccs := len(s.block.Succs); nsuccs != 1 { + s.errorf("Jump-terminated block has %d successors; expected 1", nsuccs) + return + } + + case *Return: + if nsuccs := len(s.block.Succs); nsuccs != 0 { + s.errorf("Return-terminated block has %d successors; expected none", nsuccs) + return + } + if na, nf := len(instr.Results), s.fn.Signature.Results().Len(); nf != na { + s.errorf("%d-ary return in %d-ary function", na, nf) + } + + case *Panic: + if nsuccs := len(s.block.Succs); nsuccs != 0 { + s.errorf("Panic-terminated block has %d successors; expected none", nsuccs) + return + } + + case *Unreachable: + if nsuccs := len(s.block.Succs); nsuccs != 0 { + s.errorf("Unreachable-terminated block has %d successors; expected none", nsuccs) + return + } + + case *ConstantSwitch: + if nsuccs := len(s.block.Succs); nsuccs != len(instr.Conds) { + s.errorf("ConstantSwitch-terminated block has %d successors for %d constants", + nsuccs, len(instr.Conds)) + return + } + + default: + s.errorf("non-control flow instruction at end of block") + } +} + +func (s *sanity) checkBlock(b *BasicBlock, index int) { + s.block = b + + if b.Index != index { + s.errorf("block has incorrect Index %d", b.Index) + } + if b.parent != s.fn { + s.errorf("block has incorrect parent %s", b.parent) + } + + // Check all blocks are reachable. + // (The entry block is always implicitly reachable, + // as is the Recover block, if any.) + if (index > 0 && b != b.parent.Recover) && len(b.Preds) == 0 { + s.warnf("unreachable block") + if b.Instrs == nil { + // Since this block is about to be pruned, + // tolerating transient problems in it + // simplifies other optimizations. + return + } + } + + // Check predecessor and successor relations are dual, + // and that all blocks in CFG belong to same function. + for _, a := range b.Preds { + if !slices.Contains(a.Succs, b) { + s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs) + } + if a.parent != s.fn { + s.errorf("predecessor %s belongs to different function %s", a, a.parent) + } + } + for _, c := range b.Succs { + if !slices.Contains(c.Preds, b) { + s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds) + } + if c.parent != s.fn { + s.errorf("successor %s belongs to different function %s", c, c.parent) + } + } + + // Check each instruction is sane. + n := len(b.Instrs) + if n == 0 { + s.errorf("basic block contains no instructions") + } + var rands [10]*Value // reuse storage + for j, instr := range b.Instrs { + if instr == nil { + s.errorf("nil instruction at index %d", j) + continue + } + if b2 := instr.Block(); b2 == nil { + s.errorf("nil Block() for instruction at index %d", j) + continue + } else if b2 != b { + s.errorf("wrong Block() (%s) for instruction at index %d ", b2, j) + continue + } + if j < n-1 { + s.checkInstr(j, instr) + } else { + s.checkFinalInstr(instr) + } + + // Check Instruction.Operands. + operands: + for i, op := range instr.Operands(rands[:0]) { + if op == nil { + s.errorf("nil operand pointer %d of %s", i, instr) + continue + } + val := *op + if val == nil { + continue // a nil operand is ok + } + + // Check that "untyped" types only appear on constant operands. + if _, ok := (*op).(*Const); !ok { + if basic, ok := (*op).Type().Underlying().(*types.Basic); ok { + if basic.Info()&types.IsUntyped != 0 { + s.errorf("operand #%d of %s is untyped: %s", i, instr, basic) + } + } + } + + // Check that Operands that are also Instructions belong to same function. + // TODO(adonovan): also check their block dominates block b. + if val, ok := val.(Instruction); ok { + if val.Block() == nil { + s.errorf("operand %d of %s is an instruction (%s) that belongs to no block", i, instr, val) + } else if val.Parent() != s.fn { + s.errorf("operand %d of %s is an instruction (%s) from function %s", i, instr, val, val.Parent()) + } + } + + // Check that each function-local operand of + // instr refers back to instr. (NB: quadratic) + switch val := val.(type) { + case *Const, *Global, *Builtin: + continue // not local + case *Function: + if val.parent == nil { + continue // only anon functions are local + } + } + + // TODO(adonovan): check val.Parent() != nil <=> val.Referrers() is defined. + + if refs := val.Referrers(); refs != nil { + for _, ref := range *refs { + if ref == instr { + continue operands + } + } + s.errorf("operand %d of %s (%s) does not refer to us", i, instr, val) + } else { + s.errorf("operand %d of %s (%s) has no referrers", i, instr, val) + } + } + } +} + +func (s *sanity) checkReferrerList(v Value) { + refs := v.Referrers() + if refs == nil { + s.errorf("%s has missing referrer list", v.Name()) + return + } + for i, ref := range *refs { + if _, ok := s.instrs[ref]; !ok { + if val, ok := ref.(Value); ok { + s.errorf("%s.Referrers()[%d] = %s = %s is not an instruction belonging to this function", v.Name(), i, val.Name(), val) + } else { + s.errorf("%s.Referrers()[%d] = %s is not an instruction belonging to this function", v.Name(), i, ref) + } + } + } +} + +func (s *sanity) checkFunctionParams() { + signature := s.fn.Signature + params := s.fn.Params + + // startSigParams is the start of signature.Params() within params. + startSigParams := 0 + if signature.Recv() != nil { + startSigParams = 1 + } + + if startSigParams+signature.Params().Len() != len(params) { + s.errorf("function has %d parameters in signature but has %d after building", + startSigParams+signature.Params().Len(), len(params)) + return + } + + for i, param := range params { + var sigType types.Type + si := i - startSigParams + if si < 0 { + sigType = signature.Recv().Type() + } else { + sigType = signature.Params().At(si).Type() + } + + if !types.Identical(sigType, param.Type()) { + s.errorf("expect type %s in signature but got type %s in param %d", sigType, param.Type(), i) + } + } +} + +// checkTransientFields checks whether all transient fields of Function are cleared. +func (s *sanity) checkTransientFields() { + fn := s.fn + if fn.build != nil { + s.errorf("function transient field 'build' is not nil") + } + if fn.currentBlock != nil { + s.errorf("function transient field 'currentBlock' is not nil") + } + if fn.vars != nil { + s.errorf("function transient field 'vars' is not nil") + } + if fn.results != nil { + s.errorf("function transient field 'results' is not nil") + } + if fn.returnVars != nil { + s.errorf("function transient field 'returnVars' is not nil") + } + if fn.targets != nil { + s.errorf("function transient field 'targets' is not nil") + } + if fn.lblocks != nil { + s.errorf("function transient field 'lblocks' is not nil") + } + if fn.subst != nil { + s.errorf("function transient field 'subst' is not nil") + } + if fn.jump != nil { + s.errorf("function transient field 'jump' is not nil") + } + if fn.deferstack != nil { + s.errorf("function transient field 'deferstack' is not nil") + } + if fn.source != nil { + s.errorf("function transient field 'source' is not nil") + } + if fn.exits != nil { + s.errorf("function transient field 'exits' is not nil") + } + if fn.uniq != 0 { + s.errorf("function transient field 'uniq' is not zero") + } +} + +func (s *sanity) checkFunction(fn *Function) bool { + s.fn = fn + s.checkFunctionParams() + s.checkTransientFields() + + // TODO(taking): Sanity check origin, typeparams, and typeargs. + if fn.Prog == nil { + s.errorf("nil Prog") + } + + var buf bytes.Buffer + _ = fn.String() // must not crash + _ = fn.RelString(fn.relPkg()) // must not crash + WriteFunction(&buf, fn) // must not crash + + // All functions have a package, except delegates (which are + // shared across packages, or duplicated as weak symbols in a + // separate-compilation model), and error.Error. + if fn.Pkg == nil { + if strings.HasPrefix(fn.Synthetic, "from type information (on demand)") || + strings.HasPrefix(fn.Synthetic, "wrapper ") || + strings.HasPrefix(fn.Synthetic, "bound ") || + strings.HasPrefix(fn.Synthetic, "thunk ") || + strings.HasSuffix(fn.name, "Error") || + strings.HasPrefix(fn.Synthetic, "instance ") || + strings.HasPrefix(fn.Synthetic, "instantiation ") || + fn.parent != nil && fn.parent.hasTypeArgs() /* anon fun in instance */ { + // ok + } else { + s.errorf("nil Pkg") + } + } + if src, syn := fn.Synthetic == "", fn.Syntax() != nil; src != syn { + if fn.hasTypeArgs() && fn.Prog.mode&InstantiateGenerics != 0 { + // ok (instantiation with InstantiateGenerics on) + } else if fn.hasTypeArgs() && fn.topLevelOrigin != nil { + // ok (we always have the syntax set for instantiation) + } else if _, rng := fn.syntax.(*ast.RangeStmt); rng && fn.Synthetic == "range-over-func yield" { + // ok (range-func-yields are both synthetic and keep syntax) + } else { + s.errorf("got fromSource=%t, hasSyntax=%t; want same values", src, syn) + } + } + + // Build the set of valid referrers. + s.instrs = make(map[Instruction]struct{}) + + // instrs are the instructions that are present in the function. + for instr := range fn.instrs() { + s.instrs[instr] = unit{} + } + + // Check all Locals allocations appear in the function instruction. + for i, l := range fn.Locals { + if _, present := s.instrs[l]; !present { + s.warnf("function doesn't contain Local alloc %s", l.Name()) + } + + if l.Parent() != fn { + s.errorf("Local %s at index %d has wrong parent", l.Name(), i) + } + if l.Heap { + s.errorf("Local %s at index %d has Heap flag set", l.Name(), i) + } + } + for i, p := range fn.Params { + if p.Parent() != fn { + s.errorf("Param %s at index %d has wrong parent", p.Name(), i) + } + // Check common suffix of Signature and Params match type. + if sig := fn.Signature; sig != nil { + j := i - len(fn.Params) + sig.Params().Len() // index within sig.Params + if j < 0 { + continue + } + if !types.Identical(p.Type(), sig.Params().At(j).Type()) { + s.errorf("Param %s at index %d has wrong type (%s, versus %s in Signature)", p.Name(), i, p.Type(), sig.Params().At(j).Type()) + } + } + + s.checkReferrerList(p) + } + for i, fv := range fn.FreeVars { + if fv.Parent() != fn { + s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i) + } + s.checkReferrerList(fv) + } + + if fn.Blocks != nil && len(fn.Blocks) == 0 { + // Function _had_ blocks (so it's not external) but + // they were "optimized" away, even the entry block. + s.errorf("Blocks slice is non-nil but empty") + } + for i, b := range fn.Blocks { + if b == nil { + s.warnf("nil *BasicBlock at f.Blocks[%d]", i) + continue + } + s.checkBlock(b, i) + } + if fn.Recover != nil && fn.Blocks[fn.Recover.Index] != fn.Recover { + s.errorf("Recover block is not in Blocks slice") + } + + s.block = nil + for i, anon := range fn.AnonFuncs { + if anon.Parent() != fn { + s.errorf("AnonFuncs[%d]=%s but %s.Parent()=%s", i, anon, anon, anon.Parent()) + } + if i != int(anon.anonIdx) { + s.errorf("AnonFuncs[%d]=%s but %s.anonIdx=%d", i, anon, anon, anon.anonIdx) + } + } + s.fn = nil + return !s.insane +} + +// sanityCheckPackage checks invariants of packages upon creation. +// It does not require that the package is built. +// Unlike sanityCheck (for functions), it just panics at the first error. +func sanityCheckPackage(pkg *Package) { + if pkg.Pkg == nil { + panic(fmt.Sprintf("Package %s has no Object", pkg)) + } + if pkg.info != nil { + panic(fmt.Sprintf("package %s field 'info' is not cleared", pkg)) + } + if pkg.files != nil { + panic(fmt.Sprintf("package %s field 'files' is not cleared", pkg)) + } + if pkg.created != nil { + panic(fmt.Sprintf("package %s field 'created' is not cleared", pkg)) + } + if pkg.initVersion != nil { + panic(fmt.Sprintf("package %s field 'initVersion' is not cleared", pkg)) + } + + _ = pkg.String() // must not crash + + for name, mem := range pkg.Members { + if name != mem.Name() { + panic(fmt.Sprintf("%s: %T.Name() = %s, want %s", + pkg.Pkg.Path(), mem, mem.Name(), name)) + } + obj := mem.Object() + if obj == nil { + // This check is sound because fields + // {Global,Function}.object have type + // types.Object. (If they were declared as + // *types.{Var,Func}, we'd have a non-empty + // interface containing a nil pointer.) + + continue // not all members have typechecker objects + } + if obj.Name() != name { + if obj.Name() == "init" && strings.HasPrefix(mem.Name(), "init#") { + // Ok. The name of a declared init function varies between + // its types.Func ("init") and its ir.Function ("init#%d"). + } else { + panic(fmt.Sprintf("%s: %T.Object().Name() = %s, want %s", + pkg.Pkg.Path(), mem, obj.Name(), name)) + } + } + } +} diff --git a/vendor/honnef.co/go/tools/go/ir/scc.go b/vendor/honnef.co/go/tools/go/ir/scc.go new file mode 100644 index 0000000..d7f7408 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/scc.go @@ -0,0 +1,106 @@ +package ir + +import ( + "math" +) + +type sccState struct { + fn *Function + index int + lows BlockMap[int] + F []int + sccs []*SCC +} + +func (s *sccState) scc(v *BasicBlock) { + // This is Tarjan's algorithm, with some optimizations from [1] applied. + // + // [1] R. E. Tarjan and U. Zwick, “Finding strong components using depth-first search,” Apr. 11, 2022, arXiv: arXiv:2201.07197. doi: 10.48550/arXiv.2201.07197. + vi := v.Index + lows := s.lows + + s.index += 2 + lows[vi] = s.index + + for _, w := range v.Succs { + wi := w.Index + + if lows[wi] == 0 { + s.scc(w) + } + + if lows[wi] < lows[vi] { + lows[vi] = lows[wi] | 1 + } + } + + if lows[vi]&1 == 0 { + leaderLow := lows[vi] + + scc := &SCC{ + Index: len(s.sccs), + } + F := s.F + for lows[F[len(F)-1]] >= leaderLow { + x := F[len(F)-1] + F = F[:len(F)-1] + + lows[x] = math.MaxInt + scc.Blocks = append(scc.Blocks, s.fn.Blocks[x]) + s.fn.Blocks[x].SCC = scc + } + s.F = F + + lows[vi] = math.MaxInt + scc.Blocks = append(scc.Blocks, v) + v.SCC = scc + s.sccs = append(s.sccs, scc) + } else { + s.F = append(s.F, vi) + } +} + +// buildSCCs computes the strongly connected components of fn's control flow graph. +func buildSCCs(fn *Function) { + n := len(fn.Blocks) + + if n == 0 { + return + } + + state := sccState{ + fn: fn, + index: 0, + // The +1 makes space for the sentinel value stored in F. + lows: make(BlockMap[int], n+1), + F: []int{n}, + } + + // All blocks are reachable from the entry block. + state.scc(fn.Blocks[0]) + + seen := make([]bool, len(state.sccs)) + for _, scc := range state.sccs { + clear(seen) + for _, b := range scc.Blocks { + for _, succ := range b.Succs { + if succ.SCC != b.SCC && !seen[succ.SCC.Index] { + b.SCC.Succs = append(b.SCC.Succs, succ.SCC) + succ.SCC.Preds = append(succ.SCC.Preds, b.SCC) + seen[succ.SCC.Index] = true + } + } + } + } + + fn.SCCs = state.sccs + + // This iterates over the SCCs in reverse topological order, i.e., "bottom + // up". This allows us to build up reachability sets. + for _, scc := range fn.SCCs { + scc.reachable.SetBit(&scc.reachable, scc.Index, 1) + for _, succ := range scc.Succs { + scc.reachable.Or(&scc.reachable, &succ.reachable) + } + } +} diff --git a/vendor/honnef.co/go/tools/go/ir/source.go b/vendor/honnef.co/go/tools/go/ir/source.go new file mode 100644 index 0000000..bd05db7 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/source.go @@ -0,0 +1,273 @@ +// Copyright 2013 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 ir + +// This file defines utilities for working with source positions +// or source-level named entities ("objects"). + +// TODO(adonovan): test that {Value,Instruction}.Pos() positions match +// the originating syntax, as specified. + +import ( + "go/ast" + "go/token" + "go/types" +) + +// EnclosingFunction returns the function that contains the syntax +// node denoted by path. +// +// Syntax associated with package-level variable specifications is +// enclosed by the package's init() function. +// +// Returns nil if not found; reasons might include: +// - the node is not enclosed by any function. +// - the node is within an anonymous function (FuncLit) and +// its IR function has not been created yet +// (pkg.Build() has not yet been called). +func EnclosingFunction(pkg *Package, path []ast.Node) *Function { + // Start with package-level function... + fn := findEnclosingPackageLevelFunction(pkg, path) + if fn == nil { + return nil // not in any function + } + + // ...then walk down the nested anonymous functions. + n := len(path) +outer: + for i := range path { + if lit, ok := path[n-1-i].(*ast.FuncLit); ok { + for _, anon := range fn.AnonFuncs { + if anon.Pos() == lit.Type.Func { + fn = anon + continue outer + } + } + // IR function not found: + // - package not yet built, or maybe + // - builder skipped FuncLit in dead block + // (in principle; but currently the Builder + // generates even dead FuncLits). + return nil + } + } + return fn +} + +// HasEnclosingFunction returns true if the AST node denoted by path +// is contained within the declaration of some function or +// package-level variable. +// +// Unlike EnclosingFunction, the behaviour of this function does not +// depend on whether IR code for pkg has been built, so it can be +// used to quickly reject check inputs that will cause +// EnclosingFunction to fail, prior to IR building. +func HasEnclosingFunction(pkg *Package, path []ast.Node) bool { + return findEnclosingPackageLevelFunction(pkg, path) != nil +} + +// findEnclosingPackageLevelFunction returns the Function +// corresponding to the package-level function enclosing path. +func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function { + if n := len(path); n >= 2 { // [... {Gen,Func}Decl File] + switch decl := path[n-2].(type) { + case *ast.GenDecl: + if decl.Tok == token.VAR && n >= 3 { + // Package-level 'var' initializer. + return pkg.init + } + + case *ast.FuncDecl: + if decl.Recv == nil && decl.Name.Name == "init" { + // Explicit init() function. + for _, b := range pkg.init.Blocks { + for _, instr := range b.Instrs { + if instr, ok := instr.(*Call); ok { + if callee, ok := instr.Call.Value.(*Function); ok && callee.Pkg == pkg && callee.Pos() == decl.Name.NamePos { + return callee + } + } + } + } + // Hack: return non-nil when IR is not yet + // built so that HasEnclosingFunction works. + return pkg.init + } + // Declared function/method. + return findNamedFunc(pkg, decl.Name.NamePos) + } + } + return nil // not in any function +} + +// findNamedFunc returns the named function whose FuncDecl.Ident is at +// position pos. +func findNamedFunc(pkg *Package, pos token.Pos) *Function { + // Look at all package members and method sets of named types. + // Not very efficient. + for _, mem := range pkg.Members { + switch mem := mem.(type) { + case *Function: + if mem.Pos() == pos { + return mem + } + case *Type: + mset := pkg.Prog.MethodSets.MethodSet(types.NewPointer(mem.Type())) + for i, n := 0, mset.Len(); i < n; i++ { + // Don't call Program.Method: avoid creating wrappers. + obj := mset.At(i).Obj().(*types.Func) + if obj.Pos() == pos { + // obj from MethodSet may not be the origin type. + m := obj.Origin() + return pkg.values[m].(*Function) + } + } + } + } + return nil +} + +// ValueForExpr returns the IR Value that corresponds to non-constant +// expression e. +// +// It returns nil if no value was found, e.g. +// - the expression is not lexically contained within f; +// - f was not built with debug information; or +// - e is a constant expression. (For efficiency, no debug +// information is stored for constants. Use +// go/types.Info.Types[e].Value instead.) +// - e is a reference to nil or a built-in function. +// - the value was optimised away. +// +// If e is an addressable expression used in an lvalue context, +// value is the address denoted by e, and isAddr is true. +// +// The types of e (or &e, if isAddr) and the result are equal +// (modulo "untyped" bools resulting from comparisons). +// +// (Tip: to find the ir.Value given a source position, use +// astutil.PathEnclosingInterval to locate the ast.Node, then +// EnclosingFunction to locate the Function, then ValueForExpr to find +// the ir.Value.) +func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) { + e = ast.Unparen(e) + entry := f.exprToValue[e] + return entry.v, entry.isAddr +} + +// --- Lookup functions for source-level named entities (types.Objects) --- + +// Package returns the IR Package corresponding to the specified +// type-checker package object. +// It returns nil if no such IR package has been created. +func (prog *Program) Package(obj *types.Package) *Package { + return prog.packages[obj] +} + +// packageLevelMember returns the package-level member corresponding +// to the specified symbol, which may be a package-level const +// (*NamedConst), var (*Global) or func/method (*Function) of some +// package in prog. +// +// It returns nil if the object belongs to a package that has not been +// created by prog.CreatePackage. +func (prog *Program) packageLevelMember(obj types.Object) Member { + if pkg, ok := prog.packages[obj.Pkg()]; ok { + return pkg.values[obj] + } + return nil +} + +// FuncValue returns the SSA function or (non-interface) method +// denoted by the specified func symbol. It returns nil if the symbol +// denotes an interface method, or belongs to a package that was not +// created by prog.CreatePackage. +func (prog *Program) FuncValue(obj *types.Func) *Function { + fn, _ := prog.packageLevelMember(obj).(*Function) + return fn +} + +// ConstValue returns the IR Value denoted by the source-level named +// constant obj. +func (prog *Program) ConstValue(obj *types.Const) *Const { + // TODO(adonovan): opt: share (don't reallocate) + // Consts for const objects and constant ast.Exprs. + + // Universal constant? {true,false,nil} + if obj.Parent() == types.Universe { + return NewConst(obj.Val(), obj.Type(), nil) + } + // Package-level named constant? + if v := prog.packageLevelMember(obj); v != nil { + return v.(*NamedConst).Value + } + return NewConst(obj.Val(), obj.Type(), nil) +} + +// VarValue returns the IR Value that corresponds to a specific +// identifier denoting the source-level named variable obj. +// +// VarValue returns nil if a local variable was not found, perhaps +// because its package was not built, the debug information was not +// requested during IR construction, or the value was optimized away. +// +// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval), +// and that ident must resolve to obj. +// +// pkg is the package enclosing the reference. (A reference to a var +// always occurs within a function, so we need to know where to find it.) +// +// If the identifier is a field selector and its base expression is +// non-addressable, then VarValue returns the value of that field. +// For example: +// +// func f() struct {x int} +// f().x // VarValue(x) returns a *Field instruction of type int +// +// All other identifiers denote addressable locations (variables). +// For them, VarValue may return either the variable's address or its +// value, even when the expression is evaluated only for its value; the +// situation is reported by isAddr, the second component of the result. +// +// If !isAddr, the returned value is the one associated with the +// specific identifier. For example, +// +// var x int // VarValue(x) returns Const 0 here +// x = 1 // VarValue(x) returns Const 1 here +// +// It is not specified whether the value or the address is returned in +// any particular case, as it may depend upon optimizations performed +// during IR code generation, such as registerization, constant +// folding, avoidance of materialization of subexpressions, etc. +func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) { + // All references to a var are local to some function, possibly init. + fn := EnclosingFunction(pkg, ref) + if fn == nil { + return // e.g. def of struct field; IR not built? + } + + id := ref[0].(*ast.Ident) + + // Defining ident of a parameter? + if id.Pos() == obj.Pos() { + for _, param := range fn.Params { + if param.Object() == obj { + return param, false + } + } + } + + // Other ident? + if v, ok := fn.exprToValue[id]; ok { + return v.v, v.isAddr + } + + // Defining ident of package-level var? + if v := prog.packageLevelMember(obj); v != nil { + return v.(*Global), true + } + + return // e.g. debug info not requested, or var optimized away +} diff --git a/vendor/honnef.co/go/tools/go/ir/ssa.go b/vendor/honnef.co/go/tools/go/ir/ssa.go new file mode 100644 index 0000000..e927736 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/ssa.go @@ -0,0 +1,2241 @@ +// Copyright 2013 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 ir + +// This package defines a high-level intermediate representation for +// Go programs using static single-assignment (SSA) form. + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "iter" + "math/big" + "reflect" + "slices" + "strings" + "sync" + "unsafe" + + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/typeparams" +) + +const ( + // Replace CompositeValue with only constant values with AggregateConst. Currently disabled because it breaks field + // tracking in U1000. + doSimplifyConstantCompositeValues = false +) + +type ID int + +// A Program is a partial or complete Go program converted to IR form. +type Program struct { + Fset *token.FileSet // position information for the files of this Program + imported map[string]*Package // all importable Packages, keyed by import path + packages map[*types.Package]*Package // all created Packages + mode BuilderMode // set of mode bits for IR construction + MethodSets typeutil.MethodSetCache // cache of type-checker's method-sets + + canon *canonizer // type canonicalization map + ctxt *types.Context // cache for type checking instantiations + + methodsMu sync.Mutex + methodSets typeutil.Map[*methodSet] // maps type to its concrete *methodSet + + // memoization of whether a type refers to type parameters + hasParamsMu sync.Mutex + hasParams typeparams.Free + + // set of concrete types used as MakeInterface operands + makeInterfaceTypesMu sync.Mutex + makeInterfaceTypes map[types.Type]unit // (may contain redundant identical types) + + // objectMethods is a memoization of objectMethod + // to avoid creation of duplicate methods from type information. + objectMethodsMu sync.Mutex + objectMethods map[*types.Func]*Function + + noReturn func(*types.Func) bool // (optional) predicate that decides whether a given call cannot return +} + +// A Package is a single analyzed Go package containing Members for +// all package-level functions, variables, constants and types it +// declares. These may be accessed directly via Members, or via the +// type-specific accessor methods Func, Type, Var and Const. +// +// Members also contains entries for "init" (the synthetic package +// initializer) and "init#%d", the nth declared init function, +// and unspecified other things too. +type Package struct { + Prog *Program // the owning program + Pkg *types.Package // the corresponding go/types.Package + Members map[string]Member // all package members keyed by name (incl. init and init#%d) + Functions []*Function // all functions, excluding anonymous ones + values map[types.Object]Member // mapping of package objects to members (incl. methods). Contains *NamedConst, *Global, *Function (values but not types) + init *Function // Func("init"); the package's init function + debug bool // include full debug info in this package + syntax bool // package was loaded from syntax + + // The following fields are set transiently, then cleared + // after building. + buildOnce sync.Once // ensures package building occurs once + ninit int32 // number of init functions + info *types.Info // package type information + files []*ast.File // package ASTs + created []*Function // members created as a result of building this package (includes declared functions, wrappers) + initVersion map[ast.Expr]string // goversion to use for each global var init expr +} + +// A Member is a member of a Go package, implemented by *NamedConst, +// *Global, *Function, or *Type; they are created by package-level +// const, var, func and type declarations respectively. +type Member interface { + Name() string // declared name of the package member + String() string // package-qualified name of the package member + RelString(*types.Package) string // like String, but relative refs are unqualified + Object() types.Object // typechecker's object for this member, if any + Type() types.Type // type of the package member + Token() token.Token // token.{VAR,FUNC,CONST,TYPE} + Package() *Package // the containing package +} + +// A Type is a Member of a Package representing a package-level named type. +type Type struct { + object *types.TypeName + pkg *Package +} + +// A NamedConst is a Member of a Package representing a package-level +// named constant. +// +// Pos() returns the position of the declaring ast.ValueSpec.Names[*] +// identifier. +// +// NB: a NamedConst is not a Value; it contains a constant Value, which +// it augments with the name and position of its 'const' declaration. +type NamedConst struct { + object *types.Const + Value *Const + pkg *Package +} + +// A Value is an IR value that can be referenced by an instruction. +type Value interface { + // Name returns the name of this value, and determines how + // this Value appears when used as an operand of an + // Instruction. + // + // This is the same as the source name for Parameters, + // Builtins, Functions, FreeVars, Globals. + // For constants, it is a representation of the constant's value + // and type. For all other Values this is the name of the + // virtual register defined by the instruction. + // + // The name of an IR Value is not semantically significant, + // and may not even be unique within a function. + Name() string + + // If this value is an Instruction, String returns its + // disassembled form; otherwise it returns unspecified + // human-readable information about the Value, such as its + // kind, name and type. + String() string + + // Type returns the type of this value. Many instructions + // (e.g. IndexAddr) change their behaviour depending on the + // types of their operands. + Type() types.Type + + // Parent returns the function to which this Value belongs. + // It returns nil for named Functions, Builtin and Global. + Parent() *Function + + // Referrers returns the list of instructions that have this + // value as one of their operands; it may contain duplicates + // if an instruction has a repeated operand. + // + // Referrers actually returns a pointer through which the + // caller may perform mutations to the object's state. + // + // Referrers is currently only defined if Parent()!=nil, + // i.e. for the function-local values FreeVar, Parameter, + // Functions (iff anonymous) and all value-defining instructions. + // It returns nil for named Functions, Builtin and Global. + // + // Instruction.Operands contains the inverse of this relation. + Referrers() *[]Instruction + + Operands(rands []*Value) []*Value // nil for non-Instructions + + // Source returns the AST node responsible for creating this + // value. A single AST node may be responsible for more than one + // value, and not all values have an associated AST node. + // + // Do not use this method to find a Value given an ast.Expr; use + // ValueForExpr instead. + Source() ast.Node + + // Pos returns Source().Pos() if Source is not nil, else it + // returns token.NoPos. + Pos() token.Pos +} + +// An Instruction is an IR instruction that computes a new Value or +// has some effect. +// +// An Instruction that defines a value (e.g. BinOp) also implements +// the Value interface; an Instruction that only has an effect (e.g. Store) +// does not. +type Instruction interface { + setSource(ast.Node) + setID(ID) + + Comment() string + + // String returns the disassembled form of this value. + // + // Examples of Instructions that are Values: + // "x + y" (BinOp) + // "len([])" (Call) + // Note that the name of the Value is not printed. + // + // Examples of Instructions that are not Values: + // "return x" (Return) + // "*y = x" (Store) + // + // (The separation Value.Name() from Value.String() is useful + // for some analyses which distinguish the operation from the + // value it defines, e.g., 'y = local int' is both an allocation + // of memory 'local int' and a definition of a pointer y.) + String() string + + // ID returns the ID of this instruction. IDs are unique within a single + // function and are densely numbered, but may contain gaps. + // Globally, instructions are identified by their addresses. However, + // IDs exist to facilitate efficient storage of mappings between + // instructions and data when analysing functions. + // + // NB: IDs are allocated late in the IR construction process and + // are not available to early stages of said process. + ID() ID + + // Parent returns the function to which this instruction + // belongs. + Parent() *Function + + // Block returns the basic block to which this instruction + // belongs. + Block() *BasicBlock + + // setBlock sets the basic block to which this instruction belongs. + setBlock(*BasicBlock) + + // Operands returns the operands of this instruction: the + // set of Values it references. + // + // Specifically, it appends their addresses to rands, a + // user-provided slice, and returns the resulting slice, + // permitting avoidance of memory allocation. + // + // The operands are appended in undefined order, but the order + // is consistent for a given Instruction; the addresses are + // always non-nil but may point to a nil Value. Clients may + // store through the pointers, e.g. to effect a value + // renaming. + // + // Value.Referrers is a subset of the inverse of this + // relation. (Referrers are not tracked for all types of + // Values.) + Operands(rands []*Value) []*Value + + Referrers() *[]Instruction // nil for non-Values + + // Source returns the AST node responsible for creating this + // instruction. A single AST node may be responsible for more than + // one instruction, and not all instructions have an associated + // AST node. + Source() ast.Node + + // Pos returns Source().Pos() if Source is not nil, else it + // returns token.NoPos. + Pos() token.Pos +} + +// A Node is a node in the IR value graph. Every concrete type that +// implements Node is also either a Value, an Instruction, or both. +// +// Node contains the methods common to Value and Instruction, plus the +// Operands and Referrers methods generalized to return nil for +// non-Instructions and non-Values, respectively. +// +// Node is provided to simplify IR graph algorithms. Clients should +// use the more specific and informative Value or Instruction +// interfaces where appropriate. +type Node interface { + // Common methods: + String() string + Source() ast.Node + Pos() token.Pos + Parent() *Function + + // Partial methods: + Operands(rands []*Value) []*Value // nil for non-Instructions + Referrers() *[]Instruction // nil for non-Values +} + +// Function represents the parameters, results, and code of a function +// or method. +// +// If Blocks is nil, this indicates an external function for which no +// Go source code is available. In this case, FreeVars, Locals, and +// Params are nil too. Clients performing whole-program analysis must +// handle external functions specially. +// +// Blocks contains the function's control-flow graph (CFG). +// Blocks[0] is the function entry point; block order is not otherwise +// semantically significant, though it may affect the readability of +// the disassembly. +// To iterate over the blocks in dominance order, use DomPreorder(). +// +// Recover is an optional second entry point to which control resumes +// after a recovered panic. The Recover block may contain only a return +// statement, preceded by a load of the function's named return +// parameters, if any. +// +// A nested function (Parent()!=nil) that refers to one or more +// lexically enclosing local variables ("free variables") has FreeVars. +// Such functions cannot be called directly but require a +// value created by MakeClosure which, via its Bindings, supplies +// values for these parameters. +// +// If the function is a method (Signature.Recv() != nil) then the first +// element of Params is the receiver parameter. +// +// A Go package may declare many functions called "init". +// For each one, Object().Name() returns "init" but Name() returns +// "init#1", etc, in declaration order. +// +// Pos() returns the declaring ast.FuncLit.Type.Func or the position +// of the ast.FuncDecl.Name, if the function was explicit in the +// source. Synthetic wrappers, for which Synthetic != "", may share +// the same position as the function they wrap. +// Syntax.Pos() always returns the position of the declaring "func" token. +// +// When the operand of a range statement is an iterator function, +// the loop body is transformed into a synthetic anonymous function +// that is passed as the yield argument in a call to the iterator. +// In that case, Function.Source() is the ast.RangeStmt. +// and Function.Syntax is the ast.RangeStmt. +// +// Synthetic functions, for which Synthetic != "", are functions +// that do not appear in the source AST. These include: +// - method wrappers, +// - thunks, +// - bound functions, +// - empty functions built from loaded type information, +// - yield functions created from range-over-func loops, +// - package init functions, and +// - instantiations of generic functions. +// +// Synthetic wrapper functions may share the same position +// as the function they wrap. +// +// Type() returns the function's Signature. +// +// A generic function is a function or method that has uninstantiated type +// parameters (TypeParams() != nil). Consider a hypothetical generic +// method, (*Map[K,V]).Get. It may be instantiated with all +// non-parameterized types as (*Map[string,int]).Get or with +// parameterized types as (*Map[string,U]).Get, where U is a type parameter. +// In both instantiations, Origin() refers to the instantiated generic +// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of +// the generic method. TypeArgs() refers to [string,U] or [string,int], +// respectively, and is nil in the generic method. +type Function struct { + name string + object *types.Func // symbol for declared function (nil for FuncLit or synthetic init) + method *selection // info about provenance of synthetic methods; thunk => non-nil + Signature *types.Signature + pos token.Pos + syntax ast.Node + + // source information + Synthetic string // provenance of synthetic function; "" for true source functions + info *types.Info // type annotations (if syntax != nil) + goversion string // Go version of syntax (NB: init is special) + + parent *Function // enclosing function if anon; nil if global + Pkg *Package // enclosing package; nil for shared funcs (wrappers and error.Error) + Prog *Program // enclosing program + + exprToValue map[ast.Expr]struct { + v Value + isAddr bool + } + + buildshared *task // wait for a shared function to be done building (may be nil if <=1 builder ever needs to wait) + + // These fields are populated only when the function body is built: + + Params []*Parameter // function parameters; for methods, includes receiver + FreeVars []*FreeVar // free variables whose values must be supplied by closure + Locals []*Alloc // frame-allocated variables of this function + Blocks []*BasicBlock // basic blocks of the function; nil => external + Recover *BasicBlock // optional; control transfers here after recovered panic + AnonFuncs []*Function // anonymous functions (from FuncLit, RangeStmt) directly beneath this one + referrers []Instruction // referring instructions (iff Parent() != nil) + anonIdx int32 // position of a nested function in parent's AnonFuncs. fn.Parent()!=nil => fn.Parent().AnonFunc[fn.anonIdx] == fn. + + SCCs []*SCC // SCC DAG in reverse topological order + + recvtypeparams *types.TypeParamList // receiver type parameters of this function. recvtypeparams.Len() > 0 => method on generic or instance of generic type + recvtypeargs []types.Type // type arguments that instantiated recvtypeparams. len(recvtypeargs) > 0 => method on instance of generic type + typeparams *types.TypeParamList // type parameters of this function. typeparams.Len() > 0 => generic or instance of generic function or method + typeargs []types.Type // type arguments that instantiated typeparams. len(typeargs) > 0 => instance of generic function or method + topLevelOrigin *Function // the origin function if this is an instance of a source function. nil if Parent()!=nil. + generic *generic // instances of this function, if generic + + // The following fields are cleared after building. + build buildFunc // algorithm to build function body (nil => built) + currentBlock *BasicBlock // where to emit code + vars map[*types.Var]Value // addresses of local variables + results []*Alloc // result allocations of the current function + returnVars []*types.Var // variables for a return statement. Either results or for range-over-func a parent's results + targets *targets // linked stack of branch targets + lblocks map[*types.Label]*lblock // labelled blocks + subst *subster // type parameter substitutions (if non-nil) + jump *types.Var // synthetic variable for the yield state (non-nil => range-over-func) + deferstack *types.Var // synthetic variable holding enclosing ssa:deferstack() + source *Function // nearest enclosing source function + exits []*exit // exits of the function that need to be resolved + uniq int64 // source of unique ints within the source tree while building + liftableBlockMap BlockMap[liftableBlockDesc] + + blocksets [4]BlockSet +} + +// BasicBlock represents an IR basic block. +// +// The final element of Instrs is always an explicit transfer of +// control (If, Jump, Return, Panic, or Unreachable). +// +// A block may contain no Instructions only if it is unreachable, +// i.e., Preds is nil. Empty blocks are typically pruned. +// +// BasicBlocks and their Preds/Succs relation form a (possibly cyclic) +// graph independent of the IR Value graph: the control-flow graph or +// CFG. It is illegal for multiple edges to exist between the same +// pair of blocks. +// +// Each BasicBlock is also a node in the dominator tree of the CFG. +// The tree may be navigated using Idom()/Dominees() and queried using +// Dominates(). +// +// The order of Preds and Succs is significant (to Phi and If +// instructions, respectively). +type BasicBlock struct { + Index int // index of this block within Parent().Blocks + Comment string // optional label; no semantic significance + parent *Function // parent function + Instrs []Instruction // instructions in order + Preds, Succs []*BasicBlock // predecessors and successors + SCC *SCC // strongly connected component + succs2 [2]*BasicBlock // initial space for Succs + dom domInfo // dominator tree info + gaps int // number of nil Instrs (transient) + rundefers int // number of rundefers (transient) +} + +type SCC struct { + Index int + Blocks []*BasicBlock + Preds, Succs []*SCC + reachable big.Int +} + +// Pure values ---------------------------------------- + +// A FreeVar represents a free variable of the function to which it +// belongs. +// +// FreeVars are used to implement anonymous functions, whose free +// variables are lexically captured in a closure formed by +// MakeClosure. The value of such a free var is an Alloc or another +// FreeVar and is considered a potentially escaping heap address, with +// pointer type. +// +// FreeVars are also used to implement bound method closures. Such a +// free var represents the receiver value and may be of any type that +// has concrete methods. +// +// Pos() returns the position of the value that was captured, which +// belongs to an enclosing function. +type FreeVar struct { + node + + name string + typ types.Type + parent *Function + referrers []Instruction + + // Transiently needed during building. + outer Value // the Value captured from the enclosing context. +} + +// A Parameter represents an input parameter of a function. +type Parameter struct { + node + + name string + object *types.Var // non-nil + typ types.Type + parent *Function + referrers []Instruction +} + +// A Const represents a value known at build time. +// +// Consts include true constants of boolean, numeric, and string types, as +// defined by the Go spec; these are represented by a non-nil Value field. +// +// Consts also include the "zero" value of any type, of which the nil values +// of various pointer-like types are a special case; these are represented +// by a nil Value field. +// +// Example printed form: +// +// 42:int +// "hello":untyped string +// 3+4i:MyComplex +// nil:*int +// nil:[]string +// [3]int{}:[3]int +// struct{x string}{}:struct{x string} +// 0:interface{int|int64} +// nil:interface{bool|int} // no go/constant representation +type Const struct { + node + + typ types.Type + Value constant.Value +} + +type AggregateConst struct { + node + + typ types.Type + Values []Value +} + +type CompositeValue struct { + register + + // Bitmap records which elements were explicitly provided. For example, [4]byte{2: x} would have a bitmap of 0010. + Bitmap big.Int + // The number of bits set in Bitmap + // TODO(dh): remove NumSet. we can just query the bitmap. + NumSet int + // Dense list of values in the composite literal. Omitted elements are filled in with zero values. + Values []Value +} + +type Constant interface { + Value + aConstant() + RelString(*types.Package) string +} + +func (*Const) aConstant() {} +func (*AggregateConst) aConstant() {} + +// A Global is a named Value holding the address of a package-level +// variable. +// +// Pos() returns the position of the ast.ValueSpec.Names[*] +// identifier. +type Global struct { + node + + name string + object types.Object // a *types.Var; may be nil for synthetics e.g. init$guard + typ types.Type + + Pkg *Package +} + +// A Builtin represents a specific use of a built-in function, e.g. len. +// +// Builtins are immutable values. Builtins do not have addresses. +// Builtins can only appear in CallCommon.Func. +// +// Name() indicates the function: one of the built-in functions from the +// Go spec (excluding "make" and "new") or one of these ir-defined +// intrinsics: +// +// // wrapnilchk returns ptr if non-nil, panics otherwise. +// // (For use in indirection wrappers.) +// func ir:wrapnilchk(ptr *T, recvType, methodName string) *T +// +// // noreturnWasPanic returns true if the previously called +// // function panicked, false if it exited the process. +// func ir:noreturnWasPanic() bool +// +// Object() returns a *types.Builtin for built-ins defined by the spec, +// nil for others. +// +// Type() returns a *types.Signature representing the effective +// signature of the built-in for this call. +type Builtin struct { + node + + name string + sig *types.Signature +} + +// Value-defining instructions ---------------------------------------- + +// The Alloc instruction reserves space for a variable of the given type, +// zero-initializes it, and yields its address. +// +// Alloc values are always addresses, and have pointer types, so the +// type of the allocated variable is actually +// Type().Underlying().(*types.Pointer).Elem(). +// +// If Heap is false, Alloc zero-initializes the same local variable in +// the call frame and returns its address; in this case the Alloc must +// be present in Function.Locals. We call this a "local" alloc. +// +// If Heap is true, Alloc allocates a new zero-initialized variable +// each time the instruction is executed. We call this a "new" alloc. +// +// When Alloc is applied to a channel, map or slice type, it returns +// the address of an uninitialized (nil) reference of that kind; store +// the result of MakeSlice, MakeMap or MakeChan in that location to +// instantiate these types. +// +// Pos() returns the ast.CompositeLit.Lbrace for a composite literal, +// or the ast.CallExpr.Rparen for a call to new() or for a call that +// allocates a varargs slice. +// +// Example printed form: +// +// t0 = local int +// t1 = new int +type Alloc struct { + register + Heap bool + index int // dense numbering; for lifting +} + +// The Phi instruction represents an SSA φ-node, which combines values +// that differ across incoming control-flow edges and yields a new +// value. Within a block, all φ-nodes must appear before all non-φ +// nodes. +// +// Pos() returns the position of the && or || for short-circuit +// control-flow joins, or that of the *Alloc for φ-nodes inserted +// during SSA renaming. +// +// Example printed form: +// +// t2 = phi [0: t0, 1: t1] +type Phi struct { + register + Edges []Value // Edges[i] is value for Block().Preds[i] + + live bool // used during lifting +} + +// The Call instruction represents a function or method call. +// +// The Call instruction yields the function result if there is exactly +// one. Otherwise it returns a tuple, the components of which are +// accessed via Extract. +// +// See CallCommon for generic function call documentation. +// +// Pos() returns the ast.CallExpr.Lparen, if explicit in the source. +// +// Example printed form: +// +// t2 = println(t0, t1) +// t4 = t3() +// t7 = invoke t5.Println(...t6) +type Call struct { + register + Call CallCommon +} + +// The BinOp instruction yields the result of binary operation X Op Y. +// +// Pos() returns the ast.BinaryExpr.OpPos, if explicit in the source. +// +// Example printed form: +// +// t1 = t0 + 1:int +type BinOp struct { + register + // One of: + // ADD SUB MUL QUO REM + - * / % + // AND OR XOR SHL SHR AND_NOT & | ^ << >> &^ + // EQL NEQ LSS LEQ GTR GEQ == != < <= < >= + Op token.Token + X, Y Value +} + +// The UnOp instruction yields the result of Op X. +// XOR is bitwise complement. +// SUB is negation. +// NOT is logical negation. +// +// Example printed form: +// +// t0 = ^x +type UnOp struct { + register + Op token.Token // One of: NOT SUB XOR ! - ^ + X Value +} + +// The Load instruction loads a value from a memory address. +// +// For implicit memory loads, Pos() returns the position of the +// most closely associated source-level construct; the details are not +// specified. +// +// Example printed form: +// +// t2 = Load t1 +type Load struct { + register + X Value +} + +// The ChangeType instruction applies to X a value-preserving type +// change to Type(). +// +// Type changes are permitted: +// - between a named type and its underlying type. +// - between two named types of the same underlying type. +// - between (possibly named) pointers to identical base types. +// - from a bidirectional channel to a read- or write-channel, +// optionally adding/removing a name. +// - between a type (t) and an instance of the type (tσ), i.e. +// Type() == σ(X.Type()) (or X.Type()== σ(Type())) where +// σ is the type substitution of Parent().TypeParams by +// Parent().TypeArgs. +// +// This operation cannot fail dynamically. +// +// Type changes may to be to or from a type parameter (or both). All +// types in the type set of X.Type() have a value-preserving type +// change to all types in the type set of Type(). +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Example printed form: +// +// t1 = changetype *int <- IntPtr (t0) +type ChangeType struct { + register + X Value +} + +// The Convert instruction yields the conversion of value X to type +// Type(). One or both of those types is basic (but possibly named). +// +// A conversion may change the value and representation of its operand. +// Conversions are permitted: +// - between real numeric types. +// - between complex numeric types. +// - between string and []byte or []rune. +// - between pointers and unsafe.Pointer. +// - between unsafe.Pointer and uintptr. +// - from (Unicode) integer to (UTF-8) string. +// +// A conversion may imply a type name change also. +// +// Conversions may to be to or from a type parameter. All types in +// the type set of X.Type() can be converted to all types in the type +// set of Type(). +// +// This operation cannot fail dynamically. +// +// Conversions of untyped string/number/bool constants to a specific +// representation are eliminated during IR construction. +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Example printed form: +// +// t1 = convert []byte <- string (t0) +type Convert struct { + register + X Value +} + +// The MultiConvert instruction yields the conversion of value X to type +// Type(). Either X.Type() or Type() must be a type parameter. Each +// type in the type set of X.Type() can be converted to each type in the +// type set of Type(). +// +// See the documentation for Convert, ChangeType, SliceToArray, and SliceToArrayPointer +// for the conversions that are permitted. +// +// This operation can fail dynamically (see SliceToArrayPointer). +// +// Example printed form: +// +// t1 = multiconvert D <- S (t0) [*[2]rune <- []rune | string <- []rune] +type MultiConvert struct { + register + X Value + from, to types.Type +} + +// ChangeInterface constructs a value of one interface type from a +// value of another interface type known to be assignable to it. +// This operation cannot fail. +// +// Pos() returns the ast.CallExpr.Lparen if the instruction arose from +// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the +// instruction arose from an explicit e.(T) operation; or token.NoPos +// otherwise. +// +// Example printed form: +// +// t1 = change interface interface{} <- I (t0) +type ChangeInterface struct { + register + X Value +} + +// The SliceToArrayPointer instruction yields the conversion of slice X to +// array pointer. +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Conversion may to be to or from a type parameter. All types in +// the type set of X.Type() must be a slice types that can be converted to +// all types in the type set of Type() which must all be pointer to array +// types. +// +// This operation can fail dynamically if the length of the slice is less +// than the length of the array. +// +// Example printed form: +// +// t1 = slice to array pointer *[4]byte <- []byte (t0) +type SliceToArrayPointer struct { + register + X Value +} + +// The SliceToArray instruction yields the conversion of slice X to +// array. +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Example printed form: +// +// t2 = SliceToArray <[4]byte> t1 +type SliceToArray struct { + register + X Value +} + +// MakeInterface constructs an instance of an interface type from a +// value of a concrete type. +// +// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set +// of X, and Program.MethodValue(m) to find the implementation of a method. +// +// To construct the zero value of an interface type T, use: +// +// NewConst(constant.MakeNil(), T, pos) +// +// Pos() returns the ast.CallExpr.Lparen, if the instruction arose +// from an explicit conversion in the source. +// +// Example printed form: +// +// t1 = make interface{} <- int (42:int) +// t1 = make Stringer <- t0 +type MakeInterface struct { + register + X Value +} + +// The MakeClosure instruction yields a closure value whose code is +// Fn and whose free variables' values are supplied by Bindings. +// +// Type() returns a (possibly named) *types.Signature. +// +// Pos() returns the ast.FuncLit.Type.Func for a function literal +// closure or the ast.SelectorExpr.Sel for a bound method closure. +// +// Example printed form: +// +// t0 = make closure anon@1.2 [x y z] +// t1 = make closure bound$(main.I).add [i] +type MakeClosure struct { + register + Fn Value // always a *Function + Bindings []Value // values for each free variable in Fn.FreeVars +} + +// The MakeMap instruction creates a new hash-table-based map object +// and yields a value of kind map. +// +// Type() returns a (possibly named) *types.Map. +// +// Pos() returns the ast.CallExpr.Lparen, if created by make(map), or +// the ast.CompositeLit.Lbrack if created by a literal. +// +// Example printed form: +// +// t1 = make map[string]int t0 +// t1 = make StringIntMap t0 +type MakeMap struct { + register + Reserve Value // initial space reservation; nil => default +} + +// The MakeChan instruction creates a new channel object and yields a +// value of kind chan. +// +// Type() returns a (possibly named) *types.Chan. +// +// Pos() returns the ast.CallExpr.Lparen for the make(chan) that +// created it. +// +// Example printed form: +// +// t1 = make chan int t0 +// t1 = make IntChan t0 +type MakeChan struct { + register + Size Value // int; size of buffer; zero => synchronous. +} + +// The MakeSlice instruction yields a slice of length Len backed by a +// newly allocated array of length Cap. +// +// Both Len and Cap must be non-nil Values of integer type. +// +// (Alloc(types.Array) followed by Slice will not suffice because +// Alloc can only create arrays of constant length.) +// +// Type() returns a (possibly named) *types.Slice. +// +// Pos() returns the ast.CallExpr.Lparen for the make([]T) that +// created it. +// +// Example printed form: +// +// t2 = make []string t1 t0 +// t2 = make StringSlice t1 t0 +type MakeSlice struct { + register + Len Value + Cap Value +} + +// The Slice instruction yields a slice of an existing string, slice +// or *array X between optional integer bounds Low and High. +// +// Dynamically, this instruction panics if X evaluates to a nil *array +// pointer. +// +// Type() returns string if the type of X was string, otherwise a +// *types.Slice with the same element type as X. +// +// Pos() returns the ast.SliceExpr.Lbrack if created by a x[:] slice +// operation, the ast.CompositeLit.Lbrace if created by a literal, or +// NoPos if not explicit in the source (e.g. a variadic argument slice). +// +// Example printed form: +// +// t2 = slice t0[t1:] +type Slice struct { + register + X Value // slice, string, or *array + Low, High, Max Value // each may be nil +} + +// The FieldAddr instruction yields the address of Field of *struct X. +// +// The field is identified by its index within the field list of the +// struct type of X. +// +// Dynamically, this instruction panics if X evaluates to a nil +// pointer. +// +// Type() returns a (possibly named) *types.Pointer. +// +// Pos() returns the position of the ast.SelectorExpr.Sel for the +// field, if explicit in the source. For implicit selections, returns +// the position of the inducing explicit selection. If produced for a +// struct literal S{f: e}, it returns the position of the colon; for +// S{e} it returns the start of expression e. +// +// Example printed form: +// +// t1 = &t0.name [#1] +type FieldAddr struct { + register + X Value // *struct + Field int // index into CoreType(CoreType(X.Type()).(*types.Pointer).Elem()).(*types.Struct).Fields +} + +// The Field instruction yields the Field of struct X. +// +// The field is identified by its index within the field list of the +// struct type of X; by using numeric indices we avoid ambiguity of +// package-local identifiers and permit compact representations. +// +// Pos() returns the position of the ast.SelectorExpr.Sel for the +// field, if explicit in the source. For implicit selections, returns +// the position of the inducing explicit selection. +// +// Example printed form: +// +// t1 = t0.name [#1] +type Field struct { + register + X Value // struct + Field int // index into CoreType(X.Type()).(*types.Struct).Fields +} + +// The IndexAddr instruction yields the address of the element at +// index Index of collection X. Index is an integer expression. +// +// The elements of maps and strings are not addressable; use Lookup (map), +// Index (string), or MapUpdate instead. +// +// Dynamically, this instruction panics if X evaluates to a nil *array +// pointer. +// +// Type() returns a (possibly named) *types.Pointer. +// +// Pos() returns the ast.IndexExpr.Lbrack for the index operation, if +// explicit in the source. +// +// Example printed form: +// +// t2 = &t0[t1] +type IndexAddr struct { + register + X Value // *array, slice or type parameter with types array, *array, or slice. + Index Value // numeric index +} + +// The Index instruction yields element Index of collection X, an array, +// string or type parameter containing an array, a string, a pointer to an, +// array or a slice. +// +// Pos() returns the ast.IndexExpr.Lbrack for the index operation, if +// explicit in the source. +// +// Example printed form: +// +// t2 = t0[t1] +type Index struct { + register + X Value // array, string or type parameter with types array, *array, slice, or string. + Index Value // integer index +} + +// The MapLookup instruction yields element Index of collection X, a map. +// Index is the appropriate key type. +// +// If CommaOk, the result is a 2-tuple of the value above and a +// boolean indicating the result of a map membership test for the key. +// The components of the tuple are accessed using Extract. +// +// Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source. +// +// Example printed form: +// +// t2 = t0[t1] +// t5 = t3[t4],ok +type MapLookup struct { + register + X Value // map + Index Value // key-typed index + CommaOk bool // return a value,ok pair +} + +// The StringLookup instruction yields element Index of collection X, a string. +// Index is an integer expression. +// +// Pos() returns the ast.IndexExpr.Lbrack, if explicit in the source. +// +// Example printed form: +// +// t3 = StringLookup t2 t1 +type StringLookup struct { + register + X Value // string + Index Value // numeric index +} + +// SelectState is a helper for Select. +// It represents one goal state and its corresponding communication. +type SelectState struct { + Dir types.ChanDir // direction of case (SendOnly or RecvOnly) + Chan Value // channel to use (for send or receive) + Send Value // value to send (for send) + Pos token.Pos // position of token.ARROW + DebugNode ast.Node // ast.SendStmt or ast.UnaryExpr(<-) [debug mode] +} + +// The Select instruction tests whether (or blocks until) one +// of the specified sent or received states is entered. +// +// Let n be the number of States for which Dir==RECV and Tᵢ (0 ≤ i < n) +// be the element type of each such state's Chan. +// Select returns an n+2-tuple +// +// (index int, recvOk bool, r₀ T₀, ... rₙ-1 Tₙ-1) +// +// The tuple's components, described below, must be accessed via the +// Extract instruction. +// +// If Blocking, select waits until exactly one state holds, i.e. a +// channel becomes ready for the designated operation of sending or +// receiving; select chooses one among the ready states +// pseudorandomly, performs the send or receive operation, and sets +// 'index' to the index of the chosen channel. +// +// If !Blocking, select doesn't block if no states hold; instead it +// returns immediately with index equal to -1. +// +// If the chosen channel was used for a receive, the rᵢ component is +// set to the received value, where i is the index of that state among +// all n receive states; otherwise rᵢ has the zero value of type Tᵢ. +// Note that the receive index i is not the same as the state +// index index. +// +// The second component of the triple, recvOk, is a boolean whose value +// is true iff the selected operation was a receive and the receive +// successfully yielded a value. +// +// Pos() returns the ast.SelectStmt.Select. +// +// Example printed form: +// +// t3 = select nonblocking [<-t0, t1<-t2] +// t4 = select blocking [] +type Select struct { + register + States []*SelectState + Blocking bool +} + +// The Range instruction yields an iterator over the domain and range +// of X, which must be a string or map. +// +// Elements are accessed via Next. +// +// Type() returns an opaque and degenerate "rangeIter" type. +// +// Pos() returns the ast.RangeStmt.For. +// +// Example printed form: +// +// t0 = range "hello":string +type Range struct { + register + X Value // string or map +} + +// The Next instruction reads and advances the (map or string) +// iterator Iter and returns a 3-tuple value (ok, k, v). If the +// iterator is not exhausted, ok is true and k and v are the next +// elements of the domain and range, respectively. Otherwise ok is +// false and k and v are undefined. +// +// Components of the tuple are accessed using Extract. +// +// The IsString field distinguishes iterators over strings from those +// over maps, as the Type() alone is insufficient: consider +// map[int]rune. +// +// Type() returns a *types.Tuple for the triple (ok, k, v). +// The types of k and/or v may be types.Invalid. +// +// Example printed form: +// +// t1 = next t0 +type Next struct { + register + Iter Value + IsString bool // true => string iterator; false => map iterator. +} + +// The TypeAssert instruction tests whether interface value X has type +// AssertedType. +// +// If !CommaOk, on success it returns v, the result of the conversion +// (defined below); on failure it panics. +// +// If CommaOk: on success it returns a pair (v, true) where v is the +// result of the conversion; on failure it returns (z, false) where z +// is AssertedType's zero value. The components of the pair must be +// accessed using the Extract instruction. +// +// If Underlying: tests whether interface value X has the underlying +// type AssertedType. +// +// If AssertedType is a concrete type, TypeAssert checks whether the +// dynamic type in interface X is equal to it, and if so, the result +// of the conversion is a copy of the value in the interface. +// +// If AssertedType is an interface, TypeAssert checks whether the +// dynamic type of the interface is assignable to it, and if so, the +// result of the conversion is a copy of the interface value X. +// If AssertedType is a superinterface of X.Type(), the operation will +// fail iff the operand is nil. (Contrast with ChangeInterface, which +// performs no nil-check.) +// +// Type() reflects the actual type of the result, possibly a +// 2-types.Tuple; AssertedType is the asserted type. +// +// Depending on the TypeAssert's purpose, Pos may return: +// - the ast.CallExpr.Lparen of an explicit T(e) conversion; +// - the ast.TypeAssertExpr.Lparen of an explicit e.(T) operation; +// - the ast.CaseClause.Case of a case of a type-switch statement; +// - the Ident(m).NamePos of an interface method value i.m +// (for which TypeAssert may be used to effect the nil check). +// +// Example printed form: +// +// t1 = typeassert t0.(int) +// t3 = typeassert,ok t2.(T) +type TypeAssert struct { + register + X Value + AssertedType types.Type + CommaOk bool +} + +// The Extract instruction yields component Index of Tuple. +// +// This is used to access the results of instructions with multiple +// return values, such as Call, TypeAssert, Next, Recv, +// MapLookup and others. +// +// Example printed form: +// +// t1 = extract t0 #1 +type Extract struct { + register + Tuple Value + Index int +} + +// Instructions executed for effect. They do not yield a value. -------------------- + +// The Jump instruction transfers control to the sole successor of its +// owning block. +// +// A Jump must be the last instruction of its containing BasicBlock. +// +// Pos() returns NoPos. +// +// Example printed form: +// +// jump done +type Jump struct { + anInstruction +} + +// The Unreachable pseudo-instruction signals that execution cannot +// continue after the preceding function call because it terminates +// the process. +// +// An Unreachable instruction must be the last instruction of its +// containing BasicBlock, which must have no successors. +// +// Example printed form: +// +// Unreachable +type Unreachable struct { + anInstruction +} + +// The If instruction transfers control to one of the two successors +// of its owning block, depending on the boolean Cond: the first if +// true, the second if false. +// +// An If instruction must be the last instruction of its containing +// BasicBlock. +// +// Pos() returns the *ast.IfStmt, if explicit in the source. +// +// Example printed form: +// +// if t0 goto done else body +type If struct { + anInstruction + Cond Value +} + +type ConstantSwitch struct { + anInstruction + Tag Value + // Constant branch conditions. A nil Value denotes the (implicit + // or explicit) default branch. + Conds []Value +} + +type TypeSwitch struct { + register + Tag Value + Conds []types.Type +} + +// The Return instruction returns values and control back to the calling +// function. +// +// len(Results) is always equal to the number of results in the +// function's signature. +// +// If len(Results) > 1, Return returns a tuple value with the specified +// components which the caller must access using Extract instructions. +// +// There is no instruction to return a ready-made tuple like those +// returned by a "value,ok"-mode TypeAssert, MapLookup or Recv or +// a tail-call to a function with multiple result parameters. +// +// Return must be the last instruction of its containing BasicBlock. +// Such a block has no successors. +// +// Pos() returns the ast.ReturnStmt.Return, if explicit in the source. +// +// Example printed form: +// +// return +// return t0, t1 +type Return struct { + anInstruction + Results []Value +} + +// The RunDefers instruction pops and invokes the entire stack of +// procedure calls pushed by Defer instructions in this function. +// +// It is legal to encounter multiple 'rundefers' instructions in a +// single control-flow path through a function; this is useful in +// the combined init() function, for example. +// +// Pos() returns NoPos. +// +// Example printed form: +// +// rundefers +type RunDefers struct { + anInstruction +} + +// The Panic instruction initiates a panic with value X. +// +// A Panic instruction must be the last instruction of its containing +// BasicBlock, which must have no successors. +// +// NB: 'go panic(x)' and 'defer panic(x)' do not use this instruction; +// they are treated as calls to a built-in function. +// +// Pos() returns the ast.CallExpr.Lparen if this panic was explicit +// in the source. +// +// Example printed form: +// +// panic t0 +type Panic struct { + anInstruction + X Value // an interface{} +} + +// The Go instruction creates a new goroutine and calls the specified +// function within it. +// +// See CallCommon for generic function call documentation. +// +// Pos() returns the ast.GoStmt.Go. +// +// Example printed form: +// +// go println(t0, t1) +// go t3() +// go invoke t5.Println(...t6) +type Go struct { + anInstruction + Call CallCommon +} + +// The Defer instruction pushes the specified call onto a stack of +// functions to be called by a RunDefers instruction or by a panic. +// +// If DeferStack != nil, it indicates the defer list that the defer is +// added to. Defer list values come from the Builtin function +// ssa:deferstack. Calls to ssa:deferstack() produces the defer stack +// of the current function frame. DeferStack allows for deferring into an +// alternative function stack than the current function. +// +// See CallCommon for generic function call documentation. +// +// Pos() returns the ast.DeferStmt.Defer. +// +// Example printed form: +// +// defer println(t0, t1) +// defer t3() +// defer invoke t5.Println(...t6) +type Defer struct { + anInstruction + Call CallCommon + DeferStack Value // stack of deferred functions (from ssa:deferstack() intrinsic) onto which this function is pushed +} + +// The Send instruction sends X on channel Chan. +// +// Pos() returns the ast.SendStmt.Arrow, if explicit in the source. +// +// Example printed form: +// +// send t0 <- t1 +type Send struct { + anInstruction + Chan, X Value +} + +// The Recv instruction receives from channel Chan. +// +// If CommaOk, the result is a 2-tuple of the value above +// and a boolean indicating the success of the receive. The +// components of the tuple are accessed using Extract. +// +// Pos() returns the ast.UnaryExpr.OpPos, if explicit in the source. +// For receive operations implicit in ranging over a channel, +// Pos() returns the ast.RangeStmt.For. +// +// Example printed form: +// +// t2 = Recv t1 +// t3 = Recv <(int, bool)> t1 +type Recv struct { + register + Chan Value + CommaOk bool +} + +// The Store instruction stores Val at address Addr. +// Stores can be of arbitrary types. +// +// Pos() returns the position of the source-level construct most closely +// associated with the memory store operation. +// Since implicit memory stores are numerous and varied and depend upon +// implementation choices, the details are not specified. +// +// Example printed form: +// +// *x = y +type Store struct { + anInstruction + Addr Value + Val Value +} + +// The BlankStore instruction is emitted for assignments to the blank +// identifier. +// +// BlankStore is a pseudo-instruction: it has no dynamic effect. +// +// Pos() returns NoPos. +// +// Example printed form: +// +// BlankStore t1 +type BlankStore struct { + anInstruction + Val Value +} + +// The MapUpdate instruction updates the association of Map[Key] to +// Value. +// +// Pos() returns the ast.KeyValueExpr.Colon or ast.IndexExpr.Lbrack, +// if explicit in the source. +// +// Example printed form: +// +// t0[t1] = t2 +type MapUpdate struct { + anInstruction + Map Value + Key Value + Value Value +} + +// A debugRef instruction maps a source-level expression Expr to the +// IR value X that represents the value (!IsAddr) or address (IsAddr) +// of that expression. +// +// debugRef is a pseudo-instruction: it has no dynamic effect. +// +// Pos() returns Expr.Pos(), the start position of the source-level +// expression. This is not the same as the "designated" token as +// documented at Value.Pos(). e.g. CallExpr.Pos() does not return the +// position of the ("designated") Lparen token. +// +// If Expr is an *ast.Ident denoting a var or func, Object() returns +// the object; though this information can be obtained from the type +// checker, including it here greatly facilitates debugging. +// For non-Ident expressions, Object() returns nil. +// +// DebugRefs are generated only for functions built with debugging +// enabled; see Package.SetDebugMode() and the GlobalDebug builder +// mode flag. +// +// DebugRefs are not emitted for ast.Idents referring to constants or +// predeclared identifiers, since they are trivial and numerous. +// Nor are they emitted for ast.ParenExprs. +// +// (By representing these as instructions, rather than out-of-band, +// consistency is maintained during transformation passes by the +// ordinary SSA renaming machinery.) +// +// Example printed form: +// +// ; *ast.CallExpr @ 102:9 is t5 +// ; var x float64 @ 109:72 is x +// ; address of *ast.CompositeLit @ 216:10 is t0 +type debugRef struct { + anInstruction + Expr ast.Expr // the referring expression (never *ast.ParenExpr) + object types.Object // the identity of the source var/func + IsAddr bool // Expr is addressable and X is the address it denotes + X Value // the value or address of Expr +} + +// Embeddable mix-ins and helpers for common parts of other structs. ----------- + +// register is a mix-in embedded by all IR values that are also +// instructions, i.e. virtual registers, and provides a uniform +// implementation of most of the Value interface: Value.Name() is a +// numbered register (e.g. "t0"); the other methods are field accessors. +// +// Temporary names are automatically assigned to each register on +// completion of building a function in IR form. +type register struct { + anInstruction + typ types.Type // type of virtual register + referrers []Instruction +} + +type node struct { + source ast.Node +} + +func (n *node) setSource(source ast.Node) { n.source = source } +func (n *node) Source() ast.Node { return n.source } + +func (n *node) Pos() token.Pos { + if n.source != nil { + return n.source.Pos() + } + return token.NoPos +} + +// anInstruction is a mix-in embedded by all Instructions. +// It provides the implementations of the Block and setBlock methods. +type anInstruction struct { + node + id ID + block *BasicBlock // the basic block of this instruction + comment string +} + +func (instr *anInstruction) setID(id ID) { + instr.id = id +} + +func (instr anInstruction) ID() ID { + return instr.id +} + +func (instr anInstruction) Comment() string { + return instr.comment +} + +// CallCommon is contained by Go, Defer and Call to hold the +// common parts of a function or method call. +// +// Each CallCommon exists in one of two modes, function call and +// interface method invocation, or "call" and "invoke" for short. +// +// 1. "call" mode: when Method is nil (!IsInvoke), a CallCommon +// represents an ordinary function call of the value in Value, +// which may be a *Builtin, a *Function or any other value of kind +// 'func'. +// +// Value may be one of: +// +// (a) a *Function, indicating a statically dispatched call +// to a package-level function, an anonymous function, or +// a method of a named type. +// (b) a *MakeClosure, indicating an immediately applied +// function literal with free variables. +// (c) a *Builtin, indicating a statically dispatched call +// to a built-in function. +// (d) any other value, indicating a dynamically dispatched +// function call. +// +// StaticCallee returns the identity of the callee in cases +// (a) and (b), nil otherwise. +// +// Args contains the arguments to the call. If Value is a method, +// Args[0] contains the receiver parameter. +// +// Example printed form: +// +// t2 = println(t0, t1) +// go t3() +// defer t5(...t6) +// +// 2. "invoke" mode: when Method is non-nil (IsInvoke), a CallCommon +// represents a dynamically dispatched call to an interface method. +// In this mode, Value is the interface value and Method is the +// interface's abstract method. The interface value may be a type +// parameter. Note: an interface method may be shared by multiple +// interfaces due to embedding; Value.Type() provides the specific +// interface used for this call. +// +// Value is implicitly supplied to the concrete method implementation +// as the receiver parameter; in other words, Args[0] holds not the +// receiver but the first true argument. +// +// Example printed form: +// +// t1 = invoke t0.String() +// go invoke t3.Run(t2) +// defer invoke t4.Handle(...t5) +// +// For all calls to variadic functions (Signature().Variadic()), +// the last element of Args is a slice. +type CallCommon struct { + Value Value // receiver (invoke mode) or func value (call mode) + Method *types.Func // interface method (invoke mode) + Args []Value // actual parameters (in static method call, includes receiver) + + source ast.Node +} + +// IsInvoke returns true if this call has "invoke" (not "call") mode. +func (c *CallCommon) IsInvoke() bool { + return c.Method != nil +} + +func (c *CallCommon) Pos() token.Pos { + if c.source != nil { + return c.source.Pos() + } + return token.NoPos +} + +// Signature returns the signature of the called function. +// +// For an "invoke"-mode call, the signature of the interface method is +// returned. +// +// In either "call" or "invoke" mode, if the callee is a method, its +// receiver is represented by sig.Recv, not sig.Params().At(0). +func (c *CallCommon) Signature() *types.Signature { + if c.Method != nil { + return c.Method.Type().(*types.Signature) + } + return typeutil.CoreType(c.Value.Type()).(*types.Signature) +} + +// StaticCallee returns the callee if this is a trivially static +// "call"-mode call to a function. +func (c *CallCommon) StaticCallee() *Function { + switch fn := c.Value.(type) { + case *Function: + return fn + case *MakeClosure: + return fn.Fn.(*Function) + } + return nil +} + +// Description returns a description of the mode of this call suitable +// for a user interface, e.g., "static method call". +func (c *CallCommon) Description() string { + switch fn := c.Value.(type) { + case *Builtin: + return "built-in function call" + case *MakeClosure: + return "static function closure call" + case *Function: + if fn.Signature.Recv() != nil { + return "static method call" + } + return "static function call" + } + if c.IsInvoke() { + return "dynamic method call" // ("invoke" mode) + } + return "dynamic function call" +} + +// The CallInstruction interface, implemented by *Go, *Defer and *Call, +// exposes the common parts of function-calling instructions, +// yet provides a way back to the Value defined by *Call alone. +type CallInstruction interface { + Instruction + Common() *CallCommon // returns the common parts of the call + Value() *Call +} + +func (s *Call) Common() *CallCommon { return &s.Call } +func (s *Defer) Common() *CallCommon { return &s.Call } +func (s *Go) Common() *CallCommon { return &s.Call } + +func (s *Call) setSource(source ast.Node) { s.source = source; s.Call.source = source } +func (s *Defer) setSource(source ast.Node) { s.source = source; s.Call.source = source } +func (s *Go) setSource(source ast.Node) { s.source = source; s.Call.source = source } + +func (s *Call) Value() *Call { return s } +func (s *Defer) Value() *Call { return nil } +func (s *Go) Value() *Call { return nil } + +func (v *Builtin) Type() types.Type { return v.sig } +func (v *Builtin) Name() string { return v.name } +func (*Builtin) Referrers() *[]Instruction { return nil } +func (v *Builtin) Pos() token.Pos { return token.NoPos } +func (v *Builtin) Object() types.Object { return types.Universe.Lookup(v.name) } +func (v *Builtin) Parent() *Function { return nil } + +func (v *FreeVar) Type() types.Type { return v.typ } +func (v *FreeVar) Name() string { return v.name } +func (v *FreeVar) Referrers() *[]Instruction { return &v.referrers } +func (v *FreeVar) Parent() *Function { return v.parent } + +func (v *Global) Type() types.Type { return v.typ } +func (v *Global) Name() string { return v.name } +func (v *Global) Parent() *Function { return nil } +func (v *Global) Referrers() *[]Instruction { return nil } +func (v *Global) Token() token.Token { return token.VAR } +func (v *Global) Object() types.Object { return v.object } +func (v *Global) String() string { return v.RelString(nil) } +func (v *Global) Package() *Package { return v.Pkg } +func (v *Global) RelString(from *types.Package) string { return relString(v, from) } + +func (v *Function) Name() string { return v.name } +func (v *Function) Type() types.Type { return v.Signature } +func (v *Function) Token() token.Token { return token.FUNC } +func (v *Function) Object() types.Object { + if v.object != nil { + return types.Object(v.object) + } + return nil +} +func (v *Function) String() string { return v.RelString(nil) } +func (v *Function) Package() *Package { return v.Pkg } +func (v *Function) Parent() *Function { return v.parent } +func (v *Function) Referrers() *[]Instruction { + if v.parent != nil { + return &v.referrers + } + return nil +} + +// Returns returns an iterator over all blocks containing return statements. +func (v *Function) Returns() iter.Seq[*BasicBlock] { + return func(yield func(*BasicBlock) bool) { + for _, b := range v.Blocks { + if _, ok := b.Control().(*Return); ok { + if !yield(b) { + return + } + } + } + } +} + +// Exits returns an iterator over all blocks that have no successors. +func (v *Function) Exits() iter.Seq[*BasicBlock] { + return func(yield func(*BasicBlock) bool) { + for _, b := range v.Blocks { + if len(b.Succs) == 0 { + if !yield(b) { + return + } + } + } + } +} + +// Nodes returns an iterator over the basic block indices. +func (v *Function) Nodes() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range v.Blocks { + if !yield(i) { + return + } + } + } +} + +// NumNodes returns the number of basic blocks in the graph. +func (v *Function) NumNodes() int { return len(v.Blocks) } + +// Out returns an iterator over the successor block indices of a given node. +func (v *Function) Out(node int) iter.Seq[int] { + return func(yield func(int) bool) { + for _, succ := range v.Blocks[node].Succs { + if !yield(succ.Index) { + return + } + } + } +} + +func (v *Parameter) Type() types.Type { return v.typ } +func (v *Parameter) Name() string { return v.name } +func (v *Parameter) Object() types.Object { return v.object } +func (v *Parameter) Referrers() *[]Instruction { return &v.referrers } +func (v *Parameter) Pos() token.Pos { return v.object.Pos() } +func (v *Parameter) Parent() *Function { return v.parent } + +// TypeParams are the function's type parameters if generic or the +// type parameters that were instantiated if fn is an instantiation. +// +// Specifically, the resulting list behaves like: +// +// func f // [] +// func f[P] // [P] +// func (T) m // [] +// func (T) m[P] // [P] +// func (T[P]) m // [P] +// func (T[P]) m[Q] // [P (index=0), Q (index=0)] +// +// Note that receiver type parameters precede other type parameters. +// Also, type parameters may have the same index if they come from +// different source type parameter lists. +func (fn *Function) TypeParams() *types.TypeParamList { + return consTypeParamLists(fn.recvtypeparams, fn.typeparams) +} + +func consTypeParamLists(l, r *types.TypeParamList) *types.TypeParamList { + if l.Len() == 0 { + return r + } + if r.Len() == 0 { + return l + } + + tpars := make([]*types.TypeParam, l.Len()+r.Len()) + for i := range l.Len() { + tpars[i] = l.At(i) + } + for i := range r.Len() { + tpars[i+l.Len()] = r.At(i) + } + // This logic unsafely assumes (and asserts) that the layout of the + // TypeParamList is identical to that of a slice of TypeParams. This + // is a hack while we work on getting a constructor for TypeParamList + // approved (see go.dev/issue/79603). + t := reflect.TypeFor[types.TypeParamList]() + if t.NumField() != 1 { + panic("TypeParamList has unexpected fields") + } + if f := t.Field(0); f.Offset != 0 || f.Type != reflect.TypeFor[[]*types.TypeParam]() { + panic("TypeParamList field is not []*TypeParam") + } + return (*types.TypeParamList)(unsafe.Pointer(&tpars)) +} + +// TypeArgs are the types that TypeParams() were instantiated by to create fn +// from fn.Origin(). +// +// Specifically, the resulting slice behaves like: +// +// f // [] +// f[int] // [int] +// T.m // [] +// T.m[int] // [int] +// T[int].m // [int] +// T[int].m[uint] // [int, uint] +// +// Note that receiver type arguments precede other type arguments. +func (fn *Function) TypeArgs() []types.Type { + return slices.Concat(fn.recvtypeargs, fn.typeargs) +} + +// Origin returns the generic function from which fn was instantiated, +// or nil if fn is not an instantiation. +func (fn *Function) Origin() *Function { + if fn.parent != nil && fn.parent.hasTypeArgs() { + // Nested functions are BUILT at a different time than their instances. + // Build declared package if not yet BUILT. This is not an expected use + // case, but is simple and robust. + fn.declaredPackage().Build() + } + return origin(fn) +} + +// hasTypeParams returns whether fn has any type parameters +func (fn *Function) hasTypeParams() bool { + return fn.recvtypeparams.Len()+fn.typeparams.Len() > 0 +} + +// hasTypeArgs returns whether fn has any type arguments +func (fn *Function) hasTypeArgs() bool { + return len(fn.recvtypeargs)+len(fn.typeargs) > 0 +} + +// subrtargs returns fn's receiver type parameters substituted with receiver type arguments +func (fn *Function) subrtargs(m *types.Func) []types.Type { + return fn.subst.types(receiverTypeArgs(m)) +} + +// subtargs returns fn's type parameters substituted with (possibly implied) type arguments +func (fn *Function) subtargs(id *ast.Ident) []types.Type { + return fn.subst.types(instanceArgs(fn.info, id)) +} + +// targstr returns a comma-separated string of the types in targs +func targstr(targs []types.Type) string { + var sb strings.Builder + if len(targs) > 0 { + sb.WriteString("[") + for i := range targs { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString(targs[i].String()) + } + sb.WriteString("]") + } + return sb.String() +} + +// origin is the function that fn is an instantiation of. Returns nil if fn is +// not an instantiation. +// +// Precondition: fn and the origin function are done building. +func origin(fn *Function) *Function { + if fn.parent != nil && fn.parent.hasTypeArgs() { + return origin(fn.parent).AnonFuncs[fn.anonIdx] + } + return fn.topLevelOrigin +} + +func (v *Alloc) Type() types.Type { return v.typ } +func (v *Alloc) Referrers() *[]Instruction { return &v.referrers } + +func (v *register) Type() types.Type { return v.typ } +func (v *register) setType(typ types.Type) { v.typ = typ } +func (v *register) Name() string { return fmt.Sprintf("t%d", v.id) } +func (v *register) Referrers() *[]Instruction { return &v.referrers } + +func (v *anInstruction) Parent() *Function { return v.block.parent } +func (v *anInstruction) Block() *BasicBlock { return v.block } +func (v *anInstruction) setBlock(block *BasicBlock) { v.block = block } +func (v *anInstruction) Referrers() *[]Instruction { return nil } + +func (t *Type) Name() string { return t.object.Name() } +func (t *Type) Pos() token.Pos { return t.object.Pos() } +func (t *Type) Type() types.Type { return t.object.Type() } +func (t *Type) Token() token.Token { return token.TYPE } +func (t *Type) Object() types.Object { return t.object } +func (t *Type) String() string { return t.RelString(nil) } +func (t *Type) Package() *Package { return t.pkg } +func (t *Type) RelString(from *types.Package) string { return relString(t, from) } + +func (c *NamedConst) Name() string { return c.object.Name() } +func (c *NamedConst) Pos() token.Pos { return c.object.Pos() } +func (c *NamedConst) String() string { return c.RelString(nil) } +func (c *NamedConst) Type() types.Type { return c.object.Type() } +func (c *NamedConst) Token() token.Token { return token.CONST } +func (c *NamedConst) Object() types.Object { return c.object } +func (c *NamedConst) Package() *Package { return c.pkg } +func (c *NamedConst) RelString(from *types.Package) string { return relString(c, from) } + +func (v *Function) Pos() token.Pos { return v.pos } +func (v *Function) Source() ast.Node { return v.syntax } + +func (d *debugRef) Object() types.Object { return d.object } + +// Func returns the package-level function of the specified name, +// or nil if not found. +func (p *Package) Func(name string) (f *Function) { + f, _ = p.Members[name].(*Function) + return +} + +// Var returns the package-level variable of the specified name, +// or nil if not found. +func (p *Package) Var(name string) (g *Global) { + g, _ = p.Members[name].(*Global) + return +} + +// Const returns the package-level constant of the specified name, +// or nil if not found. +func (p *Package) Const(name string) (c *NamedConst) { + c, _ = p.Members[name].(*NamedConst) + return +} + +// Type returns the package-level type of the specified name, +// or nil if not found. +func (p *Package) Type(name string) (t *Type) { + t, _ = p.Members[name].(*Type) + return +} + +func (s *debugRef) Pos() token.Pos { return s.Expr.Pos() } + +// Operands. + +func (v *Alloc) Operands(rands []*Value) []*Value { + return rands +} + +func (v *BinOp) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Y) +} + +func (c *CallCommon) Operands(rands []*Value) []*Value { + rands = append(rands, &c.Value) + for i := range c.Args { + rands = append(rands, &c.Args[i]) + } + return rands +} + +func (s *Go) Operands(rands []*Value) []*Value { + return s.Call.Operands(rands) +} + +func (s *Call) Operands(rands []*Value) []*Value { + return s.Call.Operands(rands) +} + +func (s *Defer) Operands(rands []*Value) []*Value { + return append(s.Call.Operands(rands), &s.DeferStack) +} + +func (v *ChangeInterface) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *ChangeType) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *Convert) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *MultiConvert) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *SliceToArrayPointer) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *SliceToArray) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (s *debugRef) Operands(rands []*Value) []*Value { + return append(rands, &s.X) +} + +func (v *Extract) Operands(rands []*Value) []*Value { + return append(rands, &v.Tuple) +} + +func (v *Field) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *FieldAddr) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (s *If) Operands(rands []*Value) []*Value { + return append(rands, &s.Cond) +} + +func (s *ConstantSwitch) Operands(rands []*Value) []*Value { + rands = append(rands, &s.Tag) + for i := range s.Conds { + rands = append(rands, &s.Conds[i]) + } + return rands +} + +func (s *TypeSwitch) Operands(rands []*Value) []*Value { + rands = append(rands, &s.Tag) + return rands +} + +func (v *Index) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Index) +} + +func (v *IndexAddr) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Index) +} + +func (*Jump) Operands(rands []*Value) []*Value { + return rands +} + +func (*Unreachable) Operands(rands []*Value) []*Value { + return rands +} + +func (v *MapLookup) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Index) +} + +func (v *StringLookup) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Index) +} + +func (v *MakeChan) Operands(rands []*Value) []*Value { + return append(rands, &v.Size) +} + +func (v *MakeClosure) Operands(rands []*Value) []*Value { + rands = append(rands, &v.Fn) + for i := range v.Bindings { + rands = append(rands, &v.Bindings[i]) + } + return rands +} + +func (v *MakeInterface) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *MakeMap) Operands(rands []*Value) []*Value { + return append(rands, &v.Reserve) +} + +func (v *MakeSlice) Operands(rands []*Value) []*Value { + return append(rands, &v.Len, &v.Cap) +} + +func (v *MapUpdate) Operands(rands []*Value) []*Value { + return append(rands, &v.Map, &v.Key, &v.Value) +} + +func (v *Next) Operands(rands []*Value) []*Value { + return append(rands, &v.Iter) +} + +func (s *Panic) Operands(rands []*Value) []*Value { + return append(rands, &s.X) +} + +func (v *Phi) Operands(rands []*Value) []*Value { + for i := range v.Edges { + rands = append(rands, &v.Edges[i]) + } + return rands +} + +func (v *Range) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (s *Return) Operands(rands []*Value) []*Value { + for i := range s.Results { + rands = append(rands, &s.Results[i]) + } + return rands +} + +func (*RunDefers) Operands(rands []*Value) []*Value { + return rands +} + +func (v *Select) Operands(rands []*Value) []*Value { + for i := range v.States { + rands = append(rands, &v.States[i].Chan, &v.States[i].Send) + } + return rands +} + +func (s *Send) Operands(rands []*Value) []*Value { + return append(rands, &s.Chan, &s.X) +} + +func (recv *Recv) Operands(rands []*Value) []*Value { + return append(rands, &recv.Chan) +} + +func (v *Slice) Operands(rands []*Value) []*Value { + return append(rands, &v.X, &v.Low, &v.High, &v.Max) +} + +func (s *Store) Operands(rands []*Value) []*Value { + return append(rands, &s.Addr, &s.Val) +} + +func (s *BlankStore) Operands(rands []*Value) []*Value { + return append(rands, &s.Val) +} + +func (v *TypeAssert) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *UnOp) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *Load) Operands(rands []*Value) []*Value { + return append(rands, &v.X) +} + +func (v *AggregateConst) Operands(rands []*Value) []*Value { + for i := range v.Values { + rands = append(rands, &v.Values[i]) + } + return rands +} + +func (v *CompositeValue) Operands(rands []*Value) []*Value { + for i := range v.Values { + rands = append(rands, &v.Values[i]) + } + return rands +} + +// Non-Instruction Values: +func (v *Builtin) Operands(rands []*Value) []*Value { return rands } +func (v *FreeVar) Operands(rands []*Value) []*Value { return rands } +func (v *Const) Operands(rands []*Value) []*Value { return rands } +func (v *Function) Operands(rands []*Value) []*Value { return rands } +func (v *Global) Operands(rands []*Value) []*Value { return rands } +func (v *Parameter) Operands(rands []*Value) []*Value { return rands } diff --git a/vendor/honnef.co/go/tools/go/ir/subst.go b/vendor/honnef.co/go/tools/go/ir/subst.go new file mode 100644 index 0000000..07b636a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/subst.go @@ -0,0 +1,577 @@ +// Copyright 2022 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 ir + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/aliases" +) + +// subster defines a type substitution operation of a set of type parameters +// to type parameter free replacement types. Substitution is done within +// the context of a package-level function instantiation. *Named types +// declared in the function are unique to the instantiation. +// +// For example, given a parameterized function F +// +// func F[S, T any]() any { +// type X struct{ s S; next *X } +// var p *X +// return p +// } +// +// calling the instantiation F[string, int]() returns an interface +// value (*X[string,int], nil) where the underlying value of +// X[string,int] is a struct{s string; next *X[string,int]}. +// +// A nil *subster is a valid, empty substitution map. It always acts as +// the identity function. This allows for treating parameterized and +// non-parameterized functions identically while compiling to ssa. +// +// Not concurrency-safe. +// +// Note: Some may find it helpful to think through some of the most +// complex substitution cases using lambda calculus inspired notation. +// subst.typ() solves evaluating a type expression E +// within the body of a function Fn[m] with the type parameters m +// once we have applied the type arguments N. +// We can succinctly write this as a function application: +// +// ((λm. E) N) +// +// go/types does not provide this interface directly. +// So what subster provides is a type substitution operation +// +// E[m:=N] +type subster struct { + replacements map[*types.TypeParam]types.Type // values should contain no type params + cache map[types.Type]types.Type // cache of subst results + origin *types.Func // types.Objects declared within this origin function are unique within this context + ctxt *types.Context // speeds up repeated instantiations + uniqueness typeutil.Map[types.Type] // determines the uniqueness of the instantiations within the function + // TODO(taking): consider adding Pos +} + +// Returns a subster that replaces rtparams[i] with rtargs[i] and tparams[i] with targs[i]. +// Uses ctxt as a cache. rtargs and targs should not contain any types in rtparams or tparams. +// fn is the generic function for which we are substituting. +func makeSubster(ctxt *types.Context, fn *types.Func, rtparams *types.TypeParamList, rtargs []types.Type, tparams *types.TypeParamList, targs []types.Type) *subster { + got := len(rtargs) + len(targs) + want := rtparams.Len() + tparams.Len() + if got != want { + panic(fmt.Sprintf("makeSubster argument count must match: got %d; want %d", got, want)) + } + + subst := &subster{ + replacements: make(map[*types.TypeParam]types.Type, want), + cache: make(map[types.Type]types.Type), + origin: fn.Origin(), + ctxt: ctxt, + } + for i := 0; i < rtparams.Len(); i++ { + subst.replacements[rtparams.At(i)] = rtargs[i] + } + for i := 0; i < tparams.Len(); i++ { + subst.replacements[tparams.At(i)] = targs[i] + } + return subst +} + +// typ returns the type of t with the type parameter tparams[i] substituted +// for the type targs[i] where subst was created using tparams and targs. +func (subst *subster) typ(t types.Type) (res types.Type) { + if subst == nil { + return t // A nil subst is type preserving. + } + if r, ok := subst.cache[t]; ok { + return r + } + defer func() { + subst.cache[t] = res + }() + + switch t := t.(type) { + case *types.TypeParam: + if r := subst.replacements[t]; r != nil { + return r + } + return t + + case *types.Basic: + return t + + case *types.Array: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewArray(r, t.Len()) + } + return t + + case *types.Slice: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewSlice(r) + } + return t + + case *types.Pointer: + if r := subst.typ(t.Elem()); r != t.Elem() { + return types.NewPointer(r) + } + return t + + case *types.Tuple: + return subst.tuple(t) + + case *types.Struct: + return subst.struct_(t) + + case *types.Map: + key := subst.typ(t.Key()) + elem := subst.typ(t.Elem()) + if key != t.Key() || elem != t.Elem() { + return types.NewMap(key, elem) + } + return t + + case *types.Chan: + if elem := subst.typ(t.Elem()); elem != t.Elem() { + return types.NewChan(t.Dir(), elem) + } + return t + + case *types.Signature: + return subst.signature(t) + + case *types.Union: + return subst.union(t) + + case *types.Interface: + return subst.interface_(t) + + case *types.Alias: + return subst.alias(t) + + case *types.Named: + return subst.named(t) + + case *typeutil.Iterator: + elem := subst.typ(t.Elem()) + if elem != t.Elem() { + return typeutil.NewIterator(elem) + } + return t + + case *typeutil.DeferStack: + return t + + default: + panic(fmt.Sprintf("internal error: unhandled type %T", t)) + } +} + +// types returns the result of {subst.typ(ts[i])}. +func (subst *subster) types(ts []types.Type) []types.Type { + res := make([]types.Type, len(ts)) + for i := range ts { + res[i] = subst.typ(ts[i]) + } + return res +} + +func (subst *subster) tuple(t *types.Tuple) *types.Tuple { + if t != nil { + if vars := subst.varlist(t); vars != nil { + return types.NewTuple(vars...) + } + } + return t +} + +type varlist interface { + At(i int) *types.Var + Len() int +} + +// fieldlist is an adapter for structs for the varlist interface. +type fieldlist struct { + str *types.Struct +} + +func (fl fieldlist) At(i int) *types.Var { return fl.str.Field(i) } +func (fl fieldlist) Len() int { return fl.str.NumFields() } + +func (subst *subster) struct_(t *types.Struct) *types.Struct { + if t != nil { + if fields := subst.varlist(fieldlist{t}); fields != nil { + tags := make([]string, t.NumFields()) + for i, n := 0, t.NumFields(); i < n; i++ { + tags[i] = t.Tag(i) + } + return types.NewStruct(fields, tags) + } + } + return t +} + +// varlist returns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i. +func (subst *subster) varlist(in varlist) []*types.Var { + var out []*types.Var // nil => no updates + for i, n := 0, in.Len(); i < n; i++ { + v := in.At(i) + w := subst.var_(v) + if v != w && out == nil { + out = make([]*types.Var, n) + for j := 0; j < i; j++ { + out[j] = in.At(j) + } + } + if out != nil { + out[i] = w + } + } + return out +} + +func (subst *subster) var_(v *types.Var) *types.Var { + if v != nil { + if typ := subst.typ(v.Type()); typ != v.Type() { + if v.IsField() { + return types.NewField(v.Pos(), v.Pkg(), v.Name(), typ, v.Embedded()) + } + return types.NewParam(v.Pos(), v.Pkg(), v.Name(), typ) + } + } + return v +} + +func (subst *subster) union(u *types.Union) *types.Union { + var out []*types.Term // nil => no updates + + for i, n := 0, u.Len(); i < n; i++ { + t := u.Term(i) + r := subst.typ(t.Type()) + if r != t.Type() && out == nil { + out = make([]*types.Term, n) + for j := 0; j < i; j++ { + out[j] = u.Term(j) + } + } + if out != nil { + out[i] = types.NewTerm(t.Tilde(), r) + } + } + + if out != nil { + return types.NewUnion(out) + } + return u +} + +func (subst *subster) interface_(iface *types.Interface) *types.Interface { + if iface == nil { + return nil + } + + // methods for the interface. Initially nil if there is no known change needed. + // Signatures for the method where recv is nil. NewInterfaceType fills in the receivers. + var methods []*types.Func + initMethods := func(n int) { // copy first n explicit methods + methods = make([]*types.Func, iface.NumExplicitMethods()) + for i := range n { + f := iface.ExplicitMethod(i) + norecv := changeRecv(f.Type().(*types.Signature), nil) + methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), norecv) + } + } + for i := 0; i < iface.NumExplicitMethods(); i++ { + f := iface.ExplicitMethod(i) + // On interfaces, we need to cycle break on anonymous interface types + // being in a cycle with their signatures being in cycles with their receivers + // that do not go through a Named. + norecv := changeRecv(f.Type().(*types.Signature), nil) + sig := subst.typ(norecv) + if sig != norecv && methods == nil { + initMethods(i) + } + if methods != nil { + methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), sig.(*types.Signature)) + } + } + + var embeds []types.Type + initEmbeds := func(n int) { // copy first n embedded types + embeds = make([]types.Type, iface.NumEmbeddeds()) + for i := range n { + embeds[i] = iface.EmbeddedType(i) + } + } + for i := 0; i < iface.NumEmbeddeds(); i++ { + e := iface.EmbeddedType(i) + r := subst.typ(e) + if e != r && embeds == nil { + initEmbeds(i) + } + if embeds != nil { + embeds[i] = r + } + } + + if methods == nil && embeds == nil { + return iface + } + if methods == nil { + initMethods(iface.NumExplicitMethods()) + } + if embeds == nil { + initEmbeds(iface.NumEmbeddeds()) + } + return types.NewInterfaceType(methods, embeds).Complete() +} + +func (subst *subster) alias(t *types.Alias) types.Type { + // See subster.named. This follows the same strategy. + tparams := t.TypeParams() + targs := t.TypeArgs() + tname := t.Obj() + torigin := t.Origin() + + if !declaredWithin(tname, subst.origin) { + // t is declared outside of the function origin. So t is a package level type alias. + if targs.Len() == 0 { + // No type arguments so no instantiation needed. + return t + } + + // Instantiate with the substituted type arguments. + newTArgs := subst.typelist(targs) + return subst.instantiate(torigin, newTArgs) + } + + if targs.Len() == 0 { + // t is declared within the function origin and has no type arguments. + // + // Example: This corresponds to A or B in F, but not A[int]: + // + // func F[T any]() { + // type A[S any] = struct{t T, s S} + // type B = T + // var x A[int] + // ... + // } + // + // This is somewhat different than *Named as *Alias cannot be created recursively. + + // Copy and substitute type params. + var newTParams []*types.TypeParam + for cur := range tparams.TypeParams() { + cobj := cur.Obj() + cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil) + ntp := types.NewTypeParam(cname, nil) + subst.cache[cur] = ntp // See the comment "Note: Subtle" in subster.named. + newTParams = append(newTParams, ntp) + } + + // Substitute rhs. + rhs := subst.typ(t.Rhs()) + + // Create the fresh alias. + obj := aliases.New(tname.Pos(), tname.Pkg(), tname.Name(), rhs, newTParams) + + // Substitute into all of the constraints after they are created. + for i, ntp := range newTParams { + bound := tparams.At(i).Constraint() + ntp.SetConstraint(subst.typ(bound)) + } + return obj.Type() + } + + // t is declared within the function origin and has type arguments. + // + // Example: This corresponds to A[int] in F. Cases A and B are handled above. + // func F[T any]() { + // type A[S any] = struct{t T, s S} + // type B = T + // var x A[int] + // ... + // } + subOrigin := subst.typ(torigin) + subTArgs := subst.typelist(targs) + return subst.instantiate(subOrigin, subTArgs) +} + +func (subst *subster) named(t *types.Named) types.Type { + // A Named type is a user defined type. + // Ignoring generics, Named types are canonical: they are identical if + // and only if they have the same defining symbol. + // Generics complicate things, both if the type definition itself is + // parameterized, and if the type is defined within the scope of a + // parameterized function. In this case, two named types are identical if + // and only if their identifying symbols are identical, and all type + // arguments bindings in scope of the named type definition (including the + // type parameters of the definition itself) are equivalent. + // + // Notably: + // 1. For type definition type T[P1 any] struct{}, T[A] and T[B] are identical + // only if A and B are identical. + // 2. Inside the generic func Fn[m any]() any { type T struct{}; return T{} }, + // the result of Fn[A] and Fn[B] have identical type if and only if A and + // B are identical. + // 3. Both 1 and 2 could apply, such as in + // func F[m any]() any { type T[x any] struct{}; return T{} } + // + // A subster replaces type parameters within a function scope, and therefore must + // also replace free type parameters in the definitions of local types. + // + // Note: There are some detailed notes sprinkled throughout that borrow from + // lambda calculus notation. These contain some over simplifying math. + // + // LC: One way to think about subster is that it is a way of evaluating + // ((λm. E) N) as E[m:=N]. + // Each Named type t has an object *TypeName within a scope S that binds an + // underlying type expression U. U can refer to symbols within S (+ S's ancestors). + // Let x = t.TypeParams() and A = t.TypeArgs(). + // Each Named type t is then either: + // U where len(x) == 0 && len(A) == 0 + // λx. U where len(x) != 0 && len(A) == 0 + // ((λx. U) A) where len(x) == len(A) + // In each case, we will evaluate t[m:=N]. + tparams := t.TypeParams() // x + targs := t.TypeArgs() // A + + if !declaredWithin(t.Obj(), subst.origin) { + // t is declared outside of Fn[m]. + // + // In this case, we can skip substituting t.Underlying(). + // The underlying type cannot refer to the type parameters. + // + // LC: Let free(E) be the set of free type parameters in an expression E. + // Then whenever m ∉ free(E), then E = E[m:=N]. + // t ∉ Scope(fn) so therefore m ∉ free(U) and m ∩ x = ∅. + if targs.Len() == 0 { + // t has no type arguments. So it does not need to be instantiated. + // + // This is the normal case in real Go code, where t is not parameterized, + // declared at some package scope, and m is a TypeParam from a parameterized + // function F[m] or method. + // + // LC: m ∉ free(A) lets us conclude m ∉ free(t). So t=t[m:=N]. + return t + } + + // t is declared outside of Fn[m] and has type arguments. + // The type arguments may contain type parameters m so + // substitute the type arguments, and instantiate the substituted + // type arguments. + // + // LC: Evaluate this as ((λx. U) A') where A' = A[m := N]. + newTArgs := subst.typelist(targs) + return subst.instantiate(t.Origin(), newTArgs) + } + + // t is declared within Fn[m]. + + if targs.Len() == 0 { // no type arguments? + assert(t == t.Origin(), "local parameterized type abstraction must be an origin type") + + // t has no type arguments. + // The underlying type of t may contain the function's type parameters, + // replace these, and create a new type. + // + // Subtle: We short circuit substitution and use a newly created type in + // subst, i.e. cache[t]=fresh, to preemptively replace t with fresh + // in recursive types during traversal. This both breaks infinite cycles + // and allows for constructing types with the replacement applied in + // subst.typ(U). + // + // A new copy of the Named and Typename (and constraints) per function + // instantiation matches the semantics of Go, which treats all function + // instantiations F[N] as having distinct local types. + // + // LC: x.Len()=0 can be thought of as a special case of λx. U. + // LC: Evaluate (λx. U)[m:=N] as (λx'. U') where U'=U[x:=x',m:=N]. + tname := t.Obj() + obj := types.NewTypeName(tname.Pos(), tname.Pkg(), tname.Name(), nil) + fresh := types.NewNamed(obj, nil, nil) + var newTParams []*types.TypeParam + for cur := range tparams.TypeParams() { + cobj := cur.Obj() + cname := types.NewTypeName(cobj.Pos(), cobj.Pkg(), cobj.Name(), nil) + ntp := types.NewTypeParam(cname, nil) + subst.cache[cur] = ntp + newTParams = append(newTParams, ntp) + } + fresh.SetTypeParams(newTParams) + subst.cache[t] = fresh + subst.cache[fresh] = fresh + fresh.SetUnderlying(subst.typ(t.Underlying())) + // Substitute into all of the constraints after they are created. + for i, ntp := range newTParams { + bound := tparams.At(i).Constraint() + ntp.SetConstraint(subst.typ(bound)) + } + return fresh + } + + // t is defined within Fn[m] and t has type arguments (an instantiation). + // We reduce this to the two cases above: + // (1) substitute the function's type parameters into t.Origin(). + // (2) substitute t's type arguments A and instantiate the updated t.Origin() with these. + // + // LC: Evaluate ((λx. U) A)[m:=N] as (t' A') where t' = (λx. U)[m:=N] and A'=A [m:=N] + subOrigin := subst.typ(t.Origin()) + subTArgs := subst.typelist(targs) + return subst.instantiate(subOrigin, subTArgs) +} + +func (subst *subster) instantiate(orig types.Type, targs []types.Type) types.Type { + i, err := types.Instantiate(subst.ctxt, orig, targs, false) + assert(err == nil, "failed to Instantiate named (Named or Alias) type") + if c, _ := subst.uniqueness.At(i); c != nil { + return c + } + subst.uniqueness.Set(i, i) + return i +} + +func (subst *subster) typelist(l *types.TypeList) []types.Type { + res := make([]types.Type, l.Len()) + for i := 0; i < l.Len(); i++ { + res[i] = subst.typ(l.At(i)) + } + return res +} + +func (subst *subster) signature(t *types.Signature) types.Type { + tparams := t.TypeParams() + + // We are choosing not to support tparams.Len() > 0 until a need has been observed in practice. + // + // There are some known usages for types.Types coming from types.{Eval,CheckExpr}. + // To support tparams.Len() > 0, we just need to do the following [pseudocode]: + // targs := {subst.replacements[tparams[i]]]}; Instantiate(ctxt, t, targs, false) + + assert(tparams.Len() == 0, "Substituting types.Signatures with generic functions are currently unsupported.") + + // Either: + // (1)non-generic function. + // no type params to substitute + // (2)generic method and recv needs to be substituted. + + // Receivers can be either: + // named + // pointer to named + // interface + // nil + // interface is the problematic case. We need to cycle break there! + recv := subst.var_(t.Recv()) + params := subst.tuple(t.Params()) + results := subst.tuple(t.Results()) + if recv != t.Recv() || params != t.Params() || results != t.Results() { + return types.NewSignatureType(recv, nil, nil, params, results, t.Variadic()) + } + return t +} diff --git a/vendor/honnef.co/go/tools/go/ir/task.go b/vendor/honnef.co/go/tools/go/ir/task.go new file mode 100644 index 0000000..7a44404 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/task.go @@ -0,0 +1,103 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ir + +import ( + "sync/atomic" +) + +// Each task has two states: it is initially "active", +// and transitions to "done". +// +// tasks form a directed graph. An edge from x to y (with y in x.edges) +// indicates that the task x waits on the task y to be done. +// Cycles are permitted. +// +// Calling x.wait() blocks the calling goroutine until task x, +// and all the tasks transitively reachable from x are done. +// +// The nil *task is always considered done. +type task struct { + done chan unit // close when the task is done. + edges map[*task]unit // set of predecessors of this task. + transitive atomic.Bool // true once it is known all predecessors are done. +} + +func (x *task) isTransitivelyDone() bool { return x == nil || x.transitive.Load() } + +// addEdge creates an edge from x to y, indicating that +// x.wait() will not return before y is done. +// All calls to x.addEdge(...) should happen before x.markDone(). +func (x *task) addEdge(y *task) { + if x == y || y.isTransitivelyDone() { + return // no work remaining + } + + // heuristic done check + select { + case <-x.done: + panic("cannot add an edge to a done task") + default: + } + + if x.edges == nil { + x.edges = make(map[*task]unit) + } + x.edges[y] = unit{} +} + +// markDone changes the task's state to markDone. +func (x *task) markDone() { + if x != nil { + close(x.done) + } +} + +// wait blocks until x and all the tasks it can reach through edges are done. +func (x *task) wait() { + if x.isTransitivelyDone() { + return // already known to be done. Skip allocations. + } + + // Use BFS to wait on u.done to be closed, for all u transitively + // reachable from x via edges. + // + // This work can be repeated by multiple workers doing wait(). + // + // Note: Tarjan's SCC algorithm is able to mark SCCs as transitively done + // as soon as the SCC has been visited. This is theoretically faster, but is + // a more complex algorithm. Until we have evidence, we need the more complex + // algorithm, the simpler algorithm BFS is implemented. + // + // In Go 1.23, ssa/TestStdlib reaches <=3 *tasks per wait() in most schedules + // On some schedules, there is a cycle building net/http and internal/trace/testtrace + // due to slices functions. + work := []*task{x} + enqueued := map[*task]unit{x: {}} + for i := 0; i < len(work); i++ { + u := work[i] + if u.isTransitivelyDone() { // already transitively done + work[i] = nil + continue + } + <-u.done // wait for u to be marked done. + + for v := range u.edges { + if _, ok := enqueued[v]; !ok { + enqueued[v] = unit{} + work = append(work, v) + } + } + } + + // work is transitively closed over dependencies. + // u in work is done (or transitively done and skipped). + // u is transitively done. + for _, u := range work { + if u != nil { + x.transitive.Store(true) + } + } +} diff --git a/vendor/honnef.co/go/tools/go/ir/typeset.go b/vendor/honnef.co/go/tools/go/ir/typeset.go new file mode 100644 index 0000000..6154e4f --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/typeset.go @@ -0,0 +1,175 @@ +// Copyright 2022 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 ir + +import ( + "go/types" + + "honnef.co/go/tools/go/types/typeutil" +) + +// Utilities for dealing with type sets. + +const debug = false + +// typeset is an iterator over the (type/underlying type) pairs of the +// specific type terms of the type set implied by t. +// If t is a type parameter, the implied type set is the type set of t's constraint. +// In that case, if there are no specific terms, typeset calls yield with (nil, nil). +// If t is not a type parameter, the implied type set consists of just t. +// In any case, typeset is guaranteed to call yield at least once. +func typeset(typ types.Type, yield func(t, u types.Type) bool) { + switch typ := types.Unalias(typ).(type) { + case *types.TypeParam, *types.Interface: + terms := termListOf(typ) + if len(terms) == 0 { + yield(nil, nil) + return + } + for _, term := range terms { + u := types.Unalias(term.Type()) + if !term.Tilde() { + u = u.Underlying() + } + if debug { + assert(types.Identical(u, u.Underlying()), "Unalias(x) == under(x) for ~x terms") + } + if !yield(term.Type(), u) { + break + } + } + return + default: + yield(typ, typ.Underlying()) + } +} + +// termListOf returns the type set of typ as a normalized term set. Returns an empty set on an error. +func termListOf(typ types.Type) []*types.Term { + return typeutil.NewTypeSet(typ).Terms +} + +// typeSetIsEmpty returns true if a typeset is empty. +func typeSetIsEmpty(typ types.Type) bool { + var empty bool + typeset(typ, func(t, _ types.Type) bool { + empty = t == nil + return false + }) + return empty +} + +// isBytestring returns true if T has the same terms as interface{[]byte | string}. +// These act like a core type for some operations: slice expressions, append and copy. +// +// See https://go.dev/ref/spec#Core_types for the details on bytestring. +func isBytestring(T types.Type) bool { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return false + } + + hasBytes, hasString := false, false + ok := underIs(U, func(t types.Type) bool { + switch { + case isString(t): + hasString = true + return true + case isByteSlice(t): + hasBytes = true + return true + default: + return false + } + }) + return ok && hasBytes && hasString +} + +// underIs calls f with the underlying types of the type terms +// of the type set of typ and reports whether all calls to f returned true. +// If there are no specific terms, underIs returns the result of f(nil). +func underIs(typ types.Type, f func(types.Type) bool) bool { + var ok bool + typeset(typ, func(t, u types.Type) bool { + ok = f(u) + return ok + }) + return ok +} + +// indexType returns the element type and index mode of a IndexExpr over a type. +// It returns an invalid mode if the type is not indexable; this should never occur in a well-typed program. +func indexType(typ types.Type) (types.Type, indexMode) { + switch U := typ.Underlying().(type) { + case *types.Array: + return U.Elem(), ixArrVar + case *types.Pointer: + if arr, ok := U.Elem().Underlying().(*types.Array); ok { + return arr.Elem(), ixVar + } + case *types.Slice: + return U.Elem(), ixVar + case *types.Map: + return U.Elem(), ixMap + case *types.Basic: + return tByte, ixValue // must be a string + case *types.Interface: + var elem types.Type + mode := ixInvalid + typeset(typ, func(t, _ types.Type) bool { + if t == nil { + return false // empty set + } + e, m := indexType(t) + if elem == nil { + elem, mode = e, m + } + if debug && !types.Identical(elem, e) { // if type checked, just a sanity check + mode = ixInvalid + return false + } + // Update the mode to the most constrained address type. + mode = mode.meet(m) + return mode != ixInvalid + }) + return elem, mode + } + return nil, ixInvalid +} + +// An indexMode specifies the (addressing) mode of an index operand. +// +// Addressing mode of an index operation is based on the set of +// underlying types. +// Hasse diagram of the indexMode meet semi-lattice: +// +// ixVar ixMap +// | | +// ixArrVar | +// | | +// ixValue | +// \ / +// ixInvalid +type indexMode byte + +const ( + ixInvalid indexMode = iota // index is invalid + ixValue // index is a computed value (not addressable) + ixArrVar // like ixVar, but index operand contains an array + ixVar // index is an addressable variable + ixMap // index is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment) +) + +// meet is the address type that is constrained by both x and y. +func (x indexMode) meet(y indexMode) indexMode { + if (x == ixMap || y == ixMap) && x != y { + return ixInvalid + } + // Use int representation and return min. + if x < y { + return y + } + return x +} diff --git a/vendor/honnef.co/go/tools/go/ir/util.go b/vendor/honnef.co/go/tools/go/ir/util.go new file mode 100644 index 0000000..d845c9a --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/util.go @@ -0,0 +1,436 @@ +// Copyright 2013 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 ir + +// This file defines a number of miscellaneous utility functions. + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "io" + "os" + "sync" + _ "unsafe" // for go:linkname hack + + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/exp/typeparams" +) + +type unit struct{} + +//// AST utilities + +// isBlankIdent returns true iff e is an Ident with name "_". +// They have no associated types.Object, and thus no type. +func isBlankIdent(e ast.Expr) bool { + id, ok := e.(*ast.Ident) + return ok && id.Name == "_" +} + +//// Type utilities. Some of these belong in go/types. + +// isString reports whether t is exactly a string type. +// t is assumed to be an Underlying type (not Named or Alias). +func isString(t types.Type) bool { + basic, ok := t.(*types.Basic) + return ok && basic.Info()&types.IsString != 0 +} + +// isByteSlice reports whether t is of the form []~bytes. +// t is assumed to be an Underlying type (not Named or Alias). +func isByteSlice(t types.Type) bool { + if b, ok := t.(*types.Slice); ok { + e, _ := b.Elem().Underlying().(*types.Basic) + return e != nil && e.Kind() == types.Byte + } + return false +} + +// isRuneSlice reports whether t is of the form []~runes. +// t is assumed to be an Underlying type (not Named or Alias). +func isRuneSlice(t types.Type) bool { + if b, ok := t.(*types.Slice); ok { + e, _ := b.Elem().Underlying().(*types.Basic) + return e != nil && e.Kind() == types.Rune + } + return false +} + +// isBasicConvTypes returns true when the type set of a type +// can be one side of a Convert operation. This is when: +// - All are basic, []byte, or []rune. +// - At least 1 is basic. +// - At most 1 is []byte or []rune. +func isBasicConvTypes(typ types.Type) bool { + basics, cnt := 0, 0 + ok := underIs(typ, func(t types.Type) bool { + cnt++ + if isBasic(t) { + basics++ + return true + } + return isByteSlice(t) || isRuneSlice(t) + }) + return ok && basics >= 1 && cnt-basics <= 1 +} + +// isPointer reports whether t's underlying type is a pointer. +func isPointer(t types.Type) bool { + return is[*types.Pointer](t.Underlying()) +} + +// isPointerCore reports whether t's core type is a pointer. +// +// (Most pointer manipulation is related to receivers, in which case +// isPointer is appropriate. tecallers can use isPointer(t). +func isPointerCore(t types.Type) bool { + return is[*types.Pointer](typeutil.CoreType(t)) +} + +func is[T any](x any) bool { + _, ok := x.(T) + return ok +} + +// deref returns a pointer's element type; otherwise it returns typ. +func deref(typ types.Type) types.Type { + orig := typ + typ = types.Unalias(typ) + + if t, ok := typ.(*types.TypeParam); ok { + if ctyp := typeutil.CoreType(t); ctyp != nil { + // This can happen, for example, with len(T) where T is a + // type parameter whose core type is a pointer to array. + typ = ctyp + } + } + if p, ok := typ.Underlying().(*types.Pointer); ok { + return p.Elem() + } + return orig +} + +// recvType returns the receiver type of method obj. +func recvType(obj *types.Func) types.Type { + return obj.Type().(*types.Signature).Recv().Type() +} + +// fieldOf returns the index'th field of the (core type of) a struct type; +// otherwise returns nil. +func fieldOf(typ types.Type, index int) *types.Var { + if st, ok := typeutil.CoreType(typ).(*types.Struct); ok { + if 0 <= index && index < st.NumFields() { + return st.Field(index) + } + } + return nil +} + +// isUntyped reports whether typ is the type of an untyped constant. +func isUntyped(typ types.Type) bool { + // No Underlying/Unalias: untyped constant types cannot be Named or Alias. + b, ok := typ.(*types.Basic) + return ok && b.Info()&types.IsUntyped != 0 +} + +// declaredWithin reports whether an object is declared within a function. +// +// obj must not be a method or a field. +func declaredWithin(obj types.Object, fn *types.Func) bool { + if obj.Pos() != token.NoPos { + return fn.Scope().Contains(obj.Pos()) // trust the positions if they exist. + } + if fn.Pkg() != obj.Pkg() { + return false // fast path for different packages + } + + // Traverse Parent() scopes for fn.Scope(). + for p := obj.Parent(); p != nil; p = p.Parent() { + if p == fn.Scope() { + return true + } + } + return false +} + +// logStack prints the formatted "start" message to stderr and +// returns a closure that prints the corresponding "end" message. +// Call using 'defer logStack(...)()' to show builder stack on panic. +// Don't forget trailing parens! +func logStack(format string, args ...any) func() { + msg := fmt.Sprintf(format, args...) + io.WriteString(os.Stderr, msg) + io.WriteString(os.Stderr, "\n") + return func() { + io.WriteString(os.Stderr, msg) + io.WriteString(os.Stderr, " end\n") + } +} + +// newVar creates a 'var' for use in a types.Tuple. +func newVar(name string, typ types.Type) *types.Var { + return types.NewParam(token.NoPos, nil, name, typ) +} + +// anonVar creates an anonymous 'var' for use in a types.Tuple. +func anonVar(typ types.Type) *types.Var { + return newVar("", typ) +} + +var lenResults = types.NewTuple(anonVar(tInt)) + +// makeLen returns the len builtin specialized to type func(T)int. +func makeLen(T types.Type) *Builtin { + lenParams := types.NewTuple(anonVar(T)) + return &Builtin{ + name: "len", + sig: types.NewSignatureType(nil, nil, nil, lenParams, lenResults, false), + } +} + +func assert(x bool, msg string) { + if !x { + panic(msg) + } +} + +// BlockMap is a mapping from basic blocks (identified by their indices) to values. +type BlockMap[T any] []T + +// isBasic reports whether t is a basic type. +func isBasic(t types.Type) bool { + _, ok := t.(*types.Basic) + return ok +} + +// receiverTypeArgs returns the type arguments to a method's receiver. +// Returns an empty list if the receiver does not have type arguments. +func receiverTypeArgs(method *types.Func) []types.Type { + recv := method.Signature().Recv() + named := func() (named *types.Named) { + t := recv.Type() + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + t = ptr.Elem() + } + named, _ = types.Unalias(t).(*types.Named) + return named + }() + if named == nil { + return nil // recv is anonymous struct/interface + } + ts := named.TypeArgs() + if ts.Len() == 0 { + return nil + } + targs := make([]types.Type, ts.Len()) + for i := 0; i < ts.Len(); i++ { + targs[i] = ts.At(i) + } + return targs +} + +// recvAsFirstArg takes a method signature and returns a function +// signature with receiver as the first parameter. +func recvAsFirstArg(sig *types.Signature) *types.Signature { + params := make([]*types.Var, 0, 1+sig.Params().Len()) + params = append(params, sig.Recv()) + for v := range sig.Params().Variables() { + params = append(params, v) + } + return types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic()) +} + +// instance returns whether an expression is a simple or qualified identifier +// that is a generic instantiation. +func instance(info *types.Info, expr ast.Expr) bool { + // Compare the logic here against go/types.instantiatedIdent, + // which also handles *IndexExpr and *IndexListExpr. + var id *ast.Ident + switch x := expr.(type) { + case *ast.Ident: + id = x + case *ast.SelectorExpr: + id = x.Sel + default: + return false + } + _, ok := info.Instances[id] + return ok +} + +// isNonTypeParamInterface reports whether t is an interface type but not a type parameter. +func isNonTypeParamInterface(t types.Type) bool { + return !typeparams.IsTypeParam(t) && types.IsInterface(t) +} + +// instanceArgs returns the Instance[id].TypeArgs as a slice. +func instanceArgs(info *types.Info, id *ast.Ident) []types.Type { + targList := info.Instances[id].TypeArgs + if targList == nil { + return nil + } + + targs := make([]types.Type, targList.Len()) + for i, n := 0, targList.Len(); i < n; i++ { + targs[i] = targList.At(i) + } + return targs +} + +// Mapping of a type T to a canonical instance C s.t. types.Identical(T, C). +// Thread-safe. +type canonizer struct { + mu sync.Mutex + types typeutil.Map[types.Type] // map from type to a canonical instance + lists typeListMap // map from a list of types to a canonical instance +} + +func newCanonizer() *canonizer { + c := &canonizer{} + h := typeutil.MakeHasher() + c.types.SetHasher(h) + c.lists.hasher = h + return c +} + +// List returns a canonical representative of a list of types. +// Representative of the empty list is nil. +func (c *canonizer) List(ts []types.Type) *typeList { + if len(ts) == 0 { + return nil + } + + unaliasAll := func(ts []types.Type) []types.Type { + // Is there some top level alias? + var found bool + for _, t := range ts { + if _, ok := t.(*types.Alias); ok { + found = true + break + } + } + if !found { + return ts // no top level alias + } + + cp := make([]types.Type, len(ts)) // copy with top level aliases removed. + for i, t := range ts { + cp[i] = types.Unalias(t) + } + return cp + } + l := unaliasAll(ts) + + c.mu.Lock() + defer c.mu.Unlock() + return c.lists.rep(l) +} + +// Type returns a canonical representative of type T. +// Removes top-level aliases. +// +// For performance, reasons the canonical instance is order-dependent, +// and may contain deeply nested aliases. +func (c *canonizer) Type(T types.Type) types.Type { + T = types.Unalias(T) // remove the top level alias. + + c.mu.Lock() + defer c.mu.Unlock() + + if r, _ := c.types.At(T); r != nil { + return r + } + c.types.Set(T, T) + return T +} + +// A type for representing a canonized list of types. +type typeList []types.Type + +func (l *typeList) identical(ts []types.Type) bool { + if l == nil { + return len(ts) == 0 + } + n := len(*l) + if len(ts) != n { + return false + } + for i, left := range *l { + right := ts[i] + if !types.Identical(left, right) { + return false + } + } + return true +} + +type typeListMap struct { + hasher typeutil.Hasher + buckets map[uint32][]*typeList +} + +// rep returns a canonical representative of a slice of types. +func (m *typeListMap) rep(ts []types.Type) *typeList { + if m == nil || len(ts) == 0 { + return nil + } + + if m.buckets == nil { + m.buckets = make(map[uint32][]*typeList) + } + + h := m.hash(ts) + bucket := m.buckets[h] + for _, l := range bucket { + if l.identical(ts) { + return l + } + } + + // not present. create a representative. + cp := make(typeList, len(ts)) + copy(cp, ts) + rep := &cp + + m.buckets[h] = append(bucket, rep) + return rep +} + +func (m *typeListMap) hash(ts []types.Type) uint32 { + if m == nil { + return 0 + } + // Some smallish prime far away from typeutil.Hash. + n := len(ts) + h := uint32(13619) + 2*uint32(n) + for i := range n { + h += 3 * m.hasher.Hash(ts[i]) + } + return h +} + +// instantiateMethod instantiates m with targs and returns a canonical representative for this method. +func (canon *canonizer) instantiateMethod(m *types.Func, targs []types.Type, ctxt *types.Context) *types.Func { + recv := recvType(m) + if p, ok := types.Unalias(recv).(*types.Pointer); ok { + recv = p.Elem() + } + named := types.Unalias(recv).(*types.Named) + inst, err := types.Instantiate(ctxt, named.Origin(), targs, false) + if err != nil { + panic(err) + } + rep := canon.Type(inst) + obj, _, _ := types.LookupFieldOrMethod(rep, true, m.Pkg(), m.Name()) + return obj.(*types.Func) +} + +// Exposed to irutil using the linkname hack. +// +//go:linkname isSyntactic honnef.co/go/tools/go/ir.isSyntactic +func isSyntactic(pkg *Package) bool { return pkg.syntax } diff --git a/vendor/honnef.co/go/tools/go/ir/wrappers.go b/vendor/honnef.co/go/tools/go/ir/wrappers.go new file mode 100644 index 0000000..1d7b3e4 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/ir/wrappers.go @@ -0,0 +1,357 @@ +// Copyright 2013 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 ir + +// This file defines synthesis of Functions that delegate to declared +// methods; they come in three kinds: +// +// (1) wrappers: methods that wrap declared methods, performing +// implicit pointer indirections and embedded field selections. +// +// (2) thunks: funcs that wrap declared methods. Like wrappers, +// thunks perform indirections and field selections. The thunk's +// first parameter is used as the receiver for the method call. +// +// (3) bounds: funcs that wrap declared methods. The bound's sole +// free variable, supplied by a closure, is used as the receiver +// for the method call. No indirections or field selections are +// performed since they can be done before the call. + +import ( + "fmt" + "go/types" +) + +// -- wrappers ----------------------------------------------------------- + +// createWrapper returns a synthetic method that delegates to the +// declared method denoted by meth.Obj(), first performing any +// necessary pointer indirections or field selections implied by meth. +// +// The resulting method's receiver type is meth.Recv(). +// +// This function is versatile but quite subtle! Consider the +// following axes of variation when making changes: +// - optional receiver indirection +// - optional implicit field selections +// - optional method type arguments +// - meth.Obj() may denote a concrete or an interface method +// - the result may be a thunk or a wrapper. +func createWrapper(prog *Program, sel *selection, targs []types.Type) *Function { + obj := sel.obj.(*types.Func) // the declared function + name, sig := maybeInstance(prog, obj.Name(), sel.typ.(*types.Signature), targs) + + var recv *types.Var // wrapper's receiver or thunk's params[0] + var description string + if sel.kind == types.MethodExpr { + name += "$thunk" + description = "thunk" + recv = sig.Params().At(0) + } else { + description = "wrapper" + recv = sig.Recv() + } + + description = fmt.Sprintf("%s for %s", description, obj) + if prog.mode&LogSource != 0 { + defer logStack("create %s to (%s)", description, recv.Type())() + } + /* method wrapper */ + return &Function{ + name: name, + method: sel, + object: obj, + Signature: sig, + Synthetic: description, + Prog: prog, + pos: obj.Pos(), + typeargs: targs, + // wrappers have no syntax + build: (*builder).buildWrapper, + info: nil, + goversion: "", + } +} + +// maybeInstance returns name and sig instantiated to reflect any type arguments in targs. +func maybeInstance(prog *Program, name string, sig *types.Signature, targs []types.Type) (string, *types.Signature) { + if len(targs) > 0 { + name = fmt.Sprintf("%s%s", name, targstr(targs)) + instSig, err := types.Instantiate(prog.ctxt, sig, targs, false) + if err != nil { + // validate was false, we should never get an error + panic(err) + } + sig = prog.canon.Type(instSig).(*types.Signature) + } + return name, sig +} + +// buildWrapper builds fn.Body for a method wrapper. +func (b *builder) buildWrapper(fn *Function) { + var recv *types.Var // wrapper's receiver or thunk's params[0] + var start int // first regular param + if fn.method.kind == types.MethodExpr { + recv = fn.Signature.Params().At(0) + start = 1 + } else { + recv = fn.Signature.Recv() + } + + fn.startBody() + fn.addSpilledParam(recv, nil) + createParams(fn, start) + + indices := fn.method.index + + var v Value = fn.Locals[0] // spilled receiver + if isPointer(fn.method.recv) { + v = emitLoad(fn, v, nil) + + // For simple indirection wrappers, perform an informative nil-check: + // "value method (T).f called using nil *T pointer" + if len(indices) == 1 && !isPointer(recvType(fn.object)) { + var c Call + c.Call.Value = &Builtin{ + name: "ssa:wrapnilchk", + sig: types.NewSignatureType(nil, nil, nil, types.NewTuple(anonVar(fn.method.recv), anonVar(tString), anonVar(tString)), types.NewTuple(anonVar(fn.method.recv)), false), + } + c.Call.Args = []Value{ + v, + stringConst(deref(fn.method.recv).String(), nil), + stringConst(fn.method.obj.Name(), nil), + } + c.setType(v.Type()) + v = fn.emit(&c, nil) + } + } + + // Invariant: v is a pointer, either + // value of *A receiver param, or + // address of A spilled receiver. + + // We use pointer arithmetic (FieldAddr possibly followed by + // Load) in preference to value extraction (Field possibly + // preceded by Load). + + v = emitImplicitSelections(fn, v, indices[:len(indices)-1], nil) + + // Invariant: v is a pointer, either + // value of implicit *C field, or + // address of implicit C field. + + var c Call + if r := recvType(fn.object); !types.IsInterface(r) { // concrete method + if !isPointer(r) { + v = emitLoad(fn, v, nil) + } + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) + c.Call.Args = append(c.Call.Args, v) + } else { + c.Call.Method = fn.object + c.Call.Value = emitLoad(fn, v, nil) // interface (possibly a typeparam) + } + for _, arg := range fn.Params[1:] { + c.Call.Args = append(c.Call.Args, arg) + } + emitTailCall(fn, &c, nil) + fn.finishBody() +} + +// createParams creates parameters for wrapper method fn based on its +// Signature.Params, which do not include the receiver. +// start is the index of the first regular parameter to use. +func createParams(fn *Function, start int) { + tparams := fn.Signature.Params() + for i, n := start, tparams.Len(); i < n; i++ { + fn.addParamVar(tparams.At(i), nil) + } +} + +// -- bounds ----------------------------------------------------------- + +// createBound returns a bound method wrapper (or "bound"), a synthetic +// function that delegates to a concrete or interface method denoted +// by obj. The resulting function has no receiver, but has one free +// variable which will be used as the method's receiver in the +// tail-call. +// +// Use MakeClosure with such a wrapper to construct a bound method +// closure. e.g.: +// +// type T int or: type T interface { meth() } +// func (t T) meth() +// var t T +// f := t.meth +// f() // calls t.meth() +// +// f is a closure of a synthetic wrapper defined as if by: +// +// f := func() { return t.meth() } +// +// Unlike createWrapper, createBound need perform no indirection or field +// selections because that can be done before the closure is +// constructed. +func createBound(prog *Program, obj *types.Func, targs []types.Type) *Function { + description := fmt.Sprintf("bound method wrapper for %s", obj) + if prog.mode&LogSource != 0 { + defer logStack("%s", description)() + } + name, sig := maybeInstance(prog, obj.Name(), obj.Type().(*types.Signature), targs) + + /* bound method wrapper */ + fn := &Function{ + name: name + "$bound", + object: obj, + Signature: changeRecv(sig, nil), // drop receiver + Synthetic: description, + Prog: prog, + pos: obj.Pos(), + typeargs: targs, + // wrappers have no syntax + build: (*builder).buildBound, + info: nil, + goversion: "", + } + fn.FreeVars = []*FreeVar{{name: "recv", typ: recvType(obj), parent: fn}} // (cyclic) + return fn +} + +// buildBound builds fn.Body for a bound method closure. +func (b *builder) buildBound(fn *Function) { + fn.startBody() + createParams(fn, 0) + var c Call + + recv := fn.FreeVars[0] + if !types.IsInterface(recvType(fn.object)) { // concrete + c.Call.Value = fn.Prog.objectMethod(fn.object, fn.typeargs, b) + c.Call.Args = []Value{recv} + } else { + c.Call.Method = fn.object + c.Call.Value = recv // interface (possibly a typeparam) + } + for _, arg := range fn.Params { + c.Call.Args = append(c.Call.Args, arg) + } + emitTailCall(fn, &c, nil) + fn.finishBody() +} + +// -- thunks ----------------------------------------------------------- + +// createThunk returns a thunk, a synthetic function that delegates to a +// concrete or interface method denoted by sel.obj. The resulting +// function has no receiver, but has an additional (first) regular +// parameter. +// +// Precondition: sel.kind == types.MethodExpr. +// +// type T int or: type T interface { meth() } +// func (t T) meth() +// f := T.meth +// var t T +// f(t) // calls t.meth() +// +// f is a synthetic wrapper defined as if by: +// +// f := func(t T) { return t.meth() } +func createThunk(prog *Program, sel *selection, targs []types.Type) *Function { + if sel.kind != types.MethodExpr { + panic(sel) + } + + fn := createWrapper(prog, sel, targs) + if fn.Signature.Recv() != nil { + panic(fn) // unexpected receiver + } + return fn +} + +func changeRecv(s *types.Signature, recv *types.Var) *types.Signature { + return types.NewSignatureType(recv, nil, nil, s.Params(), s.Results(), s.Variadic()) +} + +// A local version of *types.Selection. +// Needed for some additional control, such as creating a MethodExpr for an instantiation. +type selection struct { + kind types.SelectionKind + recv types.Type + typ types.Type + obj types.Object + index []int + indirect bool +} + +func toSelection(sel *types.Selection) *selection { + return &selection{ + kind: sel.Kind(), + recv: sel.Recv(), + typ: sel.Type(), + obj: sel.Obj(), + index: sel.Index(), + indirect: sel.Indirect(), + } +} + +// -- instantiations -------------------------------------------------- + +// buildInstantiationWrapper builds the body of an instantiation +// wrapper fn. The body calls the original generic function, +// bracketed by ChangeType conversions on its arguments and results. +func (b *builder) buildInstantiationWrapper(fn *Function) { + orig := fn.topLevelOrigin + sig := fn.Signature + + fn.startBody() + if sig.Recv() != nil { + fn.addParamVar(sig.Recv(), nil) + } + createParams(fn, 0) + + // Create body. Add a call to origin generic function + // and make type changes between argument and parameters, + // as well as return values. + var c Call + c.Call.Value = orig + if res := orig.Signature.Results(); res.Len() == 1 { + c.typ = res.At(0).Type() + } else { + c.typ = res + } + + // parameter of instance becomes an argument to the call + // to the original generic function. + argOffset := 0 + for i, arg := range fn.Params { + var typ types.Type + if i == 0 && sig.Recv() != nil { + typ = orig.Signature.Recv().Type() + argOffset = 1 + } else { + typ = orig.Signature.Params().At(i - argOffset).Type() + } + c.Call.Args = append(c.Call.Args, emitTypeCoercion(fn, arg, typ, nil)) + } + + results := fn.emit(&c, nil) + var ret Return + switch res := sig.Results(); res.Len() { + case 0: + // no results, do nothing. + case 1: + ret.Results = []Value{emitTypeCoercion(fn, results, res.At(0).Type(), nil)} + default: + for i := 0; i < sig.Results().Len(); i++ { + v := emitExtract(fn, results, i, nil) + ret.Results = append(ret.Results, emitTypeCoercion(fn, v, res.At(i).Type(), nil)) + } + } + + fn.emit(&ret, nil) + fn.currentBlock = nil + + fn.finishBody() +} diff --git a/vendor/honnef.co/go/tools/go/loader/hash.go b/vendor/honnef.co/go/tools/go/loader/hash.go new file mode 100644 index 0000000..8c22cca --- /dev/null +++ b/vendor/honnef.co/go/tools/go/loader/hash.go @@ -0,0 +1,98 @@ +package loader + +import ( + "fmt" + "runtime" + "sort" + "strings" + + "honnef.co/go/tools/go/buildid" + "honnef.co/go/tools/lintcmd/cache" +) + +// computeHash computes a package's hash. The hash is based on all Go +// files that make up the package, as well as the hashes of imported +// packages. +func computeHash(pkg *PackageSpec) (cache.ActionID, error) { + key := cache.NewHash("package " + pkg.PkgPath) + fmt.Fprintf(key, "goos %s goarch %s\n", runtime.GOOS, runtime.GOARCH) + fmt.Fprintf(key, "import %q\n", pkg.PkgPath) + + // Compute the hashes of all files making up the package. As an + // optimization, we use the build ID that Go already computed for us, + // because it is virtually identical to hashing all CompiledGoFiles. It is + // also sensitive to the Go version declared in go.mod. + success := false + if pkg.ExportFile != "" { + id, err := getBuildid(pkg.ExportFile) + if err == nil { + if idx := strings.IndexRune(id, '/'); idx > -1 { + fmt.Fprintf(key, "files %s\n", id[:idx]) + success = true + } + } + } + if !success { + for _, f := range pkg.CompiledGoFiles { + h, err := cache.FileHash(f) + if err != nil { + return cache.ActionID{}, err + } + fmt.Fprintf(key, "file %s %x\n", f, h) + } + if pkg.Module != nil && pkg.Module.GoMod != "" { + // The go.mod file specifies the language version, which affects how + // packages are analyzed. + h, err := cache.FileHash(pkg.Module.GoMod) + if err != nil { + // TODO(dh): this doesn't work for tests because the go.mod file doesn't + // exist on disk and is instead provided via an overlay. However, we're + // unlikely to get here in the first place, as reading the build ID from + // the export file is likely to succeed. + return cache.ActionID{}, fmt.Errorf("couldn't hash go.mod: %w", err) + } else { + fmt.Fprintf(key, "file %s %x\n", pkg.Module.GoMod, h) + } + } + } + + imps := make([]*PackageSpec, 0, len(pkg.Imports)) + for _, v := range pkg.Imports { + imps = append(imps, v) + } + sort.Slice(imps, func(i, j int) bool { + return imps[i].PkgPath < imps[j].PkgPath + }) + + for _, dep := range imps { + if dep.ExportFile == "" { + fmt.Fprintf(key, "import %s \n", dep.PkgPath) + } else { + id, err := getBuildid(dep.ExportFile) + if err == nil { + fmt.Fprintf(key, "import %s %s\n", dep.PkgPath, id) + } else { + fh, err := cache.FileHash(dep.ExportFile) + if err != nil { + return cache.ActionID{}, err + } + fmt.Fprintf(key, "import %s %x\n", dep.PkgPath, fh) + } + } + } + return key.Sum(), nil +} + +var buildidCache = map[string]string{} + +func getBuildid(f string) (string, error) { + if h, ok := buildidCache[f]; ok { + return h, nil + } + h, err := buildid.ReadFile(f) + if err != nil { + return "", err + } + buildidCache[f] = h + return h, nil +} diff --git a/vendor/honnef.co/go/tools/go/loader/loader.go b/vendor/honnef.co/go/tools/go/loader/loader.go new file mode 100644 index 0000000..0e65309 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/loader/loader.go @@ -0,0 +1,379 @@ +package loader + +import ( + "errors" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + "go/types" + "os" + "time" + + "honnef.co/go/tools/config" + "honnef.co/go/tools/lintcmd/cache" + + "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/go/packages" +) + +const MaxFileSize = 50 * 1024 * 1024 // 50 MB + +var errMaxFileSize = errors.New("file exceeds max file size") + +type PackageSpec struct { + ID string + Name string + PkgPath string + // Errors that occurred while building the import graph. These will + // primarily be parse errors or failure to resolve imports, but + // may also be other errors. + Errors []packages.Error + GoFiles []string + CompiledGoFiles []string + OtherFiles []string + ExportFile string + Imports map[string]*PackageSpec + TypesSizes types.Sizes + Hash cache.ActionID + Module *packages.Module + + Config config.Config +} + +func (spec *PackageSpec) String() string { + return spec.ID +} + +type Package struct { + *PackageSpec + + // Errors that occurred while loading the package. These will + // primarily be parse or type errors, but may also be lower-level + // failures such as file-system ones. + Errors []packages.Error + Types *types.Package + Fset *token.FileSet + Syntax []*ast.File + TypesInfo *types.Info +} + +// Graph resolves patterns and returns packages with all the +// information required to later load type information, and optionally +// syntax trees. +// +// The provided config can set any setting with the exception of Mode. +func Graph(cfg *packages.Config, patterns ...string) ([]*PackageSpec, error) { + var dcfg packages.Config + if cfg != nil { + dcfg = *cfg + } + dcfg.Mode = packages.NeedName | + packages.NeedImports | + packages.NeedDeps | + packages.NeedExportFile | + packages.NeedFiles | + packages.NeedCompiledGoFiles | + packages.NeedTypesSizes | + packages.NeedModule + pkgs, err := packages.Load(&dcfg, patterns...) + if err != nil { + return nil, err + } + + m := map[*packages.Package]*PackageSpec{} + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + spec := &PackageSpec{ + ID: pkg.ID, + Name: pkg.Name, + PkgPath: pkg.PkgPath, + Errors: pkg.Errors, + GoFiles: pkg.GoFiles, + CompiledGoFiles: pkg.CompiledGoFiles, + OtherFiles: pkg.OtherFiles, + ExportFile: pkg.ExportFile, + Imports: map[string]*PackageSpec{}, + TypesSizes: pkg.TypesSizes, + Module: pkg.Module, + } + for path, imp := range pkg.Imports { + spec.Imports[path] = m[imp] + } + if cdir := config.Dir(pkg.GoFiles); cdir != "" { + cfg, err := config.Load(cdir) + if err != nil { + spec.Errors = append(spec.Errors, convertError(err)...) + } + spec.Config = cfg + } else { + spec.Config = config.DefaultConfig + } + spec.Hash, err = computeHash(spec) + if err != nil { + spec.Errors = append(spec.Errors, convertError(err)...) + } + m[pkg] = spec + }) + out := make([]*PackageSpec, 0, len(pkgs)) + for _, pkg := range pkgs { + if len(pkg.CompiledGoFiles) == 0 && len(pkg.Errors) == 0 && pkg.PkgPath != "unsafe" { + // If a package consists only of test files, then + // go/packages incorrectly(?) returns an empty package for + // the non-test variant. Get rid of those packages. See + // #646. + // + // Do not, however, skip packages that have errors. Those, + // too, may have no files, but we want to print the + // errors. + continue + } + out = append(out, m[pkg]) + } + + return out, nil +} + +type program struct { + fset *token.FileSet + packages map[string]*types.Package + options *Options +} + +type Stats struct { + Source time.Duration + Export map[*PackageSpec]time.Duration +} + +type Options struct { + // The Go language version to use for the type checker. If unset, or if set + // to "module", it will default to the Go version specified in the module; + // if there is no module, it will default to the version of Go the + // executable was built with. + GoVersion string +} + +// Load loads the package described in spec. Imports will be loaded +// from export data, while the package itself will be loaded from +// source. +// +// An error will only be returned for system failures, such as failure +// to read export data from disk. Syntax and type errors, among +// others, will only populate the returned package's Errors field. +func Load(spec *PackageSpec, opts *Options) (*Package, Stats, error) { + if opts == nil { + opts = &Options{} + } + if opts.GoVersion == "" { + opts.GoVersion = "module" + } + prog := &program{ + fset: token.NewFileSet(), + packages: map[string]*types.Package{}, + options: opts, + } + + stats := Stats{ + Export: map[*PackageSpec]time.Duration{}, + } + for _, imp := range spec.Imports { + if imp.PkgPath == "unsafe" { + continue + } + t := time.Now() + _, err := prog.loadFromExport(imp) + stats.Export[imp] = time.Since(t) + if err != nil { + return nil, stats, err + } + } + t := time.Now() + pkg, err := prog.loadFromSource(spec) + if err == errMaxFileSize { + pkg, err = prog.loadFromExport(spec) + } + stats.Source = time.Since(t) + return pkg, stats, err +} + +// loadFromExport loads a package from export data. +func (prog *program) loadFromExport(spec *PackageSpec) (*Package, error) { + // log.Printf("Loading package %s from export", spec) + if spec.ExportFile == "" { + return nil, fmt.Errorf("no export data for %q", spec.ID) + } + f, err := os.Open(spec.ExportFile) + if err != nil { + return nil, err + } + defer f.Close() + + r, err := gcexportdata.NewReader(f) + if err != nil { + return nil, err + } + tpkg, err := gcexportdata.Read(r, prog.fset, prog.packages, spec.PkgPath) + if err != nil { + return nil, err + } + pkg := &Package{ + PackageSpec: spec, + Types: tpkg, + Fset: prog.fset, + } + // runtime.SetFinalizer(pkg, func(pkg *Package) { + // log.Println("Unloading package", pkg.PkgPath) + // }) + return pkg, nil +} + +// loadFromSource loads a package from source. All of its dependencies +// must have been loaded already. +func (prog *program) loadFromSource(spec *PackageSpec) (*Package, error) { + if len(spec.Errors) > 0 { + panic("LoadFromSource called on package with errors") + } + + pkg := &Package{ + PackageSpec: spec, + Types: types.NewPackage(spec.PkgPath, spec.Name), + Syntax: make([]*ast.File, len(spec.CompiledGoFiles)), + Fset: prog.fset, + TypesInfo: &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + Instances: make(map[*ast.Ident]types.Instance), + FileVersions: make(map[*ast.File]string), + }, + } + // runtime.SetFinalizer(pkg, func(pkg *Package) { + // log.Println("Unloading package", pkg.PkgPath) + // }) + + // OPT(dh): many packages have few files, much fewer than there + // are CPU cores. Additionally, parsing each individual file is + // very fast. A naive parallel implementation of this loop won't + // be faster, and tends to be slower due to extra scheduling, + // bookkeeping and potentially false sharing of cache lines. + for i, file := range spec.CompiledGoFiles { + f, err := os.Open(file) + if err != nil { + return nil, err + } + fi, err := f.Stat() + if err != nil { + return nil, err + } + if fi.Size() >= MaxFileSize { + return nil, errMaxFileSize + } + af, err := parser.ParseFile(prog.fset, file, f, parser.ParseComments|parser.SkipObjectResolution) + f.Close() + if err != nil { + pkg.Errors = append(pkg.Errors, convertError(err)...) + return pkg, nil + } + pkg.Syntax[i] = af + } + importer := func(path string) (*types.Package, error) { + if path == "unsafe" { + return types.Unsafe, nil + } + if path == "C" { + // go/packages doesn't tell us that cgo preprocessing + // failed. When we subsequently try to parse the package, + // we'll encounter the raw C import. + return nil, errors.New("cgo preprocessing failed") + } + ispecpkg := spec.Imports[path] + if ispecpkg == nil { + return nil, fmt.Errorf("trying to import %q in the context of %q returned nil PackageSpec", path, spec) + } + ipkg := prog.packages[ispecpkg.PkgPath] + if ipkg == nil { + return nil, fmt.Errorf("trying to import %q (%q) in the context of %q returned nil PackageSpec", ispecpkg.PkgPath, path, spec) + } + return ipkg, nil + } + tc := &types.Config{ + Importer: importerFunc(importer), + Error: func(err error) { + pkg.Errors = append(pkg.Errors, convertError(err)...) + }, + } + if prog.options.GoVersion == "module" { + if spec.Module != nil && spec.Module.GoVersion != "" { + tc.GoVersion = "go" + spec.Module.GoVersion + } else { + tags := build.Default.ReleaseTags + tc.GoVersion = tags[len(tags)-1] + } + } else { + tc.GoVersion = prog.options.GoVersion + } + // Note that the type-checker can return a non-nil error even though the Go + // compiler has already successfully built this package (which is an + // invariant of getting to this point), for example because of the Go + // version passed to the type checker. + err := types.NewChecker(tc, pkg.Fset, pkg.Types, pkg.TypesInfo).Files(pkg.Syntax) + return pkg, err +} + +func convertError(err error) []packages.Error { + var errs []packages.Error + // taken from go/packages + switch err := err.(type) { + case packages.Error: + // from driver + errs = append(errs, err) + + case *os.PathError: + // from parser + errs = append(errs, packages.Error{ + Pos: err.Path + ":1", + Msg: err.Err.Error(), + Kind: packages.ParseError, + }) + + case scanner.ErrorList: + // from parser + for _, err := range err { + errs = append(errs, packages.Error{ + Pos: err.Pos.String(), + Msg: err.Msg, + Kind: packages.ParseError, + }) + } + + case types.Error: + // from type checker + errs = append(errs, packages.Error{ + Pos: err.Fset.Position(err.Pos).String(), + Msg: err.Msg, + Kind: packages.TypeError, + }) + + case config.ParseError: + errs = append(errs, packages.Error{ + Pos: fmt.Sprintf("%s:%d:%d", err.Filename, err.Position.Line, err.Position.Col), + Msg: fmt.Sprintf("%s (last key parsed: %q)", err.Message, err.LastKey), + Kind: packages.ParseError, + }) + default: + errs = append(errs, packages.Error{ + Pos: "-", + Msg: err.Error(), + Kind: packages.UnknownError, + }) + } + return errs +} + +type importerFunc func(path string) (*types.Package, error) + +func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) } diff --git a/vendor/honnef.co/go/tools/go/types/typeutil/ext.go b/vendor/honnef.co/go/tools/go/types/typeutil/ext.go new file mode 100644 index 0000000..19b4ae5 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/types/typeutil/ext.go @@ -0,0 +1,27 @@ +package typeutil + +import ( + "fmt" + "go/types" +) + +type Iterator struct { + elem types.Type +} + +func (t *Iterator) Underlying() types.Type { return t } +func (t *Iterator) String() string { return fmt.Sprintf("iterator(%s)", t.elem) } +func (t *Iterator) Elem() types.Type { return t.elem } + +func NewIterator(elem types.Type) *Iterator { + return &Iterator{elem: elem} +} + +type DeferStack struct{} + +func (t *DeferStack) Underlying() types.Type { return t } +func (t *DeferStack) String() string { return "deferStack" } + +func NewDeferStack() *DeferStack { + return &DeferStack{} +} diff --git a/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go b/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go new file mode 100644 index 0000000..a53c095 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/types/typeutil/typeparams.go @@ -0,0 +1,114 @@ +package typeutil + +import ( + "errors" + "go/types" + "slices" + + "golang.org/x/exp/typeparams" +) + +type TypeSet struct { + Terms []*types.Term + empty bool +} + +func NewTypeSet(typ types.Type) TypeSet { + terms, err := typeparams.NormalTerms(typ) + if err != nil { + if errors.Is(err, typeparams.ErrEmptyTypeSet) { + return TypeSet{nil, true} + } else { + // We couldn't determine the type set. Assume it's all types. + return TypeSet{nil, false} + } + } + return TypeSet{terms, false} +} + +// CoreType returns the type set's core type, or nil if it has none. +// The function only looks at type terms and may thus return core types for some empty type sets, such as +// 'interface { map[int]string; foo() }' +func (ts TypeSet) CoreType() types.Type { + if len(ts.Terms) == 0 { + // Either the type set is empty, or it isn't constrained. Either way it doesn't have a core type. + return nil + } + typ := ts.Terms[0].Type().Underlying() + for _, term := range ts.Terms[1:] { + ut := term.Type().Underlying() + if types.Identical(typ, ut) { + continue + } + + ch1, ok := typ.(*types.Chan) + if !ok { + return nil + } + ch2, ok := ut.(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch1.Elem(), ch2.Elem()) { + return nil + } + if ch1.Dir() == types.SendRecv { + // typ is currently a bidirectional channel. The term's type is either also bidirectional, or + // unidirectional. Use the term's type. + typ = ut + } else if ch2.Dir() == types.SendRecv { + // typ is currently a unidirectional channel and the term's type is bidirectional, which means it has no + // effect. + continue + } else if ch1.Dir() != ch2.Dir() { + // typ is not bidirectional and typ and term disagree about the direction + return nil + } + } + return typ +} + +// CoreType is a wrapper for NewTypeSet(typ).CoreType() +func CoreType(typ types.Type) types.Type { + return NewTypeSet(typ).CoreType() +} + +// All calls fn for each term in the type set and reports whether all invocations returned true. +// If the type set is empty or unconstrained, All immediately returns false. +func (ts TypeSet) All(fn func(*types.Term) bool) bool { + if len(ts.Terms) == 0 { + return false + } + for _, term := range ts.Terms { + if !fn(term) { + return false + } + } + return true +} + +// Any calls fn for each term in the type set and reports whether any invocation returned true. +// It stops after the first call that returned true. +func (ts TypeSet) Any(fn func(*types.Term) bool) bool { + return slices.ContainsFunc(ts.Terms, fn) +} + +// All is a wrapper for NewTypeSet(typ).All(fn). +func All(typ types.Type, fn func(*types.Term) bool) bool { + return NewTypeSet(typ).All(fn) +} + +// Any is a wrapper for NewTypeSet(typ).Any(fn). +func Any(typ types.Type, fn func(*types.Term) bool) bool { + return NewTypeSet(typ).Any(fn) +} + +func IsType[T types.Type](term *types.Term) bool { + _, ok := term.Type().Underlying().(T) + return ok +} + +//go:fix inline +func IsSlice(term *types.Term) bool { + return IsType[*types.Slice](term) +} diff --git a/vendor/honnef.co/go/tools/go/types/typeutil/unify.go b/vendor/honnef.co/go/tools/go/types/typeutil/unify.go new file mode 100644 index 0000000..ced507c --- /dev/null +++ b/vendor/honnef.co/go/tools/go/types/typeutil/unify.go @@ -0,0 +1,345 @@ +// Copyright 2019 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. + +// The code in this file is copied from +// x/tools/gopls/internal/golang/implementation.go + +package typeutil + +import ( + "fmt" + "go/types" + "iter" + "reflect" +) + +// Unify reports whether the types of x and y match. +// +// If unifier is nil, unify reports only whether it succeeded. +// If unifier is non-nil, it is populated with the values +// of type parameters determined during a successful unification. +// If unification succeeds without binding a type parameter, that parameter +// will not be present in the map. +// +// On entry, the unifier's contents are treated as the values of already-bound type +// parameters, constraining the unification. +// +// For example, if unifier is an empty (not nil) map on entry, then the types +// +// func[T any](T, int) +// +// and +// +// func[U any](bool, U) +// +// will unify, with T=bool and U=int. +// That is, the contents of unifier after unify returns will be +// +// {T: bool, U: int} +// +// where "T" is the type parameter T and "bool" is the basic type for bool. +// +// But if unifier is {T: int} is int on entry, then unification will fail, because T +// does not unify with bool. +// +// Unify does not preserve aliases. For example, given the following: +// +// type String = string +// type A[T] = T +// +// unification succeeds with T bound to string, not String. +// +// See also: unify in cache/methodsets/fingerprint, which implements +// unification for type fingerprints, for the global index. +// +// BUG: literal interfaces are not handled properly. But this function is currently +// used only for signatures, where such types are very rare. +func Unify(x, y types.Type, unifier map[*types.TypeParam]types.Type) bool { + // bindings[tp] is the binding for type parameter tp. + // Although type parameters are nominally bound to types, each bindings[tp] + // is a pointer to a type, so unbound variables that unify can share a binding. + bindings := map[*types.TypeParam]*types.Type{} + + // Bindings is initialized with pointers to the provided types. + for tp, t := range unifier { + bindings[tp] = &t + } + + // bindingFor returns the *types.Type in bindings for tp if tp is not nil, + // creating one if needed. + bindingFor := func(tp *types.TypeParam) *types.Type { + if tp == nil { + return nil + } + b := bindings[tp] + if b == nil { + b = new(types.Type) + bindings[tp] = b + } + return b + } + + // bind sets b to t if b does not occur in t. + bind := func(b *types.Type, t types.Type) bool { + for tp := range typeParams(t) { + if b == bindings[tp] { + return false // failed "occurs" check + } + } + *b = t + return true + } + + // uni performs the actual unification. + depth := 0 + var uni func(x, y types.Type) bool + uni = func(x, y types.Type) bool { + // Panic if recursion gets too deep, to detect bugs before + // overflowing the stack. + depth++ + defer func() { depth-- }() + if depth > 100 { + panic("unify: max depth exceeded") + } + + x = types.Unalias(x) + y = types.Unalias(y) + + tpx, _ := x.(*types.TypeParam) + tpy, _ := y.(*types.TypeParam) + if tpx != nil || tpy != nil { + // Identical type params unify. + if tpx == tpy { + return true + } + bx := bindingFor(tpx) + by := bindingFor(tpy) + + // If both args are type params and neither is bound, have them share a binding. + if bx != nil && by != nil && *bx == nil && *by == nil { + // Arbitrarily give y's binding to x. + bindings[tpx] = by + return true + } + // Treat param bindings like original args in what follows. + if bx != nil && *bx != nil { + x = *bx + } + if by != nil && *by != nil { + y = *by + } + // If the x param is unbound, bind it to y. + if bx != nil && *bx == nil { + return bind(bx, y) + } + // If the y param is unbound, bind it to x. + if by != nil && *by == nil { + return bind(by, x) + } + // Unify the binding of a bound parameter. + return uni(x, y) + } + + // Neither arg is a type param. + + if reflect.TypeOf(x) != reflect.TypeOf(y) { + return false // mismatched types + } + + switch x := x.(type) { + case *types.Array: + y := y.(*types.Array) + return x.Len() == y.Len() && + uni(x.Elem(), y.Elem()) + + case *types.Basic: + y := y.(*types.Basic) + return x.Kind() == y.Kind() + + case *types.Chan: + y := y.(*types.Chan) + return x.Dir() == y.Dir() && + uni(x.Elem(), y.Elem()) + + case *types.Interface: + y := y.(*types.Interface) + // TODO(adonovan,jba): fix: for correctness, we must check + // that both interfaces have the same set of methods + // modulo type parameters, while avoiding the risk of + // unbounded interface recursion. + // + // Since non-empty interface literals are vanishingly + // rare in methods signatures, we ignore this for now. + // If more precision is needed we could compare method + // names and arities, still without full recursion. + return x.NumMethods() == y.NumMethods() + + case *types.Map: + y := y.(*types.Map) + return uni(x.Key(), y.Key()) && + uni(x.Elem(), y.Elem()) + + case *types.Named: + y := y.(*types.Named) + if x.Origin() != y.Origin() { + return false // different named types + } + xtargs := x.TypeArgs() + ytargs := y.TypeArgs() + if xtargs.Len() != ytargs.Len() { + return false // arity error (ill-typed) + } + for i := range xtargs.Len() { + if !uni(xtargs.At(i), ytargs.At(i)) { + return false // mismatched type args + } + } + return true + + case *types.Pointer: + y := y.(*types.Pointer) + return uni(x.Elem(), y.Elem()) + + case *types.Signature: + y := y.(*types.Signature) + return x.Variadic() == y.Variadic() && + uni(x.Params(), y.Params()) && + uni(x.Results(), y.Results()) + + case *types.Slice: + y := y.(*types.Slice) + return uni(x.Elem(), y.Elem()) + + case *types.Struct: + y := y.(*types.Struct) + if x.NumFields() != y.NumFields() { + return false + } + for i := range x.NumFields() { + xf := x.Field(i) + yf := y.Field(i) + if xf.Embedded() != yf.Embedded() || + xf.Name() != yf.Name() || + x.Tag(i) != y.Tag(i) || + !xf.Exported() && xf.Pkg() != yf.Pkg() || + !uni(xf.Type(), yf.Type()) { + return false + } + } + return true + + case *types.Tuple: + y := y.(*types.Tuple) + if x.Len() != y.Len() { + return false + } + for i := range x.Len() { + if !uni(x.At(i).Type(), y.At(i).Type()) { + return false + } + } + return true + + default: // incl. *Union, *TypeParam + panic(fmt.Sprintf("unexpected Type %#v", x)) + } + } + + if !uni(x, y) { + clear(unifier) + return false + } + + // Populate the input map with the resulting types. + if unifier != nil { + for tparam, tptr := range bindings { + unifier[tparam] = *tptr + } + } + return true +} + +// typeParams yields all the free type parameters within t that are relevant for +// unification. +// +// Note: this function is tailored for the specific needs of the unification algorithm. +// Don't try to use it for other purposes, see [typeparams.Free] instead. +func typeParams(t types.Type) iter.Seq[*types.TypeParam] { + return func(yield func(*types.TypeParam) bool) { + seen := map[*types.TypeParam]bool{} // yield each type param only once + + // tps(t) yields each TypeParam in t and returns false to stop. + var tps func(types.Type) bool + tps = func(t types.Type) bool { + t = types.Unalias(t) + + switch t := t.(type) { + case *types.TypeParam: + if seen[t] { + return true + } + seen[t] = true + return yield(t) + + case *types.Basic: + return true + + case *types.Array: + return tps(t.Elem()) + + case *types.Chan: + return tps(t.Elem()) + + case *types.Interface: + // TODO(jba): implement. + return true + + case *types.Map: + return tps(t.Key()) && tps(t.Elem()) + + case *types.Named: + if t.Origin() == t { + // generic type: look at type params + return every(t.TypeParams().TypeParams(), + func(tp *types.TypeParam) bool { return tps(tp) }) + } + // instantiated type: look at type args + return every(t.TypeArgs().Types(), tps) + + case *types.Pointer: + return tps(t.Elem()) + + case *types.Signature: + return tps(t.Params()) && tps(t.Results()) + + case *types.Slice: + return tps(t.Elem()) + + case *types.Struct: + return every(t.Fields(), + func(v *types.Var) bool { return tps(v.Type()) }) + + case *types.Tuple: + return every(t.Variables(), + func(v *types.Var) bool { return tps(v.Type()) }) + + default: // incl. *Union + panic(fmt.Sprintf("unexpected Type %#v", t)) + } + } + + tps(t) + } +} + +// every reports whether every pred(t) for t in seq returns true, +// stopping at the first false element. +func every[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if !pred(t) { + return false + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/go/types/typeutil/upstream.go b/vendor/honnef.co/go/tools/go/types/typeutil/upstream.go new file mode 100644 index 0000000..064964e --- /dev/null +++ b/vendor/honnef.co/go/tools/go/types/typeutil/upstream.go @@ -0,0 +1,52 @@ +package typeutil + +import ( + "go/ast" + "go/types" + _ "unsafe" + + "golang.org/x/tools/go/types/typeutil" +) + +type MethodSetCache = typeutil.MethodSetCache +type Hasher = typeutil.Hasher + +func Callee(info *types.Info, call *ast.CallExpr) types.Object { + return typeutil.Callee(info, call) +} + +func IntuitiveMethodSet(T types.Type, msets *MethodSetCache) []*types.Selection { + return typeutil.IntuitiveMethodSet(T, msets) +} + +func MakeHasher() Hasher { + return typeutil.MakeHasher() +} + +type Map[V any] struct { + m typeutil.Map +} + +func (m *Map[V]) Delete(key types.Type) bool { return m.m.Delete(key) } +func (m *Map[V]) At(key types.Type) (V, bool) { + v := m.m.At(key) + if v == nil { + var zero V + return zero, false + } else { + return v.(V), true + } +} +func (m *Map[V]) Set(key types.Type, value V) { m.m.Set(key, value) } +func (m *Map[V]) Len() int { return m.m.Len() } +func (m *Map[V]) Iterate(f func(key types.Type, value V)) { + ff := func(key types.Type, value any) { + f(key, value.(V)) + } + m.m.Iterate(ff) + +} +func (m *Map[V]) Keys() []types.Type { return m.m.Keys() } +func (m *Map[V]) String() string { return m.m.String() } +func (m *Map[V]) KeysString() string { return m.m.KeysString() } +func (m *Map[V]) SetHasher(h typeutil.Hasher) { m.m.SetHasher(h) } diff --git a/vendor/honnef.co/go/tools/go/types/typeutil/util.go b/vendor/honnef.co/go/tools/go/types/typeutil/util.go new file mode 100644 index 0000000..bb50662 --- /dev/null +++ b/vendor/honnef.co/go/tools/go/types/typeutil/util.go @@ -0,0 +1,211 @@ +package typeutil + +import ( + "bytes" + "go/types" + "strings" + "sync" + + "golang.org/x/exp/typeparams" +) + +var bufferPool = &sync.Pool{ + New: func() any { + buf := bytes.NewBuffer(nil) + buf.Grow(64) + return buf + }, +} + +func FuncName(f *types.Func) string { + // We don't care about aliases in this function because we use FuncName to check calls + // to known methods, and method receivers are determined by the method declaration, + // not the call. Thus, even if a user does 'type Alias = *sync.Mutex' and calls + // Alias.Lock, we'll still see it as (*sync.Mutex).Lock. + + buf := bufferPool.Get().(*bytes.Buffer) + buf.Reset() + if f.Type() != nil { + sig := f.Type().(*types.Signature) + if recv := sig.Recv(); recv != nil { + buf.WriteByte('(') + if _, ok := recv.Type().(*types.Interface); ok { + // gcimporter creates abstract methods of + // named interfaces using the interface type + // (not the named type) as the receiver. + // Don't print it in full. + buf.WriteString("interface") + } else { + types.WriteType(buf, recv.Type(), nil) + } + buf.WriteByte(')') + buf.WriteByte('.') + } else if f.Pkg() != nil { + writePackage(buf, f.Pkg()) + } + } + buf.WriteString(f.Name()) + s := buf.String() + bufferPool.Put(buf) + return s +} + +func writePackage(buf *bytes.Buffer, pkg *types.Package) { + if pkg == nil { + return + } + s := pkg.Path() + if s != "" { + buf.WriteString(s) + buf.WriteByte('.') + } +} + +// Dereference returns a pointer's element type; otherwise it returns +// T. +func Dereference(T types.Type) types.Type { + if p, ok := T.Underlying().(*types.Pointer); ok { + return p.Elem() + } + return T +} + +// DereferenceR returns a pointer's element type; otherwise it returns +// T. If the element type is itself a pointer, DereferenceR will be +// applied recursively. +func DereferenceR(T types.Type) types.Type { + if p, ok := T.Underlying().(*types.Pointer); ok { + return DereferenceR(p.Elem()) + } + return T +} + +func IsObject(obj types.Object, name string) bool { + var path string + if pkg := obj.Pkg(); pkg != nil { + path = pkg.Path() + "." + } + return path+obj.Name() == name +} + +// IsTypeName reports whether obj represents the qualified name. If obj is a type alias, +// IsTypeName checks both the alias and the aliased type, if the aliased type has a type +// name. +func IsTypeName(obj *types.TypeName, name string) bool { + var qf string + if idx := strings.LastIndex(name, "."); idx != -1 { + qf = name[:idx] + name = name[idx+1:] + } + if obj.Name() == name && + ((qf == "" && obj.Pkg() == nil) || (obj.Pkg() != nil && obj.Pkg().Path() == qf)) { + return true + } + + if !obj.IsAlias() { + return false + } + + // FIXME(dh): we should peel away one layer of alias at a time; this is blocked on + // github.com/golang/go/issues/66559 + if typ, ok := types.Unalias(obj.Type()).(interface{ Obj() *types.TypeName }); ok { + return IsTypeName(typ.Obj(), name) + } + + return false +} + +func IsPointerToTypeWithName(typ types.Type, name string) bool { + ptr, ok := types.Unalias(typ).(*types.Pointer) + if !ok { + return false + } + return IsTypeWithName(ptr.Elem(), name) +} + +// IsTypeWithName reports whether typ represents a type with the qualified name, If typ is +// a type alias, IsTypeWithName checks both the alias and the aliased type. The following +// types can have names: Basic, Named, Alias. +func IsTypeWithName(typ types.Type, name string) bool { + switch typ := typ.(type) { + case *types.Basic: + return typ.Name() == name + case *types.Named: + return IsTypeName(typ.Obj(), name) + case *types.Alias: + // FIXME(dh): we should peel away one layer of alias at a time; this is blocked on + // github.com/golang/go/issues/66559 + + // IsTypeName already handles aliases to other aliases or named types; our + // fallback is required for aliases to basic types. + return IsTypeName(typ.Obj(), name) || IsTypeWithName(types.Unalias(typ), name) + default: + return false + } +} + +// IsPointerLike returns true if type T is like a pointer. This returns true for all nillable types, +// unsafe.Pointer, and type sets where at least one term is pointer-like. +func IsPointerLike(T types.Type) bool { + switch T := T.Underlying().(type) { + case *types.Interface: + if T.IsMethodSet() { + return true + } else { + terms, err := typeparams.NormalTerms(T) + if err != nil { + return false + } + for _, term := range terms { + if IsPointerLike(term.Type()) { + return true + } + } + return false + } + case *types.Chan, *types.Map, *types.Signature, *types.Pointer, *types.Slice: + return true + case *types.Basic: + return T.Kind() == types.UnsafePointer + } + return false +} + +type Field struct { + Var *types.Var + Tag string + Path []int +} + +// FlattenFields recursively flattens T and embedded structs, +// returning a list of fields. If multiple fields with the same name +// exist, all will be returned. +func FlattenFields(T *types.Struct) []Field { + return flattenFields(T, nil, nil) +} + +func flattenFields(T *types.Struct, path []int, seen map[types.Type]bool) []Field { + if seen == nil { + seen = map[types.Type]bool{} + } + if seen[T] { + return nil + } + seen[T] = true + var out []Field + for i := 0; i < T.NumFields(); i++ { + field := T.Field(i) + tag := T.Tag(i) + np := append(path[:len(path):len(path)], i) + if field.Anonymous() { + if s, ok := Dereference(field.Type()).Underlying().(*types.Struct); ok { + out = append(out, flattenFields(s, np, seen)...) + } else { + out = append(out, Field{field, tag, np}) + } + } else { + out = append(out, Field{field, tag, np}) + } + } + return out +} diff --git a/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go b/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go new file mode 100644 index 0000000..e2f1f09 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/iterutil/iterutil.go @@ -0,0 +1,21 @@ +package iterutil + +import "iter" + +func All[T any](seq iter.Seq[T], fn func(T) bool) bool { + for v := range seq { + if !fn(v) { + return false + } + } + return true +} + +func Any[T any](seq iter.Seq[T], fn func(T) bool) bool { + for v := range seq { + if fn(v) { + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go b/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go new file mode 100644 index 0000000..1e2cc49 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/passes/buildir/buildir.go @@ -0,0 +1,90 @@ +// 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 buildir defines an Analyzer that constructs the IR +// of an error-free package and returns the set of all +// functions within it. It does not report any diagnostics itself but +// may be used as an input to other analyzers. +// +// THIS INTERFACE IS EXPERIMENTAL AND MAY BE SUBJECT TO INCOMPATIBLE CHANGE. +package buildir + +import ( + "reflect" + + "honnef.co/go/tools/go/ir" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/ctrlflow" +) + +var Debug = struct { + Mode ir.BuilderMode +}{} + +var Analyzer = &analysis.Analyzer{ + Name: "buildir", + Doc: "build IR for later passes", + Run: run, + ResultType: reflect.TypeFor[*IR](), + Requires: []*analysis.Analyzer{ctrlflow.Analyzer}, +} + +// IR provides intermediate representation for all the +// source functions in the current package. +type IR struct { + Pkg *ir.Package + SrcFuncs []*ir.Function +} + +func run(pass *analysis.Pass) (any, error) { + cfgs := pass.ResultOf[ctrlflow.Analyzer].(*ctrlflow.CFGs) + + // Plundered from ssautil.BuildPackage. + + // We must create a new Program for each Package because the + // analysis API provides no place to hang a Program shared by + // all Packages. Consequently, IR Packages and Functions do not + // have a canonical representation across an analysis session of + // multiple packages. This is unlikely to be a problem in + // practice because the analysis API essentially forces all + // packages to be analysed independently, so any given call to + // Analysis.Run on a package will see only IR objects belonging + // to a single Program. + + mode := ir.GlobalDebug + if Debug.Mode != 0 { + mode = Debug.Mode + } + + prog := ir.NewProgram(pass.Fset, mode) + + prog.SetNoReturn(cfgs.NoReturn) + + // Create IR packages for direct imports. + for _, p := range pass.Pkg.Imports() { + prog.CreatePackage(p, nil, nil, true) + } + + // Create and build the primary package. + irpkg := prog.CreatePackage(pass.Pkg, pass.Files, pass.TypesInfo, false) + irpkg.Build() + + // Compute list of source functions, including literals, + // in source order. + var addAnons func(f *ir.Function) + funcs := make([]*ir.Function, len(irpkg.Functions)) + copy(funcs, irpkg.Functions) + addAnons = func(f *ir.Function) { + for _, anon := range f.AnonFuncs { + funcs = append(funcs, anon) + addAnons(anon) + } + } + for _, fn := range irpkg.Functions { + addAnons(fn) + } + + return &IR{Pkg: irpkg, SrcFuncs: funcs}, nil +} diff --git a/vendor/honnef.co/go/tools/internal/renameio/UPSTREAM b/vendor/honnef.co/go/tools/internal/renameio/UPSTREAM new file mode 100644 index 0000000..84eb321 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/renameio/UPSTREAM @@ -0,0 +1,2 @@ +This package is a copy of cmd/go/internal/renameio. +The upstream package no longer exists, as the Go project replaced all of its uses with the lockedfile package. diff --git a/vendor/honnef.co/go/tools/internal/renameio/renameio.go b/vendor/honnef.co/go/tools/internal/renameio/renameio.go new file mode 100644 index 0000000..4e02a42 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/renameio/renameio.go @@ -0,0 +1,93 @@ +// 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 renameio writes files atomically by renaming temporary files. +package renameio + +import ( + "bytes" + "io" + "math/rand" + "os" + "path/filepath" + "strconv" + + "honnef.co/go/tools/internal/robustio" +) + +const patternSuffix = ".tmp" + +// Pattern returns a glob pattern that matches the unrenamed temporary files +// created when writing to filename. +func Pattern(filename string) string { + return filepath.Join(filepath.Dir(filename), filepath.Base(filename)+patternSuffix) +} + +// WriteFile is like ioutil.WriteFile, but first writes data to an arbitrary +// file in the same directory as filename, then renames it atomically to the +// final name. +// +// That ensures that the final location, if it exists, is always a complete file. +func WriteFile(filename string, data []byte, perm os.FileMode) (err error) { + return WriteToFile(filename, bytes.NewReader(data), perm) +} + +// WriteToFile is a variant of WriteFile that accepts the data as an io.Reader +// instead of a slice. +func WriteToFile(filename string, data io.Reader, perm os.FileMode) (err error) { + f, err := tempFile(filepath.Dir(filename), filepath.Base(filename), perm) + if err != nil { + return err + } + defer func() { + // Only call os.Remove on f.Name() if we failed to rename it: otherwise, + // some other process may have created a new file with the same name after + // that. + if err != nil { + f.Close() + os.Remove(f.Name()) + } + }() + + if _, err := io.Copy(f, data); err != nil { + return err + } + // Sync the file before renaming it: otherwise, after a crash the reader may + // observe a 0-length file instead of the actual contents. + // See https://golang.org/issue/22397#issuecomment-380831736. + if err := f.Sync(); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + + return robustio.Rename(f.Name(), filename) +} + +// ReadFile is like ioutil.ReadFile, but on Windows retries spurious errors that +// may occur if the file is concurrently replaced. +// +// Errors are classified heuristically and retries are bounded, so even this +// function may occasionally return a spurious error on Windows. +// If so, the error will likely wrap one of: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +func ReadFile(filename string) ([]byte, error) { + return robustio.ReadFile(filename) +} + +// tempFile creates a new temporary file with given permission bits. +func tempFile(dir, prefix string, perm os.FileMode) (f *os.File, err error) { + for range 10000 { + name := filepath.Join(dir, prefix+strconv.Itoa(rand.Intn(1000000000))+patternSuffix) + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, perm) + if os.IsExist(err) { + continue + } + break + } + return +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/UPSTREAM b/vendor/honnef.co/go/tools/internal/robustio/UPSTREAM new file mode 100644 index 0000000..b41cf06 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/UPSTREAM @@ -0,0 +1,6 @@ +This package is a copy of cmd/go/internal/robustio. +It is mostly in sync with upstream according to the last commit we've looked at, +with the exception of still using I/O functions that work with older Go versions. + +The last upstream commit we've looked at was: +dc04f3ba1f25313bc9c97e728620206c235db9ee diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio.go b/vendor/honnef.co/go/tools/internal/robustio/robustio.go new file mode 100644 index 0000000..15b3377 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio.go @@ -0,0 +1,53 @@ +// Copyright 2019 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 robustio wraps I/O functions that are prone to failure on Windows, +// transparently retrying errors up to an arbitrary timeout. +// +// Errors are classified heuristically and retries are bounded, so the functions +// in this package do not completely eliminate spurious errors. However, they do +// significantly reduce the rate of failure in practice. +// +// If so, the error will likely wrap one of: +// The functions in this package do not completely eliminate spurious errors, +// but substantially reduce their rate of occurrence in practice. +package robustio + +// Rename is like os.Rename, but on Windows retries errors that may occur if the +// file is concurrently read or overwritten. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func Rename(oldpath, newpath string) error { + return rename(oldpath, newpath) +} + +// ReadFile is like os.ReadFile, but on Windows retries errors that may +// occur if the file is concurrently replaced. +// +// (See golang.org/issue/31247 and golang.org/issue/32188.) +func ReadFile(filename string) ([]byte, error) { + return readFile(filename) +} + +// RemoveAll is like os.RemoveAll, but on Windows retries errors that may occur +// if an executable file in the directory has recently been executed. +// +// (See golang.org/issue/19491.) +func RemoveAll(path string) error { + return removeAll(path) +} + +// IsEphemeralError reports whether err is one of the errors that the functions +// in this package attempt to mitigate. +// +// Errors considered ephemeral include: +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION +// +// This set may be expanded in the future; programs must not rely on the +// non-ephemerality of any given error. +func IsEphemeralError(err error) bool { + return isEphemeralError(err) +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go new file mode 100644 index 0000000..99fd8eb --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_darwin.go @@ -0,0 +1,21 @@ +// Copyright 2019 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 robustio + +import ( + "errors" + "syscall" +) + +const errFileNotFound = syscall.ENOENT + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + return errno == errFileNotFound + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go new file mode 100644 index 0000000..c56e36c --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_flaky.go @@ -0,0 +1,91 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build windows || darwin + +package robustio + +import ( + "errors" + "math/rand" + "os" + "syscall" + "time" +) + +const arbitraryTimeout = 2000 * time.Millisecond + +// retry retries ephemeral errors from f up to an arbitrary timeout +// to work around filesystem flakiness on Windows and Darwin. +func retry(f func() (err error, mayRetry bool)) error { + var ( + bestErr error + lowestErrno syscall.Errno + start time.Time + nextSleep time.Duration = 1 * time.Millisecond + ) + for { + err, mayRetry := f() + if err == nil || !mayRetry { + return err + } + + var errno syscall.Errno + if errors.As(err, &errno) && (lowestErrno == 0 || errno < lowestErrno) { + bestErr = err + lowestErrno = errno + } else if bestErr == nil { + bestErr = err + } + + if start.IsZero() { + start = time.Now() + } else if d := time.Since(start) + nextSleep; d >= arbitraryTimeout { + break + } + time.Sleep(nextSleep) + nextSleep += time.Duration(rand.Int63n(int64(nextSleep))) + } + + return bestErr +} + +// rename is like os.Rename, but retries ephemeral errors. +// +// On Windows it wraps os.Rename, which (as of 2019-06-04) uses MoveFileEx with +// MOVEFILE_REPLACE_EXISTING. +// +// Windows also provides a different system call, ReplaceFile, +// that provides similar semantics, but perhaps preserves more metadata. (The +// documentation on the differences between the two is very sparse.) +// +// Empirical error rates with MoveFileEx are lower under modest concurrency, so +// for now we're sticking with what the os package already provides. +func rename(oldpath, newpath string) (err error) { + return retry(func() (err error, mayRetry bool) { + err = os.Rename(oldpath, newpath) + return err, isEphemeralError(err) + }) +} + +// readFile is like os.ReadFile, but retries ephemeral errors. +func readFile(filename string) ([]byte, error) { + var b []byte + err := retry(func() (err error, mayRetry bool) { + b, err = os.ReadFile(filename) + + // Unlike in rename, we do not retry errFileNotFound here: it can occur + // as a spurious error, but the file may also genuinely not exist, so the + // increase in robustness is probably not worth the extra latency. + return err, isEphemeralError(err) && !errors.Is(err, errFileNotFound) + }) + return b, err +} + +func removeAll(path string) error { + return retry(func() (err error, mayRetry bool) { + err = os.RemoveAll(path) + return err, isEphemeralError(err) + }) +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go new file mode 100644 index 0000000..da9a46e --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_other.go @@ -0,0 +1,27 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !windows && !darwin + +package robustio + +import ( + "os" +) + +func rename(oldpath, newpath string) error { + return os.Rename(oldpath, newpath) +} + +func readFile(filename string) ([]byte, error) { + return os.ReadFile(filename) +} + +func removeAll(path string) error { + return os.RemoveAll(path) +} + +func isEphemeralError(err error) bool { + return false +} diff --git a/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go b/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go new file mode 100644 index 0000000..200070a --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/robustio/robustio_windows.go @@ -0,0 +1,27 @@ +// Copyright 2019 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 robustio + +import ( + "errors" + "syscall" +) + +const ERROR_SHARING_VIOLATION = 32 +const errFileNotFound = syscall.ERROR_FILE_NOT_FOUND + +// isEphemeralError returns true if err may be resolved by waiting. +func isEphemeralError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + switch errno { + case syscall.ERROR_ACCESS_DENIED, + syscall.ERROR_FILE_NOT_FOUND, + ERROR_SHARING_VIOLATION: + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go b/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go new file mode 100644 index 0000000..6bae3f7 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/sharedcheck/lint.go @@ -0,0 +1,208 @@ +package sharedcheck + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/facts/tokenfile" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +func CheckRangeStringRunes(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + cb := func(node ast.Node) bool { + rng, ok := node.(*ast.RangeStmt) + if !ok || !astutil.IsBlank(rng.Key) { + return true + } + + v, _ := fn.ValueForExpr(rng.X) + + // Check that we're converting from string to []rune + val, _ := v.(*ir.Convert) + if val == nil { + return true + } + Tsrc, ok := typeutil.CoreType(val.X.Type()).(*types.Basic) + if !ok || Tsrc.Kind() != types.String { + return true + } + Tdst, ok := typeutil.CoreType(val.Type()).(*types.Slice) + if !ok { + return true + } + TdstElem, ok := types.Unalias(Tdst.Elem()).(*types.Basic) + if !ok || TdstElem.Kind() != types.Int32 { + return true + } + + // Check that the result of the conversion is only used to + // range over + refs := val.Referrers() + if refs == nil { + return true + } + + // Expect two refs: one for obtaining the length of the slice, + // one for accessing the elements + if len(*refs) != 2 { + // TODO(dh): right now, we check that only one place + // refers to our slice. This will miss cases such as + // ranging over the slice twice. Ideally, we'd ensure that + // the slice is only used for ranging over (without + // accessing the key), but that is harder to do because in + // IR form, ranging over a slice looks like an ordinary + // loop with index increments and slice accesses. We'd + // have to look at the associated AST node to check that + // it's a range statement. + return true + } + + pass.Reportf(rng.Pos(), "should range over string, not []rune(string)") + + return true + } + if source := fn.Source(); source != nil { + ast.Inspect(source, cb) + } + } + return nil, nil +} + +// RedundantTypeInDeclarationChecker returns a checker that flags variable declarations with redundantly specified types. +// That is, it flags 'var v T = e' where e's type is identical to T and 'var v = e' (or 'v := e') would have the same effect. +// +// It does not flag variables under the following conditions, to reduce the number of false positives: +// - global variables – these often specify types to aid godoc +// - files that use cgo – cgo code generation and pointer checking emits redundant types +// +// It does not flag variables under the following conditions, unless flagHelpfulTypes is true, to reduce the number of noisy positives: +// - packages that import syscall or unsafe – these sometimes use this form of assignment to make sure types are as expected +// - variables named the blank identifier – a pattern used to confirm the types of variables +// - untyped expressions on the rhs – the explicitness might aid readability +func RedundantTypeInDeclarationChecker(verb string, flagHelpfulTypes bool) *analysis.Analyzer { + fn := func(pass *analysis.Pass) (any, error) { + eval := func(expr ast.Expr) (types.TypeAndValue, error) { + info := &types.Info{ + Types: map[ast.Expr]types.TypeAndValue{}, + } + err := types.CheckExpr(pass.Fset, pass.Pkg, expr.Pos(), expr, info) + return info.Types[expr], err + } + + if !flagHelpfulTypes { + // Don't look at code in low-level packages + for _, imp := range pass.Pkg.Imports() { + if imp.Path() == "syscall" || imp.Path() == "unsafe" { + return nil, nil + } + } + } + + fn := func(node ast.Node) { + decl := node.(*ast.GenDecl) + if decl.Tok != token.VAR { + return + } + + gen, _ := code.Generator(pass, decl.Pos()) + if gen == generated.Cgo { + // TODO(dh): remove this exception once we can use UsesCgo + return + } + + // Delay looking up parent AST nodes until we have to + checkedDecl := false + + specLoop: + for _, spec := range decl.Specs { + spec := spec.(*ast.ValueSpec) + if spec.Type == nil { + continue + } + if len(spec.Names) != len(spec.Values) { + continue + } + Tlhs := pass.TypesInfo.TypeOf(spec.Type) + for i, v := range spec.Values { + if !flagHelpfulTypes && spec.Names[i].Name == "_" { + continue specLoop + } + Trhs := pass.TypesInfo.TypeOf(v) + if !types.Identical(Tlhs, Trhs) { + continue specLoop + } + + // Some expressions are untyped and get converted to the lhs type implicitly. + // This applies to untyped constants, shift operations with an untyped lhs, and possibly others. + // + // Check if the type is truly redundant, i.e. if the type on the lhs doesn't match the default type of the untyped constant. + tv, err := eval(v) + if err != nil { + panic(err) + } + if b, ok := types.Unalias(tv.Type).(*types.Basic); ok && (b.Info()&types.IsUntyped) != 0 { + if Tlhs != types.Default(b) { + // The rhs is untyped and its default type differs from the explicit type on the lhs + continue specLoop + } + switch v := v.(type) { + case *ast.Ident: + // Only flag named constant rhs if it's a predeclared identifier. + // Don't flag other named constants, as the explicit type may aid readability. + if pass.TypesInfo.ObjectOf(v).Pkg() != nil && !flagHelpfulTypes { + continue specLoop + } + case *ast.BasicLit: + // Do flag basic literals + default: + // Don't flag untyped rhs expressions unless flagHelpfulTypes is set + if !flagHelpfulTypes { + continue specLoop + } + } + } + } + + if !checkedDecl { + // Don't flag global variables. These often have explicit types for godoc's sake. + path, _ := astutil.PathEnclosingInterval(code.File(pass, decl), decl.Pos(), decl.Pos()) + pathLoop: + for _, el := range path { + switch el.(type) { + case *ast.FuncDecl, *ast.FuncLit: + checkedDecl = true + break pathLoop + } + } + if !checkedDecl { + // decl is not inside a function + break specLoop + } + } + + report.Report(pass, spec.Type, fmt.Sprintf("%s omit type %s from declaration; it will be inferred from the right-hand side", verb, report.Render(pass, spec.Type)), report.FilterGenerated(), + report.Fixes(edit.Fix("Remove redundant type", edit.Delete(spec.Type)))) + } + } + code.Preorder(pass, fn, (*ast.GenDecl)(nil)) + return nil, nil + } + + return &analysis.Analyzer{ + Run: fn, + Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer, tokenfile.Analyzer}, + } +} diff --git a/vendor/honnef.co/go/tools/internal/sync/sync.go b/vendor/honnef.co/go/tools/internal/sync/sync.go new file mode 100644 index 0000000..e78ad50 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/sync/sync.go @@ -0,0 +1,36 @@ +package sync + +type Semaphore struct { + ch chan struct{} +} + +func NewSemaphore(size int) Semaphore { + return Semaphore{ + ch: make(chan struct{}, size), + } +} + +func (sem Semaphore) Acquire() { + sem.ch <- struct{}{} +} + +func (sem Semaphore) AcquireMaybe() bool { + select { + case sem.ch <- struct{}{}: + return true + default: + return false + } +} + +func (sem Semaphore) Release() { + <-sem.ch +} + +func (sem Semaphore) Len() int { + return len(sem.ch) +} + +func (sem Semaphore) Cap() int { + return cap(sem.ch) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go b/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go new file mode 100644 index 0000000..a4ae04b --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/aliases/aliases.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aliases + +import ( + "go/token" + "go/types" +) + +// New creates a new TypeName in Package pkg that +// is an alias for the type rhs. +func New(pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName { + tname := types.NewTypeName(pos, pkg, name, nil) + types.NewAlias(tname, rhs).SetTypeParams(tparams) + return tname +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go b/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go new file mode 100644 index 0000000..1bd7b78 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex/typeindex.go @@ -0,0 +1,33 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeindex defines an analyzer that provides a +// [honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex.Index]. +// +// Like [golang.org/x/tools/go/analysis/passes/inspect], it is +// intended to be used as a helper by other analyzers; it reports no +// diagnostics of its own. +package typeindex + +import ( + "reflect" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" +) + +var Analyzer = &analysis.Analyzer{ + Name: "typeindex", + Doc: "indexes of type information for later passes", + URL: "https://pkg.go.dev/honnef.co/go/tools/internal/xtools-internal/analysis/typeindex", + Run: func(pass *analysis.Pass) (any, error) { + inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + return typeindex.New(inspect, pass.Pkg, pass.TypesInfo), nil + }, + RunDespiteErrors: true, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + ResultType: reflect.TypeFor[*typeindex.Index](), +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go new file mode 100644 index 0000000..2c9b6bb --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/clone.go @@ -0,0 +1,71 @@ +// Copyright 2023 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 astutil + +import ( + "go/ast" + "reflect" +) + +// CloneNode returns a deep copy of a Node. +// It omits pointers to ast.{Scope,Object} variables. +func CloneNode[T ast.Node](n T) T { + return cloneNode(n).(T) +} + +func cloneNode(n ast.Node) ast.Node { + var clone func(x reflect.Value) reflect.Value + set := func(dst, src reflect.Value) { + src = clone(src) + if src.IsValid() { + dst.Set(src) + } + } + clone = func(x reflect.Value) reflect.Value { + switch x.Kind() { + case reflect.Pointer: + if x.IsNil() { + return x + } + // Skip fields of types potentially involved in cycles. + switch x.Interface().(type) { + case *ast.Object, *ast.Scope: + return reflect.Zero(x.Type()) + } + y := reflect.New(x.Type().Elem()) + set(y.Elem(), x.Elem()) + return y + + case reflect.Struct: + y := reflect.New(x.Type()).Elem() + for i := 0; i < x.Type().NumField(); i++ { + set(y.Field(i), x.Field(i)) + } + return y + + case reflect.Slice: + if x.IsNil() { + return x + } + y := reflect.MakeSlice(x.Type(), x.Len(), x.Cap()) + for i := 0; i < x.Len(); i++ { + set(y.Index(i), x.Index(i)) + } + return y + + case reflect.Interface: + y := reflect.New(x.Type()).Elem() + set(y, x.Elem()) + return y + + case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: + panic(x) // unreachable in AST + + default: + return x // bool, string, number + } + } + return clone(reflect.ValueOf(n)).Interface().(ast.Node) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go new file mode 100644 index 0000000..40a3472 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/comment.go @@ -0,0 +1,143 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "go/token" + "iter" + "sort" + "strings" +) + +// Deprecation returns the paragraph of the doc comment that starts with the +// conventional "Deprecation: " marker, as defined by +// https://go.dev/wiki/Deprecated, or "" if the documented symbol is not +// deprecated. +func Deprecation(doc *ast.CommentGroup) string { + for p := range strings.SplitSeq(doc.Text(), "\n\n") { + // There is still some ambiguity for deprecation message. This function + // only returns the paragraph introduced by "Deprecated: ". More + // information related to the deprecation may follow in additional + // paragraphs, but the deprecation message should be able to stand on + // its own. See golang/go#38743. + if strings.HasPrefix(p, "Deprecated: ") { + return p + } + } + return "" +} + +// -- plundered from the future (CL 605517, issue #68021) -- + +// TODO(adonovan): replace with ast.Directive in go1.26 (#68021). +// Beware of our local mods to handle analysistest +// "want" comments on the same line. + +// A directive is a comment line with special meaning to the Go +// toolchain or another tool. It has the form: +// +// //tool:name args +// +// The "tool:" portion is missing for the three directives named +// line, extern, and export. +// +// See https://go.dev/doc/comment#Syntax for details of Go comment +// syntax and https://pkg.go.dev/cmd/compile#hdr-Compiler_Directives +// for details of directives used by the Go compiler. +type Directive struct { + Pos token.Pos // of preceding "//" + Tool string + Name string + Args string // may contain internal spaces +} + +// isDirective reports whether c is a comment directive. +// This code is also in go/printer. +func isDirective(c string) bool { + // "//line " is a line directive. + // "//extern " is for gccgo. + // "//export " is for cgo. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { + return true + } + + // "//[a-z0-9]+:[a-z0-9]" + // (The // has been removed.) + colon := strings.Index(c, ":") + if colon <= 0 || colon+1 >= len(c) { + return false + } + for i := 0; i <= colon+1; i++ { + if i == colon { + continue + } + b := c[i] + if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { + return false + } + } + return true +} + +// Directives returns the directives within the comment. +func Directives(g *ast.CommentGroup) (res []*Directive) { + if g != nil { + // Avoid (*ast.CommentGroup).Text() as it swallows directives. + for _, c := range g.List { + if len(c.Text) > 2 && + c.Text[1] == '/' && + c.Text[2] != ' ' && + isDirective(c.Text[2:]) { + + tool, nameargs, ok := strings.Cut(c.Text[2:], ":") + if !ok { + // Must be one of {line,extern,export}. + tool, nameargs = "", tool + } + name, args, _ := strings.Cut(nameargs, " ") // tab?? + // Permit an additional line comment after the args, chiefly to support + // [golang.org/x/tools/go/analysis/analysistest]. + args, _, _ = strings.Cut(args, "//") + res = append(res, &Directive{ + Pos: c.Slash, + Tool: tool, + Name: name, + Args: strings.TrimSpace(args), + }) + } + } + } + return +} + +// Comments returns an iterator over the comments overlapping the specified interval. +// Comments are sorted by position in the file, so we can use binary search. +func Comments(file *ast.File, start, end token.Pos) iter.Seq[*ast.Comment] { + return func(yield func(*ast.Comment) bool) { + // Find the first comment group that overlaps the range. + i := sort.Search(len(file.Comments), func(i int) bool { + return file.Comments[i].End() >= start + }) + for _, cg := range file.Comments[i:] { + if cg.Pos() > end { + return + } + // Find the first comment in the group that overlaps the range. + j := sort.Search(len(cg.List), func(j int) bool { + return cg.List[j].End() >= start + }) + for _, co := range cg.List[j:] { + if co.Pos() > end { + return + } + if !yield(co) { + return + } + } + } + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go new file mode 100644 index 0000000..74e75d1 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/cursor.go @@ -0,0 +1,38 @@ +// Copyright 2026 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 astutil + +import ( + "go/ast" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" +) + +// UnparenCursor returns the cursor for an expression with any +// enclosing parentheses removed, similar to [ast.Unparen]. +// It is often prudent to call this before switching on the +// type of cur.Node(). +// +// See also [UnparenEnclosingCursor]. +func UnparenCursor(cur inspector.Cursor) inspector.Cursor { + for is[*ast.ParenExpr](cur) { + cur, _ = cur.FirstChild() + } + return cur +} + +// UnparenEnclosingCursor returns the first element of +// the [Cursor.Enclosing] sequence that is not itself enclosed +// in parens. It is often prudent to call this before switching on +// cur.ParentEdge(). +// +// See also [UnparenCursor]. +func UnparenEnclosingCursor(cur inspector.Cursor) inspector.Cursor { + for cur.ParentEdgeKind() == edge.ParenExpr_X { + cur = cur.Parent() + } + return cur +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go new file mode 100644 index 0000000..210f392 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/equal.go @@ -0,0 +1,107 @@ +// Copyright 2023 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 astutil + +import ( + "go/ast" + "go/token" + "reflect" +) + +// Equal reports whether two nodes are structurally equal, +// ignoring fields of type [token.Pos], [ast.Object], +// and [ast.Scope], and comments. +// +// The operands x and y may be nil. +// A nil slice is not equal to an empty slice. +// +// The provided function determines whether two identifiers +// should be considered identical. +func Equal(x, y ast.Node, identical func(x, y *ast.Ident) bool) bool { + if x == nil || y == nil { + return x == y + } + return equal(reflect.ValueOf(x), reflect.ValueOf(y), identical) +} + +// EqualSyntax reports whether x and y are equal. +// Identifiers are considered equal if they are spelled the same. +// Comments are ignored. +func EqualSyntax(x, y ast.Expr) bool { + sameName := func(x, y *ast.Ident) bool { return x.Name == y.Name } + return Equal(x, y, sameName) +} + +func equal(x, y reflect.Value, identical func(x, y *ast.Ident) bool) bool { + // Ensure types are the same + if x.Type() != y.Type() { + return false + } + switch x.Kind() { + case reflect.Pointer: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + switch t := x.Interface().(type) { + // Skip fields of types potentially involved in cycles. + case *ast.Object, *ast.Scope, *ast.CommentGroup: + return true + case *ast.Ident: + return identical(t, y.Interface().(*ast.Ident)) + default: + return equal(x.Elem(), y.Elem(), identical) + } + + case reflect.Interface: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + return equal(x.Elem(), y.Elem(), identical) + + case reflect.Struct: + for i := range x.NumField() { + xf := x.Field(i) + yf := y.Field(i) + // Skip position fields. + if xpos, ok := xf.Interface().(token.Pos); ok { + ypos := yf.Interface().(token.Pos) + // Numeric value of a Pos is not significant but its "zeroness" is, + // because it is often significant, e.g. CallExpr.Variadic(Ellipsis), ChanType.Arrow. + if xpos.IsValid() != ypos.IsValid() { + return false + } + } else if !equal(xf, yf, identical) { + return false + } + } + return true + + case reflect.Slice: + if x.IsNil() || y.IsNil() { + return x.IsNil() == y.IsNil() + } + if x.Len() != y.Len() { + return false + } + for i := range x.Len() { + if !equal(x.Index(i), y.Index(i), identical) { + return false + } + } + return true + + case reflect.String: + return x.String() == y.String() + + case reflect.Bool: + return x.Bool() == y.Bool() + + case reflect.Int: + return x.Int() == y.Int() + + default: + panic(x) + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go new file mode 100644 index 0000000..8b81ea4 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/fields.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "go/ast" + "iter" +) + +// FlatFields 'flattens' an ast.FieldList, returning an iterator over each +// (name, field) combination in the list. For unnamed fields, the identifier is +// nil. +func FlatFields(list *ast.FieldList) iter.Seq2[*ast.Ident, *ast.Field] { + return func(yield func(*ast.Ident, *ast.Field) bool) { + if list == nil { + return + } + + for _, field := range list.List { + if len(field.Names) == 0 { + if !yield(nil, field) { + return + } + } else { + for _, name := range field.Names { + if !yield(name, field) { + return + } + } + } + } + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go new file mode 100644 index 0000000..d0a3941 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/purge.go @@ -0,0 +1,93 @@ +// Copyright 2023 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 astutil provides various AST utility functions for gopls. +package astutil + +import ( + "bytes" + "go/scanner" + "go/token" +) + +// PurgeFuncBodies returns a copy of src in which the contents of each +// outermost {...} region have been deleted, except for struct and +// interface type bodies and the bodies of length-elided array +// literals ([...]T), whose element count is part of the type. It +// includes function bodies, function-literal bodies, and the bodies +// of slice, map, and explicitly-sized array composite literals (whose +// contents don't affect the type of the enclosing declaration). This +// reduces the amount of work required to parse the top-level +// declarations. +// +// PurgeFuncBodies does not preserve newlines or position information. +// Also, if the input is invalid, parsing the output of +// PurgeFuncBodies may result in a different tree due to its effects +// on parser error recovery. +func PurgeFuncBodies(src []byte) []byte { + // Destroy the content of any {...}-bracketed regions that are + // not immediately preceded by a "struct" or "interface" token, + // and that are not the body of a length-elided array literal. + // That includes function bodies, switch/select bodies, and most + // composite literals; this will lead to non-void functions that + // don't have return statements, which of course is a type error, + // but that's ok. + + var out bytes.Buffer + file := token.NewFileSet().AddFile("", -1, len(src)) + var sc scanner.Scanner + sc.Init(file, src, nil, 0) + var prev token.Token + var cursor int // last consumed src offset + var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve + var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{" + for { + pos, tok, _ := sc.Scan() + if tok == token.EOF { + break + } + switch tok { + case token.COMMENT: + // TODO(adonovan): opt: skip, to save an estimated 20% of time. + + case token.SEMICOLON: + ellipsis = false + + case token.RBRACK: + // "...]" occurs only in the array-type prefix of a + // composite literal; variadic "..." is followed by + // a type or ")", never "]". + if prev == token.ELLIPSIS { + ellipsis = true + } + + case token.LBRACE: + if prev == token.STRUCT || prev == token.INTERFACE { + pos = -1 // type body: preserve (don't consume ellipsis) + } else if ellipsis { + pos = -1 // [...]T literal body: preserve + ellipsis = false + } + braces = append(braces, pos) + + case token.RBRACE: + if last := len(braces) - 1; last >= 0 { + top := braces[last] + braces = braces[:last] + if top < 0 { + // preserve + } else if len(braces) == 0 { // toplevel only + // Delete {...} body. + start := file.Offset(top) + end := file.Offset(pos) + out.Write(src[cursor : start+len("{")]) + cursor = end + } + } + } + prev = tok + } + out.Write(src[cursor:]) + return out.Bytes() +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go new file mode 100644 index 0000000..eb49d45 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/stringlit.go @@ -0,0 +1,103 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "go/token" + "strconv" + "unicode/utf8" +) + +// RangeInStringLiteral calculates the positional range within a string literal +// corresponding to the specified start and end byte offsets within the logical string. +func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (Range, error) { + startPos, err := PosInStringLiteral(lit, start) + if err != nil { + return Range{}, fmt.Errorf("start: %v", err) + } + endPos, err := PosInStringLiteral(lit, end) + if err != nil { + return Range{}, fmt.Errorf("end: %v", err) + } + return Range{startPos, endPos}, nil +} + +// PosInStringLiteral returns the position within a string literal +// corresponding to the specified byte offset within the logical +// string that it denotes. +func PosInStringLiteral(lit *ast.BasicLit, offset int) (token.Pos, error) { + raw := lit.Value + + value, err := strconv.Unquote(raw) + if err != nil { + return 0, err + } + if !(0 <= offset && offset <= len(value)) { + return 0, fmt.Errorf("invalid offset") + } + + pos, _ := walkStringLiteral(lit, lit.End(), offset) + return pos, nil +} + +// OffsetInStringLiteral returns the byte offset within the logical (unquoted) +// string corresponding to the specified source position. +func OffsetInStringLiteral(lit *ast.BasicLit, pos token.Pos) (int, error) { + if !NodeContainsPos(lit, pos) { + return 0, fmt.Errorf("invalid position") + } + + raw := lit.Value + + value, err := strconv.Unquote(raw) + if err != nil { + return 0, err + } + + _, offset := walkStringLiteral(lit, pos, len(value)) + return offset, nil +} + +// walkStringLiteral iterates through the raw string literal to map between +// a file position and a logical byte offset. It stops when it reaches +// either the targetPos or the targetOffset. +// +// TODO(hxjiang): consider making an iterator. +func walkStringLiteral(lit *ast.BasicLit, targetPos token.Pos, targetOffset int) (token.Pos, int) { + raw := lit.Value + norm := int(lit.End()-lit.Pos()) > len(lit.Value) + + // remove quotes + quote := raw[0] // '"' or '`' + raw = raw[1 : len(raw)-1] + + var ( + i = 0 // byte index within logical value + pos = lit.Pos() + 1 // position within literal + ) + + for raw != "" { + r, _, rest, _ := strconv.UnquoteChar(raw, quote) // can't fail + sz := len(raw) - len(rest) // length of literal char in raw bytes + + nextPos := pos + token.Pos(sz) + if norm && r == '\n' { + nextPos++ + } + nextI := i + utf8.RuneLen(r) // length of logical char in "cooked" bytes + + if nextPos > targetPos || nextI > targetOffset { + break + } + + raw = raw[sz:] + i = nextI + pos = nextPos + } + + return pos, i +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go new file mode 100644 index 0000000..3d2fb04 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/unpack.go @@ -0,0 +1,61 @@ +// Copyright 2023 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 astutil + +import ( + "go/ast" + + "honnef.co/go/tools/internal/xtools-internal/typeparams" +) + +// UnpackRecv unpacks a receiver type expression, reporting whether it is a +// pointer receiver, along with the type name identifier and any receiver type +// parameter identifiers. +// +// Copied (with modifications) from go/types. +func UnpackRecv(rtyp ast.Expr) (ptr bool, rname *ast.Ident, tparams []*ast.Ident) { +L: // unpack receiver type + // This accepts invalid receivers such as ***T and does not + // work for other invalid receivers, but we don't care. The + // validity of receiver expressions is checked elsewhere. + for { + switch t := rtyp.(type) { + case *ast.ParenExpr: + rtyp = t.X + case *ast.StarExpr: + ptr = true + rtyp = t.X + default: + break L + } + } + + // unpack type parameters, if any + switch rtyp.(type) { + case *ast.IndexExpr, *ast.IndexListExpr: + var indices []ast.Expr + rtyp, _, indices, _ = typeparams.UnpackIndexExpr(rtyp) + for _, arg := range indices { + var par *ast.Ident + switch arg := arg.(type) { + case *ast.Ident: + par = arg + default: + // ignore errors + } + if par == nil { + par = &ast.Ident{NamePos: arg.Pos(), Name: "_"} + } + tparams = append(tparams, par) + } + } + + // unpack receiver name + if name, _ := rtyp.(*ast.Ident); name != nil { + rname = name + } + + return +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go new file mode 100644 index 0000000..6d974db --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/astutil/util.go @@ -0,0 +1,261 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package astutil + +import ( + "fmt" + "go/ast" + "go/printer" + "go/token" + "strings" + + "golang.org/x/tools/go/ast/inspector" + "honnef.co/go/tools/internal/xtools-internal/moreiters" +) + +// NodeContains reports whether the Pos/End range of node n encloses +// the given range. +// +// It is inclusive of both end points, to allow hovering (etc) when +// the cursor is immediately after a node. +// +// Like [NodeRange], it treats the range of an [ast.File] as the +// file's complete extent. +// +// Precondition: n must not be nil. +func NodeContains(n ast.Node, rng Range) bool { + return NodeRange(n).Contains(rng) +} + +// NodeContainsPos reports whether the Pos/End range of node n encloses +// the given pos. +// +// Like [NodeRange], it treats the range of an [ast.File] as the +// file's complete extent. +func NodeContainsPos(n ast.Node, pos token.Pos) bool { + return NodeRange(n).ContainsPos(pos) +} + +// EnclosingFile returns the syntax tree for the file enclosing c. +// +// TODO(adonovan): promote this to a method of Cursor. +func EnclosingFile(c inspector.Cursor) *ast.File { + c, _ = moreiters.First(c.Enclosing((*ast.File)(nil))) + return c.Node().(*ast.File) +} + +// DocComment returns the doc comment for a node, if any. +func DocComment(n ast.Node) *ast.CommentGroup { + switch n := n.(type) { + case *ast.FuncDecl: + return n.Doc + case *ast.GenDecl: + return n.Doc + case *ast.ValueSpec: + return n.Doc + case *ast.TypeSpec: + return n.Doc + case *ast.File: + return n.Doc + case *ast.ImportSpec: + return n.Doc + case *ast.Field: + return n.Doc + } + return nil +} + +// Format returns a string representation of the node n. +func Format(fset *token.FileSet, n ast.Node) string { + var buf strings.Builder + printer.Fprint(&buf, fset, n) // ignore errors + return buf.String() +} + +// -- Range -- + +// Range is a Pos interval. +// It implements [analysis.Range] and [ast.Node]. +type Range struct{ Start, EndPos token.Pos } + +// RangeOf constructs a Range. +// +// RangeOf exists to pacify the "unkeyed literal" (composites) vet +// check. It would be nice if there were a way for a type to add +// itself to the allowlist. +func RangeOf(start, end token.Pos) Range { return Range{start, end} } + +// NodeRange returns the extent of node n as a Range. +// +// For unfortunate historical reasons, the Pos/End extent of an +// ast.File runs from the start of its package declaration---excluding +// copyright comments, build tags, and package documentation---to the +// end of its last declaration, excluding any trailing comments. So, +// as a special case, if n is an [ast.File], NodeContains uses +// n.FileStart <= pos && pos <= n.FileEnd to report whether the +// position lies anywhere within the file. +func NodeRange(n ast.Node) Range { + if file, ok := n.(*ast.File); ok { + return Range{file.FileStart, file.FileEnd} // entire file + } + return Range{n.Pos(), n.End()} +} + +func (r Range) Pos() token.Pos { return r.Start } +func (r Range) End() token.Pos { return r.EndPos } + +// ContainsPos reports whether the range (inclusive of both end points) +// includes the specified position. +func (r Range) ContainsPos(pos token.Pos) bool { + return r.Contains(RangeOf(pos, pos)) +} + +// Contains reports whether the range (inclusive of both end points) +// includes the specified range. +func (r Range) Contains(rng Range) bool { + return r.Start <= rng.Start && rng.EndPos <= r.EndPos +} + +// IsValid reports whether the range is valid. +func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos } + +// -- + +// Select returns the syntax nodes identified by a user's text +// selection. It returns three nodes: the innermost node that wholly +// encloses the selection; and the first and last nodes that are +// wholly enclosed by the selection. +// +// For example, given this selection: +// +// { f(); g(); /* comment */ } +// ~~~~~~~~~~~ +// +// Select returns the enclosing BlockStmt, the f() CallExpr, and the g() CallExpr. +// +// If the selection does not wholly enclose any nodes, Select returns an error +// and invalid start/end nodes, but it may return a valid enclosing node. +// +// Callers that require exactly one syntax tree (e.g. just f() or just +// g()) should check that the returned start and end nodes are +// identical. +// +// This function is intended to be called early in the handling of a +// user's request, since it is tolerant of sloppy selection including +// extraneous whitespace and comments. Use it in new code instead of +// PathEnclosingInterval. When the exact extent of a node is known, +// use [Cursor.FindByPos] instead. +// +// TODO(hxjiang): Consider refactoring the function signature. It is currently +// confusing that an error is returned even when a valid enclosing node is +// successfully found. Consider grouping all cursors into one struct. +func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) { + curEnclosing, ok := curFile.FindByPos(start, end) + if !ok { + return noCursor, noCursor, noCursor, fmt.Errorf("invalid selection") + } + + // Find the first and last node wholly within the (start, end) range. + // We'll narrow the effective selection to them, to exclude whitespace. + // (This matches the functionality of PathEnclosingInterval.) + var curStart, curEnd inspector.Cursor + rng := RangeOf(start, end) + for cur := range curEnclosing.Preorder() { + if rng.Contains(NodeRange(cur.Node())) { + // The start node has the least Pos. + if !curStart.Valid() { + curStart = cur + } + // The end node has the greatest End. + // End positions do not change monotonically, + // so we must compute the max. + if !curEnd.Valid() || + cur.Node().End() > curEnd.Node().End() { + curEnd = cur + } + } + } + if !curStart.Valid() { + // The selection is valid (inside curEnclosing) but contains no + // complete nodes. This happens for point selections (start == end), + // or selections covering only only spaces, comments, and punctuation + // tokens. + // Return the enclosing node so the caller can still use the context. + return curEnclosing, noCursor, noCursor, fmt.Errorf("invalid selection") + } + return curEnclosing, curStart, curEnd, nil +} + +var noCursor inspector.Cursor + +// MaybeParenthesize returns new, possibly wrapped in parens if needed +// to preserve operator precedence when it replaces old, whose parent +// is parentNode. +// +// (This would be more naturally written in terms of Cursor, but one of +// the callers--the inliner--does not have cursors handy.) +func MaybeParenthesize(parentNode ast.Node, old, new ast.Expr) ast.Expr { + if needsParens(parentNode, old, new) { + new = &ast.ParenExpr{X: new} + } + return new +} + +func needsParens(parentNode ast.Node, old, new ast.Expr) bool { + // An expression beneath a non-expression + // has no precedence ambiguity. + parent, ok := parentNode.(ast.Expr) + if !ok { + return false + } + + precedence := func(n ast.Node) int { + switch n := n.(type) { + case *ast.UnaryExpr, *ast.StarExpr: + return token.UnaryPrec + case *ast.BinaryExpr: + return n.Op.Precedence() + } + return -1 + } + + // Parens are not required if the new node + // is not unary or binary. + newprec := precedence(new) + if newprec < 0 { + return false + } + + // Parens are required if parent and child are both + // unary or binary and the parent has higher precedence. + if precedence(parent) > newprec { + return true + } + + // Was the old node the operand of a postfix operator? + // f().sel + // f()[i:j] + // f()[i] + // f().(T) + // f()(x) + switch parent := parent.(type) { + case *ast.SelectorExpr: + return parent.X == old + case *ast.IndexExpr: + return parent.X == old + case *ast.SliceExpr: + return parent.X == old + case *ast.TypeAssertExpr: + return parent.X == old + case *ast.CallExpr: + return parent.Fun == old + } + return false +} + +func is[T any](n any) bool { + _, ok := n.(T) + return ok +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go new file mode 100644 index 0000000..4473255 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/allpaths.go @@ -0,0 +1,24 @@ +// Copyright 2026 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 graph + +// AllPaths returns the set of nodes that are part of at least one path from src to dst. +func AllPaths[NodeID comparable](g Graph[NodeID], src, dst NodeID) map[NodeID]bool { + // We intersect the forward closure of 'src' with + // the reverse closure of 'dst'. This is not the most + // efficient implementation, but it's the clearest, + // and the previous one had bugs. + + fwd := Reachable(g, src) + rev := Reachable(Transpose(g), dst) + + // Intersection + for n := range fwd { + if !rev[n] { + delete(fwd, n) + } + } + return fwd +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go new file mode 100644 index 0000000..ae32706 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/compact.go @@ -0,0 +1,89 @@ +// Copyright 2026 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 graph + +import "iter" + +// A CompactGraph is a Graph with nodes that are compactly numbered from [0, +// NumNodes()). +// +// Compactly numbered graphs are useful for many graph algorithms, and many +// graph representations are naturally compact. +// +// To compact an arbitrary graph, use [Compact]. +type CompactGraph interface { + Graph[int] + IsCompact() +} + +// A nodePreserving graph is a transformation of another graph that preserves +// node IDs. +type nodePreserving interface { + Graph[int] + unwrapPreservingNodes() Graph[int] +} + +// Compact takes a Graph with arbitrary NodeIDs and returns a compact graph. +// +// If g implements [CompactGraph], it assumes g is already compact and simply +// returns g and an identity mapping. +func Compact[NodeID comparable](g Graph[NodeID]) (CompactGraph, *Index[NodeID]) { + // If it's already compact, simply return it. + if gc, ok := g.(CompactGraph); ok { + // The above assertion ensures NodeID is int, so we know this type + // assertion will always succeed. + return gc, any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID]) + } + + // If it's a transformation and the underlying graph is compact, we can use + // an identity index. Though we still need to build a compactGraph to + // satisfy the CompactGraph interface. + g2, _ := g.(nodePreserving) + for g2 != nil { + unwrapped := g2.unwrapPreservingNodes() + if gc, ok := unwrapped.(CompactGraph); ok { + index := any(NewIdentityIndex(gc.NumNodes())).(*Index[NodeID]) + cg := compactGraph[NodeID]{g, index} + return &cg, index + } + g2, _ = unwrapped.(nodePreserving) + } + + // Nope, just build an index. + cg := compactGraph[NodeID]{g, NewIndex(g.Nodes())} + return &cg, cg.m +} + +type compactGraph[NodeID comparable] struct { + g Graph[NodeID] + m *Index[NodeID] +} + +func (g *compactGraph[NodeID]) Nodes() iter.Seq[int] { + return func(yield func(int) bool) { + for i := range g.g.NumNodes() { + if !yield(i) { + break + } + } + } +} + +func (g *compactGraph[NodeID]) NumNodes() int { + return g.g.NumNodes() +} + +func (g *compactGraph[NodeID]) Out(node int) iter.Seq[int] { + id := g.m.Value(node) + return func(yield func(int) bool) { + for nid := range g.g.Out(id) { + if !yield(g.m.Index(nid)) { + break + } + } + } +} + +func (g *compactGraph[NodeID]) IsCompact() {} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go new file mode 100644 index 0000000..fdea880 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/graph.go @@ -0,0 +1,33 @@ +// Copyright 2026 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 graph provides a common abstraction for directed graphs and standard +// graph algorithms. +// +// In general, this package does not provide or assume any concrete graph +// representation. It's up to the caller of this package to implement the +// [Graph] interface, either directly or as an adapter around another type. +package graph + +import "iter" + +// A Graph implements a directed graph where nodes in the graph are identified +// by the NodeID type. +// +// If a concrete graph type stores additional information about nodes and/or +// edges, it will conventionally provide methods of the form: +// +// Node(node NodeID) nodeInfo +// Edge(from, to NodeID) edgeInfo +type Graph[NodeID comparable] interface { + // Nodes yields all nodes in this graph. + Nodes() iter.Seq[NodeID] + + // NumNodes returns the total number of nodes in this graph. + NumNodes() int + + // Out yields the out-edges of node. Out must be deterministic, though + // otherwise there is no constraint on the order of the returned sequence. + Out(node NodeID) iter.Seq[NodeID] +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go new file mode 100644 index 0000000..394213e --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/index.go @@ -0,0 +1,96 @@ +// Copyright 2026 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 graph + +import ( + "fmt" + "iter" + "slices" +) + +// An Index is an immutable, bijective map between [0, N) and an ordered list of keys. +type Index[Key comparable] struct { + // There are three Index representations: + // + // - If identN > 0, an identity map of [0, identN). + // - If index == nil, a sorted integer index in values. + // - Otherwise, a full index in values and index. + + identN int + values []Key + index map[Key]int +} + +// NewIndex returns an index for the specified list of values. +func NewIndex[Key comparable](values iter.Seq[Key]) *Index[Key] { + vs := slices.Collect(values) + if len(vs) == 0 { + return new(Index[Key]) + } + + // Fast path: a naturally sorted list needs no index. (Sadly, there's no way + // to ask "is Key ordered?") + if vi, ok := any(vs).([]int); ok && slices.IsSorted(vi) { + return &Index[Key]{values: vs} + } + + index := make(map[Key]int, len(vs)) + for i, v := range vs { + index[v] = i + } + return &Index[Key]{values: vs, index: index} +} + +// NewIdentityIndex returns an index that maps [0, n) to [0, n). +func NewIdentityIndex(n int) *Index[int] { + if n < 0 { + panic("n < 0") + } + // If n == 0, this is actually a "sorted integer index", but it doesn't + // matter because everything is out of bounds either way. + return &Index[int]{identN: n} +} + +// Value maps an index to a key. +func (ix *Index[Key]) Value(index int) Key { + if ix.identN > 0 { + if index < 0 || index >= ix.identN { + panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) + } + return any(index).(Key) + } + if index < 0 || index >= len(ix.values) { + panic(fmt.Sprintf("index %d out of range [0, %d)", index, ix.identN)) + } + return ix.values[index] +} + +// Index maps a key to an index. +func (ix *Index[Key]) Index(key Key) int { + if key, ok := any(key).(int); ok { + // Integer-only optimizations. + switch { + case ix.identN > 0: + // Identity. + if 0 <= key && key < ix.identN { + return key + } + goto oob + + case ix.index == nil: + // Sorted integers. + if i, ok := slices.BinarySearch(any(ix.values).([]int), key); ok { + return i + } + goto oob + } + } + if i, ok := ix.index[key]; ok { + return i + } + +oob: + panic(fmt.Sprintf("key %v not in index", key)) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go new file mode 100644 index 0000000..795a434 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/order.go @@ -0,0 +1,94 @@ +// Copyright 2026 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 graph + +import "slices" + +// Postorder returns the sequence of nodes in the spanning DAG of g, in +// postorder. +// +// For rootless subgraphs, it breaks cycles by starting at the lowest numbered +// node. +// +// This algorithm runs in O(V + E) time and O(V + E) space. +func Postorder[NodeID comparable](g Graph[NodeID]) []NodeID { + cg, nodeMap := Compact(g) + + numNodes := cg.NumNodes() + if numNodes == 0 { + return nil + } + + result := make([]NodeID, 0, numNodes) + visited := newBitset(numNodes) + onStack := newBitset(numNodes) + + // visit performs a Depth-First Search. + var visit func(u int) + visit = func(u int) { + if !visited.add(u) { + return + } + onStack.add(u) + + for v := range cg.Out(u) { + if onStack.contains(v) { + // Cycle detected (back-edge). + // To resolve, we simply skip processing this edge further in the + // current recursion, effectively "breaking" the cycle at this point. + continue + } + visit(v) + } + + onStack.remove(u) + // Post-order: add to result after all descendants are processed. + result = append(result, nodeMap.Value(u)) + } + + // Visit every node in ascending order to ensure stability. + for u := range numNodes { + visit(u) + } + + return result +} + +// ReversePostorder returns the nodes of the graph in reverse post-order. +// +// If g is a directed acyclic graph (DAG), the result is a topological sort of +// g. +// +// See [Postorder] for how this handles back-edges and cycles. +// +// This algorithm runs in O(V + E) time and O(V + E) space. +func ReversePostorder[NodeID comparable](g Graph[NodeID]) []NodeID { + result := Postorder(g) + slices.Reverse(result) + return result +} + +// bitset is a simple fixed-size bitset used to reduce memory overhead. +type bitset []uint64 + +func newBitset(n int) bitset { + return make(bitset, (n+63)/64) +} + +func (b bitset) add(u int) bool { + if b.contains(u) { + return false + } + b[u/64] |= 1 << (u % 64) + return true +} + +func (b bitset) remove(u int) { + b[u/64] &= ^(1 << (u % 64)) +} + +func (b bitset) contains(u int) bool { + return b[u/64]&(1<<(u%64)) != 0 +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go new file mode 100644 index 0000000..dd14eae --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/reachable.go @@ -0,0 +1,23 @@ +// Copyright 2026 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 graph + +// Reachable returns the set of nodes reachable from the given roots. +func Reachable[NodeID comparable](g Graph[NodeID], roots ...NodeID) map[NodeID]bool { + seen := make(map[NodeID]bool) + var visit func(node NodeID) + visit = func(node NodeID) { + if !seen[node] { + seen[node] = true + for e := range g.Out(node) { + visit(e) + } + } + } + for _, root := range roots { + visit(root) + } + return seen +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go new file mode 100644 index 0000000..fb4764f --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/scc.go @@ -0,0 +1,39 @@ +// Copyright 2026 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 graph + +import "slices" + +// SCCs computes the strongly connected components of the graph g. +func SCCs[NodeID comparable](g Graph[NodeID]) [][]NodeID { + // Use Kosaraju's algorithm. Tarjan is overkill here. + + // Forward pass + S := Postorder(g) + + // Reverse pass + gt := Transpose(g) + seen := make(map[NodeID]bool) + var scc []NodeID + var sccs [][]NodeID + var rvisit func(NodeID) + rvisit = func(u NodeID) { + if !seen[u] { + seen[u] = true + scc = append(scc, u) + for v := range gt.Out(u) { + rvisit(v) + } + } + } + for _, root := range slices.Backward(S) { + if !seen[root] { + scc = nil + rvisit(root) + sccs = append(sccs, scc) + } + } + return sccs +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go new file mode 100644 index 0000000..e7457e8 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/shortest.go @@ -0,0 +1,45 @@ +// Copyright 2026 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 graph + +import "slices" + +// ShortestPath returns a shortest path from src to dst in g. +// It returns the path as a slice of nodes starting with src and ending with dst. +// If no path is found, it returns nil. +func ShortestPath[NodeID comparable](g Graph[NodeID], src, dst NodeID) []NodeID { + if src == dst { + return []NodeID{src} + } + + pred := make(map[NodeID]NodeID) + queue := []NodeID{src} + // Mark src as seen. + pred[src] = src + + for len(queue) > 0 { + n := queue[0] + queue = queue[1:] + + if n == dst { + // Reconstruct path + var path []NodeID + for curr := dst; curr != src; curr = pred[curr] { + path = append(path, curr) + } + path = append(path, src) + slices.Reverse(path) + return path + } + + for v := range g.Out(n) { + if _, seen := pred[v]; !seen { + pred[v] = n + queue = append(queue, v) + } + } + } + return nil +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go new file mode 100644 index 0000000..431fc5f --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/graph/transpose.go @@ -0,0 +1,51 @@ +// Copyright 2026 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 graph + +import ( + "iter" + "slices" +) + +type transpose[NodeID comparable] struct { + Graph Graph[NodeID] + preds map[NodeID][]NodeID +} + +// transpose returns a graph like g but with all edges reversed. Node IDs are +// identical to the underlying graph. +// +// Transpose preserves compactness. +func Transpose[NodeID comparable](g Graph[NodeID]) Graph[NodeID] { + if g, ok := g.(transpose[NodeID]); ok { + // Transpose(Transpose(g)) == g + return g.Graph + } + + preds := make(map[NodeID][]NodeID) + for nid := range g.Nodes() { + for succ := range g.Out(nid) { + preds[succ] = append(preds[succ], nid) + } + } + return transpose[NodeID]{g, preds} +} + +func (t transpose[NodeID]) NumNodes() int { + return len(t.preds) +} + +func (t transpose[NodeID]) Nodes() iter.Seq[NodeID] { + return t.Graph.Nodes() +} + +func (t transpose[NodeID]) Out(n NodeID) iter.Seq[NodeID] { + return slices.Values(t.preds[n]) +} + +//lint:ignore U1000 False positive in Staticcheck 2026.1 and older. +func (t transpose[NodeID]) unwrapPreservingNodes() Graph[NodeID] { + return t.Graph +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go b/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go new file mode 100644 index 0000000..3dc81c4 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/moreiters/iters.go @@ -0,0 +1,63 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package moreiters + +import "iter" + +// First returns the first value of seq and true. +// If seq is empty, it returns the zero value of T and false. +func First[T any](seq iter.Seq[T]) (z T, ok bool) { + for t := range seq { + return t, true + } + return z, false +} + +// Contains reports whether x is an element of the sequence seq. +func Contains[T comparable](seq iter.Seq[T], x T) bool { + for cand := range seq { + if cand == x { + return true + } + } + return false +} + +// Every reports whether every pred(t) for t in seq returns true, +// stopping at the first false element. +func Every[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if !pred(t) { + return false + } + } + return true +} + +// Any reports whether any pred(t) for t in seq returns true. +func Any[T any](seq iter.Seq[T], pred func(T) bool) bool { + for t := range seq { + if pred(t) { + return true + } + } + return false +} + +// Len returns the number of elements in the sequence (by iterating). +func Len[T any](seq iter.Seq[T]) (n int) { + for range seq { + n++ + } + return +} + +// Empty reports whether the sequence contains no elements. +func Empty[T any](seq iter.Seq[T]) bool { + for range seq { + return false + } + return true +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go new file mode 100644 index 0000000..cdae2b8 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/common.go @@ -0,0 +1,68 @@ +// Copyright 2021 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 typeparams contains common utilities for writing tools that +// interact with generic Go code, as introduced with Go 1.18. It +// supplements the standard library APIs. Notably, the StructuralTerms +// API computes a minimal representation of the structural +// restrictions on a type parameter. +// +// An external version of these APIs is available in the +// golang.org/x/exp/typeparams module. +package typeparams + +import ( + "go/ast" + "go/token" + "go/types" +) + +// UnpackIndexExpr extracts data from AST nodes that represent index +// expressions. +// +// For an ast.IndexExpr, the resulting indices slice will contain exactly one +// index expression. For an ast.IndexListExpr (go1.18+), it may have a variable +// number of index expressions. +// +// For nodes that don't represent index expressions, the first return value of +// UnpackIndexExpr will be nil. +func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) { + switch e := n.(type) { + case *ast.IndexExpr: + return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack + case *ast.IndexListExpr: + return e.X, e.Lbrack, e.Indices, e.Rbrack + } + return nil, token.NoPos, nil, token.NoPos +} + +// PackIndexExpr returns an *ast.IndexExpr or *ast.IndexListExpr, depending on +// the cardinality of indices. Calling PackIndexExpr with len(indices) == 0 +// will panic. +func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack token.Pos) ast.Expr { + switch len(indices) { + case 0: + panic("empty indices") + case 1: + return &ast.IndexExpr{ + X: x, + Lbrack: lbrack, + Index: indices[0], + Rbrack: rbrack, + } + default: + return &ast.IndexListExpr{ + X: x, + Lbrack: lbrack, + Indices: indices, + Rbrack: rbrack, + } + } +} + +// IsTypeParam reports whether t is a type parameter (or an alias of one). +func IsTypeParam(t types.Type) bool { + _, ok := types.Unalias(t).(*types.TypeParam) + return ok +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go new file mode 100644 index 0000000..2e05de4 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/coretype.go @@ -0,0 +1,157 @@ +// Copyright 2022 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 typeparams + +import ( + "fmt" + "go/types" +) + +// CoreType returns the core type of T or nil if T does not have a core type. +// +// As of Go1.25, the notion of a core type has been removed from the language spec. +// See https://go.dev/blog/coretypes for more details. +// TODO(mkalil): We should eventually consider removing all uses of CoreType. +func CoreType(T types.Type) types.Type { + U := T.Underlying() + if _, ok := U.(*types.Interface); !ok { + return U // for non-interface types, + } + + terms, err := NormalTerms(U) + if len(terms) == 0 || err != nil { + // len(terms) -> empty type set of interface. + // err != nil => U is invalid, exceeds complexity bounds, or has an empty type set. + return nil // no core type. + } + + U = terms[0].Type().Underlying() + var identical int // i in [0,identical) => Identical(U, terms[i].Type().Underlying()) + for identical = 1; identical < len(terms); identical++ { + if !types.Identical(U, terms[identical].Type().Underlying()) { + break + } + } + + if identical == len(terms) { + // From the deprecated core types spec: + // "There is a single type U which is the underlying type of all types in the type set of T" + return U + } + ch, ok := U.(*types.Chan) + if !ok { + return nil // no core type as identical < len(terms) and U is not a channel. + } + // From the deprecated core types spec: + // "the type chan E if T contains only bidirectional channels, or the type chan<- E or + // <-chan E depending on the direction of the directional channels present." + for chans := identical; chans < len(terms); chans++ { + curr, ok := terms[chans].Type().Underlying().(*types.Chan) + if !ok { + return nil + } + if !types.Identical(ch.Elem(), curr.Elem()) { + return nil // channel elements are not identical. + } + if ch.Dir() == types.SendRecv { + // ch is bidirectional. We can safely always use curr's direction. + ch = curr + } else if curr.Dir() != types.SendRecv && ch.Dir() != curr.Dir() { + // ch and curr are not bidirectional and not the same direction. + return nil + } + } + return ch +} + +// NormalTerms returns a slice of terms representing the normalized structural +// type restrictions of a type, if any. +// +// For all types other than *types.TypeParam, *types.Interface, and +// *types.Union, this is just a single term with Tilde() == false and +// Type() == typ. For *types.TypeParam, *types.Interface, and *types.Union, see +// below. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration type +// T[P interface{~int; m()}] int the structural restriction of the type +// parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// NormalTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, NormalTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the type is +// invalid, exceeds complexity bounds, or has an empty type set. In the latter +// case, NormalTerms returns ErrEmptyTypeSet. +// +// NormalTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func NormalTerms(T types.Type) ([]*types.Term, error) { + // typeSetOf(T) == typeSetOf(Unalias(T)) + typ := types.Unalias(T) + if named, ok := typ.(*types.Named); ok { + typ = named.Underlying() + } + switch typ := typ.(type) { + case *types.TypeParam: + return StructuralTerms(typ) + case *types.Union: + return UnionTermSet(typ) + case *types.Interface: + return InterfaceTermSet(typ) + default: + return []*types.Term{types.NewTerm(false, T)}, nil + } +} + +// Deref returns the type of the variable pointed to by t, +// if t's core type is a pointer; otherwise it returns t. +// +// Do not assume that Deref(T)==T implies T is not a pointer: +// consider "type T *T", for example. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func Deref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} + +// MustDeref returns the type of the variable pointed to by t. +// It panics if t's core type is not a pointer. +// +// TODO(adonovan): ideally this would live in typesinternal, but that +// creates an import cycle. Move there when we melt this package down. +func MustDeref(t types.Type) types.Type { + if ptr, ok := CoreType(t).(*types.Pointer); ok { + return ptr.Elem() + } + panic(fmt.Sprintf("%v is not a pointer", t)) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go new file mode 100644 index 0000000..4c39187 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/free.go @@ -0,0 +1,129 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typeparams + +import ( + "go/types" +) + +// Free is a memoization of the set of free type parameters within a +// type. It makes a sequence of calls to [Free.Has] for overlapping +// types more efficient. The zero value is ready for use. +// +// NOTE: Adapted from go/types/infer.go. If it is later exported, factor. +type Free struct { + seen map[types.Type]bool +} + +// Has reports whether the specified type has a free type parameter. +func (w *Free) Has(typ types.Type) (res bool) { + // detect cycles + if x, ok := w.seen[typ]; ok { + return x + } + if w.seen == nil { + w.seen = make(map[types.Type]bool) + } + w.seen[typ] = false + defer func() { + w.seen[typ] = res + }() + + switch t := typ.(type) { + case nil, *types.Basic: // TODO(gri) should nil be handled here? + break + + case *types.Alias: + if t.TypeParams().Len() > t.TypeArgs().Len() { + return true // This is an uninstantiated Alias. + } + // The expansion of an alias can have free type parameters, + // whether or not the alias itself has type parameters: + // + // func _[K comparable]() { + // type Set = map[K]bool // free(Set) = {K} + // type MapTo[V] = map[K]V // free(Map[foo]) = {V} + // } + // + // So, we must Unalias. + return w.Has(types.Unalias(t)) + + case *types.Array: + return w.Has(t.Elem()) + + case *types.Slice: + return w.Has(t.Elem()) + + case *types.Struct: + for i, n := 0, t.NumFields(); i < n; i++ { + if w.Has(t.Field(i).Type()) { + return true + } + } + + case *types.Pointer: + return w.Has(t.Elem()) + + case *types.Tuple: + n := t.Len() + for i := range n { + if w.Has(t.At(i).Type()) { + return true + } + } + + case *types.Signature: + // t.tparams may not be nil if we are looking at a signature + // of a generic function type (or an interface method) that is + // part of the type we're testing. We don't care about these type + // parameters. + // Similarly, the receiver of a method may declare (rather than + // use) type parameters, we don't care about those either. + // Thus, we only need to look at the input and result parameters. + return w.Has(t.Params()) || w.Has(t.Results()) + + case *types.Interface: + for i, n := 0, t.NumMethods(); i < n; i++ { + if w.Has(t.Method(i).Type()) { + return true + } + } + terms, err := InterfaceTermSet(t) + if err != nil { + return false // ill typed + } + for _, term := range terms { + if w.Has(term.Type()) { + return true + } + } + + case *types.Map: + return w.Has(t.Key()) || w.Has(t.Elem()) + + case *types.Chan: + return w.Has(t.Elem()) + + case *types.Named: + args := t.TypeArgs() + if params := t.TypeParams(); params.Len() > args.Len() { + return true // this is an uninstantiated named type. + } + for i, n := 0, args.Len(); i < n; i++ { + if w.Has(args.At(i)) { + return true + } + } + return w.Has(t.Underlying()) // recurse for types local to parameterized functions + + case *types.TypeParam: + return true + + default: + panic(t) // unreachable + } + + return false +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go new file mode 100644 index 0000000..ba36787 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/normalize.go @@ -0,0 +1,214 @@ +// Copyright 2021 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 typeparams + +import ( + "errors" + "fmt" + "go/types" + "os" + "strings" +) + +const debug = false + +var ErrEmptyTypeSet = errors.New("empty type set") + +// StructuralTerms returns a slice of terms representing the normalized +// structural type restrictions of a type parameter, if any. +// +// Structural type restrictions of a type parameter are created via +// non-interface types embedded in its constraint interface (directly, or via a +// chain of interface embeddings). For example, in the declaration +// +// type T[P interface{~int; m()}] int +// +// the structural restriction of the type parameter P is ~int. +// +// With interface embedding and unions, the specification of structural type +// restrictions may be arbitrarily complex. For example, consider the +// following: +// +// type A interface{ ~string|~[]byte } +// +// type B interface{ int|string } +// +// type C interface { ~string|~int } +// +// type T[P interface{ A|B; C }] int +// +// In this example, the structural type restriction of P is ~string|int: A|B +// expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int, +// which when intersected with C (~string|~int) yields ~string|int. +// +// StructuralTerms computes these expansions and reductions, producing a +// "normalized" form of the embeddings. A structural restriction is normalized +// if it is a single union containing no interface terms, and is minimal in the +// sense that removing any term changes the set of types satisfying the +// constraint. It is left as a proof for the reader that, modulo sorting, there +// is exactly one such normalized form. +// +// Because the minimal representation always takes this form, StructuralTerms +// returns a slice of tilde terms corresponding to the terms of the union in +// the normalized structural restriction. An error is returned if the +// constraint interface is invalid, exceeds complexity bounds, or has an empty +// type set. In the latter case, StructuralTerms returns ErrEmptyTypeSet. +// +// StructuralTerms makes no guarantees about the order of terms, except that it +// is deterministic. +func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) { + constraint := tparam.Constraint() + if constraint == nil { + return nil, fmt.Errorf("%s has nil constraint", tparam) + } + iface, _ := constraint.Underlying().(*types.Interface) + if iface == nil { + return nil, fmt.Errorf("constraint is %T, not *types.Interface", constraint.Underlying()) + } + return InterfaceTermSet(iface) +} + +// InterfaceTermSet computes the normalized terms for a constraint interface, +// returning an error if the term set cannot be computed or is empty. In the +// latter case, the error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) { + return computeTermSet(iface) +} + +// UnionTermSet computes the normalized terms for a union, returning an error +// if the term set cannot be computed or is empty. In the latter case, the +// error will be ErrEmptyTypeSet. +// +// See the documentation of StructuralTerms for more information on +// normalization. +func UnionTermSet(union *types.Union) ([]*types.Term, error) { + return computeTermSet(union) +} + +func computeTermSet(typ types.Type) ([]*types.Term, error) { + tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0) + if err != nil { + return nil, err + } + if tset.terms.isEmpty() { + return nil, ErrEmptyTypeSet + } + if tset.terms.isAll() { + return nil, nil + } + var terms []*types.Term + for _, term := range tset.terms { + terms = append(terms, types.NewTerm(term.tilde, term.typ)) + } + return terms, nil +} + +// A termSet holds the normalized set of terms for a given type. +// +// The name termSet is intentionally distinct from 'type set': a type set is +// all types that implement a type (and includes method restrictions), whereas +// a term set just represents the structural restrictions on a type. +type termSet struct { + complete bool + terms termlist +} + +func indentf(depth int, format string, args ...any) { + fmt.Fprintf(os.Stderr, strings.Repeat(".", depth)+format+"\n", args...) +} + +func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth int) (res *termSet, err error) { + if t == nil { + panic("nil type") + } + + if debug { + indentf(depth, "%s", t.String()) + defer func() { + if err != nil { + indentf(depth, "=> %s", err) + } else { + indentf(depth, "=> %s", res.terms.String()) + } + }() + } + + const maxTermCount = 100 + if tset, ok := seen[t]; ok { + if !tset.complete { + return nil, fmt.Errorf("cycle detected in the declaration of %s", t) + } + return tset, nil + } + + // Mark the current type as seen to avoid infinite recursion. + tset := new(termSet) + defer func() { + tset.complete = true + }() + seen[t] = tset + + switch u := t.Underlying().(type) { + case *types.Interface: + // The term set of an interface is the intersection of the term sets of its + // embedded types. + tset.terms = allTermlist + for embedded := range u.EmbeddedTypes() { + if _, ok := embedded.Underlying().(*types.TypeParam); ok { + return nil, fmt.Errorf("invalid embedded type %T", embedded) + } + tset2, err := computeTermSetInternal(embedded, seen, depth+1) + if err != nil { + return nil, err + } + tset.terms = tset.terms.intersect(tset2.terms) + } + case *types.Union: + // The term set of a union is the union of term sets of its terms. + tset.terms = nil + for t := range u.Terms() { + var terms termlist + switch t.Type().Underlying().(type) { + case *types.Interface: + tset2, err := computeTermSetInternal(t.Type(), seen, depth+1) + if err != nil { + return nil, err + } + terms = tset2.terms + case *types.TypeParam, *types.Union: + // A stand-alone type parameter or union is not permitted as union + // term. + return nil, fmt.Errorf("invalid union term %T", t) + default: + if t.Type() == types.Typ[types.Invalid] { + continue + } + terms = termlist{{t.Tilde(), t.Type()}} + } + tset.terms = tset.terms.union(terms) + if len(tset.terms) > maxTermCount { + return nil, fmt.Errorf("exceeded max term count %d", maxTermCount) + } + } + case *types.TypeParam: + panic("unreachable") + default: + // For all other types, the term set is just a single non-tilde term + // holding the type itself. + if u != types.Typ[types.Invalid] { + tset.terms = termlist{{false, t}} + } + } + return tset, nil +} + +// under is a facade for the go/types internal function of the same name. It is +// used by typeterm.go. +func under(t types.Type) types.Type { + return t.Underlying() +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go new file mode 100644 index 0000000..9bc2914 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/termlist.go @@ -0,0 +1,169 @@ +// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. +// Source: ../../cmd/compile/internal/types2/termlist.go + +// Copyright 2021 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import ( + "go/types" + "strings" +) + +// A termlist represents the type set represented by the union +// t1 ∪ y2 ∪ ... tn of the type sets of the terms t1 to tn. +// A termlist is in normal form if all terms are disjoint. +// termlist operations don't require the operands to be in +// normal form. +type termlist []*term + +// allTermlist represents the set of all types. +// It is in normal form. +var allTermlist = termlist{new(term)} + +// termSep is the separator used between individual terms. +const termSep = " | " + +// String prints the termlist exactly (without normalization). +func (xl termlist) String() string { + if len(xl) == 0 { + return "∅" + } + var buf strings.Builder + for i, x := range xl { + if i > 0 { + buf.WriteString(termSep) + } + buf.WriteString(x.String()) + } + return buf.String() +} + +// isEmpty reports whether the termlist xl represents the empty set of types. +func (xl termlist) isEmpty() bool { + // If there's a non-nil term, the entire list is not empty. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil { + return false + } + } + return true +} + +// isAll reports whether the termlist xl represents the set of all types. +func (xl termlist) isAll() bool { + // If there's a 𝓤 term, the entire list is 𝓤. + // If the termlist is in normal form, this requires at most + // one iteration. + for _, x := range xl { + if x != nil && x.typ == nil { + return true + } + } + return false +} + +// norm returns the normal form of xl. +func (xl termlist) norm() termlist { + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + used := make([]bool, len(xl)) + var rl termlist + for i, xi := range xl { + if xi == nil || used[i] { + continue + } + for j := i + 1; j < len(xl); j++ { + xj := xl[j] + if xj == nil || used[j] { + continue + } + if u1, u2 := xi.union(xj); u2 == nil { + // If we encounter a 𝓤 term, the entire list is 𝓤. + // Exit early. + // (Note that this is not just an optimization; + // if we continue, we may end up with a 𝓤 term + // and other terms and the result would not be + // in normal form.) + if u1.typ == nil { + return allTermlist + } + xi = u1 + used[j] = true // xj is now unioned into xi - ignore it in future iterations + } + } + rl = append(rl, xi) + } + return rl +} + +// union returns the union xl ∪ yl. +func (xl termlist) union(yl termlist) termlist { + return append(xl, yl...).norm() +} + +// intersect returns the intersection xl ∩ yl. +func (xl termlist) intersect(yl termlist) termlist { + if xl.isEmpty() || yl.isEmpty() { + return nil + } + + // Quadratic algorithm, but good enough for now. + // TODO(gri) fix asymptotic performance + var rl termlist + for _, x := range xl { + for _, y := range yl { + if r := x.intersect(y); r != nil { + rl = append(rl, r) + } + } + } + return rl.norm() +} + +// equal reports whether xl and yl represent the same type set. +func (xl termlist) equal(yl termlist) bool { + // TODO(gri) this should be more efficient + return xl.subsetOf(yl) && yl.subsetOf(xl) +} + +// includes reports whether t ∈ xl. +func (xl termlist) includes(t types.Type) bool { + for _, x := range xl { + if x.includes(t) { + return true + } + } + return false +} + +// supersetOf reports whether y ⊆ xl. +func (xl termlist) supersetOf(y *term) bool { + for _, x := range xl { + if y.subsetOf(x) { + return true + } + } + return false +} + +// subsetOf reports whether xl ⊆ yl. +func (xl termlist) subsetOf(yl termlist) bool { + if yl.isEmpty() { + return xl.isEmpty() + } + + // each term x of xl must be a subset of yl + for _, x := range xl { + if !yl.supersetOf(x) { + return false // x is not a subset yl + } + } + return true +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go new file mode 100644 index 0000000..fa758cd --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typeparams/typeterm.go @@ -0,0 +1,172 @@ +// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT. +// Source: ../../cmd/compile/internal/types2/typeterm.go + +// Copyright 2021 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. + +// Code generated by copytermlist.go DO NOT EDIT. + +package typeparams + +import "go/types" + +// A term describes elementary type sets: +// +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +type term struct { + tilde bool // valid if typ != nil + typ types.Type +} + +func (x *term) String() string { + switch { + case x == nil: + return "∅" + case x.typ == nil: + return "𝓤" + case x.tilde: + return "~" + x.typ.String() + default: + return x.typ.String() + } +} + +// equal reports whether x and y represent the same type set. +func (x *term) equal(y *term) bool { + // easy cases + switch { + case x == nil || y == nil: + return x == y + case x.typ == nil || y.typ == nil: + return x.typ == y.typ + } + // ∅ ⊂ x, y ⊂ 𝓤 + + return x.tilde == y.tilde && types.Identical(x.typ, y.typ) +} + +// union returns the union x ∪ y: zero, one, or two non-nil terms. +func (x *term) union(y *term) (_, _ *term) { + // easy cases + switch { + case x == nil && y == nil: + return nil, nil // ∅ ∪ ∅ == ∅ + case x == nil: + return y, nil // ∅ ∪ y == y + case y == nil: + return x, nil // x ∪ ∅ == x + case x.typ == nil: + return x, nil // 𝓤 ∪ y == 𝓤 + case y.typ == nil: + return y, nil // x ∪ 𝓤 == 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return x, y // x ∪ y == (x, y) if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∪ ~t == ~t + // ~t ∪ T == ~t + // T ∪ ~t == ~t + // T ∪ T == T + if x.tilde || !y.tilde { + return x, nil + } + return y, nil +} + +// intersect returns the intersection x ∩ y. +func (x *term) intersect(y *term) *term { + // easy cases + switch { + case x == nil || y == nil: + return nil // ∅ ∩ y == ∅ and ∩ ∅ == ∅ + case x.typ == nil: + return y // 𝓤 ∩ y == y + case y.typ == nil: + return x // x ∩ 𝓤 == x + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return nil // x ∩ y == ∅ if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ∩ ~t == ~t + // ~t ∩ T == T + // T ∩ ~t == T + // T ∩ T == T + if !x.tilde || y.tilde { + return x + } + return y +} + +// includes reports whether t ∈ x. +func (x *term) includes(t types.Type) bool { + // easy cases + switch { + case x == nil: + return false // t ∈ ∅ == false + case x.typ == nil: + return true // t ∈ 𝓤 == true + } + // ∅ ⊂ x ⊂ 𝓤 + + u := t + if x.tilde { + u = under(u) + } + return types.Identical(x.typ, u) +} + +// subsetOf reports whether x ⊆ y. +func (x *term) subsetOf(y *term) bool { + // easy cases + switch { + case x == nil: + return true // ∅ ⊆ y == true + case y == nil: + return false // x ⊆ ∅ == false since x != ∅ + case y.typ == nil: + return true // x ⊆ 𝓤 == true + case x.typ == nil: + return false // 𝓤 ⊆ y == false since y != 𝓤 + } + // ∅ ⊂ x, y ⊂ 𝓤 + + if x.disjoint(y) { + return false // x ⊆ y == false if x ∩ y == ∅ + } + // x.typ == y.typ + + // ~t ⊆ ~t == true + // ~t ⊆ T == false + // T ⊆ ~t == true + // T ⊆ T == true + return !x.tilde || y.tilde +} + +// disjoint reports whether x ∩ y == ∅. +// x.typ and y.typ must not be nil. +func (x *term) disjoint(y *term) bool { + if debug && (x.typ == nil || y.typ == nil) { + panic("invalid argument(s)") + } + ux := x.typ + if y.tilde { + ux = under(ux) + } + uy := y.typ + if x.tilde { + uy = under(uy) + } + return !types.Identical(ux, uy) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go new file mode 100644 index 0000000..7ebe976 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/classify_call.go @@ -0,0 +1,137 @@ +// 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 typesinternal + +import ( + "fmt" + "go/ast" + "go/types" + _ "unsafe" // for go:linkname hack +) + +// CallKind describes the function position of an [*ast.CallExpr]. +type CallKind int + +const ( + CallStatic CallKind = iota // static call to known function + CallInterface // dynamic call through an interface method + CallDynamic // dynamic call of a func value + CallBuiltin // call to a builtin function + CallConversion // a conversion (not a call) +) + +var callKindNames = []string{ + "CallStatic", + "CallInterface", + "CallDynamic", + "CallBuiltin", + "CallConversion", +} + +func (k CallKind) String() string { + if i := int(k); i >= 0 && i < len(callKindNames) { + return callKindNames[i] + } + return fmt.Sprintf("typeutil.CallKind(%d)", k) +} + +// ClassifyCall classifies the function position of a call expression ([*ast.CallExpr]). +// It distinguishes among true function calls, calls to builtins, and type conversions, +// and further classifies function calls as static calls (where the function is known), +// dynamic interface calls, and other dynamic calls. +// +// For the declarations: +// +// func f() {} +// func g[T any]() {} +// var v func() +// var s []func() +// type I interface { M() } +// var i I +// +// ClassifyCall returns the following: +// +// f() CallStatic +// g[int]() CallStatic +// i.M() CallInterface +// min(1, 2) CallBuiltin +// v() CallDynamic +// s[0]() CallDynamic +// int(x) CallConversion +// []byte("") CallConversion +func ClassifyCall(info *types.Info, call *ast.CallExpr) CallKind { + if info.Types == nil { + panic("ClassifyCall: info.Types is nil") + } + tv := info.Types[call.Fun] + if tv.IsType() { + return CallConversion + } + if tv.IsBuiltin() { + return CallBuiltin + } + obj := info.Uses[UsedIdent(info, call.Fun)] + // Classify the call by the type of the object, if any. + switch obj := obj.(type) { + case *types.Func: + if interfaceMethod(obj) { + return CallInterface + } + return CallStatic + default: + return CallDynamic + } +} + +// UsedIdent returns the identifier such that info.Uses[UsedIdent(info, e)] +// is the [types.Object] used by e, if any. +// +// If e is one of various forms of reference: +// +// f, c, v, T lexical reference +// pkg.X qualified identifier +// f[T] or pkg.F[K,V] instantiations of the above kinds +// expr.f field or method value selector +// T.f method expression selector +// +// UsedIdent returns the identifier whose is associated value in [types.Info.Uses] +// is the object to which it refers. +// +// For the declarations: +// +// func F[T any] {...} +// type I interface { M() } +// var ( +// x int +// s struct { f int } +// a []int +// i I +// ) +// +// UsedIdent returns the following: +// +// Expr UsedIdent +// x x +// s.f f +// F[int] F +// i.M M +// I.M M +// min min +// int int +// 1 nil +// a[0] nil +// []byte nil +// +// Note: if e is an instantiated function or method, UsedIdent returns +// the corresponding generic function or method on the generic type. +func UsedIdent(info *types.Info, e ast.Expr) *ast.Ident { + return usedIdent(info, e) +} + +//go:linkname usedIdent golang.org/x/tools/go/types/typeutil.usedIdent +func usedIdent(info *types.Info, e ast.Expr) *ast.Ident + +//go:linkname interfaceMethod golang.org/x/tools/go/types/typeutil.interfaceMethod +func interfaceMethod(f *types.Func) bool diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go new file mode 100644 index 0000000..89eeea1 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/element.go @@ -0,0 +1,137 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/types" + + "golang.org/x/tools/go/types/typeutil" +) + +// ForEachElement calls f for type T and each type reachable from its +// type through reflection. It does this by recursively stripping off +// type constructors; in addition, for each named type N, the type *N +// is added to the result as it may have additional methods. +// +// The caller must provide an initially empty set used to de-duplicate +// identical types, potentially across multiple calls to ForEachElement. +// (Its final value holds all the elements seen, matching the arguments +// passed to f.) +// +// TODO(adonovan): share/harmonize with go/callgraph/rta. +func ForEachElement(rtypes *typeutil.Map, msets *typeutil.MethodSetCache, T types.Type, f func(types.Type)) { + var visit func(T types.Type, skip bool) + visit = func(T types.Type, skip bool) { + if !skip { + if seen, _ := rtypes.Set(T, true).(bool); seen { + return // de-dup + } + + f(T) // notify caller of new element type + } + + // Recursion over signatures of each method. + tmset := msets.MethodSet(T) + for method := range tmset.Methods() { + sig := method.Type().(*types.Signature) + if sig.TypeParams() != nil { + continue // skip type-parameterized methods + } + + // It is tempting to call visit(sig, false) + // but, as noted in golang.org/cl/65450043, + // the Signature.Recv field is ignored by + // types.Identical and typeutil.Map, which + // is confusing at best. + // + // More importantly, the true signature rtype + // reachable from a method using reflection + // has no receiver but an extra ordinary parameter. + // For the Read method of io.Reader we want: + // func(Reader, []byte) (int, error) + // but here sig is: + // func([]byte) (int, error) + // with .Recv = Reader (though it is hard to + // notice because it doesn't affect Signature.String + // or types.Identical). + // + // TODO(adonovan): construct and visit the correct + // non-method signature with an extra parameter + // (though since unnamed func types have no methods + // there is essentially no actual demand for this). + // + // TODO(adonovan): document whether or not it is + // safe to skip non-exported methods (as RTA does). + visit(sig.Params(), true) // skip the Tuple + visit(sig.Results(), true) // skip the Tuple + } + + switch T := T.(type) { + case *types.Alias: + visit(types.Unalias(T), skip) // emulates the pre-Alias behavior + + case *types.Basic: + // nop + + case *types.Interface: + // nop---handled by recursion over method set. + + case *types.Pointer: + visit(T.Elem(), false) + + case *types.Slice: + visit(T.Elem(), false) + + case *types.Chan: + visit(T.Elem(), false) + + case *types.Map: + visit(T.Key(), false) + visit(T.Elem(), false) + + case *types.Signature: + if T.Recv() != nil { + panic(fmt.Sprintf("Signature %s has Recv %s", T, T.Recv())) + } + visit(T.Params(), true) // skip the Tuple + visit(T.Results(), true) // skip the Tuple + + case *types.Named: + // A pointer-to-named type can be derived from a named + // type via reflection. It may have methods too. + visit(types.NewPointer(T), false) + + // Consider 'type T struct{S}' where S has methods. + // Reflection provides no way to get from T to struct{S}, + // only to S, so the method set of struct{S} is unwanted, + // so set 'skip' flag during recursion. + visit(T.Underlying(), true) // skip the unnamed type + + case *types.Array: + visit(T.Elem(), false) + + case *types.Struct: + for i, n := 0, T.NumFields(); i < n; i++ { + // TODO(adonovan): document whether or not + // it is safe to skip non-exported fields. + visit(T.Field(i).Type(), false) + } + + case *types.Tuple: + for i, n := 0, T.Len(); i < n; i++ { + visit(T.At(i).Type(), false) + } + + case *types.TypeParam, *types.Union: + // forEachReachable must not be called on parameterized types. + panic(fmt.Sprintf("ForEachElement called on type containing %T", T)) + + default: + panic(fmt.Sprintf("ForEachElement called on unexpected type %T", T)) + } + } + visit(T, false) +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go new file mode 100644 index 0000000..bd94474 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode.go @@ -0,0 +1,1558 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +type ErrorCode int + +// This file defines the error codes that can be produced during type-checking. +// Collectively, these codes provide an identifier that may be used to +// implement special handling for certain types of errors. +// +// Error codes should be fine-grained enough that the exact nature of the error +// can be easily determined, but coarse enough that they are not an +// implementation detail of the type checking algorithm. As a rule-of-thumb, +// errors should be considered equivalent if there is a theoretical refactoring +// of the type checker in which they are emitted in exactly one place. For +// example, the type checker emits different error messages for "too many +// arguments" and "too few arguments", but one can imagine an alternative type +// checker where this check instead just emits a single "wrong number of +// arguments", so these errors should have the same code. +// +// Error code names should be as brief as possible while retaining accuracy and +// distinctiveness. In most cases names should start with an adjective +// describing the nature of the error (e.g. "invalid", "unused", "misplaced"), +// and end with a noun identifying the relevant language object. For example, +// "DuplicateDecl" or "InvalidSliceExpr". For brevity, naming follows the +// convention that "bad" implies a problem with syntax, and "invalid" implies a +// problem with types. + +const ( + // InvalidSyntaxTree occurs if an invalid syntax tree is provided + // to the type checker. It should never happen. + InvalidSyntaxTree ErrorCode = -1 +) + +const ( + _ ErrorCode = iota + + // Test is reserved for errors that only apply while in self-test mode. + Test + + /* package names */ + + // BlankPkgName occurs when a package name is the blank identifier "_". + // + // Per the spec: + // "The PackageName must not be the blank identifier." + BlankPkgName + + // MismatchedPkgName occurs when a file's package name doesn't match the + // package name already established by other files. + MismatchedPkgName + + // InvalidPkgUse occurs when a package identifier is used outside of a + // selector expression. + // + // Example: + // import "fmt" + // + // var _ = fmt + InvalidPkgUse + + /* imports */ + + // BadImportPath occurs when an import path is not valid. + BadImportPath + + // BrokenImport occurs when importing a package fails. + // + // Example: + // import "amissingpackage" + BrokenImport + + // ImportCRenamed occurs when the special import "C" is renamed. "C" is a + // pseudo-package, and must not be renamed. + // + // Example: + // import _ "C" + ImportCRenamed + + // UnusedImport occurs when an import is unused. + // + // Example: + // import "fmt" + // + // func main() {} + UnusedImport + + /* initialization */ + + // InvalidInitCycle occurs when an invalid cycle is detected within the + // initialization graph. + // + // Example: + // var x int = f() + // + // func f() int { return x } + InvalidInitCycle + + /* decls */ + + // DuplicateDecl occurs when an identifier is declared multiple times. + // + // Example: + // var x = 1 + // var x = 2 + DuplicateDecl + + // InvalidDeclCycle occurs when a declaration cycle is not valid. + // + // Example: + // import "unsafe" + // + // type T struct { + // a [n]int + // } + // + // var n = unsafe.Sizeof(T{}) + InvalidDeclCycle + + // InvalidTypeCycle occurs when a cycle in type definitions results in a + // type that is not well-defined. + // + // Example: + // import "unsafe" + // + // type T [unsafe.Sizeof(T{})]int + InvalidTypeCycle + + /* decls > const */ + + // InvalidConstInit occurs when a const declaration has a non-constant + // initializer. + // + // Example: + // var x int + // const _ = x + InvalidConstInit + + // InvalidConstVal occurs when a const value cannot be converted to its + // target type. + // + // TODO(findleyr): this error code and example are not very clear. Consider + // removing it. + // + // Example: + // const _ = 1 << "hello" + InvalidConstVal + + // InvalidConstType occurs when the underlying type in a const declaration + // is not a valid constant type. + // + // Example: + // const c *int = 4 + InvalidConstType + + /* decls > var (+ other variable assignment codes) */ + + // UntypedNilUse occurs when the predeclared (untyped) value nil is used to + // initialize a variable declared without an explicit type. + // + // Example: + // var x = nil + UntypedNilUse + + // WrongAssignCount occurs when the number of values on the right-hand side + // of an assignment or initialization expression does not match the number + // of variables on the left-hand side. + // + // Example: + // var x = 1, 2 + WrongAssignCount + + // UnassignableOperand occurs when the left-hand side of an assignment is + // not assignable. + // + // Example: + // func f() { + // const c = 1 + // c = 2 + // } + UnassignableOperand + + // NoNewVar occurs when a short variable declaration (':=') does not declare + // new variables. + // + // Example: + // func f() { + // x := 1 + // x := 2 + // } + NoNewVar + + // MultiValAssignOp occurs when an assignment operation (+=, *=, etc) does + // not have single-valued left-hand or right-hand side. + // + // Per the spec: + // "In assignment operations, both the left- and right-hand expression lists + // must contain exactly one single-valued expression" + // + // Example: + // func f() int { + // x, y := 1, 2 + // x, y += 1 + // return x + y + // } + MultiValAssignOp + + // InvalidIfaceAssign occurs when a value of type T is used as an + // interface, but T does not implement a method of the expected interface. + // + // Example: + // type I interface { + // f() + // } + // + // type T int + // + // var x I = T(1) + InvalidIfaceAssign + + // InvalidChanAssign occurs when a chan assignment is invalid. + // + // Per the spec, a value x is assignable to a channel type T if: + // "x is a bidirectional channel value, T is a channel type, x's type V and + // T have identical element types, and at least one of V or T is not a + // defined type." + // + // Example: + // type T1 chan int + // type T2 chan int + // + // var x T1 + // // Invalid assignment because both types are named + // var _ T2 = x + InvalidChanAssign + + // IncompatibleAssign occurs when the type of the right-hand side expression + // in an assignment cannot be assigned to the type of the variable being + // assigned. + // + // Example: + // var x []int + // var _ int = x + IncompatibleAssign + + // UnaddressableFieldAssign occurs when trying to assign to a struct field + // in a map value. + // + // Example: + // func f() { + // m := make(map[string]struct{i int}) + // m["foo"].i = 42 + // } + UnaddressableFieldAssign + + /* decls > type (+ other type expression codes) */ + + // NotAType occurs when the identifier used as the underlying type in a type + // declaration or the right-hand side of a type alias does not denote a type. + // + // Example: + // var S = 2 + // + // type T S + NotAType + + // InvalidArrayLen occurs when an array length is not a constant value. + // + // Example: + // var n = 3 + // var _ = [n]int{} + InvalidArrayLen + + // BlankIfaceMethod occurs when a method name is '_'. + // + // Per the spec: + // "The name of each explicitly specified method must be unique and not + // blank." + // + // Example: + // type T interface { + // _(int) + // } + BlankIfaceMethod + + // IncomparableMapKey occurs when a map key type does not support the == and + // != operators. + // + // Per the spec: + // "The comparison operators == and != must be fully defined for operands of + // the key type; thus the key type must not be a function, map, or slice." + // + // Example: + // var x map[T]int + // + // type T []int + IncomparableMapKey + + // InvalidIfaceEmbed occurs when a non-interface type is embedded in an + // interface. + // + // Example: + // type T struct {} + // + // func (T) m() + // + // type I interface { + // T + // } + InvalidIfaceEmbed + + // InvalidPtrEmbed occurs when an embedded field is of the pointer form *T, + // and T itself is itself a pointer, an unsafe.Pointer, or an interface. + // + // Per the spec: + // "An embedded field must be specified as a type name T or as a pointer to + // a non-interface type name *T, and T itself may not be a pointer type." + // + // Example: + // type T *int + // + // type S struct { + // *T + // } + InvalidPtrEmbed + + /* decls > func and method */ + + // BadRecv occurs when a method declaration does not have exactly one + // receiver parameter. + // + // Example: + // func () _() {} + BadRecv + + // InvalidRecv occurs when a receiver type expression is not of the form T + // or *T, or T is a pointer type. + // + // Example: + // type T struct {} + // + // func (**T) m() {} + InvalidRecv + + // DuplicateFieldAndMethod occurs when an identifier appears as both a field + // and method name. + // + // Example: + // type T struct { + // m int + // } + // + // func (T) m() {} + DuplicateFieldAndMethod + + // DuplicateMethod occurs when two methods on the same receiver type have + // the same name. + // + // Example: + // type T struct {} + // func (T) m() {} + // func (T) m(i int) int { return i } + DuplicateMethod + + /* decls > special */ + + // InvalidBlank occurs when a blank identifier is used as a value or type. + // + // Per the spec: + // "The blank identifier may appear as an operand only on the left-hand side + // of an assignment." + // + // Example: + // var x = _ + InvalidBlank + + // InvalidIota occurs when the predeclared identifier iota is used outside + // of a constant declaration. + // + // Example: + // var x = iota + InvalidIota + + // MissingInitBody occurs when an init function is missing its body. + // + // Example: + // func init() + MissingInitBody + + // InvalidInitSig occurs when an init function declares parameters or + // results. + // + // Example: + // func init() int { return 1 } + InvalidInitSig + + // InvalidInitDecl occurs when init is declared as anything other than a + // function. + // + // Example: + // var init = 1 + InvalidInitDecl + + // InvalidMainDecl occurs when main is declared as anything other than a + // function, in a main package. + InvalidMainDecl + + /* exprs */ + + // TooManyValues occurs when a function returns too many values for the + // expression context in which it is used. + // + // Example: + // func ReturnTwo() (int, int) { + // return 1, 2 + // } + // + // var x = ReturnTwo() + TooManyValues + + // NotAnExpr occurs when a type expression is used where a value expression + // is expected. + // + // Example: + // type T struct {} + // + // func f() { + // T + // } + NotAnExpr + + /* exprs > const */ + + // TruncatedFloat occurs when a float constant is truncated to an integer + // value. + // + // Example: + // var _ int = 98.6 + TruncatedFloat + + // NumericOverflow occurs when a numeric constant overflows its target type. + // + // Example: + // var x int8 = 1000 + NumericOverflow + + /* exprs > operation */ + + // UndefinedOp occurs when an operator is not defined for the type(s) used + // in an operation. + // + // Example: + // var c = "a" - "b" + UndefinedOp + + // MismatchedTypes occurs when operand types are incompatible in a binary + // operation. + // + // Example: + // var a = "hello" + // var b = 1 + // var c = a - b + MismatchedTypes + + // DivByZero occurs when a division operation is provable at compile + // time to be a division by zero. + // + // Example: + // const divisor = 0 + // var x int = 1/divisor + DivByZero + + // NonNumericIncDec occurs when an increment or decrement operator is + // applied to a non-numeric value. + // + // Example: + // func f() { + // var c = "c" + // c++ + // } + NonNumericIncDec + + /* exprs > ptr */ + + // UnaddressableOperand occurs when the & operator is applied to an + // unaddressable expression. + // + // Example: + // var x = &1 + UnaddressableOperand + + // InvalidIndirection occurs when a non-pointer value is indirected via the + // '*' operator. + // + // Example: + // var x int + // var y = *x + InvalidIndirection + + /* exprs > [] */ + + // NonIndexableOperand occurs when an index operation is applied to a value + // that cannot be indexed. + // + // Example: + // var x = 1 + // var y = x[1] + NonIndexableOperand + + // InvalidIndex occurs when an index argument is not of integer type, + // negative, or out-of-bounds. + // + // Example: + // var s = [...]int{1,2,3} + // var x = s[5] + // + // Example: + // var s = []int{1,2,3} + // var _ = s[-1] + // + // Example: + // var s = []int{1,2,3} + // var i string + // var _ = s[i] + InvalidIndex + + // SwappedSliceIndices occurs when constant indices in a slice expression + // are decreasing in value. + // + // Example: + // var _ = []int{1,2,3}[2:1] + SwappedSliceIndices + + /* operators > slice */ + + // NonSliceableOperand occurs when a slice operation is applied to a value + // whose type is not sliceable, or is unaddressable. + // + // Example: + // var x = [...]int{1, 2, 3}[:1] + // + // Example: + // var x = 1 + // var y = 1[:1] + NonSliceableOperand + + // InvalidSliceExpr occurs when a three-index slice expression (a[x:y:z]) is + // applied to a string. + // + // Example: + // var s = "hello" + // var x = s[1:2:3] + InvalidSliceExpr + + /* exprs > shift */ + + // InvalidShiftCount occurs when the right-hand side of a shift operation is + // either non-integer, negative, or too large. + // + // Example: + // var ( + // x string + // y int = 1 << x + // ) + InvalidShiftCount + + // InvalidShiftOperand occurs when the shifted operand is not an integer. + // + // Example: + // var s = "hello" + // var x = s << 2 + InvalidShiftOperand + + /* exprs > chan */ + + // InvalidReceive occurs when there is a channel receive from a value that + // is either not a channel, or is a send-only channel. + // + // Example: + // func f() { + // var x = 1 + // <-x + // } + InvalidReceive + + // InvalidSend occurs when there is a channel send to a value that is not a + // channel, or is a receive-only channel. + // + // Example: + // func f() { + // var x = 1 + // x <- "hello!" + // } + InvalidSend + + /* exprs > literal */ + + // DuplicateLitKey occurs when an index is duplicated in a slice, array, or + // map literal. + // + // Example: + // var _ = []int{0:1, 0:2} + // + // Example: + // var _ = map[string]int{"a": 1, "a": 2} + DuplicateLitKey + + // MissingLitKey occurs when a map literal is missing a key expression. + // + // Example: + // var _ = map[string]int{1} + MissingLitKey + + // InvalidLitIndex occurs when the key in a key-value element of a slice or + // array literal is not an integer constant. + // + // Example: + // var i = 0 + // var x = []string{i: "world"} + InvalidLitIndex + + // OversizeArrayLit occurs when an array literal exceeds its length. + // + // Example: + // var _ = [2]int{1,2,3} + OversizeArrayLit + + // MixedStructLit occurs when a struct literal contains a mix of positional + // and named elements. + // + // Example: + // var _ = struct{i, j int}{i: 1, 2} + MixedStructLit + + // InvalidStructLit occurs when a positional struct literal has an incorrect + // number of values. + // + // Example: + // var _ = struct{i, j int}{1,2,3} + InvalidStructLit + + // MissingLitField occurs when a struct literal refers to a field that does + // not exist on the struct type. + // + // Example: + // var _ = struct{i int}{j: 2} + MissingLitField + + // DuplicateLitField occurs when a struct literal contains duplicated + // fields. + // + // Example: + // var _ = struct{i int}{i: 1, i: 2} + DuplicateLitField + + // UnexportedLitField occurs when a positional struct literal implicitly + // assigns an unexported field of an imported type. + UnexportedLitField + + // InvalidLitField occurs when a field name is not a valid identifier. + // + // Example: + // var _ = struct{i int}{1: 1} + InvalidLitField + + // UntypedLit occurs when a composite literal omits a required type + // identifier. + // + // Example: + // type outer struct{ + // inner struct { i int } + // } + // + // var _ = outer{inner: {1}} + UntypedLit + + // InvalidLit occurs when a composite literal expression does not match its + // type. + // + // Example: + // type P *struct{ + // x int + // } + // var _ = P {} + InvalidLit + + /* exprs > selector */ + + // AmbiguousSelector occurs when a selector is ambiguous. + // + // Example: + // type E1 struct { i int } + // type E2 struct { i int } + // type T struct { E1; E2 } + // + // var x T + // var _ = x.i + AmbiguousSelector + + // UndeclaredImportedName occurs when a package-qualified identifier is + // undeclared by the imported package. + // + // Example: + // import "go/types" + // + // var _ = types.NotAnActualIdentifier + UndeclaredImportedName + + // UnexportedName occurs when a selector refers to an unexported identifier + // of an imported package. + // + // Example: + // import "reflect" + // + // type _ reflect.flag + UnexportedName + + // UndeclaredName occurs when an identifier is not declared in the current + // scope. + // + // Example: + // var x T + UndeclaredName + + // MissingFieldOrMethod occurs when a selector references a field or method + // that does not exist. + // + // Example: + // type T struct {} + // + // var x = T{}.f + MissingFieldOrMethod + + /* exprs > ... */ + + // BadDotDotDotSyntax occurs when a "..." occurs in a context where it is + // not valid. + // + // Example: + // var _ = map[int][...]int{0: {}} + BadDotDotDotSyntax + + // NonVariadicDotDotDot occurs when a "..." is used on the final argument to + // a non-variadic function. + // + // Example: + // func printArgs(s []string) { + // for _, a := range s { + // println(a) + // } + // } + // + // func f() { + // s := []string{"a", "b", "c"} + // printArgs(s...) + // } + NonVariadicDotDotDot + + // MisplacedDotDotDot occurs when a "..." is used somewhere other than the + // final argument to a function call. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := []int{1,2,3} + // printArgs(0, a...) + // } + MisplacedDotDotDot + + // InvalidDotDotDotOperand occurs when a "..." operator is applied to a + // single-valued operand. + // + // Example: + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func f() { + // a := 1 + // printArgs(a...) + // } + // + // Example: + // func args() (int, int) { + // return 1, 2 + // } + // + // func printArgs(args ...int) { + // for _, a := range args { + // println(a) + // } + // } + // + // func g() { + // printArgs(args()...) + // } + InvalidDotDotDotOperand + + // InvalidDotDotDot occurs when a "..." is used in a non-variadic built-in + // function. + // + // Example: + // var s = []int{1, 2, 3} + // var l = len(s...) + InvalidDotDotDot + + /* exprs > built-in */ + + // UncalledBuiltin occurs when a built-in function is used as a + // function-valued expression, instead of being called. + // + // Per the spec: + // "The built-in functions do not have standard Go types, so they can only + // appear in call expressions; they cannot be used as function values." + // + // Example: + // var _ = copy + UncalledBuiltin + + // InvalidAppend occurs when append is called with a first argument that is + // not a slice. + // + // Example: + // var _ = append(1, 2) + InvalidAppend + + // InvalidCap occurs when an argument to the cap built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = cap(s) + InvalidCap + + // InvalidClose occurs when close(...) is called with an argument that is + // not of channel type, or that is a receive-only channel. + // + // Example: + // func f() { + // var x int + // close(x) + // } + InvalidClose + + // InvalidCopy occurs when the arguments are not of slice type or do not + // have compatible type. + // + // See https://golang.org/ref/spec#Appending_and_copying_slices for more + // information on the type requirements for the copy built-in. + // + // Example: + // func f() { + // var x []int + // y := []int64{1,2,3} + // copy(x, y) + // } + InvalidCopy + + // InvalidComplex occurs when the complex built-in function is called with + // arguments with incompatible types. + // + // Example: + // var _ = complex(float32(1), float64(2)) + InvalidComplex + + // InvalidDelete occurs when the delete built-in function is called with a + // first argument that is not a map. + // + // Example: + // func f() { + // m := "hello" + // delete(m, "e") + // } + InvalidDelete + + // InvalidImag occurs when the imag built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = imag(int(1)) + InvalidImag + + // InvalidLen occurs when an argument to the len built-in function is not of + // supported type. + // + // See https://golang.org/ref/spec#Length_and_capacity for information on + // which underlying types are supported as arguments to cap and len. + // + // Example: + // var s = 2 + // var x = len(s) + InvalidLen + + // SwappedMakeArgs occurs when make is called with three arguments, and its + // length argument is larger than its capacity argument. + // + // Example: + // var x = make([]int, 3, 2) + SwappedMakeArgs + + // InvalidMake occurs when make is called with an unsupported type argument. + // + // See https://golang.org/ref/spec#Making_slices_maps_and_channels for + // information on the types that may be created using make. + // + // Example: + // var x = make(int) + InvalidMake + + // InvalidReal occurs when the real built-in function is called with an + // argument that does not have complex type. + // + // Example: + // var _ = real(int(1)) + InvalidReal + + /* exprs > assertion */ + + // InvalidAssert occurs when a type assertion is applied to a + // value that is not of interface type. + // + // Example: + // var x = 1 + // var _ = x.(float64) + InvalidAssert + + // ImpossibleAssert occurs for a type assertion x.(T) when the value x of + // interface cannot have dynamic type T, due to a missing or mismatching + // method on T. + // + // Example: + // type T int + // + // func (t *T) m() int { return int(*t) } + // + // type I interface { m() int } + // + // var x I + // var _ = x.(T) + ImpossibleAssert + + /* exprs > conversion */ + + // InvalidConversion occurs when the argument type cannot be converted to the + // target. + // + // See https://golang.org/ref/spec#Conversions for the rules of + // convertibility. + // + // Example: + // var x float64 + // var _ = string(x) + InvalidConversion + + // InvalidUntypedConversion occurs when there is no valid implicit + // conversion from an untyped value satisfying the type constraints of the + // context in which it is used. + // + // Example: + // var _ = 1 + "" + InvalidUntypedConversion + + /* offsetof */ + + // BadOffsetofSyntax occurs when unsafe.Offsetof is called with an argument + // that is not a selector expression. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Offsetof(x) + BadOffsetofSyntax + + // InvalidOffsetof occurs when unsafe.Offsetof is called with a method + // selector, rather than a field selector, or when the field is embedded via + // a pointer. + // + // Per the spec: + // + // "If f is an embedded field, it must be reachable without pointer + // indirections through fields of the struct. " + // + // Example: + // import "unsafe" + // + // type T struct { f int } + // type S struct { *T } + // var s S + // var _ = unsafe.Offsetof(s.f) + // + // Example: + // import "unsafe" + // + // type S struct{} + // + // func (S) m() {} + // + // var s S + // var _ = unsafe.Offsetof(s.m) + InvalidOffsetof + + /* control flow > scope */ + + // UnusedExpr occurs when a side-effect free expression is used as a + // statement. Such a statement has no effect. + // + // Example: + // func f(i int) { + // i*i + // } + UnusedExpr + + // UnusedVar occurs when a variable is declared but unused. + // + // Example: + // func f() { + // x := 1 + // } + UnusedVar + + // MissingReturn occurs when a function with results is missing a return + // statement. + // + // Example: + // func f() int {} + MissingReturn + + // WrongResultCount occurs when a return statement returns an incorrect + // number of values. + // + // Example: + // func ReturnOne() int { + // return 1, 2 + // } + WrongResultCount + + // OutOfScopeResult occurs when the name of a value implicitly returned by + // an empty return statement is shadowed in a nested scope. + // + // Example: + // func factor(n int) (i int) { + // for i := 2; i < n; i++ { + // if n%i == 0 { + // return + // } + // } + // return 0 + // } + OutOfScopeResult + + /* control flow > if */ + + // InvalidCond occurs when an if condition is not a boolean expression. + // + // Example: + // func checkReturn(i int) { + // if i { + // panic("non-zero return") + // } + // } + InvalidCond + + /* control flow > for */ + + // InvalidPostDecl occurs when there is a declaration in a for-loop post + // statement. + // + // Example: + // func f() { + // for i := 0; i < 10; j := 0 {} + // } + InvalidPostDecl + + // InvalidChanRange occurs when a send-only channel used in a range + // expression. + // + // Example: + // func sum(c chan<- int) { + // s := 0 + // for i := range c { + // s += i + // } + // } + InvalidChanRange + + // InvalidIterVar occurs when two iteration variables are used while ranging + // over a channel. + // + // Example: + // func f(c chan int) { + // for k, v := range c { + // println(k, v) + // } + // } + InvalidIterVar + + // InvalidRangeExpr occurs when the type of a range expression is not array, + // slice, string, map, or channel. + // + // Example: + // func f(i int) { + // for j := range i { + // println(j) + // } + // } + InvalidRangeExpr + + /* control flow > switch */ + + // MisplacedBreak occurs when a break statement is not within a for, switch, + // or select statement of the innermost function definition. + // + // Example: + // func f() { + // break + // } + MisplacedBreak + + // MisplacedContinue occurs when a continue statement is not within a for + // loop of the innermost function definition. + // + // Example: + // func sumeven(n int) int { + // proceed := func() { + // continue + // } + // sum := 0 + // for i := 1; i <= n; i++ { + // if i % 2 != 0 { + // proceed() + // } + // sum += i + // } + // return sum + // } + MisplacedContinue + + // MisplacedFallthrough occurs when a fallthrough statement is not within an + // expression switch. + // + // Example: + // func typename(i interface{}) string { + // switch i.(type) { + // case int64: + // fallthrough + // case int: + // return "int" + // } + // return "unsupported" + // } + MisplacedFallthrough + + // DuplicateCase occurs when a type or expression switch has duplicate + // cases. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // case 1: + // println("One") + // } + // } + DuplicateCase + + // DuplicateDefault occurs when a type or expression switch has multiple + // default clauses. + // + // Example: + // func printInt(i int) { + // switch i { + // case 1: + // println("one") + // default: + // println("One") + // default: + // println("1") + // } + // } + DuplicateDefault + + // BadTypeKeyword occurs when a .(type) expression is used anywhere other + // than a type switch. + // + // Example: + // type I interface { + // m() + // } + // var t I + // var _ = t.(type) + BadTypeKeyword + + // InvalidTypeSwitch occurs when .(type) is used on an expression that is + // not of interface type. + // + // Example: + // func f(i int) { + // switch x := i.(type) {} + // } + InvalidTypeSwitch + + // InvalidExprSwitch occurs when a switch expression is not comparable. + // + // Example: + // func _() { + // var a struct{ _ func() } + // switch a /* ERROR cannot switch on a */ { + // } + // } + InvalidExprSwitch + + /* control flow > select */ + + // InvalidSelectCase occurs when a select case is not a channel send or + // receive. + // + // Example: + // func checkChan(c <-chan int) bool { + // select { + // case c: + // return true + // default: + // return false + // } + // } + InvalidSelectCase + + /* control flow > labels and jumps */ + + // UndeclaredLabel occurs when an undeclared label is jumped to. + // + // Example: + // func f() { + // goto L + // } + UndeclaredLabel + + // DuplicateLabel occurs when a label is declared more than once. + // + // Example: + // func f() int { + // L: + // L: + // return 1 + // } + DuplicateLabel + + // MisplacedLabel occurs when a break or continue label is not on a for, + // switch, or select statement. + // + // Example: + // func f() { + // L: + // a := []int{1,2,3} + // for _, e := range a { + // if e > 10 { + // break L + // } + // println(a) + // } + // } + MisplacedLabel + + // UnusedLabel occurs when a label is declared but not used. + // + // Example: + // func f() { + // L: + // } + UnusedLabel + + // JumpOverDecl occurs when a label jumps over a variable declaration. + // + // Example: + // func f() int { + // goto L + // x := 2 + // L: + // x++ + // return x + // } + JumpOverDecl + + // JumpIntoBlock occurs when a forward jump goes to a label inside a nested + // block. + // + // Example: + // func f(x int) { + // goto L + // if x > 0 { + // L: + // print("inside block") + // } + // } + JumpIntoBlock + + /* control flow > calls */ + + // InvalidMethodExpr occurs when a pointer method is called but the argument + // is not addressable. + // + // Example: + // type T struct {} + // + // func (*T) m() int { return 1 } + // + // var _ = T.m(T{}) + InvalidMethodExpr + + // WrongArgCount occurs when too few or too many arguments are passed by a + // function call. + // + // Example: + // func f(i int) {} + // var x = f() + WrongArgCount + + // InvalidCall occurs when an expression is called that is not of function + // type. + // + // Example: + // var x = "x" + // var y = x() + InvalidCall + + /* control flow > suspended */ + + // UnusedResults occurs when a restricted expression-only built-in function + // is suspended via go or defer. Such a suspension discards the results of + // these side-effect free built-in functions, and therefore is ineffectual. + // + // Example: + // func f(a []int) int { + // defer len(a) + // return i + // } + UnusedResults + + // InvalidDefer occurs when a deferred expression is not a function call, + // for example if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // defer int32(i) + // return i + // } + InvalidDefer + + // InvalidGo occurs when a go expression is not a function call, for example + // if the expression is a type conversion. + // + // Example: + // func f(i int) int { + // go int32(i) + // return i + // } + InvalidGo + + // All codes below were added in Go 1.17. + + /* decl */ + + // BadDecl occurs when a declaration has invalid syntax. + BadDecl + + // RepeatedDecl occurs when an identifier occurs more than once on the left + // hand side of a short variable declaration. + // + // Example: + // func _() { + // x, y, y := 1, 2, 3 + // } + RepeatedDecl + + /* unsafe */ + + // InvalidUnsafeAdd occurs when unsafe.Add is called with a + // length argument that is not of integer type. + // + // Example: + // import "unsafe" + // + // var p unsafe.Pointer + // var _ = unsafe.Add(p, float64(1)) + InvalidUnsafeAdd + + // InvalidUnsafeSlice occurs when unsafe.Slice is called with a + // pointer argument that is not of pointer type or a length argument + // that is not of integer type, negative, or out of bounds. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(x, 1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, float64(1)) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, -1) + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.Slice(&x, uint64(1) << 63) + InvalidUnsafeSlice + + // All codes below were added in Go 1.18. + + /* features */ + + // UnsupportedFeature occurs when a language feature is used that is not + // supported at this Go version. + UnsupportedFeature + + /* type params */ + + // NotAGenericType occurs when a non-generic type is used where a generic + // type is expected: in type or function instantiation. + // + // Example: + // type T int + // + // var _ T[int] + NotAGenericType + + // WrongTypeArgCount occurs when a type or function is instantiated with an + // incorrect number of type arguments, including when a generic type or + // function is used without instantiation. + // + // Errors involving failed type inference are assigned other error codes. + // + // Example: + // type T[p any] int + // + // var _ T[int, string] + // + // Example: + // func f[T any]() {} + // + // var x = f + WrongTypeArgCount + + // CannotInferTypeArgs occurs when type or function type argument inference + // fails to infer all type arguments. + // + // Example: + // func f[T any]() {} + // + // func _() { + // f() + // } + // + // Example: + // type N[P, Q any] struct{} + // + // var _ N[int] + CannotInferTypeArgs + + // InvalidTypeArg occurs when a type argument does not satisfy its + // corresponding type parameter constraints. + // + // Example: + // type T[P ~int] struct{} + // + // var _ T[string] + InvalidTypeArg // arguments? InferenceFailed + + // InvalidInstanceCycle occurs when an invalid cycle is detected + // within the instantiation graph. + // + // Example: + // func f[T any]() { f[*T]() } + InvalidInstanceCycle + + // InvalidUnion occurs when an embedded union or approximation element is + // not valid. + // + // Example: + // type _ interface { + // ~int | interface{ m() } + // } + InvalidUnion + + // MisplacedConstraintIface occurs when a constraint-type interface is used + // outside of constraint position. + // + // Example: + // type I interface { ~int } + // + // var _ I + MisplacedConstraintIface + + // InvalidMethodTypeParams occurs when methods have type parameters. + // + // It cannot be encountered with an AST parsed using go/parser. + InvalidMethodTypeParams + + // MisplacedTypeParam occurs when a type parameter is used in a place where + // it is not permitted. + // + // Example: + // type T[P any] P + // + // Example: + // type T[P any] struct{ *P } + MisplacedTypeParam + + // InvalidUnsafeSliceData occurs when unsafe.SliceData is called with + // an argument that is not of slice type. It also occurs if it is used + // in a package compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var x int + // var _ = unsafe.SliceData(x) + InvalidUnsafeSliceData + + // InvalidUnsafeString occurs when unsafe.String is called with + // a length argument that is not of integer type, negative, or + // out of bounds. It also occurs if it is used in a package + // compiled for a language version before go1.20. + // + // Example: + // import "unsafe" + // + // var b [10]byte + // var _ = unsafe.String(&b[0], -1) + InvalidUnsafeString + + // InvalidUnsafeStringData occurs if it is used in a package + // compiled for a language version before go1.20. + _ // not used anymore + +) diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go new file mode 100644 index 0000000..15ecf7c --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/errorcode_string.go @@ -0,0 +1,179 @@ +// Code generated by "stringer -type=ErrorCode"; DO NOT EDIT. + +package typesinternal + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[InvalidSyntaxTree - -1] + _ = x[Test-1] + _ = x[BlankPkgName-2] + _ = x[MismatchedPkgName-3] + _ = x[InvalidPkgUse-4] + _ = x[BadImportPath-5] + _ = x[BrokenImport-6] + _ = x[ImportCRenamed-7] + _ = x[UnusedImport-8] + _ = x[InvalidInitCycle-9] + _ = x[DuplicateDecl-10] + _ = x[InvalidDeclCycle-11] + _ = x[InvalidTypeCycle-12] + _ = x[InvalidConstInit-13] + _ = x[InvalidConstVal-14] + _ = x[InvalidConstType-15] + _ = x[UntypedNilUse-16] + _ = x[WrongAssignCount-17] + _ = x[UnassignableOperand-18] + _ = x[NoNewVar-19] + _ = x[MultiValAssignOp-20] + _ = x[InvalidIfaceAssign-21] + _ = x[InvalidChanAssign-22] + _ = x[IncompatibleAssign-23] + _ = x[UnaddressableFieldAssign-24] + _ = x[NotAType-25] + _ = x[InvalidArrayLen-26] + _ = x[BlankIfaceMethod-27] + _ = x[IncomparableMapKey-28] + _ = x[InvalidIfaceEmbed-29] + _ = x[InvalidPtrEmbed-30] + _ = x[BadRecv-31] + _ = x[InvalidRecv-32] + _ = x[DuplicateFieldAndMethod-33] + _ = x[DuplicateMethod-34] + _ = x[InvalidBlank-35] + _ = x[InvalidIota-36] + _ = x[MissingInitBody-37] + _ = x[InvalidInitSig-38] + _ = x[InvalidInitDecl-39] + _ = x[InvalidMainDecl-40] + _ = x[TooManyValues-41] + _ = x[NotAnExpr-42] + _ = x[TruncatedFloat-43] + _ = x[NumericOverflow-44] + _ = x[UndefinedOp-45] + _ = x[MismatchedTypes-46] + _ = x[DivByZero-47] + _ = x[NonNumericIncDec-48] + _ = x[UnaddressableOperand-49] + _ = x[InvalidIndirection-50] + _ = x[NonIndexableOperand-51] + _ = x[InvalidIndex-52] + _ = x[SwappedSliceIndices-53] + _ = x[NonSliceableOperand-54] + _ = x[InvalidSliceExpr-55] + _ = x[InvalidShiftCount-56] + _ = x[InvalidShiftOperand-57] + _ = x[InvalidReceive-58] + _ = x[InvalidSend-59] + _ = x[DuplicateLitKey-60] + _ = x[MissingLitKey-61] + _ = x[InvalidLitIndex-62] + _ = x[OversizeArrayLit-63] + _ = x[MixedStructLit-64] + _ = x[InvalidStructLit-65] + _ = x[MissingLitField-66] + _ = x[DuplicateLitField-67] + _ = x[UnexportedLitField-68] + _ = x[InvalidLitField-69] + _ = x[UntypedLit-70] + _ = x[InvalidLit-71] + _ = x[AmbiguousSelector-72] + _ = x[UndeclaredImportedName-73] + _ = x[UnexportedName-74] + _ = x[UndeclaredName-75] + _ = x[MissingFieldOrMethod-76] + _ = x[BadDotDotDotSyntax-77] + _ = x[NonVariadicDotDotDot-78] + _ = x[MisplacedDotDotDot-79] + _ = x[InvalidDotDotDotOperand-80] + _ = x[InvalidDotDotDot-81] + _ = x[UncalledBuiltin-82] + _ = x[InvalidAppend-83] + _ = x[InvalidCap-84] + _ = x[InvalidClose-85] + _ = x[InvalidCopy-86] + _ = x[InvalidComplex-87] + _ = x[InvalidDelete-88] + _ = x[InvalidImag-89] + _ = x[InvalidLen-90] + _ = x[SwappedMakeArgs-91] + _ = x[InvalidMake-92] + _ = x[InvalidReal-93] + _ = x[InvalidAssert-94] + _ = x[ImpossibleAssert-95] + _ = x[InvalidConversion-96] + _ = x[InvalidUntypedConversion-97] + _ = x[BadOffsetofSyntax-98] + _ = x[InvalidOffsetof-99] + _ = x[UnusedExpr-100] + _ = x[UnusedVar-101] + _ = x[MissingReturn-102] + _ = x[WrongResultCount-103] + _ = x[OutOfScopeResult-104] + _ = x[InvalidCond-105] + _ = x[InvalidPostDecl-106] + _ = x[InvalidChanRange-107] + _ = x[InvalidIterVar-108] + _ = x[InvalidRangeExpr-109] + _ = x[MisplacedBreak-110] + _ = x[MisplacedContinue-111] + _ = x[MisplacedFallthrough-112] + _ = x[DuplicateCase-113] + _ = x[DuplicateDefault-114] + _ = x[BadTypeKeyword-115] + _ = x[InvalidTypeSwitch-116] + _ = x[InvalidExprSwitch-117] + _ = x[InvalidSelectCase-118] + _ = x[UndeclaredLabel-119] + _ = x[DuplicateLabel-120] + _ = x[MisplacedLabel-121] + _ = x[UnusedLabel-122] + _ = x[JumpOverDecl-123] + _ = x[JumpIntoBlock-124] + _ = x[InvalidMethodExpr-125] + _ = x[WrongArgCount-126] + _ = x[InvalidCall-127] + _ = x[UnusedResults-128] + _ = x[InvalidDefer-129] + _ = x[InvalidGo-130] + _ = x[BadDecl-131] + _ = x[RepeatedDecl-132] + _ = x[InvalidUnsafeAdd-133] + _ = x[InvalidUnsafeSlice-134] + _ = x[UnsupportedFeature-135] + _ = x[NotAGenericType-136] + _ = x[WrongTypeArgCount-137] + _ = x[CannotInferTypeArgs-138] + _ = x[InvalidTypeArg-139] + _ = x[InvalidInstanceCycle-140] + _ = x[InvalidUnion-141] + _ = x[MisplacedConstraintIface-142] + _ = x[InvalidMethodTypeParams-143] + _ = x[MisplacedTypeParam-144] + _ = x[InvalidUnsafeSliceData-145] + _ = x[InvalidUnsafeString-146] +} + +const ( + _ErrorCode_name_0 = "InvalidSyntaxTree" + _ErrorCode_name_1 = "TestBlankPkgNameMismatchedPkgNameInvalidPkgUseBadImportPathBrokenImportImportCRenamedUnusedImportInvalidInitCycleDuplicateDeclInvalidDeclCycleInvalidTypeCycleInvalidConstInitInvalidConstValInvalidConstTypeUntypedNilUseWrongAssignCountUnassignableOperandNoNewVarMultiValAssignOpInvalidIfaceAssignInvalidChanAssignIncompatibleAssignUnaddressableFieldAssignNotATypeInvalidArrayLenBlankIfaceMethodIncomparableMapKeyInvalidIfaceEmbedInvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDotInvalidDotDotDotOperandInvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDeclInvalidChanRangeInvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString" +) + +var ( + _ErrorCode_index_1 = [...]uint16{0, 4, 16, 33, 46, 59, 71, 85, 97, 113, 126, 142, 158, 174, 189, 205, 218, 234, 253, 261, 277, 295, 312, 330, 354, 362, 377, 393, 411, 428, 443, 450, 461, 484, 499, 511, 522, 537, 551, 566, 581, 594, 603, 617, 632, 643, 658, 667, 683, 703, 721, 740, 752, 771, 790, 806, 823, 842, 856, 867, 882, 895, 910, 926, 940, 956, 971, 988, 1006, 1021, 1031, 1041, 1058, 1080, 1094, 1108, 1128, 1146, 1166, 1184, 1207, 1223, 1238, 1251, 1261, 1273, 1284, 1298, 1311, 1322, 1332, 1347, 1358, 1369, 1382, 1398, 1415, 1439, 1456, 1471, 1481, 1490, 1503, 1519, 1535, 1546, 1561, 1577, 1591, 1607, 1621, 1638, 1658, 1671, 1687, 1701, 1718, 1735, 1752, 1767, 1781, 1795, 1806, 1818, 1831, 1848, 1861, 1872, 1885, 1897, 1906, 1913, 1925, 1941, 1959, 1977, 1992, 2009, 2028, 2042, 2062, 2074, 2098, 2121, 2139, 2161, 2180} +) + +func (i ErrorCode) String() string { + switch { + case i == -1: + return _ErrorCode_name_0 + case 1 <= i && i <= 146: + i -= 1 + return _ErrorCode_name_1[_ErrorCode_index_1[i]:_ErrorCode_index_1[i+1]] + default: + return "ErrorCode(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go new file mode 100644 index 0000000..c846a53 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/fx.go @@ -0,0 +1,88 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" +) + +// NoEffects reports whether the expression has no side effects, i.e., it +// does not modify the memory state. This function is conservative: it may +// return false even when the expression has no effect. +func NoEffects(info *types.Info, expr ast.Expr) bool { + noEffects := true + ast.Inspect(expr, func(n ast.Node) bool { + switch v := n.(type) { + case nil, *ast.Ident, *ast.BasicLit, *ast.BinaryExpr, *ast.ParenExpr, + *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr, + *ast.StarExpr, *ast.CompositeLit, + // non-expressions that may appear within expressions + *ast.KeyValueExpr, + *ast.FieldList, + *ast.Field, + *ast.Ellipsis, + *ast.IndexListExpr: + // No effect. + + case *ast.ArrayType, + *ast.StructType, + *ast.ChanType, + *ast.FuncType, + *ast.MapType, + *ast.InterfaceType: + // Type syntax: no effects, recursively. + // Prune descent. + return false + + case *ast.UnaryExpr: + // Channel send <-ch has effects. + if v.Op == token.ARROW { + noEffects = false + } + + case *ast.CallExpr: + // Type conversion has no effects. + if !info.Types[v.Fun].IsType() { + if CallsPureBuiltin(info, v) { + // A call such as len(e) has no effects of its + // own, though the subexpression e might. + } else { + noEffects = false + } + } + + case *ast.FuncLit: + // A FuncLit has no effects, but do not descend into it. + return false + + default: + // All other expressions have effects + noEffects = false + } + + return noEffects + }) + return noEffects +} + +// CallsPureBuiltin reports whether call is a call of a built-in +// function that is a pure computation over its operands (analogous to +// a + operator). Because it does not depend on program state, it may +// be evaluated at any point--though not necessarily at multiple +// points (consider new, make). +func CallsPureBuiltin(info *types.Info, call *ast.CallExpr) bool { + if id, ok := ast.Unparen(call.Fun).(*ast.Ident); ok { + if b, ok := info.ObjectOf(id).(*types.Builtin); ok { + switch b.Name() { + case "len", "cap", "complex", "imag", "real", "make", "new", "max", "min": + return true + } + // Not: append clear close copy delete panic print println recover + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go new file mode 100644 index 0000000..e0d63c4 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/isnamed.go @@ -0,0 +1,71 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" + "slices" +) + +// IsTypeNamed reports whether t is (or is an alias for) a +// package-level defined type with the given package path and one of +// the given names. It returns false if t is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsTypeNamed(t types.Type, pkgPath string, names ...string) bool { + if named, ok := types.Unalias(t).(*types.Named); ok { + tname := named.Obj() + return tname != nil && + IsPackageLevel(tname) && + tname.Pkg().Path() == pkgPath && + slices.Contains(names, tname.Name()) + } + return false +} + +// IsPointerToNamed reports whether t is (or is an alias for) a pointer to a +// package-level defined type with the given package path and one of the given +// names. It returns false if t is not a pointer type. +func IsPointerToNamed(t types.Type, pkgPath string, names ...string) bool { + r := Unpointer(t) + if r == t { + return false + } + return IsTypeNamed(r, pkgPath, names...) +} + +// IsFunctionNamed reports whether obj is a package-level function +// defined in the given package and has one of the given names. +// It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.Name", +// which is important for the performance of syntax matching. +func IsFunctionNamed(obj types.Object, pkgPath string, names ...string) bool { + f, ok := obj.(*types.Func) + return ok && + IsPackageLevel(obj) && + f.Pkg().Path() == pkgPath && + f.Signature().Recv() == nil && + slices.Contains(names, f.Name()) +} + +// IsMethodNamed reports whether obj is a method defined on a +// package-level type with the given package and type name, and has +// one of the given names. It returns false if obj is nil. +// +// This function avoids allocating the concatenation of "pkg.TypeName.Name", +// which is important for the performance of syntax matching. +func IsMethodNamed(obj types.Object, pkgPath string, typeName string, names ...string) bool { + if fn, ok := obj.(*types.Func); ok { + if recv := fn.Signature().Recv(); recv != nil { + _, T := ReceiverNamed(recv) + return T != nil && + IsTypeNamed(T, pkgPath, typeName) && + slices.Contains(names, fn.Name()) + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go new file mode 100644 index 0000000..4e2756f --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/qualifier.go @@ -0,0 +1,54 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/ast" + "go/types" + "strconv" +) + +// FileQualifier returns a [types.Qualifier] function that qualifies +// imported symbols appropriately based on the import environment of a given +// file. +// If the same package is imported multiple times, the last appearance is +// recorded. +// +// TODO(adonovan): this function ignores the effect of shadowing. It +// should accept a [token.Pos] and a [types.Info] and compute only the +// set of imports that are not shadowed at that point, analogous to +// [analysis.AddImport]. It could also compute (as a side +// effect) the set of additional imports required to ensure that there +// is an accessible import for each necessary package, making it +// converge even more closely with AddImport. +func FileQualifier(f *ast.File, pkg *types.Package) types.Qualifier { + // Construct mapping of import paths to their defined names. + // It is only necessary to look at renaming imports. + imports := make(map[string]string) + for _, imp := range f.Imports { + if imp.Name != nil && imp.Name.Name != "_" { + path, _ := strconv.Unquote(imp.Path.Value) + imports[path] = imp.Name.Name + } + } + + // Define qualifier to replace full package paths with names of the imports. + return func(p *types.Package) string { + if p == nil || p == pkg { + return "" + } + + if name, ok := imports[p.Path()]; ok { + if name == "." { + return "" + } else { + return name + } + } + + // If there is no local renaming, fall back to the package name. + return p.Name() + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go new file mode 100644 index 0000000..8352ea7 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/recv.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" +) + +// ReceiverNamed returns the named type (if any) associated with the +// type of recv, which may be of the form N or *N, or aliases thereof. +// It also reports whether a Pointer was present. +// +// The named result may be nil if recv is from a method on an +// anonymous interface or struct types or in ill-typed code. +func ReceiverNamed(recv *types.Var) (isPtr bool, named *types.Named) { + t := recv.Type() + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + isPtr = true + t = ptr.Elem() + } + named, _ = types.Unalias(t).(*types.Named) + return +} + +// Unpointer returns T given *T or an alias thereof. +// For all other types it is the identity function. +// It does not look at underlying types. +// The result may be an alias. +// +// Use this function to strip off the optional pointer on a receiver +// in a field or method selection, without losing the named type +// (which is needed to compute the method set). +// +// See also [typeparams.MustDeref], which removes one level of +// indirection from the type, regardless of named types (analogous to +// a LOAD instruction). +func Unpointer(t types.Type) types.Type { + if ptr, ok := types.Unalias(t).(*types.Pointer); ok { + return ptr.Elem() + } + return t +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go new file mode 100644 index 0000000..9a8ac36 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex/typeindex.go @@ -0,0 +1,256 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typeindex provides an [Index] of type information for a +// package, allowing efficient lookup of, say, whether a given symbol +// is referenced and, if so, where from; or of the [inspector.Cursor] for +// the declaration of a particular [types.Object] symbol. +package typeindex + +import ( + "encoding/binary" + "go/ast" + "go/types" + "iter" + + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" + "golang.org/x/tools/go/types/typeutil" + "honnef.co/go/tools/internal/xtools-internal/astutil" + "honnef.co/go/tools/internal/xtools-internal/typesinternal" +) + +// New constructs an Index for the package of type-annotated syntax +// +// TODO(adonovan): accept a FileSet too? +// We regret not requiring one in inspector.New. +func New(inspect *inspector.Inspector, pkg *types.Package, info *types.Info) *Index { + ix := &Index{ + inspect: inspect, + info: info, + packages: make(map[string]*types.Package), + def: make(map[types.Object]inspector.Cursor), + uses: make(map[types.Object]*uses), + } + + addPackage := func(pkg2 *types.Package) { + if pkg2 != nil && pkg2 != pkg { + ix.packages[pkg2.Path()] = pkg2 + } + } + + for cur := range inspect.Root().Preorder((*ast.ImportSpec)(nil), (*ast.Ident)(nil)) { + switch n := cur.Node().(type) { + case *ast.ImportSpec: + // Index direct imports, including blank ones. + if pkgname := info.PkgNameOf(n); pkgname != nil { + addPackage(pkgname.Imported()) + } + + case *ast.Ident: + // Index all defining and using identifiers. + if obj := info.Defs[n]; obj != nil { + ix.def[obj] = cur + } + + if obj := info.Uses[n]; obj != nil { + // Index indirect dependencies (via fields and methods). + if !typesinternal.IsPackageLevel(obj) { + addPackage(obj.Pkg()) + } + + for { + us, ok := ix.uses[obj] + if !ok { + us = &uses{} + us.code = us.initial[:0] + ix.uses[obj] = us + } + delta := cur.Index() - us.last + if delta < 0 { + panic("non-monotonic") + } + us.code = binary.AppendUvarint(us.code, uint64(delta)) + us.last = cur.Index() + + // If n is a selection of a field or method of an instantiated + // type, also record a use of the generic field or method. + obj, ok = objectOrigin(obj) + if !ok { + break + } + } + } + } + } + return ix +} + +// objectOrigin returns the generic object for obj if it is a field or +// method of an instantied type; zero otherwise. +// +// (This operation is appropriate only for selections. +// Lexically resolved references always resolve to the generic. +// Although Named and Alias types also use Origin to express +// an instance/generic distinction, that's in the domain +// of Types; their TypeName objects always refer to the generic.) +func objectOrigin(obj types.Object) (types.Object, bool) { + var origin types.Object + switch obj := obj.(type) { + case *types.Func: + if obj.Signature().Recv() != nil { + origin = obj.Origin() // G[int].method -> G[T].method + } + case *types.Var: + if obj.IsField() { + origin = obj.Origin() // G[int].field -> G[T].field + } + } + if origin != nil && origin != obj { + return origin, true + } + return nil, false +} + +// An Index holds an index mapping [types.Object] symbols to their syntax. +// In effect, it is the inverse of [types.Info]. +type Index struct { + inspect *inspector.Inspector + info *types.Info + packages map[string]*types.Package // packages of all symbols referenced from this package + def map[types.Object]inspector.Cursor // Cursor of *ast.Ident that defines the Object + uses map[types.Object]*uses // Cursors of *ast.Idents that use the Object +} + +// A uses holds the list of Cursors of Idents that use a given symbol. +// +// The Uses map of [types.Info] is substantial, so it pays to compress +// its inverse mapping here, both in space and in CPU due to reduced +// allocation. A Cursor is 2 words; a Cursor.Index is 4 bytes; but +// since Cursors are naturally delivered in ascending order, we can +// use varint-encoded deltas at a cost of only ~1.7-2.2 bytes per use. +// +// Many variables have only one or two uses, so their encoded uses may +// fit in the 4 bytes of initial, saving further CPU and space +// essentially for free since the struct's size class is 4 words. +type uses struct { + code []byte // varint-encoded deltas of successive Cursor.Index values + last int32 // most recent Cursor.Index value; used during encoding + initial [4]byte // use slack in size class as initial space for code +} + +// Uses returns the sequence of Cursors of [*ast.Ident]s in this package +// that refer to obj. If obj is nil, the sequence is empty. +// +// Uses, unlike the Uses field of [types.Info], records additional +// entries mapping fields and methods of generic types to references +// through their corresponding instantiated objects. +func (ix *Index) Uses(obj types.Object) iter.Seq[inspector.Cursor] { + return func(yield func(inspector.Cursor) bool) { + if uses := ix.uses[obj]; uses != nil { + var last int32 + for code := uses.code; len(code) > 0; { + delta, n := binary.Uvarint(code) + last += int32(delta) + if !yield(ix.inspect.At(last)) { + return + } + code = code[n:] + } + } + } +} + +// Used reports whether any of the specified objects are used, in +// other words, obj != nil && Uses(obj) is non-empty for some obj in objs. +// +// (This treatment of nil allows Used to be called directly on the +// result of [Index.Object] so that analyzers can conveniently skip +// packages that don't use a symbol of interest.) +func (ix *Index) Used(objs ...types.Object) bool { + for _, obj := range objs { + if obj != nil && ix.uses[obj] != nil { + return true + } + } + return false +} + +// Def returns the Cursor of the [*ast.Ident] in this package +// that declares the specified object, if any. +func (ix *Index) Def(obj types.Object) (inspector.Cursor, bool) { + cur, ok := ix.def[obj] + return cur, ok +} + +// Package returns the package of the specified path, +// or nil if it is not referenced from this package. +func (ix *Index) Package(path string) *types.Package { + return ix.packages[path] +} + +// Object returns the package-level symbol name within the package of +// the specified path, or nil if the package or symbol does not exist +// or is not visible from this package. +func (ix *Index) Object(path, name string) types.Object { + if pkg := ix.Package(path); pkg != nil { + return pkg.Scope().Lookup(name) + } + return nil +} + +// Selection returns the named method or field belonging to the +// package-level type returned by Object(path, typename). +func (ix *Index) Selection(path, typename, name string) types.Object { + if obj := ix.Object(path, typename); obj != nil { + if tname, ok := obj.(*types.TypeName); ok { + obj, _, _ := types.LookupFieldOrMethod(tname.Type(), true, obj.Pkg(), name) + return obj + } + } + return nil +} + +// Calls returns the sequence of cursors for *ast.CallExpr nodes that +// call the specified callee, as defined by [typeutil.Callee]. +// If callee is nil, the sequence is empty. +func (ix *Index) Calls(callee types.Object) iter.Seq[inspector.Cursor] { + return func(yield func(inspector.Cursor) bool) { + for cur := range ix.Uses(callee) { + // The call may be of the form f() or x.f(), + // optionally with parens; ascend from f to call. + // See logic in [typesinternal.UsedIdent], to which this is dual. + // + // It is tempting but wrong to use the first + // CallExpr ancestor: we have to make sure the + // ident is in the CallExpr.Fun position, otherwise + // f(f, f) would have two spurious matches. + // Avoiding Enclosing is also significantly faster. + + // inverse unparen: f -> (f) + cur = astutil.UnparenEnclosingCursor(cur) + + // ascend selector (or qualified identifier): f -> x.f + if cur.ParentEdgeKind() == edge.SelectorExpr_Sel { + cur = astutil.UnparenEnclosingCursor(cur.Parent()) + } + + // ascend typeparams: f -> f[T]; f -> f[T1, T2] + if ek := cur.ParentEdgeKind(); ek == edge.IndexExpr_X || ek == edge.IndexListExpr_X { + cur = astutil.UnparenEnclosingCursor(cur.Parent()) + } + + // ascend from f or x.f to call + if cur.ParentEdgeKind() == edge.CallExpr_Fun { + curCall := cur.Parent() + call := curCall.Node().(*ast.CallExpr) + if typeutil.Callee(ix.info, call) == callee { + if !yield(curCall) { + return + } + } + } + } + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go new file mode 100644 index 0000000..50942b7 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/types.go @@ -0,0 +1,272 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package typesinternal provides helpful operators for dealing with +// go/types: +// +// - operators for querying typed syntax trees (e.g. [Imports], [IsFunctionNamed]); +// - functions for converting types to strings or syntax (e.g. [TypeExpr], FileQualifier]); +// - helpers for working with the [go/types] API (e.g. [NewTypesInfo]); +// - access to internal go/types APIs that are not yet +// exported (e.g. [SetUsesCgo], [ErrorCodeStartEnd], [VarKind]); and +// - common algorithms related to types (e.g. [TooNewStdSymbols]). +// +// See also: +// - [honnef.co/go/tools/internal/xtools-internal/astutil], for operations on untyped syntax; +// - [honnef.co/go/tools/internal/xtools-internal/analysisinernal], for helpers for analyzers; +// - [honnef.co/go/tools/internal/xtools-internal/refactor], for operators to compute text edits. +package typesinternal + +import ( + "go/ast" + "go/token" + "go/types" + "iter" + "reflect" + + "golang.org/x/tools/go/ast/inspector" +) + +func SetUsesCgo(conf *types.Config) bool { + v := reflect.ValueOf(conf).Elem() + + f := v.FieldByName("go115UsesCgo") + if !f.IsValid() { + f = v.FieldByName("UsesCgo") + if !f.IsValid() { + return false + } + } + + *(*bool)(f.Addr().UnsafePointer()) = true + + return true +} + +// ErrorCodeStartEnd extracts additional information from types.Error values +// generated by Go version 1.16 and later: the error code, start position, and +// end position. If all positions are valid, start <= err.Pos <= end. +// +// If the data could not be read, the final result parameter will be false. +// +// TODO(adonovan): eliminate start/end when proposal #71803 is accepted. +func ErrorCodeStartEnd(err types.Error) (code ErrorCode, start, end token.Pos, ok bool) { + var data [3]int + // By coincidence all of these fields are ints, which simplifies things. + v := reflect.ValueOf(err) + for i, name := range []string{"go116code", "go116start", "go116end"} { + f := v.FieldByName(name) + if !f.IsValid() { + return 0, 0, 0, false + } + data[i] = int(f.Int()) + } + return ErrorCode(data[0]), token.Pos(data[1]), token.Pos(data[2]), true +} + +// NameRelativeTo returns a types.Qualifier that qualifies members of +// all packages other than pkg, using only the package name. +// (By contrast, [types.RelativeTo] uses the complete package path, +// which is often excessive.) +// +// If pkg is nil, it is equivalent to [*types.Package.Name]. +// +// TODO(adonovan): all uses of this with TypeString should be +// eliminated when https://go.dev/issues/75604 is resolved. +func NameRelativeTo(pkg *types.Package) types.Qualifier { + return func(other *types.Package) string { + if pkg != nil && pkg == other { + return "" // same package; unqualified + } + return other.Name() + } +} + +// TypeNameFor returns the type name symbol for the specified type, if +// it is a [*types.Alias], [*types.Named], [*types.TypeParam], or a +// [*types.Basic] representing a type. +// +// For all other types, and for Basic types representing a builtin, +// constant, or nil, it returns nil. Be careful not to convert the +// resulting nil pointer to a [types.Object]! +// +// If t is the type of a constant, it may be an "untyped" type, which +// has no TypeName. To access the name of such types (e.g. "untyped +// int"), use [types.Basic.Name]. +func TypeNameFor(t types.Type) *types.TypeName { + switch t := t.(type) { + case *types.Alias: + return t.Obj() + case *types.Named: + return t.Obj() + case *types.TypeParam: + return t.Obj() + case *types.Basic: + // See issues #71886 and #66890 for some history. + if tname, ok := types.Universe.Lookup(t.Name()).(*types.TypeName); ok { + return tname + } + } + return nil +} + +// A NamedOrAlias is a [types.Type] that is named (as +// defined by the spec) and capable of bearing type parameters: it +// abstracts aliases ([types.Alias]) and defined types +// ([types.Named]). +// +// Every type declared by an explicit "type" declaration is a +// NamedOrAlias. (Built-in type symbols may additionally +// have type [types.Basic], which is not a NamedOrAlias, +// though the spec regards them as "named"; see [TypeNameFor].) +// +// NamedOrAlias cannot expose the Origin method, because +// [types.Alias.Origin] and [types.Named.Origin] have different +// (covariant) result types; use [Origin] instead. +type NamedOrAlias interface { + types.Type + Obj() *types.TypeName + TypeArgs() *types.TypeList + TypeParams() *types.TypeParamList + SetTypeParams(tparams []*types.TypeParam) +} + +var ( + _ NamedOrAlias = (*types.Alias)(nil) + _ NamedOrAlias = (*types.Named)(nil) +) + +// Origin returns the generic type of the Named or Alias type t if it +// is instantiated, otherwise it returns t. +func Origin(t NamedOrAlias) NamedOrAlias { + switch t := t.(type) { + case *types.Alias: + return t.Origin() + case *types.Named: + return t.Origin() + } + return t +} + +// IsPackageLevel reports whether obj is a package-level symbol. +func IsPackageLevel(obj types.Object) bool { + return obj.Pkg() != nil && obj.Parent() == obj.Pkg().Scope() +} + +// NewTypesInfo returns a *types.Info with all maps populated. +func NewTypesInfo() *types.Info { + return &types.Info{ + Types: map[ast.Expr]types.TypeAndValue{}, + Instances: map[*ast.Ident]types.Instance{}, + Defs: map[*ast.Ident]types.Object{}, + Uses: map[*ast.Ident]types.Object{}, + Implicits: map[ast.Node]types.Object{}, + Selections: map[*ast.SelectorExpr]*types.Selection{}, + Scopes: map[ast.Node]*types.Scope{}, + FileVersions: map[*ast.File]string{}, + } +} + +// EnclosingScope returns the innermost block logically enclosing the cursor. +func EnclosingScope(info *types.Info, cur inspector.Cursor) *types.Scope { + for cur := range cur.Enclosing() { + n := cur.Node() + // A function's Scope is associated with its FuncType. + switch f := n.(type) { + case *ast.FuncDecl: + n = f.Type + case *ast.FuncLit: + n = f.Type + } + if b := info.Scopes[n]; b != nil { + return b + } + } + panic("no Scope for *ast.File") +} + +// Imports reports whether path is imported by pkg. +func Imports(pkg *types.Package, path string) bool { + for _, imp := range pkg.Imports() { + if imp.Path() == path { + return true + } + } + return false +} + +// ObjectKind returns a description of the object's kind. +// +// from objectKind in go/types +func ObjectKind(obj types.Object) string { + switch obj := obj.(type) { + case *types.PkgName: + return "package name" + case *types.Const: + return "constant" + case *types.TypeName: + if obj.IsAlias() { + return "type alias" + } else if _, ok := obj.Type().(*types.TypeParam); ok { + return "type parameter" + } else { + return "defined type" + } + case *types.Var: + switch obj.Kind() { + case PackageVar: + return "package-level variable" + case LocalVar: + return "local variable" + case RecvVar: + return "receiver" + case ParamVar: + return "parameter" + case ResultVar: + return "result variable" + case FieldVar: + return "struct field" + } + case *types.Func: + if obj.Signature().Recv() != nil { + return "method" + } else { + return "function" + } + case *types.Label: + return "label" + case *types.Builtin: + return "built-in function" + case *types.Nil: + return "untyped nil" + } + return "unknown symbol" +} + +// ImplicitFieldSelections returns the sequence of implicit embedded fields +// traversed by the given selection. It skips the final leaf field or method. +// The boolean component indicates whether the traversal traversed a pointer. +func ImplicitFieldSelections(seln types.Selection) iter.Seq2[*types.Var, bool] { + return func(yield func(*types.Var, bool) bool) { + var ( + t = seln.Recv() + indices = seln.Index() + ) + for _, idx := range indices[:len(indices)-1] { + ptr, isPtr := t.Underlying().(*types.Pointer) + if isPtr { + t = ptr.Elem() + } + structType, ok := t.Underlying().(*types.Struct) + if !ok { + break + } + field := structType.Field(idx) + if !yield(field, isPtr) { + break + } + t = field.Type() + } + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go new file mode 100644 index 0000000..26499cd --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind.go @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build go1.25 + +package typesinternal + +import "go/types" + +type VarKind = types.VarKind + +const ( + PackageVar = types.PackageVar + LocalVar = types.LocalVar + RecvVar = types.RecvVar + ParamVar = types.ParamVar + ResultVar = types.ResultVar + FieldVar = types.FieldVar +) + +func GetVarKind(v *types.Var) VarKind { return v.Kind() } +func SetVarKind(v *types.Var, kind VarKind) { v.SetKind(kind) } diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go new file mode 100644 index 0000000..17b1804 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/varkind_go124.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !go1.25 + +package typesinternal + +import "go/types" + +type VarKind uint8 + +const ( + _ VarKind = iota // (not meaningful) + PackageVar // a package-level variable + LocalVar // a local variable + RecvVar // a method receiver variable + ParamVar // a function parameter variable + ResultVar // a function result variable + FieldVar // a struct field +) + +func (kind VarKind) String() string { + return [...]string{ + 0: "VarKind(0)", + PackageVar: "PackageVar", + LocalVar: "LocalVar", + RecvVar: "RecvVar", + ParamVar: "ParamVar", + ResultVar: "ResultVar", + FieldVar: "FieldVar", + }[kind] +} + +// GetVarKind returns an invalid VarKind. +func GetVarKind(v *types.Var) VarKind { return 0 } + +// SetVarKind has no effect. +func SetVarKind(v *types.Var, kind VarKind) {} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go new file mode 100644 index 0000000..d612a71 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/typesinternal/zerovalue.go @@ -0,0 +1,381 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" +) + +// ZeroString returns the string representation of the zero value for any type t. +// The boolean result indicates whether the type is or contains an invalid type +// or a non-basic (constraint) interface type. +// +// Even for invalid input types, ZeroString may return a partially correct +// string representation. The caller should use the returned isValid boolean +// to determine the validity of the expression. +// +// When assigning to a wider type (such as 'any'), it's the caller's +// responsibility to handle any necessary type conversions. +// +// This string can be used on the right-hand side of an assignment where the +// left-hand side has that explicit type. +// References to named types are qualified by an appropriate (optional) +// qualifier function. +// Exception: This does not apply to tuples. Their string representation is +// informational only and cannot be used in an assignment. +// +// See [ZeroExpr] for a variant that returns an [ast.Expr]. +func ZeroString(t types.Type, qual types.Qualifier) (_ string, isValid bool) { + switch t := t.(type) { + case *types.Basic: + switch { + case t.Info()&types.IsBoolean != 0: + return "false", true + case t.Info()&types.IsNumeric != 0: + return "0", true + case t.Info()&types.IsString != 0: + return `""`, true + case t.Kind() == types.UnsafePointer: + fallthrough + case t.Kind() == types.UntypedNil: + return "nil", true + case t.Kind() == types.Invalid: + return "invalid", false + default: + panic(fmt.Sprintf("ZeroString for unexpected type %v", t)) + } + + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return "nil", true + + case *types.Interface: + if !t.IsMethodSet() { + return "invalid", false + } + return "nil", true + + case *types.Named: + switch under := t.Underlying().(type) { + case *types.Struct, *types.Array: + return types.TypeString(t, qual) + "{}", true + default: + return ZeroString(under, qual) + } + + case *types.Alias: + switch t.Underlying().(type) { + case *types.Struct, *types.Array: + return types.TypeString(t, qual) + "{}", true + default: + // A type parameter can have alias but alias type's underlying type + // can never be a type parameter. + // Use types.Unalias to preserve the info of type parameter instead + // of call Underlying() going right through and get the underlying + // type of the type parameter which is always an interface. + return ZeroString(types.Unalias(t), qual) + } + + case *types.Array, *types.Struct: + return types.TypeString(t, qual) + "{}", true + + case *types.TypeParam: + // Assumes func new is not shadowed. + return "*new(" + types.TypeString(t, qual) + ")", true + + case *types.Tuple: + // Tuples are not normal values. + // We are currently format as "(t[0], ..., t[n])". Could be something else. + isValid := true + components := make([]string, t.Len()) + for i := 0; i < t.Len(); i++ { + comp, ok := ZeroString(t.At(i).Type(), qual) + + components[i] = comp + isValid = isValid && ok + } + return "(" + strings.Join(components, ", ") + ")", isValid + + case *types.Union: + // Variables of these types cannot be created, so it makes + // no sense to ask for their zero value. + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + default: + panic(t) // unreachable. + } +} + +// ZeroExpr returns the ast.Expr representation of the zero value for any type t. +// The boolean result indicates whether the type is or contains an invalid type +// or a non-basic (constraint) interface type. +// +// Even for invalid input types, ZeroExpr may return a partially correct ast.Expr +// representation. The caller should use the returned isValid boolean to determine +// the validity of the expression. +// +// This function is designed for types suitable for variables and should not be +// used with Tuple or Union types.References to named types are qualified by an +// appropriate (optional) qualifier function. +// +// See [ZeroString] for a variant that returns a string. +func ZeroExpr(t types.Type, qual types.Qualifier) (_ ast.Expr, isValid bool) { + switch t := t.(type) { + case *types.Basic: + switch { + case t.Info()&types.IsBoolean != 0: + return &ast.Ident{Name: "false"}, true + case t.Info()&types.IsNumeric != 0: + return &ast.BasicLit{Kind: token.INT, Value: "0"}, true + case t.Info()&types.IsString != 0: + return &ast.BasicLit{Kind: token.STRING, Value: `""`}, true + case t.Kind() == types.UnsafePointer: + fallthrough + case t.Kind() == types.UntypedNil: + return ast.NewIdent("nil"), true + case t.Kind() == types.Invalid: + return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false + default: + panic(fmt.Sprintf("ZeroExpr for unexpected type %v", t)) + } + + case *types.Pointer, *types.Slice, *types.Chan, *types.Map, *types.Signature: + return ast.NewIdent("nil"), true + + case *types.Interface: + if !t.IsMethodSet() { + return &ast.BasicLit{Kind: token.STRING, Value: `"invalid"`}, false + } + return ast.NewIdent("nil"), true + + case *types.Named: + switch under := t.Underlying().(type) { + case *types.Struct, *types.Array: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + default: + return ZeroExpr(under, qual) + } + + case *types.Alias: + switch t.Underlying().(type) { + case *types.Struct, *types.Array: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + default: + return ZeroExpr(types.Unalias(t), qual) + } + + case *types.Array, *types.Struct: + return &ast.CompositeLit{ + Type: TypeExpr(t, qual), + }, true + + case *types.TypeParam: + return &ast.StarExpr{ // *new(T) + X: &ast.CallExpr{ + // Assumes func new is not shadowed. + Fun: ast.NewIdent("new"), + Args: []ast.Expr{ + ast.NewIdent(t.Obj().Name()), + }, + }, + }, true + + case *types.Tuple: + // Unlike ZeroString, there is no ast.Expr can express tuple by + // "(t[0], ..., t[n])". + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + case *types.Union: + // Variables of these types cannot be created, so it makes + // no sense to ask for their zero value. + panic(fmt.Sprintf("invalid type for a variable: %v", t)) + + default: + panic(t) // unreachable. + } +} + +// TypeExpr returns syntax for the specified type. References to named types +// are qualified by an appropriate (optional) qualifier function. +// It may panic for types such as Tuple or Union. +// +// See also https://go.dev/issues/75604, which will provide a robust +// Type-to-valid-Go-syntax formatter. +func TypeExpr(t types.Type, qual types.Qualifier) ast.Expr { + switch t := t.(type) { + case *types.Basic: + switch t.Kind() { + case types.UnsafePointer: + return &ast.SelectorExpr{X: ast.NewIdent(qual(types.NewPackage("unsafe", "unsafe"))), Sel: ast.NewIdent("Pointer")} + default: + return ast.NewIdent(t.Name()) + } + + case *types.Pointer: + return &ast.UnaryExpr{ + Op: token.MUL, + X: TypeExpr(t.Elem(), qual), + } + + case *types.Array: + return &ast.ArrayType{ + Len: &ast.BasicLit{ + Kind: token.INT, + Value: fmt.Sprintf("%d", t.Len()), + }, + Elt: TypeExpr(t.Elem(), qual), + } + + case *types.Slice: + return &ast.ArrayType{ + Elt: TypeExpr(t.Elem(), qual), + } + + case *types.Map: + return &ast.MapType{ + Key: TypeExpr(t.Key(), qual), + Value: TypeExpr(t.Elem(), qual), + } + + case *types.Chan: + dir := ast.ChanDir(t.Dir()) + if t.Dir() == types.SendRecv { + dir = ast.SEND | ast.RECV + } + return &ast.ChanType{ + Dir: dir, + Value: TypeExpr(t.Elem(), qual), + } + + case *types.Signature: + var params []*ast.Field + for v := range t.Params().Variables() { + params = append(params, &ast.Field{ + Type: TypeExpr(v.Type(), qual), + Names: []*ast.Ident{ + { + Name: v.Name(), + }, + }, + }) + } + if t.Variadic() { + last := params[len(params)-1] + last.Type = &ast.Ellipsis{Elt: last.Type.(*ast.ArrayType).Elt} + } + var returns []*ast.Field + for v := range t.Results().Variables() { + returns = append(returns, &ast.Field{ + Type: TypeExpr(v.Type(), qual), + }) + } + return &ast.FuncType{ + Params: &ast.FieldList{ + List: params, + }, + Results: &ast.FieldList{ + List: returns, + }, + } + + case *types.TypeParam: + pkgName := qual(t.Obj().Pkg()) + if pkgName == "" || t.Obj().Pkg() == nil { + return ast.NewIdent(t.Obj().Name()) + } + return &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: ast.NewIdent(t.Obj().Name()), + } + + // types.TypeParam also implements interface NamedOrAlias. To differentiate, + // case TypeParam need to be present before case NamedOrAlias. + // TODO(hxjiang): remove this comment once TypeArgs() is added to interface + // NamedOrAlias. + case NamedOrAlias: + var expr ast.Expr = ast.NewIdent(t.Obj().Name()) + if pkgName := qual(t.Obj().Pkg()); pkgName != "." && pkgName != "" { + expr = &ast.SelectorExpr{ + X: ast.NewIdent(pkgName), + Sel: expr.(*ast.Ident), + } + } + + // TODO(hxjiang): call t.TypeArgs after adding method TypeArgs() to + // typesinternal.NamedOrAlias. + if hasTypeArgs, ok := t.(interface{ TypeArgs() *types.TypeList }); ok { + if typeArgs := hasTypeArgs.TypeArgs(); typeArgs != nil && typeArgs.Len() > 0 { + var indices []ast.Expr + for t0 := range typeArgs.Types() { + indices = append(indices, TypeExpr(t0, qual)) + } + expr = &ast.IndexListExpr{ + X: expr, + Indices: indices, + } + } + } + + return expr + + case *types.Struct: + return ast.NewIdent(t.String()) + + case *types.Interface: + return ast.NewIdent(t.String()) + + case *types.Union: + if t.Len() == 0 { + panic("Union type should have at least one term") + } + // Same as go/ast, the return expression will put last term in the + // Y field at topmost level of BinaryExpr. + // For union of type "float32 | float64 | int64", the structure looks + // similar to: + // { + // X: { + // X: float32, + // Op: | + // Y: float64, + // } + // Op: |, + // Y: int64, + // } + var union ast.Expr + for i := range t.Len() { + term := t.Term(i) + termExpr := TypeExpr(term.Type(), qual) + if term.Tilde() { + termExpr = &ast.UnaryExpr{ + Op: token.TILDE, + X: termExpr, + } + } + if i == 0 { + union = termExpr + } else { + union = &ast.BinaryExpr{ + X: union, + Op: token.OR, + Y: termExpr, + } + } + } + return union + + case *types.Tuple: + panic("invalid input type types.Tuple") + + default: + panic("unreachable") + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go new file mode 100644 index 0000000..360a5b5 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/features.go @@ -0,0 +1,49 @@ +// Copyright 2023 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 versions + +// This file contains predicates for working with file versions to +// decide when a tool should consider a language feature enabled. + +// named constants, to avoid misspelling +const ( + Go1_17 = "go1.17" + Go1_18 = "go1.18" + Go1_19 = "go1.19" + Go1_20 = "go1.20" + Go1_21 = "go1.21" + Go1_22 = "go1.22" + Go1_23 = "go1.23" + Go1_24 = "go1.24" + Go1_25 = "go1.25" + Go1_26 = "go1.26" + Go1_27 = "go1.27" +) + +// Future is an invalid unknown Go version sometime in the future. +// Do not use directly with Compare. +const Future = "" + +// AtLeast reports whether the file version v comes after a Go release. +// +// Use this predicate to enable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func AtLeast(v, release string) bool { + if v == Future { + return true // an unknown future version is always after y. + } + return Compare(Lang(v), Lang(release)) >= 0 +} + +// Before reports whether the file version v is strictly before a Go release. +// +// Use this predicate to disable a behavior once a certain Go release +// has happened (and stays enabled in the future). +func Before(v, release string) bool { + if v == Future { + return false // an unknown future version happens after y. + } + return Compare(Lang(v), Lang(release)) < 0 +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go new file mode 100644 index 0000000..bbabcd2 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/gover.go @@ -0,0 +1,172 @@ +// Copyright 2023 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. + +// This is a fork of internal/gover for use by x/tools until +// go1.21 and earlier are no longer supported by x/tools. + +package versions + +import "strings" + +// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]] +// The numbers are the original decimal strings to avoid integer overflows +// and since there is very little actual math. (Probably overflow doesn't matter in practice, +// but at the time this code was written, there was an existing test that used +// go1.99999999999, which does not fit in an int on 32-bit platforms. +// The "big decimal" representation avoids the problem entirely.) +type gover struct { + major string // decimal + minor string // decimal or "" + patch string // decimal or "" + kind string // "", "alpha", "beta", "rc" + pre string // decimal or "" +} + +// compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as toolchain versions. +// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21". +// Malformed versions compare less than well-formed versions and equal to each other. +// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0". +func compare(x, y string) int { + vx := parse(x) + vy := parse(y) + + if c := cmpInt(vx.major, vy.major); c != 0 { + return c + } + if c := cmpInt(vx.minor, vy.minor); c != 0 { + return c + } + if c := cmpInt(vx.patch, vy.patch); c != 0 { + return c + } + if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc + return c + } + if c := cmpInt(vx.pre, vy.pre); c != 0 { + return c + } + return 0 +} + +// lang returns the Go language version. For example, lang("1.2.3") == "1.2". +func lang(x string) string { + v := parse(x) + if v.minor == "" || v.major == "1" && v.minor == "0" { + return v.major + } + return v.major + "." + v.minor +} + +// isValid reports whether the version x is valid. +func isValid(x string) bool { + return parse(x) != gover{} +} + +// parse parses the Go version string x into a version. +// It returns the zero version if x is malformed. +func parse(x string) gover { + var v gover + + // Parse major version. + var ok bool + v.major, x, ok = cutInt(x) + if !ok { + return gover{} + } + if x == "" { + // Interpret "1" as "1.0.0". + v.minor = "0" + v.patch = "0" + return v + } + + // Parse . before minor version. + if x[0] != '.' { + return gover{} + } + + // Parse minor version. + v.minor, x, ok = cutInt(x[1:]) + if !ok { + return gover{} + } + if x == "" { + // Patch missing is same as "0" for older versions. + // Starting in Go 1.21, patch missing is different from explicit .0. + if cmpInt(v.minor, "21") < 0 { + v.patch = "0" + } + return v + } + + // Parse patch if present. + if x[0] == '.' { + v.patch, x, ok = cutInt(x[1:]) + if !ok || x != "" { + // Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != ""). + // Allowing them would be a bit confusing because we already have: + // 1.21 < 1.21rc1 + // But a prerelease of a patch would have the opposite effect: + // 1.21.3rc1 < 1.21.3 + // We've never needed them before, so let's not start now. + return gover{} + } + return v + } + + // Parse prerelease. + i := 0 + for i < len(x) && (x[i] < '0' || '9' < x[i]) { + if x[i] < 'a' || 'z' < x[i] { + return gover{} + } + i++ + } + if i == 0 { + return gover{} + } + v.kind, x = x[:i], x[i:] + if x == "" { + return v + } + v.pre, x, ok = cutInt(x) + if !ok || x != "" { + return gover{} + } + + return v +} + +// cutInt scans the leading decimal number at the start of x to an integer +// and returns that value and the rest of the string. +func cutInt(x string) (n, rest string, ok bool) { + i := 0 + for i < len(x) && '0' <= x[i] && x[i] <= '9' { + i++ + } + if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero + return "", "", false + } + return x[:i], x[i:], true +} + +// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers. +// (Copied from golang.org/x/mod/semver's compareInt.) +func cmpInt(x, y string) int { + if x == y { + return 0 + } + if len(x) < len(y) { + return -1 + } + if len(x) > len(y) { + return +1 + } + if x < y { + return -1 + } else { + return +1 + } +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go new file mode 100644 index 0000000..0fc10ce --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/types.go @@ -0,0 +1,33 @@ +// Copyright 2023 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 versions + +import ( + "go/ast" + "go/types" +) + +// FileVersion returns a file's Go version. +// The reported version is an unknown Future version if a +// version cannot be determined. +func FileVersion(info *types.Info, file *ast.File) string { + // In tools built with Go >= 1.22, the Go version of a file + // follow a cascades of sources: + // 1) types.Info.FileVersion, which follows the cascade: + // 1.a) file version (ast.File.GoVersion), + // 1.b) the package version (types.Config.GoVersion), or + // 2) is some unknown Future version. + // + // File versions require a valid package version to be provided to types + // in Config.GoVersion. Config.GoVersion is either from the package's module + // or the toolchain (go run). This value should be provided by go/packages + // or unitchecker.Config.GoVersion. + if v := info.FileVersions[file]; IsValid(v) { + return v + } + // Note: we could instead return runtime.Version() [if valid]. + // This would act as a max version on what a tool can support. + return Future +} diff --git a/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go new file mode 100644 index 0000000..8d1f745 --- /dev/null +++ b/vendor/honnef.co/go/tools/internal/xtools-internal/versions/versions.go @@ -0,0 +1,57 @@ +// Copyright 2023 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 versions + +import ( + "strings" +) + +// Note: If we use build tags to use go/versions when go >=1.22, +// we run into go.dev/issue/53737. Under some operations users would see an +// import of "go/versions" even if they would not compile the file. +// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include +// For this reason, this library just a clone of go/versions for the moment. + +// Lang returns the Go language version for version x. +// If x is not a valid version, Lang returns the empty string. +// For example: +// +// Lang("go1.21rc2") = "go1.21" +// Lang("go1.21.2") = "go1.21" +// Lang("go1.21") = "go1.21" +// Lang("go1") = "go1" +// Lang("bad") = "" +// Lang("1.21") = "" +func Lang(x string) string { + v := lang(stripGo(x)) + if v == "" { + return "" + } + return x[:2+len(v)] // "go"+v without allocation +} + +// Compare returns -1, 0, or +1 depending on whether +// x < y, x == y, or x > y, interpreted as Go versions. +// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21". +// Invalid versions, including the empty string, compare less than +// valid versions and equal to each other. +// The language version "go1.21" compares less than the +// release candidate and eventual releases "go1.21rc1" and "go1.21.0". +// Custom toolchain suffixes are ignored during comparison: +// "go1.21.0" and "go1.21.0-bigcorp" are equal. +func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) } + +// IsValid reports whether the version x is valid. +func IsValid(x string) bool { return isValid(stripGo(x)) } + +// stripGo converts from a "go1.21" version to a "1.21" version. +// If v does not start with "go", stripGo returns the empty string (a known invalid version). +func stripGo(v string) string { + v, _, _ = strings.Cut(v, "-") // strip -bigcorp suffix. + if len(v) < 2 || v[:2] != "go" { + return "" + } + return v[2:] +} diff --git a/vendor/honnef.co/go/tools/knowledge/arg.go b/vendor/honnef.co/go/tools/knowledge/arg.go new file mode 100644 index 0000000..c477ec4 --- /dev/null +++ b/vendor/honnef.co/go/tools/knowledge/arg.go @@ -0,0 +1,79 @@ +package knowledge + +var Args = map[string]int{ + "(*sync.Pool).Put.x": 0, + "(*text/template.Template).Parse.text": 0, + "(io.Seeker).Seek.offset": 0, + "(time.Time).Sub.u": 0, + "append.elems": 1, + "append.slice": 0, + "bytes.Equal.a": 0, + "bytes.Equal.b": 1, + "encoding/ascii85.Encode.dst": 0, + "encoding/ascii85.Encode.src": 1, + "(*encoding/base32.Encoding).Encode.dst": 0, + "(*encoding/base32.Encoding).Encode.src": 1, + "(*encoding/base64.Encoding).Encode.dst": 0, + "(*encoding/base64.Encoding).Encode.src": 1, + "encoding/binary.Write.data": 2, + "encoding/hex.Encode.dst": 0, + "encoding/hex.Encode.src": 1, + "(*encoding/json.Decoder).Decode.v": 0, + "(*encoding/json.Encoder).Encode.v": 0, + "(*encoding/xml.Decoder).Decode.v": 0, + "(*encoding/xml.Encoder).Encode.v": 0, + "errors.New.text": 0, + "fmt.Fprintf.format": 1, + "fmt.Printf.format": 0, + "fmt.Sprintf.a[0]": 1, + "fmt.Sprintf.format": 0, + "json.Marshal.v": 0, + "json.MarshalIndent.v": 0, + "json.Unmarshal.v": 1, + "len.v": 0, + "make.size[0]": 1, + "make.size[1]": 2, + "make.t": 0, + "net/url.Parse.rawurl": 0, + "os.OpenFile.flag": 1, + "os/exec.Command.name": 0, + "os/signal.Notify.c": 0, + "regexp.Compile.expr": 0, + "runtime.SetFinalizer.finalizer": 1, + "runtime.SetFinalizer.obj": 0, + "sort.Sort.data": 0, + "strconv.AppendFloat.bitSize": 4, + "strconv.AppendFloat.fmt": 2, + "strconv.AppendInt.base": 2, + "strconv.AppendUint.base": 2, + "strconv.FormatComplex.bitSize": 3, + "strconv.FormatComplex.fmt": 1, + "strconv.FormatFloat.bitSize": 3, + "strconv.FormatFloat.fmt": 1, + "strconv.FormatInt.base": 1, + "strconv.FormatUint.base": 1, + "strconv.ParseComplex.bitSize": 1, + "strconv.ParseFloat.bitSize": 1, + "strconv.ParseInt.base": 1, + "strconv.ParseInt.bitSize": 2, + "strconv.ParseUint.base": 1, + "strconv.ParseUint.bitSize": 2, + "time.Parse.layout": 0, + "time.Sleep.d": 0, + "xml.Marshal.v": 0, + "xml.MarshalIndent.v": 0, + "xml.Unmarshal.v": 1, +} + +// Arg turns the name of an argument into an argument index. +// Indices are zero-based and method receivers do not count as arguments. +// +// Arg refers to a manually compiled mapping (see the Args variable.) +// Modify the knowledge package to add new arguments. +func Arg(name string) int { + n, ok := Args[name] + if !ok { + panic("unknown argument " + name) + } + return n +} diff --git a/vendor/honnef.co/go/tools/knowledge/deprecated.go b/vendor/honnef.co/go/tools/knowledge/deprecated.go new file mode 100644 index 0000000..256f375 --- /dev/null +++ b/vendor/honnef.co/go/tools/knowledge/deprecated.go @@ -0,0 +1,256 @@ +package knowledge + +const ( + // DeprecatedNeverUse indicates that an API should never be used, regardless of Go version. + DeprecatedNeverUse = "never" + // DeprecatedUseNoLonger indicates that an API has no use anymore. + DeprecatedUseNoLonger = "no longer" +) + +// Deprecation describes when a Go API has been deprecated. +type Deprecation struct { + // The minor Go version since which this API has been deprecated. + DeprecatedSince string + // The minor Go version since which an alternative API has been available. + // May also be one of DeprecatedNeverUse or DeprecatedUseNoLonger. + AlternativeAvailableSince string +} + +// go/importer.ForCompiler contains "Deprecated:", but it refers to a single argument, not the whole function. +// Luckily, the notice starts in the middle of a paragraph, and as such isn't detected by us. + +// TODO(dh): StdlibDeprecations doesn't contain entries for internal packages and unexported API. That's fine for normal +// users, but makes the Deprecated check less useful for people working on Go itself. + +// StdlibDeprecations contains a mapping of Go API (such as variables, methods, or fields, among others) +// to information about when it has been deprecated. +var StdlibDeprecations = map[string]Deprecation{ + // FIXME(dh): AllowBinary isn't being detected as deprecated + // because the comment has a newline right after "Deprecated:" + "go/build.AllowBinary": {"go1.7", "go1.7"}, + "(archive/zip.FileHeader).CompressedSize": {"go1.1", "go1.1"}, + "(archive/zip.FileHeader).UncompressedSize": {"go1.1", "go1.1"}, + "(archive/zip.FileHeader).ModifiedTime": {"go1.10", "go1.10"}, + "(archive/zip.FileHeader).ModifiedDate": {"go1.10", "go1.10"}, + "(*archive/zip.FileHeader).ModTime": {"go1.10", "go1.10"}, + "(*archive/zip.FileHeader).SetModTime": {"go1.10", "go1.10"}, + "(go/doc.Package).Bugs": {"go1.1", "go1.1"}, + "os.SEEK_SET": {"go1.7", "go1.7"}, + "os.SEEK_CUR": {"go1.7", "go1.7"}, + "os.SEEK_END": {"go1.7", "go1.7"}, + "(net.Dialer).Cancel": {"go1.7", "go1.7"}, + "runtime.CPUProfile": {"go1.9", "go1.0"}, + "compress/flate.ReadError": {"go1.6", DeprecatedUseNoLonger}, + "compress/flate.WriteError": {"go1.6", DeprecatedUseNoLonger}, + "path/filepath.HasPrefix": {"go1.0", DeprecatedNeverUse}, + "(net/http.Transport).Dial": {"go1.7", "go1.7"}, + "(net/http.Transport).DialTLS": {"go1.14", "go1.14"}, + "(*net/http.Transport).CancelRequest": {"go1.6", "go1.5"}, + "net/http.ErrWriteAfterFlush": {"go1.7", DeprecatedUseNoLonger}, + "net/http.ErrHeaderTooLong": {"go1.8", DeprecatedUseNoLonger}, + "net/http.ErrShortBody": {"go1.8", DeprecatedUseNoLonger}, + "net/http.ErrMissingContentLength": {"go1.8", DeprecatedUseNoLonger}, + "net/http/httputil.ErrPersistEOF": {"go1.0", DeprecatedUseNoLonger}, + "net/http/httputil.ErrClosed": {"go1.0", DeprecatedUseNoLonger}, + "net/http/httputil.ErrPipeline": {"go1.0", DeprecatedUseNoLonger}, + "net/http/httputil.ServerConn": {"go1.0", "go1.0"}, + "net/http/httputil.NewServerConn": {"go1.0", "go1.0"}, + "net/http/httputil.ClientConn": {"go1.0", "go1.0"}, + "net/http/httputil.NewClientConn": {"go1.0", "go1.0"}, + "net/http/httputil.NewProxyClientConn": {"go1.0", "go1.0"}, + "(net/http.Request).Cancel": {"go1.7", "go1.7"}, + "(text/template/parse.PipeNode).Line": {"go1.1", DeprecatedUseNoLonger}, + "(text/template/parse.ActionNode).Line": {"go1.1", DeprecatedUseNoLonger}, + "(text/template/parse.BranchNode).Line": {"go1.1", DeprecatedUseNoLonger}, + "(text/template/parse.TemplateNode).Line": {"go1.1", DeprecatedUseNoLonger}, + "database/sql/driver.ColumnConverter": {"go1.9", "go1.9"}, + "database/sql/driver.Execer": {"go1.8", "go1.8"}, + "database/sql/driver.Queryer": {"go1.8", "go1.8"}, + "(database/sql/driver.Conn).Begin": {"go1.8", "go1.8"}, + "(database/sql/driver.Stmt).Exec": {"go1.8", "go1.8"}, + "(database/sql/driver.Stmt).Query": {"go1.8", "go1.8"}, + "syscall.StringByteSlice": {"go1.1", "go1.1"}, + "syscall.StringBytePtr": {"go1.1", "go1.1"}, + "syscall.StringSlicePtr": {"go1.1", "go1.1"}, + "syscall.StringToUTF16": {"go1.1", "go1.1"}, + "syscall.StringToUTF16Ptr": {"go1.1", "go1.1"}, + "(*regexp.Regexp).Copy": {"go1.12", DeprecatedUseNoLonger}, + "(archive/tar.Header).Xattrs": {"go1.10", "go1.10"}, + "archive/tar.TypeRegA": {"go1.11", "go1.1"}, + "go/types.NewInterface": {"go1.11", "go1.11"}, + "(*go/types.Interface).Embedded": {"go1.11", "go1.11"}, + "go/importer.For": {"go1.12", "go1.12"}, + "encoding/json.InvalidUTF8Error": {"go1.2", DeprecatedUseNoLonger}, + "encoding/json.UnmarshalFieldError": {"go1.2", DeprecatedUseNoLonger}, + "encoding/csv.ErrTrailingComma": {"go1.2", DeprecatedUseNoLonger}, + "(encoding/csv.Reader).TrailingComma": {"go1.2", DeprecatedUseNoLonger}, + "(net.Dialer).DualStack": {"go1.12", "go1.12"}, + "net/http.ErrUnexpectedTrailer": {"go1.12", DeprecatedUseNoLonger}, + "net/http.CloseNotifier": {"go1.11", "go1.7"}, + // This is hairy. The notice says "Not all errors in the http package related to protocol errors are of type ProtocolError", but doesn't that imply that some errors do? + "net/http.ProtocolError": {"go1.8", DeprecatedUseNoLonger}, + "(crypto/x509.CertificateRequest).Attributes": {"go1.5", "go1.3"}, + "(*crypto/x509.Certificate).CheckCRLSignature": {"go1.19", "go1.19"}, + "crypto/x509.ParseCRL": {"go1.19", "go1.19"}, + "crypto/x509.ParseDERCRL": {"go1.19", "go1.19"}, + "(*crypto/x509.Certificate).CreateCRL": {"go1.19", "go1.19"}, + "crypto/x509/pkix.TBSCertificateList": {"go1.19", "go1.19"}, + "crypto/x509/pkix.RevokedCertificate": {"go1.19", "go1.19"}, + "go/doc.ToHTML": {"go1.20", "go1.20"}, + "go/doc.ToText": {"go1.20", "go1.20"}, + "go/doc.Synopsis": {"go1.20", "go1.20"}, + "math/rand.Seed": {"go1.20", "go1.0"}, + "math/rand.Read": {"go1.20", DeprecatedNeverUse}, + + // These functions have no direct alternative, but they are insecure and should no longer be used. + "crypto/x509.IsEncryptedPEMBlock": {"go1.16", DeprecatedNeverUse}, + "crypto/x509.DecryptPEMBlock": {"go1.16", DeprecatedNeverUse}, + "crypto/x509.EncryptPEMBlock": {"go1.16", DeprecatedNeverUse}, + "crypto/dsa": {"go1.16", DeprecatedNeverUse}, + + // This function has no alternative, but also no purpose. + "(*crypto/rc4.Cipher).Reset": {"go1.12", DeprecatedNeverUse}, + "(net/http/httptest.ResponseRecorder).HeaderMap": {"go1.11", "go1.7"}, + "image.ZP": {"go1.13", "go1.0"}, + "image.ZR": {"go1.13", "go1.0"}, + "(*debug/gosym.LineTable).LineToPC": {"go1.2", "go1.2"}, + "(*debug/gosym.LineTable).PCToLine": {"go1.2", "go1.2"}, + "crypto/tls.VersionSSL30": {"go1.13", DeprecatedNeverUse}, + "(crypto/tls.Config).NameToCertificate": {"go1.14", DeprecatedUseNoLonger}, + "(*crypto/tls.Config).BuildNameToCertificate": {"go1.14", DeprecatedUseNoLonger}, + "(crypto/tls.Config).SessionTicketKey": {"go1.16", "go1.5"}, + // No alternative, no use + "(crypto/tls.ConnectionState).NegotiatedProtocolIsMutual": {"go1.16", DeprecatedNeverUse}, + // No alternative, but insecure + "(crypto/tls.ConnectionState).TLSUnique": {"go1.16", DeprecatedNeverUse}, + "image/jpeg.Reader": {"go1.4", DeprecatedNeverUse}, + + // All of these have been deprecated in favour of external libraries + "syscall.AttachLsf": {"go1.7", "go1.0"}, + "syscall.DetachLsf": {"go1.7", "go1.0"}, + "syscall.LsfSocket": {"go1.7", "go1.0"}, + "syscall.SetLsfPromisc": {"go1.7", "go1.0"}, + "syscall.LsfJump": {"go1.7", "go1.0"}, + "syscall.LsfStmt": {"go1.7", "go1.0"}, + "syscall.BpfStmt": {"go1.7", "go1.0"}, + "syscall.BpfJump": {"go1.7", "go1.0"}, + "syscall.BpfBuflen": {"go1.7", "go1.0"}, + "syscall.SetBpfBuflen": {"go1.7", "go1.0"}, + "syscall.BpfDatalink": {"go1.7", "go1.0"}, + "syscall.SetBpfDatalink": {"go1.7", "go1.0"}, + "syscall.SetBpfPromisc": {"go1.7", "go1.0"}, + "syscall.FlushBpf": {"go1.7", "go1.0"}, + "syscall.BpfInterface": {"go1.7", "go1.0"}, + "syscall.SetBpfInterface": {"go1.7", "go1.0"}, + "syscall.BpfTimeout": {"go1.7", "go1.0"}, + "syscall.SetBpfTimeout": {"go1.7", "go1.0"}, + "syscall.BpfStats": {"go1.7", "go1.0"}, + "syscall.SetBpfImmediate": {"go1.7", "go1.0"}, + "syscall.SetBpf": {"go1.7", "go1.0"}, + "syscall.CheckBpfVersion": {"go1.7", "go1.0"}, + "syscall.BpfHeadercmpl": {"go1.7", "go1.0"}, + "syscall.SetBpfHeadercmpl": {"go1.7", "go1.0"}, + "syscall.RouteRIB": {"go1.8", "go1.0"}, + "syscall.RoutingMessage": {"go1.8", "go1.0"}, + "syscall.RouteMessage": {"go1.8", "go1.0"}, + "syscall.InterfaceMessage": {"go1.8", "go1.0"}, + "syscall.InterfaceAddrMessage": {"go1.8", "go1.0"}, + "syscall.ParseRoutingMessage": {"go1.8", "go1.0"}, + "syscall.ParseRoutingSockaddr": {"go1.8", "go1.0"}, + "syscall.InterfaceAnnounceMessage": {"go1.7", "go1.0"}, + "syscall.InterfaceMulticastAddrMessage": {"go1.7", "go1.0"}, + "syscall.FormatMessage": {"go1.5", "go1.0"}, + "syscall.PostQueuedCompletionStatus": {"go1.17", "go1.0"}, + "syscall.GetQueuedCompletionStatus": {"go1.17", "go1.0"}, + "syscall.CreateIoCompletionPort": {"go1.17", "go1.0"}, + + // We choose to only track the package itself, even though all functions are deprecated individually, too. Anyone + // using ioutil directly will have to import it, and this keeps the noise down. + "io/ioutil": {"go1.19", "go1.19"}, + + "bytes.Title": {"go1.18", "go1.0"}, + "strings.Title": {"go1.18", "go1.0"}, + "(crypto/tls.Config).PreferServerCipherSuites": {"go1.18", DeprecatedUseNoLonger}, + // It's not clear if Subjects was okay to use in the past, so we err on the less noisy side of assuming that it was. + "(*crypto/x509.CertPool).Subjects": {"go1.18", DeprecatedUseNoLonger}, + "go/types.NewSignature": {"go1.18", "go1.18"}, + "(net.Error).Temporary": {"go1.18", DeprecatedNeverUse}, + // InterfaceData is another tricky case. It was deprecated in Go 1.18, but has been useless since Go 1.4, and an + // "alternative" (using your own unsafe hacks) has existed forever. We don't want to get into hairsplitting with + // users who somehow successfully used this between 1.4 and 1.18, so we'll just tag it as deprecated since 1.18. + "(reflect.Value).InterfaceData": {"go1.18", "go1.18"}, + + // The following objects are only deprecated on Windows. + "syscall.Syscall": {"go1.18", "go1.18"}, + "syscall.Syscall12": {"go1.18", "go1.18"}, + "syscall.Syscall15": {"go1.18", "go1.18"}, + "syscall.Syscall18": {"go1.18", "go1.18"}, + "syscall.Syscall6": {"go1.18", "go1.18"}, + "syscall.Syscall9": {"go1.18", "go1.18"}, + + "reflect.SliceHeader": {"go1.21", "go1.17"}, + "reflect.StringHeader": {"go1.21", "go1.20"}, + "crypto/elliptic.GenerateKey": {"go1.21", "go1.21"}, + "crypto/elliptic.Marshal": {"go1.21", "go1.21"}, + "crypto/elliptic.Unmarshal": {"go1.21", "go1.21"}, + "(*crypto/elliptic.CurveParams).Add": {"go1.21", "go1.21"}, + "(*crypto/elliptic.CurveParams).Double": {"go1.21", "go1.21"}, + "(*crypto/elliptic.CurveParams).IsOnCurve": {"go1.21", "go1.21"}, + "(*crypto/elliptic.CurveParams).ScalarBaseMult": {"go1.21", "go1.21"}, + "(*crypto/elliptic.CurveParams).ScalarMult": {"go1.21", "go1.21"}, + "(crypto/elliptic.Curve).Add": {"go1.21", "go1.21"}, + "(crypto/elliptic.Curve).Double": {"go1.21", "go1.21"}, + "(crypto/elliptic.Curve).IsOnCurve": {"go1.21", "go1.21"}, + "(crypto/elliptic.Curve).ScalarBaseMult": {"go1.21", "go1.21"}, + "(crypto/elliptic.Curve).ScalarMult": {"go1.21", "go1.21"}, + + "crypto/rsa.GenerateMultiPrimeKey": {"go1.21", DeprecatedNeverUse}, + "(crypto/rsa.PrecomputedValues).CRTValues": {"go1.21", DeprecatedNeverUse}, + "(crypto/x509.RevocationList).RevokedCertificates": {"go1.21", "go1.21"}, + + "go/ast.NewPackage": {"go1.22", "go1.0"}, + "go/ast.Importer": {"go1.22", "go1.0"}, + "go/ast.Object": {"go1.22", "go1.0"}, + "go/ast.Package": {"go1.22", "go1.0"}, + "go/ast.Scope": {"go1.22", "go1.0"}, + "html/template.ErrJSTemplate": {"go1.22", DeprecatedUseNoLonger}, + "reflect.PtrTo": {"go1.22", "go1.18"}, + + // Technically, runtime.GOROOT could be considered DeprecatedNeverUse, but + // using it used to be a lot more common and accepted. + "runtime.GOROOT": {"go1.24", DeprecatedUseNoLonger}, + // These are never safe to use; a concrete alternative was added in Go 1.2 (crypto/cipher.AEAD). + "crypto/cipher.NewCFBDecrypter": {"go1.24", "go1.2"}, + "crypto/cipher.NewCFBEncrypter": {"go1.24", "go1.2"}, + "crypto/cipher.NewOFB": {"go1.24", "go1.2"}, + + "go/ast.FilterFuncDuplicates": {"go1.25", "go1.0"}, + "go/ast.FilterImportDuplicates": {"go1.25", "go1.0"}, + "go/ast.FilterUnassociatedComments": {"go1.25", "go1.0"}, + "go/ast.FilterPackage": {"go1.25", "go1.0"}, + "go/ast.MergePackageFiles": {"go1.25", "go1.0"}, + "go/ast.PackageExports": {"go1.25", "go1.0"}, + "go/ast.MergeMode": {"go1.25", "go1.0"}, + // Go 1.11 because that's around the time x/tools/go/packages was released. + "go/parser.ParseDir": {"go1.25", "go1.11"}, + + // Go 1.25 is the first version to provide all of the alternatives mentioned + // by the deprecation note. + "(crypto/ecdsa.PublicKey).X": {"go1.26", "go1.25"}, + "(crypto/ecdsa.PublicKey).Y": {"go1.26", "go1.25"}, + "(crypto/ecdsa.PrivateKey).D": {"go1.26", "go1.25"}, + + "crypto/rsa.DecryptPKCS1v15": {"go1.26", DeprecatedNeverUse}, + "crypto/rsa.DecryptPKCS1v15SessionKey": {"go1.26", DeprecatedNeverUse}, + "crypto/rsa.PKCS1v15DecryptOptions": {"go1.26", DeprecatedNeverUse}, + "crypto/rsa.EncryptPKCS1v15": {"go1.26", DeprecatedNeverUse}, + + "(net/http/httputil.ReverseProxy).Director": {"go1.26", "go1.20"}, + + // Note that the available alternative, testing/cryptotest.SetGlobalRandom + // is only an alternative for testing, not all possible uses of the + // deprecated field. + "(crypto/tls.Config).Rand": {"go1.27", "go1.26"}, +} + +// Last imported from GOROOT/api/go1.26.txt at d3ddc4854429185e6e06ca1f7628bb790404abb5. diff --git a/vendor/honnef.co/go/tools/knowledge/doc.go b/vendor/honnef.co/go/tools/knowledge/doc.go new file mode 100644 index 0000000..72ff524 --- /dev/null +++ b/vendor/honnef.co/go/tools/knowledge/doc.go @@ -0,0 +1,2 @@ +// Package knowledge contains manually collected information about Go APIs. +package knowledge diff --git a/vendor/honnef.co/go/tools/knowledge/signatures.go b/vendor/honnef.co/go/tools/knowledge/signatures.go new file mode 100644 index 0000000..03f4d53 --- /dev/null +++ b/vendor/honnef.co/go/tools/knowledge/signatures.go @@ -0,0 +1,107 @@ +package knowledge + +import ( + "go/token" + "go/types" +) + +var Signatures = map[string]*types.Signature{ + "(io.Seeker).Seek": types.NewSignatureType(nil, nil, nil, + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.Int64]), + types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]), + ), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.Int64]), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), + false, + ), + + "(io.Writer).Write": types.NewSignatureType(nil, nil, nil, + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])), + ), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), + false, + ), + + "(io.StringWriter).WriteString": types.NewSignatureType(nil, nil, nil, + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.String]), + ), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.Int]), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), + false, + ), + + "(encoding.TextMarshaler).MarshalText": types.NewSignatureType(nil, nil, nil, + types.NewTuple(), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), + false, + ), + + "(encoding/json.Marshaler).MarshalJSON": types.NewSignatureType(nil, nil, nil, + types.NewTuple(), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.NewSlice(types.Typ[types.Byte])), + types.NewParam(token.NoPos, nil, "", types.Universe.Lookup("error").Type()), + ), + false, + ), + + "(fmt.Stringer).String": types.NewSignatureType(nil, nil, nil, + types.NewTuple(), + types.NewTuple( + types.NewParam(token.NoPos, nil, "", types.Typ[types.String]), + ), + false, + ), +} + +var Interfaces = map[string]*types.Interface{ + "fmt.Stringer": types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "String", Signatures["(fmt.Stringer).String"]), + }, + nil, + ).Complete(), + + "error": types.Universe.Lookup("error").Type().Underlying().(*types.Interface), + + "io.Writer": types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "Write", Signatures["(io.Writer).Write"]), + }, + nil, + ).Complete(), + + "io.StringWriter": types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "WriteString", Signatures["(io.StringWriter).WriteString"]), + }, + nil, + ).Complete(), + + "encoding.TextMarshaler": types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalText", Signatures["(encoding.TextMarshaler).MarshalText"]), + }, + nil, + ).Complete(), + + "encoding/json.Marshaler": types.NewInterfaceType( + []*types.Func{ + types.NewFunc(token.NoPos, nil, "MarshalJSON", Signatures["(encoding/json.Marshaler).MarshalJSON"]), + }, + nil, + ).Complete(), +} diff --git a/vendor/honnef.co/go/tools/knowledge/targets.go b/vendor/honnef.co/go/tools/knowledge/targets.go new file mode 100644 index 0000000..d9c44f8 --- /dev/null +++ b/vendor/honnef.co/go/tools/knowledge/targets.go @@ -0,0 +1,38 @@ +package knowledge + +var KnownGOOS = map[string]struct{}{ + "aix": {}, + "android": {}, + "darwin": {}, + "dragonfly": {}, + "freebsd": {}, + "hurd": {}, + "illumos": {}, + "ios": {}, + "js": {}, + "linux": {}, + "netbsd": {}, + "openbsd": {}, + "plan9": {}, + "solaris": {}, + "wasip1": {}, + "windows": {}, +} + +var KnownGOARCH = map[string]struct{}{ + "386": {}, + "amd64": {}, + "arm": {}, + "arm64": {}, + "loong64": {}, + "mips": {}, + "mipsle": {}, + "mips64": {}, + "mips64le": {}, + "ppc64": {}, + "ppc64le": {}, + "riscv64": {}, + "s390x": {}, + "sparc64": {}, + "wasm": {}, +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/UPSTREAM b/vendor/honnef.co/go/tools/lintcmd/cache/UPSTREAM new file mode 100644 index 0000000..2b0b42f --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/UPSTREAM @@ -0,0 +1,10 @@ +This package is a copy of cmd/go/internal/cache. + +Differences from upstream: +- we continue to use renameio instead of lockedfile for writing trim.txt +- we still use I/O helpers that work with earlier versions of Go. +- we use a cache directory specific to Staticcheck +- we use a Staticcheck-specific salt + +The last upstream commit we've looked at was: +06ac303f6a14b133254f757e54599c48e3c2a4ad diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/cache.go b/vendor/honnef.co/go/tools/lintcmd/cache/cache.go new file mode 100644 index 0000000..d0f4a57 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/cache.go @@ -0,0 +1,566 @@ +// Copyright 2017 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 cache implements a build artifact cache. +// +// This package is a slightly modified fork of Go's +// cmd/go/internal/cache package. +package cache + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "honnef.co/go/tools/internal/renameio" +) + +// An ActionID is a cache action key, the hash of a complete description of a +// repeatable computation (command line, environment variables, +// input file contents, executable contents). +type ActionID [HashSize]byte + +// An OutputID is a cache output key, the hash of an output of a computation. +type OutputID [HashSize]byte + +// Cache is the interface for a build artifact cache. +type Cache interface { + // Get returns the cache entry for the provided ActionID. + // On miss, the error type should be of type *entryNotFoundError. + Get(ActionID) (Entry, error) + + // Put adds an item to the cache. The seeker is only used to seek to + // the beginning. After a call to Put, the seek position is not + // guaranteed to be in any particular state. + // + // As a special case, if the ReadSeeker is of type noVerifyReadSeeker, + // the verification from GODEBUG=gocacheverify=1 is skipped. + Put(ActionID, io.ReadSeeker) (OutputID, int64, error) + + // Close closes the cache and releases any resources. For DiskCache + // this trims old entries. + Close() error + + // OutputFile returns the path to the on-disk file for the given OutputID. + OutputFile(OutputID) string +} + +// A DiskCache is a package cache backed by a file system directory tree. +type DiskCache struct { + dir string + now func() time.Time +} + +// Open opens and returns the cache in the given directory. +// +// It is safe for multiple processes on a single machine to use the +// same cache directory in a local file system simultaneously. +// They will coordinate using operating system file locks and may +// duplicate effort but will not corrupt the cache. +// +// However, it is NOT safe for multiple processes on different machines +// to share a cache directory (for example, if the directory were stored +// in a network file system). File locking is notoriously unreliable in +// network file systems and may not suffice to protect the cache. +func Open(dir string) (*DiskCache, error) { + info, err := os.Stat(dir) + if err != nil { + return nil, err + } + if !info.IsDir() { + return nil, &os.PathError{Op: "open", Path: dir, Err: fmt.Errorf("not a directory")} + } + for i := range 256 { + name := filepath.Join(dir, fmt.Sprintf("%02x", i)) + if err := os.MkdirAll(name, 0777); err != nil { + return nil, err + } + } + c := &DiskCache{ + dir: dir, + now: time.Now, + } + return c, nil +} + +// fileName returns the name of the file corresponding to the given id. +func (c *DiskCache) fileName(id [HashSize]byte, key string) string { + return filepath.Join(c.dir, fmt.Sprintf("%02x", id[0]), fmt.Sprintf("%x", id)+"-"+key) +} + +// An entryNotFoundError indicates that a cache entry was not found, with an +// optional underlying reason. +type entryNotFoundError struct { + Err error +} + +func (e *entryNotFoundError) Error() string { + if e.Err == nil { + return "cache entry not found" + } + return fmt.Sprintf("cache entry not found: %v", e.Err) +} + +func (e *entryNotFoundError) Unwrap() error { + return e.Err +} + +const ( + // action entry file is "v1 \n" + hexSize = HashSize * 2 + entrySize = 2 + 1 + hexSize + 1 + hexSize + 1 + 20 + 1 + 20 + 1 +) + +// verify controls whether to run the cache in verify mode. +// In verify mode, the cache always returns errMissing from Get +// but then double-checks in Put that the data being written +// exactly matches any existing entry. This provides an easy +// way to detect program behavior that would have been different +// had the cache entry been returned from Get. +// +// verify is enabled by setting the environment variable +// GODEBUG=gocacheverify=1. +var verify = false + +var errVerifyMode = errors.New("gocacheverify=1") + +// DebugTest is set when GODEBUG=gocachetest=1 is in the environment. +var DebugTest = false + +func init() { initEnv() } + +func initEnv() { + verify = false + debugHash = false + debug := strings.SplitSeq(os.Getenv("GODEBUG"), ",") + for f := range debug { + if f == "gocacheverify=1" { + verify = true + } + if f == "gocachehash=1" { + debugHash = true + } + if f == "gocachetest=1" { + DebugTest = true + } + } +} + +// Get looks up the action ID in the cache, +// returning the corresponding output ID and file size, if any. +// Note that finding an output ID does not guarantee that the +// saved file for that output ID is still available. +func (c *DiskCache) Get(id ActionID) (Entry, error) { + if verify { + return Entry{}, &entryNotFoundError{Err: errVerifyMode} + } + return c.get(id) +} + +type Entry struct { + OutputID OutputID + Size int64 + Time time.Time +} + +// get is Get but does not respect verify mode, so that Put can use it. +func (c *DiskCache) get(id ActionID) (Entry, error) { + missing := func(reason error) (Entry, error) { + return Entry{}, &entryNotFoundError{Err: reason} + } + f, err := os.Open(c.fileName(id, "a")) + if err != nil { + return missing(err) + } + defer f.Close() + entry := make([]byte, entrySize+1) // +1 to detect whether f is too long + if n, err := io.ReadFull(f, entry); n > entrySize { + return missing(errors.New("too long")) + } else if err != io.ErrUnexpectedEOF { + if err == io.EOF { + return missing(errors.New("file is empty")) + } + return missing(err) + } else if n < entrySize { + return missing(errors.New("entry file incomplete")) + } + if entry[0] != 'v' || entry[1] != '1' || entry[2] != ' ' || entry[3+hexSize] != ' ' || entry[3+hexSize+1+hexSize] != ' ' || entry[3+hexSize+1+hexSize+1+20] != ' ' || entry[entrySize-1] != '\n' { + return missing(errors.New("invalid header")) + } + eid, entry := entry[3:3+hexSize], entry[3+hexSize:] + eout, entry := entry[1:1+hexSize], entry[1+hexSize:] + esize, entry := entry[1:1+20], entry[1+20:] + //lint:ignore SA4006 See https://github.com/dominikh/go-tools/issues/465 + etime, entry := entry[1:1+20], entry[1+20:] + var buf [HashSize]byte + if _, err := hex.Decode(buf[:], eid); err != nil { + return missing(fmt.Errorf("decoding ID: %v", err)) + } else if buf != id { + return missing(errors.New("mismatched ID")) + } + if _, err := hex.Decode(buf[:], eout); err != nil { + return missing(fmt.Errorf("decoding output ID: %v", err)) + } + i := 0 + for i < len(esize) && esize[i] == ' ' { + i++ + } + size, err := strconv.ParseInt(string(esize[i:]), 10, 64) + if err != nil { + return missing(fmt.Errorf("parsing size: %v", err)) + } else if size < 0 { + return missing(errors.New("negative size")) + } + i = 0 + for i < len(etime) && etime[i] == ' ' { + i++ + } + tm, err := strconv.ParseInt(string(etime[i:]), 10, 64) + if err != nil { + return missing(fmt.Errorf("parsing timestamp: %v", err)) + } else if tm < 0 { + return missing(errors.New("negative timestamp")) + } + + c.used(c.fileName(id, "a")) + + return Entry{buf, size, time.Unix(0, tm)}, nil +} + +// GetFile looks up the action ID in the cache and returns +// the name of the corresponding data file. +func GetFile(c Cache, id ActionID) (file string, entry Entry, err error) { + entry, err = c.Get(id) + if err != nil { + return "", Entry{}, err + } + file = c.OutputFile(entry.OutputID) + info, err := os.Stat(file) + if err != nil { + return "", Entry{}, &entryNotFoundError{Err: err} + } + if info.Size() != entry.Size { + return "", Entry{}, &entryNotFoundError{Err: errors.New("file incomplete")} + } + return file, entry, nil +} + +// GetBytes looks up the action ID in the cache and returns +// the corresponding output bytes. +// GetBytes should only be used for data that can be expected to fit in memory. +func GetBytes(c Cache, id ActionID) ([]byte, Entry, error) { + entry, err := c.Get(id) + if err != nil { + return nil, entry, err + } + data, _ := os.ReadFile(c.OutputFile(entry.OutputID)) + if sha256.Sum256(data) != entry.OutputID { + return nil, entry, &entryNotFoundError{Err: errors.New("bad checksum")} + } + return data, entry, nil +} + +// OutputFile returns the name of the cache file storing output with the given OutputID. +func (c *DiskCache) OutputFile(out OutputID) string { + file := c.fileName(out, "d") + c.used(file) + return file +} + +// Time constants for cache expiration. +// +// We set the mtime on a cache file on each use, but at most one per mtimeInterval (1 hour), +// to avoid causing many unnecessary inode updates. The mtimes therefore +// roughly reflect "time of last use" but may in fact be older by at most an hour. +// +// We scan the cache for entries to delete at most once per trimInterval (1 day). +// +// When we do scan the cache, we delete entries that have not been used for +// at least trimLimit (5 days). Statistics gathered from a month of usage by +// Go developers found that essentially all reuse of cached entries happened +// within 5 days of the previous reuse. See golang.org/issue/22990. +const ( + mtimeInterval = 1 * time.Hour + trimInterval = 24 * time.Hour + trimLimit = 5 * 24 * time.Hour +) + +// used makes a best-effort attempt to update mtime on file, +// so that mtime reflects cache access time. +// +// Because the reflection only needs to be approximate, +// and to reduce the amount of disk activity caused by using +// cache entries, used only updates the mtime if the current +// mtime is more than an hour old. This heuristic eliminates +// nearly all of the mtime updates that would otherwise happen, +// while still keeping the mtimes useful for cache trimming. +func (c *DiskCache) used(file string) { + info, err := os.Stat(file) + if err == nil && c.now().Sub(info.ModTime()) < mtimeInterval { + return + } + os.Chtimes(file, c.now(), c.now()) +} + +// Trim removes old cache entries that are likely not to be reused. +func (c *DiskCache) Trim() { + now := c.now() + + // We maintain in dir/trim.txt the time of the last completed cache trim. + // If the cache has been trimmed recently enough, do nothing. + // This is the common case. + data, _ := renameio.ReadFile(filepath.Join(c.dir, "trim.txt")) + t, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64) + if err == nil && now.Sub(time.Unix(t, 0)) < trimInterval { + return + } + + // Trim each of the 256 subdirectories. + // We subtract an additional mtimeInterval + // to account for the imprecision of our "last used" mtimes. + cutoff := now.Add(-trimLimit - mtimeInterval) + for i := range 256 { + subdir := filepath.Join(c.dir, fmt.Sprintf("%02x", i)) + c.trimSubdir(subdir, cutoff) + } + + // Ignore errors from here: if we don't write the complete timestamp, the + // cache will appear older than it is, and we'll trim it again next time. + renameio.WriteFile(filepath.Join(c.dir, "trim.txt"), fmt.Appendf(nil, "%d", now.Unix()), 0666) +} + +// Close trims the cache and releases resources. +func (c *DiskCache) Close() error { + c.Trim() + return nil +} + +// trimSubdir trims a single cache subdirectory. +func (c *DiskCache) trimSubdir(subdir string, cutoff time.Time) { + // Read all directory entries from subdir before removing + // any files, in case removing files invalidates the file offset + // in the directory scan. Also, ignore error from f.Readdirnames, + // because we don't care about reporting the error and we still + // want to process any entries found before the error. + f, err := os.Open(subdir) + if err != nil { + return + } + names, _ := f.Readdirnames(-1) + f.Close() + + for _, name := range names { + // Remove only cache entries (xxxx-a and xxxx-d). + if !strings.HasSuffix(name, "-a") && !strings.HasSuffix(name, "-d") { + continue + } + entry := filepath.Join(subdir, name) + info, err := os.Stat(entry) + if err == nil && info.ModTime().Before(cutoff) { + os.Remove(entry) + } + } +} + +// putIndexEntry adds an entry to the cache recording that executing the action +// with the given id produces an output with the given output id (hash) and size. +func (c *DiskCache) putIndexEntry(id ActionID, out OutputID, size int64, allowVerify bool) error { + // Note: We expect that for one reason or another it may happen + // that repeating an action produces a different output hash + // (for example, if the output contains a time stamp or temp dir name). + // While not ideal, this is also not a correctness problem, so we + // don't make a big deal about it. In particular, we leave the action + // cache entries writable specifically so that they can be overwritten. + // + // Setting GODEBUG=gocacheverify=1 does make a big deal: + // in verify mode we are double-checking that the cache entries + // are entirely reproducible. As just noted, this may be unrealistic + // in some cases but the check is also useful for shaking out real bugs. + entry := fmt.Sprintf("v1 %x %x %20d %20d\n", id, out, size, time.Now().UnixNano()) + if verify && allowVerify { + old, err := c.get(id) + if err == nil && (old.OutputID != out || old.Size != size) { + // panic to show stack trace, so we can see what code is generating this cache entry. + msg := fmt.Sprintf("go: internal cache error: cache verify failed: id=%x changed:<<<\n%s\n>>>\nold: %x %d\nnew: %x %d", id, reverseHash(id), out, size, old.OutputID, old.Size) + panic(msg) + } + } + file := c.fileName(id, "a") + + // Copy file to cache directory. + mode := os.O_WRONLY | os.O_CREATE + f, err := os.OpenFile(file, mode, 0666) + if err != nil { + return err + } + _, err = f.WriteString(entry) + if err == nil { + // Truncate the file only *after* writing it. + // (This should be a no-op, but truncate just in case of previous corruption.) + // + // This differs from ioutil.WriteFile, which truncates to 0 *before* writing + // via os.O_TRUNC. Truncating only after writing ensures that a second write + // of the same content to the same file is idempotent, and does not — even + // temporarily! — undo the effect of the first write. + err = f.Truncate(int64(len(entry))) + } + if closeErr := f.Close(); err == nil { + err = closeErr + } + if err != nil { + // TODO(bcmills): This Remove potentially races with another go command writing to file. + // Can we eliminate it? + os.Remove(file) + return err + } + os.Chtimes(file, c.now(), c.now()) // mainly for tests + + return nil +} + +// Put stores the given output in the cache as the output for the action ID. +// It may read file twice. The content of file must not change between the two passes. +func (c *DiskCache) Put(id ActionID, file io.ReadSeeker) (OutputID, int64, error) { + // Unwrap noVerifyReadSeeker to determine verify mode. + allowVerify := true + if nv, ok := file.(noVerifyReadSeeker); ok { + allowVerify = false + file = nv.ReadSeeker + } + return c.put(id, file, allowVerify) +} + +// noVerifyReadSeeker is an io.ReadSeeker wrapper that indicates that +// the Put should skip GODEBUG=gocacheverify=1 verification. +type noVerifyReadSeeker struct { + io.ReadSeeker +} + +// PutNoVerify is like Cache.Put but disables the verify check +// when GODEBUG=gocacheverify=1 is set. +// It is meant for data that is OK to cache but that we expect to vary slightly +// from run to run, like test output containing times and the like. +func PutNoVerify(c Cache, id ActionID, file io.ReadSeeker) (OutputID, int64, error) { + return c.Put(id, noVerifyReadSeeker{file}) +} + +func (c *DiskCache) put(id ActionID, file io.ReadSeeker, allowVerify bool) (OutputID, int64, error) { + // Compute output ID. + h := sha256.New() + if _, err := file.Seek(0, 0); err != nil { + return OutputID{}, 0, err + } + size, err := io.Copy(h, file) + if err != nil { + return OutputID{}, 0, err + } + var out OutputID + h.Sum(out[:0]) + + // Copy to cached output file (if not already present). + if err := c.copyFile(file, out, size); err != nil { + return out, size, err + } + + // Add to cache index. + return out, size, c.putIndexEntry(id, out, size, allowVerify) +} + +// PutBytes stores the given bytes in the cache as the output for the action ID. +func PutBytes(c Cache, id ActionID, data []byte) error { + _, _, err := c.Put(id, bytes.NewReader(data)) + return err +} + +// copyFile copies file into the cache, expecting it to have the given +// output ID and size, if that file is not present already. +func (c *DiskCache) copyFile(file io.ReadSeeker, out OutputID, size int64) error { + name := c.fileName(out, "d") + info, err := os.Stat(name) + if err == nil && info.Size() == size { + // Check hash. + if f, err := os.Open(name); err == nil { + h := sha256.New() + io.Copy(h, f) + f.Close() + var out2 OutputID + h.Sum(out2[:0]) + if out == out2 { + return nil + } + } + // Hash did not match. Fall through and rewrite file. + } + + // Copy file to cache directory. + mode := os.O_RDWR | os.O_CREATE + if err == nil && info.Size() > size { // shouldn't happen but fix in case + mode |= os.O_TRUNC + } + f, err := os.OpenFile(name, mode, 0666) + if err != nil { + return err + } + defer f.Close() + if size == 0 { + // File now exists with correct size. + // Only one possible zero-length file, so contents are OK too. + // Early return here makes sure there's a "last byte" for code below. + return nil + } + + // From here on, if any of the I/O writing the file fails, + // we make a best-effort attempt to truncate the file f + // before returning, to avoid leaving bad bytes in the file. + + // Copy file to f, but also into h to double-check hash. + if _, err := file.Seek(0, 0); err != nil { + f.Truncate(0) + return err + } + h := sha256.New() + w := io.MultiWriter(f, h) + if _, err := io.CopyN(w, file, size-1); err != nil { + f.Truncate(0) + return err + } + // Check last byte before writing it; writing it will make the size match + // what other processes expect to find and might cause them to start + // using the file. + buf := make([]byte, 1) + if _, err := file.Read(buf); err != nil { + f.Truncate(0) + return err + } + h.Write(buf) + sum := h.Sum(nil) + if !bytes.Equal(sum, out[:]) { + f.Truncate(0) + return fmt.Errorf("file content changed underfoot") + } + + // Commit cache file entry. + if _, err := f.Write(buf); err != nil { + f.Truncate(0) + return err + } + if err := f.Close(); err != nil { + // Data might not have been written, + // but file may look like it is the right size. + // To be extra careful, remove cached file. + os.Remove(name) + return err + } + os.Chtimes(name, c.now(), c.now()) // mainly for tests + + return nil +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/cacheprog/cacheprog.go b/vendor/honnef.co/go/tools/lintcmd/cache/cacheprog/cacheprog.go new file mode 100644 index 0000000..6d7e4e7 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/cacheprog/cacheprog.go @@ -0,0 +1,111 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package cacheprog defines the protocol for a GOCACHEPROG program. +// +// This is a port of cmd/go/internal/cacheprog from the Go standard library, +// adapted for use with staticcheck. +package cacheprog + +import ( + "io" + "time" +) + +// Cmd is a command that can be issued to a child process. +// +// If the interface needs to grow, new commands or new versioned commands like +// "get2" can be added in the future. The initial [Response] from the child +// process indicates which commands it supports. +type Cmd string + +const ( + // CmdPut tells the cache program to store an object in the cache. + // + // [Request.ActionID] is the cache key of this object. The cache should + // store [Request.OutputID] and [Request.Body] under this key for a + // later "get" request. It must also store the Body in a file in the local + // file system and return the path to that file in [Response.DiskPath], + // which must exist at least until a "close" request. + CmdPut = Cmd("put") + + // CmdGet tells the cache program to retrieve an object from the cache. + // + // [Request.ActionID] specifies the key of the object to get. If the + // cache does not contain this object, it should set [Response.Miss] to + // true. Otherwise, it should populate the fields of [Response], + // including setting [Response.OutputID] to the OutputID of the original + // "put" request and [Response.DiskPath] to the path of a local file + // containing the Body of the original "put" request. That file must + // continue to exist at least until a "close" request. + CmdGet = Cmd("get") + + // CmdClose requests that the cache program exit gracefully. + // + // The cache program should reply to this request and then exit + // (thus closing its stdout). + CmdClose = Cmd("close") +) + +// Request is the JSON-encoded message that's sent from staticcheck to +// the GOCACHEPROG child process over stdin. Each JSON object is on +// its own line. A Request of type "put" with BodySize > 0 will be followed by +// a line containing a base64-encoded JSON string literal of the body. +type Request struct { + // ID is a unique number per process across all requests. + // It must be echoed in the Response from the child. + ID int64 + + // Command is the type of request. + // Only commands that were declared as supported by the child will be sent. + Command Cmd + + // ActionID is the cache key for "put" and "get" requests. + ActionID []byte `json:",omitempty"` // or nil if not used + + // OutputID is stored with the body for "put" requests. + OutputID []byte `json:",omitempty"` // or nil if not used + + // Body is the body for "put" requests. It's sent after the JSON object + // as a base64-encoded JSON string when BodySize is non-zero. + Body io.Reader `json:"-"` + + // BodySize is the number of bytes of Body. If zero, the body isn't written. + BodySize int64 `json:",omitempty"` +} + +// Response is the JSON response from the child process to staticcheck. +// +// With the exception of the first protocol message that the child writes to its +// stdout with ID==0 and KnownCommands populated, these are only sent in +// response to a Request from staticcheck. +// +// Responses can be sent in any order. The ID must match the request they're +// replying to. +type Response struct { + ID int64 // that corresponds to Request; they can be answered out of order + Err string `json:",omitempty"` // if non-empty, the error + + // KnownCommands is included in the first message that cache helper program + // writes to stdout on startup (with ID==0). It includes the + // Request.Command types that are supported by the program. + // + // This lets the protocol be extended gracefully over time (adding + // "get2", etc), or fail gracefully when needed. It also lets us + // verify the program wants to be a cache helper. + KnownCommands []Cmd `json:",omitempty"` + + // For "get" requests. + + Miss bool `json:",omitempty"` // cache miss + OutputID []byte `json:",omitempty"` // the OutputID stored with the body + Size int64 `json:",omitempty"` // body size in bytes + Time *time.Time `json:",omitempty"` // when the object was put in the cache (optional; used for cache expiration) + + // For "get" and "put" requests. + + // DiskPath is the absolute path on disk of the body corresponding to a + // "get" (on cache hit) or "put" request's ActionID. + DiskPath string `json:",omitempty"` +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/default.go b/vendor/honnef.co/go/tools/lintcmd/cache/default.go new file mode 100644 index 0000000..dad28b4 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/default.go @@ -0,0 +1,89 @@ +// Copyright 2017 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 cache + +import ( + "fmt" + "log" + "os" + "path/filepath" + "sync" +) + +// Default returns the default cache to use. +func Default() (Cache, error) { + defaultOnce.Do(initDefaultCache) + return defaultCache, defaultDirErr +} + +var ( + defaultOnce sync.Once + defaultCache Cache +) + +// cacheREADME is a message stored in a README in the cache directory. +// Because the cache lives outside the normal Go trees, we leave the +// README as a courtesy to explain where it came from. +const cacheREADME = `This directory holds cached build artifacts from staticcheck. +` + +// initDefaultCache does the work of finding the default cache +// the first time Default is called. +func initDefaultCache() { + dir := DefaultDir() + if err := os.MkdirAll(dir, 0777); err != nil { + log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err) + } + if _, err := os.Stat(filepath.Join(dir, "README")); err != nil { + // Best effort. + os.WriteFile(filepath.Join(dir, "README"), []byte(cacheREADME), 0666) + } + + c, err := Open(dir) + if err != nil { + log.Fatalf("failed to initialize build cache at %s: %s\n", dir, err) + } + + if prog := os.Getenv("GOCACHEPROG"); prog != "" { + defaultCache = startCacheProg(prog) + } else { + defaultCache = c + } +} + +var ( + defaultDirOnce sync.Once + defaultDir string + defaultDirErr error +) + +// DefaultDir returns the effective STATICCHECK_CACHE setting. +func DefaultDir() string { + // Save the result of the first call to DefaultDir for later use in + // initDefaultCache. cmd/go/main.go explicitly sets GOCACHE so that + // subprocesses will inherit it, but that means initDefaultCache can't + // otherwise distinguish between an explicit "off" and a UserCacheDir error. + + defaultDirOnce.Do(func() { + defaultDir = os.Getenv("STATICCHECK_CACHE") + if filepath.IsAbs(defaultDir) { + return + } + if defaultDir != "" { + defaultDirErr = fmt.Errorf("STATICCHECK_CACHE is not an absolute path") + return + } + + // Compute default location. + dir, err := os.UserCacheDir() + if err != nil { + defaultDirErr = fmt.Errorf("STATICCHECK_CACHE is not defined and %v", err) + return + } + defaultDir = filepath.Join(dir, "staticcheck") + }) + + return defaultDir +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/hash.go b/vendor/honnef.co/go/tools/lintcmd/cache/hash.go new file mode 100644 index 0000000..75c70ff --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/hash.go @@ -0,0 +1,176 @@ +// Copyright 2017 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 cache + +import ( + "bytes" + "crypto/sha256" + "fmt" + "hash" + "io" + "os" + "sync" +) + +var debugHash = false // set when GODEBUG=gocachehash=1 + +// HashSize is the number of bytes in a hash. +const HashSize = 32 + +// hashSalt is an optional salt prepended to every hash created by NewHash. +// When set (via SetSalt), it is typically the build ID of the current binary, +// so that cache entries are invalidated when the binary changes. +var hashSalt []byte + +// A Hash provides access to the canonical hash function used to index the cache. +// The current implementation uses salted SHA256, but clients must not assume this. +type Hash struct { + h hash.Hash + name string // for debugging + buf *bytes.Buffer // for verify +} + +// Subkey returns an action ID corresponding to mixing a parent +// action ID with a string description of the subkey. +func Subkey(parent ActionID, desc string) ActionID { + h := sha256.New() + h.Write([]byte("subkey:")) + h.Write(parent[:]) + h.Write([]byte(desc)) + var out ActionID + h.Sum(out[:0]) + if debugHash { + fmt.Fprintf(os.Stderr, "HASH subkey %x %q = %x\n", parent, desc, out) + } + if verify { + hashDebug.Lock() + hashDebug.m[out] = fmt.Sprintf("subkey %x %q", parent, desc) + hashDebug.Unlock() + } + return out +} + +// SetSalt sets an optional salt that is prepended to every hash created by +// NewHash. This is typically the build ID of the current binary, ensuring that +// cache entries are invalidated when the binary changes. If not called, +// NewHash operates without a salt. +func SetSalt(b []byte) { + hashSalt = b +} + +// NewHash returns a new Hash. +// The caller is expected to Write data to it and then call Sum. +func NewHash(name string) *Hash { + h := &Hash{h: sha256.New(), name: name} + if debugHash { + fmt.Fprintf(os.Stderr, "HASH[%s]\n", h.name) + } + h.Write(hashSalt) + if verify { + h.buf = new(bytes.Buffer) + } + return h +} + +// Write writes data to the running hash. +func (h *Hash) Write(b []byte) (int, error) { + if debugHash { + fmt.Fprintf(os.Stderr, "HASH[%s]: %q\n", h.name, b) + } + if h.buf != nil { + h.buf.Write(b) + } + return h.h.Write(b) +} + +// Sum returns the hash of the data written previously. +func (h *Hash) Sum() [HashSize]byte { + var out [HashSize]byte + h.h.Sum(out[:0]) + if debugHash { + fmt.Fprintf(os.Stderr, "HASH[%s]: %x\n", h.name, out) + } + if h.buf != nil { + hashDebug.Lock() + if hashDebug.m == nil { + hashDebug.m = make(map[[HashSize]byte]string) + } + hashDebug.m[out] = h.buf.String() + hashDebug.Unlock() + } + return out +} + +// In GODEBUG=gocacheverify=1 mode, +// hashDebug holds the input to every computed hash ID, +// so that we can work backward from the ID involved in a +// cache entry mismatch to a description of what should be there. +var hashDebug struct { + sync.Mutex + m map[[HashSize]byte]string +} + +// reverseHash returns the input used to compute the hash id. +func reverseHash(id [HashSize]byte) string { + hashDebug.Lock() + s := hashDebug.m[id] + hashDebug.Unlock() + return s +} + +var hashFileCache struct { + sync.Mutex + m map[string][HashSize]byte +} + +// FileHash returns the hash of the named file. +// It caches repeated lookups for a given file, +// and the cache entry for a file can be initialized +// using SetFileHash. +// The hash used by FileHash is not the same as +// the hash used by NewHash. +func FileHash(file string) ([HashSize]byte, error) { + hashFileCache.Lock() + out, ok := hashFileCache.m[file] + hashFileCache.Unlock() + + if ok { + return out, nil + } + + h := sha256.New() + f, err := os.Open(file) + if err != nil { + if debugHash { + fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) + } + return [HashSize]byte{}, err + } + _, err = io.Copy(h, f) + f.Close() + if err != nil { + if debugHash { + fmt.Fprintf(os.Stderr, "HASH %s: %v\n", file, err) + } + return [HashSize]byte{}, err + } + h.Sum(out[:0]) + if debugHash { + fmt.Fprintf(os.Stderr, "HASH %s: %x\n", file, out) + } + + SetFileHash(file, out) + return out, nil +} + +// SetFileHash sets the hash returned by FileHash for file. +func SetFileHash(file string, sum [HashSize]byte) { + hashFileCache.Lock() + if hashFileCache.m == nil { + hashFileCache.m = make(map[string][HashSize]byte) + } + hashFileCache.m[file] = sum + hashFileCache.Unlock() +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cache/prog.go b/vendor/honnef.co/go/tools/lintcmd/cache/prog.go new file mode 100644 index 0000000..3c134cb --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cache/prog.go @@ -0,0 +1,353 @@ +// Copyright 2023 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 cache + +import ( + "bufio" + "context" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "os" + "os/exec" + "strings" + "sync" + "sync/atomic" + "time" + + "honnef.co/go/tools/lintcmd/cache/cacheprog" +) + +// ProgCache implements Cache via JSON messages over stdin/stdout to a child +// helper process which can then implement whatever caching policy/mechanism it +// wants. +// +// See https://github.com/golang/go/issues/59719 +type ProgCache struct { + cmd *exec.Cmd + stdout io.ReadCloser // from the child process + stdin io.WriteCloser // to the child process + bw *bufio.Writer // to stdin + jenc *json.Encoder // to bw + + // can are the commands that the child process declared that it supports. + // This is effectively the versioning mechanism. + can map[cacheprog.Cmd]bool + + closing atomic.Bool + ctx context.Context // valid until Close via ctxCancel + ctxCancel context.CancelFunc // called on Close + readLoopDone chan struct{} // closed when readLoop returns + + mu sync.Mutex // guards following fields + nextID int64 + inFlight map[int64]chan<- *cacheprog.Response + outputFile map[OutputID]string // object => abs path on disk + + // writeMu serializes writing to the child process. + // It must never be held at the same time as mu. + writeMu sync.Mutex +} + +// startCacheProg starts the prog binary (with optional space-separated flags) +// and returns a Cache implementation that talks to it. +// +// It blocks a few seconds to wait for the child process to successfully start +// and advertise its capabilities. +func startCacheProg(progAndArgs string) Cache { + args := strings.Fields(progAndArgs) + var prog string + if len(args) > 0 { + prog = args[0] + args = args[1:] + } + + ctx, ctxCancel := context.WithCancel(context.Background()) + + cmd := exec.CommandContext(ctx, prog, args...) + out, err := cmd.StdoutPipe() + if err != nil { + log.Fatalf("StdoutPipe to GOCACHEPROG: %v", err) + } + in, err := cmd.StdinPipe() + if err != nil { + log.Fatalf("StdinPipe to GOCACHEPROG: %v", err) + } + cmd.Stderr = os.Stderr + // On close, we cancel the context. Rather than killing the helper, + // close its stdin. + cmd.Cancel = in.Close + + if err := cmd.Start(); err != nil { + log.Fatalf("error starting GOCACHEPROG program %q: %v", prog, err) + } + + pc := &ProgCache{ + ctx: ctx, + ctxCancel: ctxCancel, + cmd: cmd, + stdout: out, + stdin: in, + bw: bufio.NewWriter(in), + inFlight: make(map[int64]chan<- *cacheprog.Response), + outputFile: make(map[OutputID]string), + readLoopDone: make(chan struct{}), + } + + // Register our interest in the initial protocol message from the child to + // us, saying what it can do. + capResc := make(chan *cacheprog.Response, 1) + pc.inFlight[0] = capResc + + pc.jenc = json.NewEncoder(pc.bw) + go pc.readLoop(pc.readLoopDone) + + // Give the child process a few seconds to report its capabilities. This + // should be instant and not require any slow work by the program. + timer := time.NewTicker(5 * time.Second) + defer timer.Stop() + for { + select { + case <-timer.C: + log.Printf("# still waiting for GOCACHEPROG %v ...", prog) + case capRes := <-capResc: + can := map[cacheprog.Cmd]bool{} + for _, cmd := range capRes.KnownCommands { + can[cmd] = true + } + if len(can) == 0 { + log.Fatalf("GOCACHEPROG %v declared no supported commands", prog) + } + pc.can = can + return pc + } + } +} + +func (c *ProgCache) readLoop(readLoopDone chan<- struct{}) { + defer close(readLoopDone) + jd := json.NewDecoder(c.stdout) + for { + res := new(cacheprog.Response) + if err := jd.Decode(res); err != nil { + if c.closing.Load() { + c.mu.Lock() + for _, ch := range c.inFlight { + close(ch) + } + c.inFlight = nil + c.mu.Unlock() + return // quietly + } + if err == io.EOF { + c.mu.Lock() + inFlight := len(c.inFlight) + c.mu.Unlock() + log.Fatalf("GOCACHEPROG exited pre-Close with %v pending requests", inFlight) + } + log.Fatalf("error reading JSON from GOCACHEPROG: %v", err) + } + c.mu.Lock() + ch, ok := c.inFlight[res.ID] + delete(c.inFlight, res.ID) + c.mu.Unlock() + if ok { + ch <- res + } else { + log.Fatalf("GOCACHEPROG sent response for unknown request ID %v", res.ID) + } + } +} + +var errCacheprogClosed = errors.New("GOCACHEPROG program closed unexpectedly") + +func (c *ProgCache) send(ctx context.Context, req *cacheprog.Request) (*cacheprog.Response, error) { + resc := make(chan *cacheprog.Response, 1) + if err := c.writeToChild(req, resc); err != nil { + return nil, err + } + select { + case res := <-resc: + if res == nil { + return nil, errCacheprogClosed + } + if res.Err != "" { + return nil, errors.New(res.Err) + } + return res, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (c *ProgCache) writeToChild(req *cacheprog.Request, resc chan<- *cacheprog.Response) (err error) { + c.mu.Lock() + if c.inFlight == nil { + c.mu.Unlock() + return errCacheprogClosed + } + c.nextID++ + req.ID = c.nextID + c.inFlight[req.ID] = resc + c.mu.Unlock() + + defer func() { + if err != nil { + c.mu.Lock() + if c.inFlight != nil { + delete(c.inFlight, req.ID) + } + c.mu.Unlock() + } + }() + + c.writeMu.Lock() + defer c.writeMu.Unlock() + + if err := c.jenc.Encode(req); err != nil { + return err + } + if err := c.bw.WriteByte('\n'); err != nil { + return err + } + if req.Body != nil && req.BodySize > 0 { + if err := c.bw.WriteByte('"'); err != nil { + return err + } + e := base64.NewEncoder(base64.StdEncoding, c.bw) + wrote, err := io.Copy(e, req.Body) + if err != nil { + return err + } + if err := e.Close(); err != nil { + return nil + } + if wrote != req.BodySize { + return fmt.Errorf("short write writing body to GOCACHEPROG for action %x, output %x: wrote %v; expected %v", + req.ActionID, req.OutputID, wrote, req.BodySize) + } + if _, err := c.bw.WriteString("\"\n"); err != nil { + return err + } + } + if err := c.bw.Flush(); err != nil { + return err + } + return nil +} + +func (c *ProgCache) Get(a ActionID) (Entry, error) { + if !c.can[cacheprog.CmdGet] { + // They can't do a "get". Maybe they're a write-only cache. + return Entry{}, &entryNotFoundError{} + } + res, err := c.send(c.ctx, &cacheprog.Request{ + Command: cacheprog.CmdGet, + ActionID: a[:], + }) + if err != nil { + return Entry{}, err + } + if res.Miss { + return Entry{}, &entryNotFoundError{} + } + e := Entry{ + Size: res.Size, + } + if res.Time != nil { + e.Time = *res.Time + } else { + e.Time = time.Now() + } + if res.DiskPath == "" { + return Entry{}, &entryNotFoundError{errors.New("GOCACHEPROG didn't populate DiskPath on get hit")} + } + if copy(e.OutputID[:], res.OutputID) != len(res.OutputID) { + return Entry{}, &entryNotFoundError{errors.New("incomplete ProgResponse OutputID")} + } + c.noteOutputFile(e.OutputID, res.DiskPath) + return e, nil +} + +func (c *ProgCache) noteOutputFile(o OutputID, diskPath string) { + c.mu.Lock() + defer c.mu.Unlock() + c.outputFile[o] = diskPath +} + +func (c *ProgCache) OutputFile(o OutputID) string { + c.mu.Lock() + defer c.mu.Unlock() + return c.outputFile[o] +} + +func (c *ProgCache) Put(a ActionID, file io.ReadSeeker) (_ OutputID, size int64, _ error) { + // Unwrap noVerifyReadSeeker; ProgCache doesn't use the verify mode. + if nv, ok := file.(noVerifyReadSeeker); ok { + file = nv.ReadSeeker + } + + // Compute output ID. + h := sha256.New() + if _, err := file.Seek(0, 0); err != nil { + return OutputID{}, 0, err + } + size, err := io.Copy(h, file) + if err != nil { + return OutputID{}, 0, err + } + var out OutputID + h.Sum(out[:0]) + + if _, err := file.Seek(0, 0); err != nil { + return OutputID{}, 0, err + } + + if !c.can[cacheprog.CmdPut] { + // Child is a read-only cache. Do nothing. + return out, size, nil + } + + res, err := c.send(c.ctx, &cacheprog.Request{ + Command: cacheprog.CmdPut, + ActionID: a[:], + OutputID: out[:], + Body: file, + BodySize: size, + }) + if err != nil { + return OutputID{}, 0, err + } + if res.DiskPath == "" { + return OutputID{}, 0, errors.New("GOCACHEPROG didn't return DiskPath in put response") + } + c.noteOutputFile(out, res.DiskPath) + return out, size, err +} + +func (c *ProgCache) Close() error { + c.closing.Store(true) + var err error + + // First write a "close" message to the child so it can exit nicely + // and clean up if it wants. Only after that exchange do we cancel + // the context that kills the process. + if c.can[cacheprog.CmdClose] { + _, err = c.send(c.ctx, &cacheprog.Request{Command: cacheprog.CmdClose}) + if errors.Is(err, errCacheprogClosed) { + // Allow the child to quit without responding to close. + err = nil + } + } + // Cancel the context, which will close the helper's stdin. + c.ctxCancel() + // Wait until the helper closes its stdout. + <-c.readLoopDone + return err +} diff --git a/vendor/honnef.co/go/tools/lintcmd/cmd.go b/vendor/honnef.co/go/tools/lintcmd/cmd.go new file mode 100644 index 0000000..a6bda8f --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/cmd.go @@ -0,0 +1,831 @@ +// Package lintcmd implements the frontend of an analysis runner. +// It serves as the entry-point for the staticcheck command, and can also be used to implement custom linters that behave like staticcheck. +package lintcmd + +import ( + "bufio" + "encoding/gob" + "flag" + "fmt" + "go/token" + stdversion "go/version" + "io" + "log" + "maps" + "os" + "path/filepath" + "reflect" + "runtime" + "runtime/pprof" + "runtime/trace" + "slices" + "sort" + "strings" + "sync" + "time" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/config" + "honnef.co/go/tools/go/loader" + "honnef.co/go/tools/lintcmd/version" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/buildutil" +) + +type buildConfig struct { + Name string + Envs []string + Flags []string +} + +type caseFoldedString struct { + s string +} + +func makeCaseFoldedString(s string) caseFoldedString { + return caseFoldedString{strings.ToLower(s)} +} + +func makeCaseFoldedStrings(ss []string) []caseFoldedString { + out := make([]caseFoldedString, len(ss)) + for i, s := range ss { + out[i] = makeCaseFoldedString(s) + } + return out +} + +func (cs caseFoldedString) String() string { + return cs.s +} + +func (cs caseFoldedString) Index(idx int) byte { + return cs.s[idx] +} + +func (cs caseFoldedString) Slice(start, end int) caseFoldedString { + if end == -1 { + end = len(cs.s) + } + return caseFoldedString{cs.s[start:end]} +} + +func (cs caseFoldedString) Length() int { + return len(cs.s) +} + +// Command represents a linter command line tool. +type Command struct { + name string + analyzers map[caseFoldedString]*lint.Analyzer + version string + machineVersion string + + flags struct { + fs *flag.FlagSet + + tags string + tests bool + showIgnored bool + formatter string + + // mutually exclusive mode flags + explain string + printVersion bool + listChecks bool + merge bool + + matrix bool + + debugCpuprofile string + debugMemprofile string + debugVersion bool + debugNoCompileErrors bool + debugMeasureAnalyzers string + debugTrace string + + checks list + fail list + goVersion versionFlag + } +} + +// NewCommand returns a new Command. +func NewCommand(name string) *Command { + cmd := &Command{ + name: name, + analyzers: map[caseFoldedString]*lint.Analyzer{}, + version: "devel", + machineVersion: "devel", + } + cmd.initFlagSet(name) + return cmd +} + +// SetVersion sets the command's version. +// It is divided into a human part and a machine part. +// For example, Staticcheck 2020.2.1 had the human version "2020.2.1" and the machine version "v0.1.1". +// If you only use Semver, you can set both parts to the same value. +// +// Calling this method is optional. Both versions default to "devel", and we'll attempt to deduce more version information from the Go module. +func (cmd *Command) SetVersion(human, machine string) { + cmd.version = human + cmd.machineVersion = machine +} + +// FlagSet returns the command's flag set. +// This can be used to add additional command line arguments. +func (cmd *Command) FlagSet() *flag.FlagSet { + return cmd.flags.fs +} + +// AddAnalyzers adds analyzers to the command. +// These are lint.Analyzer analyzers, which wrap analysis.Analyzer analyzers, bundling them with structured documentation. +// +// To add analysis.Analyzer analyzers without providing structured documentation, use AddBareAnalyzers. +func (cmd *Command) AddAnalyzers(as ...*lint.Analyzer) { + for _, a := range as { + cmd.analyzers[makeCaseFoldedString(a.Analyzer.Name)] = a + } +} + +// AddBareAnalyzers adds bare analyzers to the command. +func (cmd *Command) AddBareAnalyzers(as ...*analysis.Analyzer) { + for _, a := range as { + var title, text string + if idx := strings.Index(a.Doc, "\n\n"); idx > -1 { + title = a.Doc[:idx] + text = a.Doc[idx+2:] + } + + doc := &lint.RawDocumentation{ + Title: title, + Text: text, + Severity: lint.SeverityWarning, + } + + cmd.analyzers[makeCaseFoldedString(a.Name)] = &lint.Analyzer{ + Doc: doc, + Analyzer: a, + } + } +} + +func (cmd *Command) initFlagSet(name string) { + flags := flag.NewFlagSet("", flag.ExitOnError) + cmd.flags.fs = flags + flags.Usage = usage(name, flags) + + flags.StringVar(&cmd.flags.tags, "tags", "", "List of `build tags`") + flags.BoolVar(&cmd.flags.tests, "tests", true, "Include tests") + flags.BoolVar(&cmd.flags.printVersion, "version", false, "Print version and exit") + flags.BoolVar(&cmd.flags.showIgnored, "show-ignored", false, "Don't filter ignored diagnostics") + flags.StringVar(&cmd.flags.formatter, "f", "text", "Output `format` (valid choices are 'stylish', 'text' and 'json')") + flags.StringVar(&cmd.flags.explain, "explain", "", "Print description of `check`") + flags.BoolVar(&cmd.flags.listChecks, "list-checks", false, "List all available checks") + flags.BoolVar(&cmd.flags.merge, "merge", false, "Merge results of multiple Staticcheck runs") + flags.BoolVar(&cmd.flags.matrix, "matrix", false, "Read a build config matrix from stdin") + + flags.StringVar(&cmd.flags.debugCpuprofile, "debug.cpuprofile", "", "Write CPU profile to `file`") + flags.StringVar(&cmd.flags.debugMemprofile, "debug.memprofile", "", "Write memory profile to `file`") + flags.BoolVar(&cmd.flags.debugVersion, "debug.version", false, "Print detailed version information about this program") + flags.BoolVar(&cmd.flags.debugNoCompileErrors, "debug.no-compile-errors", false, "Don't print compile errors") + flags.StringVar(&cmd.flags.debugMeasureAnalyzers, "debug.measure-analyzers", "", "Write analysis measurements to `file`. `file` will be opened for appending if it already exists.") + flags.StringVar(&cmd.flags.debugTrace, "debug.trace", "", "Write trace to `file`") + + cmd.flags.checks = list{"inherit"} + cmd.flags.fail = list{"all"} + cmd.flags.goVersion = versionFlag("module") + flags.Var(&cmd.flags.checks, "checks", "Comma-separated list of `checks` to enable.") + flags.Var(&cmd.flags.fail, "fail", "Comma-separated list of `checks` that can cause a non-zero exit status.") + flags.Var(&cmd.flags.goVersion, "go", "Target Go `version` in the format '1.x', or the literal 'module' to use the module's Go version") +} + +type list []string + +func (list *list) String() string { + return `"` + strings.Join(*list, ",") + `"` +} + +func (list *list) Set(s string) error { + if s == "" { + *list = nil + return nil + } + + elems := strings.Split(s, ",") + for i, elem := range elems { + elems[i] = strings.TrimSpace(elem) + } + *list = elems + return nil +} + +type versionFlag string + +func (v *versionFlag) String() string { + return fmt.Sprintf("%q", string(*v)) +} + +func (v *versionFlag) Set(s string) error { + if s == "module" { + *v = "module" + } else { + orig := s + if !strings.HasPrefix(s, "go") { + s = "go" + s + } + if stdversion.IsValid(s) { + *v = versionFlag(s) + } else { + return fmt.Errorf("%q is not a valid Go version", orig) + } + } + return nil +} + +// ParseFlags parses command line flags. +// It must be called before calling Run. +// After calling ParseFlags, the values of flags can be accessed. +// +// Example: +// +// cmd.ParseFlags(os.Args[1:]) +func (cmd *Command) ParseFlags(args []string) { + cmd.flags.fs.Parse(args) +} + +// diagnosticDescriptor represents the uniquely identifying information of diagnostics. +type diagnosticDescriptor struct { + Position token.Position + End token.Position + Category string + Message string +} + +func (diag diagnostic) descriptor() diagnosticDescriptor { + return diagnosticDescriptor{ + Position: diag.Position, + End: diag.End, + Category: diag.Category, + Message: diag.Message, + } +} + +type run struct { + checkedFiles map[string]struct{} + diagnostics map[diagnosticDescriptor]diagnostic +} + +func runFromLintResult(res lintResult) run { + out := run{ + checkedFiles: map[string]struct{}{}, + diagnostics: map[diagnosticDescriptor]diagnostic{}, + } + + for _, cf := range res.CheckedFiles { + out.checkedFiles[cf] = struct{}{} + } + for _, diag := range res.Diagnostics { + out.diagnostics[diag.descriptor()] = diag + } + return out +} + +func decodeGob(br io.ByteReader) ([]run, error) { + var runs []run + for { + var res lintResult + if err := gob.NewDecoder(br.(io.Reader)).Decode(&res); err != nil { + if err == io.EOF { + break + } else { + return nil, err + } + } + runs = append(runs, runFromLintResult(res)) + } + return runs, nil +} + +// Execute runs all registered analyzers and reports their findings. +// The status code returned can be used for os.Exit(cmd.Execute()). +func (cmd *Command) Execute() int { + // Set up profiling and tracing + if path := cmd.flags.debugCpuprofile; path != "" { + f, err := os.Create(path) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + } + if path := cmd.flags.debugTrace; path != "" { + f, err := os.Create(path) + if err != nil { + log.Fatal(err) + } + trace.Start(f) + } + + // Update the default config's list of enabled checks + defaultChecks := []string{"all"} + for _, a := range cmd.analyzers { + if a.Doc.NonDefault { + defaultChecks = append(defaultChecks, "-"+a.Analyzer.Name) + } + } + config.DefaultConfig.Checks = defaultChecks + + // Run the appropriate mode + var exit int + switch { + case cmd.flags.debugVersion: + exit = cmd.printDebugVersion() + case cmd.flags.listChecks: + exit = cmd.listChecks() + case cmd.flags.printVersion: + exit = cmd.printVersion() + case cmd.flags.explain != "": + exit = cmd.explain() + case cmd.flags.merge: + exit = cmd.merge() + default: + exit = cmd.lint() + } + + // Stop profiling + if cmd.flags.debugCpuprofile != "" { + pprof.StopCPUProfile() + } + if path := cmd.flags.debugMemprofile; path != "" { + f, err := os.Create(path) + if err != nil { + panic(err) + } + runtime.GC() + pprof.WriteHeapProfile(f) + } + if cmd.flags.debugTrace != "" { + trace.Stop() + } + + return exit +} + +// Run runs all registered analyzers and reports their findings. +// It always calls os.Exit and does not return. +func (cmd *Command) Run() { + os.Exit(cmd.Execute()) +} + +func (cmd *Command) printDebugVersion() int { + version.Verbose(cmd.version, cmd.machineVersion) + return 0 +} + +func (cmd *Command) listChecks() int { + cs := slices.Collect(maps.Values(cmd.analyzers)) + sort.Slice(cs, func(i, j int) bool { + return cs[i].Analyzer.Name < cs[j].Analyzer.Name + }) + for _, c := range cs { + var title string + if c.Doc != nil { + title = c.Doc.Compile().Title + } + fmt.Printf("%s %s\n", c.Analyzer.Name, title) + } + return 0 +} + +func (cmd *Command) printVersion() int { + version.Print(cmd.version, cmd.machineVersion) + return 0 +} + +func (cmd *Command) explain() int { + explain := cmd.flags.explain + check, ok := cmd.analyzers[makeCaseFoldedString(explain)] + if !ok { + fmt.Fprintln(os.Stderr, "Couldn't find check", explain) + return 1 + } + if check.Analyzer.Doc == "" { + fmt.Fprintln(os.Stderr, check.Analyzer.Name, "has no documentation") + return 1 + } + fmt.Println(check.Doc.Compile()) + fmt.Println("Online documentation\n https://staticcheck.dev/docs/checks#" + check.Analyzer.Name) + return 0 +} + +func (cmd *Command) merge() int { + var runs []run + if len(cmd.flags.fs.Args()) == 0 { + var err error + runs, err = decodeGob(bufio.NewReader(os.Stdin)) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("couldn't parse stdin: %s", err)) + return 1 + } + } else { + for _, path := range cmd.flags.fs.Args() { + someRuns, err := func(path string) ([]run, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + br := bufio.NewReader(f) + return decodeGob(br) + }(path) + if err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("couldn't parse file %s: %s", path, err)) + return 1 + } + runs = append(runs, someRuns...) + } + } + + relevantDiagnostics := mergeRuns(runs) + cs := slices.Collect(maps.Values(cmd.analyzers)) + return cmd.printDiagnostics(cs, relevantDiagnostics) +} + +func (cmd *Command) lint() int { + switch cmd.flags.formatter { + case "text", "stylish", "json", "sarif", "binary", "null": + default: + fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter) + return 2 + } + + var bconfs []buildConfig + if cmd.flags.matrix { + if cmd.flags.tags != "" { + fmt.Fprintln(os.Stderr, "cannot use -matrix and -tags together") + return 2 + } + + var err error + bconfs, err = parseBuildConfigs(os.Stdin) + if err != nil { + if perr, ok := err.(parseBuildConfigError); ok { + fmt.Fprintf(os.Stderr, ":%d couldn't parse build matrix: %s\n", perr.line, perr.err) + } else { + fmt.Fprintln(os.Stderr, err) + } + return 2 + } + } else { + bc := buildConfig{} + if cmd.flags.tags != "" { + // Validate that the tags argument is well-formed. go/packages + // doesn't detect malformed build flags and returns unhelpful + // errors. + tf := buildutil.TagsFlag{} + if err := tf.Set(cmd.flags.tags); err != nil { + fmt.Fprintln(os.Stderr, fmt.Errorf("invalid value %q for flag -tags: %s", cmd.flags.tags, err)) + return 1 + } + + bc.Flags = []string{"-tags", cmd.flags.tags} + } + bconfs = append(bconfs, bc) + } + + var measureAnalyzers func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) + if path := cmd.flags.debugMeasureAnalyzers; path != "" { + f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + log.Fatal(err) + } + + mu := &sync.Mutex{} + measureAnalyzers = func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) { + mu.Lock() + defer mu.Unlock() + // FIXME(dh): print pkg.ID + if _, err := fmt.Fprintf(f, "%s\t%s\t%d\n", analysis.Name, pkg, d.Nanoseconds()); err != nil { + log.Println("error writing analysis measurements:", err) + } + } + } + + var runs []run + cs := slices.Collect(maps.Values(cmd.analyzers)) + opts := options{ + analyzers: cs, + patterns: cmd.flags.fs.Args(), + lintTests: cmd.flags.tests, + goVersion: string(cmd.flags.goVersion), + config: config.Config{ + Checks: cmd.flags.checks, + }, + printAnalyzerMeasurement: measureAnalyzers, + } + l, err := newLinter(opts) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } + for _, bconf := range bconfs { + res, err := l.run(bconf) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } + + for _, w := range res.Warnings { + fmt.Fprintln(os.Stderr, "warning:", w) + } + + cwd, err := os.Getwd() + if err != nil { + cwd = "" + } + relPath := func(s string) string { + if cwd == "" { + return filepath.ToSlash(s) + } + out, err := filepath.Rel(cwd, s) + if err != nil { + return filepath.ToSlash(s) + } + return filepath.ToSlash(out) + } + + if cmd.flags.formatter == "binary" { + for i, s := range res.CheckedFiles { + res.CheckedFiles[i] = relPath(s) + } + for i := range res.Diagnostics { + // We turn all paths into relative, /-separated paths. This is to make -merge work correctly when + // merging runs from different OSs, with different absolute paths. + // + // We zero out Offset, because checkouts of code on different OSs may have different kinds of + // newlines and thus different offsets. We don't ever make use of the Offset, anyway. Line and + // column numbers are precomputed. + + d := &res.Diagnostics[i] + d.Position.Filename = relPath(d.Position.Filename) + d.Position.Offset = 0 + d.End.Filename = relPath(d.End.Filename) + d.End.Offset = 0 + for j := range d.Related { + r := &d.Related[j] + r.Position.Filename = relPath(r.Position.Filename) + r.Position.Offset = 0 + r.End.Filename = relPath(r.End.Filename) + r.End.Offset = 0 + } + } + err := gob.NewEncoder(os.Stdout).Encode(res) + if err != nil { + fmt.Fprintf(os.Stderr, "failed writing output: %s\n", err) + return 2 + } + } else { + runs = append(runs, runFromLintResult(res)) + } + } + + l.cache.Close() + + if cmd.flags.formatter != "binary" { + diags := mergeRuns(runs) + return cmd.printDiagnostics(cs, diags) + } + return 0 +} + +func mergeRuns(runs []run) []diagnostic { + var relevantDiagnostics []diagnostic + for _, r := range runs { + for _, diag := range r.diagnostics { + switch diag.MergeIf { + case lint.MergeIfAny: + relevantDiagnostics = append(relevantDiagnostics, diag) + case lint.MergeIfAll: + doPrint := true + for _, r := range runs { + if _, ok := r.checkedFiles[diag.Position.Filename]; ok { + if _, ok := r.diagnostics[diag.descriptor()]; !ok { + doPrint = false + } + } + } + if doPrint { + relevantDiagnostics = append(relevantDiagnostics, diag) + } + } + } + } + return relevantDiagnostics +} + +// printDiagnostics prints the diagnostics and exits the process. +func (cmd *Command) printDiagnostics(cs []*lint.Analyzer, diagnostics []diagnostic) int { + if len(diagnostics) > 1 { + sort.Slice(diagnostics, func(i, j int) bool { + di := diagnostics[i] + dj := diagnostics[j] + pi := di.Position + pj := dj.Position + + if pi.Filename != pj.Filename { + return pi.Filename < pj.Filename + } + if pi.Line != pj.Line { + return pi.Line < pj.Line + } + if pi.Column != pj.Column { + return pi.Column < pj.Column + } + if di.Message != dj.Message { + return di.Message < dj.Message + } + if di.BuildName != dj.BuildName { + return di.BuildName < dj.BuildName + } + return di.Category < dj.Category + }) + + filtered := []diagnostic{ + diagnostics[0], + } + builds := []map[string]struct{}{ + {diagnostics[0].BuildName: {}}, + } + for _, diag := range diagnostics[1:] { + // We may encounter duplicate diagnostics because one file + // can be part of many packages, and because multiple + // build configurations may check the same files. + if !filtered[len(filtered)-1].equal(diag) { + if filtered[len(filtered)-1].descriptor() == diag.descriptor() { + // Diagnostics only differ in build name, track new name + builds[len(filtered)-1][diag.BuildName] = struct{}{} + } else { + filtered = append(filtered, diag) + builds = append(builds, map[string]struct{}{}) + builds[len(filtered)-1][diag.BuildName] = struct{}{} + } + } + } + + var names []string + for i := range filtered { + names = names[:0] + for k := range builds[i] { + names = append(names, k) + } + sort.Strings(names) + filtered[i].BuildName = strings.Join(names, ",") + } + diagnostics = filtered + } + + var f formatter + switch cmd.flags.formatter { + case "text": + f = textFormatter{W: os.Stdout} + case "stylish": + f = &stylishFormatter{W: os.Stdout} + case "json": + f = jsonFormatter{W: os.Stdout} + case "sarif": + f = &sarifFormatter{ + driverName: cmd.name, + driverVersion: cmd.version, + } + if cmd.name == "staticcheck" { + f.(*sarifFormatter).driverName = "Staticcheck" + f.(*sarifFormatter).driverWebsite = "https://staticcheck.dev" + } + case "binary": + fmt.Fprintln(os.Stderr, "'-f binary' not supported in this context") + return 2 + case "null": + f = nullFormatter{} + default: + fmt.Fprintf(os.Stderr, "unsupported output format %q\n", cmd.flags.formatter) + return 2 + } + + fail := makeCaseFoldedStrings(cmd.flags.fail) + analyzerNames := make([]caseFoldedString, len(cs)) + for i, a := range cs { + analyzerNames[i] = makeCaseFoldedString(a.Analyzer.Name) + } + shouldExit := filterAnalyzerNames(analyzerNames, fail) + shouldExit[makeCaseFoldedString("staticcheck")] = true + shouldExit[makeCaseFoldedString("compile")] = true + shouldExit[makeCaseFoldedString("config")] = true + + var ( + numErrors int + numWarnings int + numIgnored int + ) + notIgnored := make([]diagnostic, 0, len(diagnostics)) + for _, diag := range diagnostics { + if diag.Category == "compile" && cmd.flags.debugNoCompileErrors { + continue + } + if diag.Severity == severityIgnored && !cmd.flags.showIgnored { + numIgnored++ + continue + } + if shouldExit[makeCaseFoldedString(diag.Category)] { + numErrors++ + } else { + diag.Severity = severityWarning + numWarnings++ + } + notIgnored = append(notIgnored, diag) + } + + f.Format(cs, notIgnored) + if f, ok := f.(statter); ok { + f.Stats(len(diagnostics), numErrors, numWarnings, numIgnored) + } + + if numErrors > 0 { + if _, ok := f.(*sarifFormatter); ok { + // When emitting SARIF, finding errors is considered success. + return 0 + } else { + return 1 + } + } + return 0 +} + +func usage(name string, fs *flag.FlagSet) func() { + return func() { + fmt.Fprintf(os.Stderr, "Usage: %s [flags] [packages]\n", name) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Flags:") + printDefaults(fs) + + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "For help about specifying packages, see 'go help packages'") + } +} + +// isZeroValue determines whether the string represents the zero +// value for a flag. +// +// this function has been copied from the Go standard library's 'flag' package. +func isZeroValue(f *flag.Flag, value string) bool { + // Build a zero value of the flag's Value type, and see if the + // result of calling its String method equals the value passed in. + // This works unless the Value type is itself an interface type. + typ := reflect.TypeOf(f.Value) + var z reflect.Value + if typ.Kind() == reflect.Pointer { + z = reflect.New(typ.Elem()) + } else { + z = reflect.Zero(typ) + } + return value == z.Interface().(flag.Value).String() +} + +// this function has been copied from the Go standard library's 'flag' package and modified to skip debug flags. +func printDefaults(fs *flag.FlagSet) { + fs.VisitAll(func(f *flag.Flag) { + // Don't print debug flags + if strings.HasPrefix(f.Name, "debug.") { + return + } + + var b strings.Builder + fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments. + name, usage := flag.UnquoteUsage(f) + if len(name) > 0 { + b.WriteString(" ") + b.WriteString(name) + } + // Boolean flags of one ASCII letter are so common we + // treat them specially, putting their usage on the same line. + if b.Len() <= 4 { // space, space, '-', 'x'. + b.WriteString("\t") + } else { + // Four spaces before the tab triggers good alignment + // for both 4- and 8-space tab stops. + b.WriteString("\n \t") + } + b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) + + if !isZeroValue(f, f.DefValue) { + if T := reflect.TypeOf(f.Value); T.Name() == "*stringValue" && T.PkgPath() == "flag" { + // put quotes on the value + fmt.Fprintf(&b, " (default %q)", f.DefValue) + } else { + fmt.Fprintf(&b, " (default %v)", f.DefValue) + } + } + fmt.Fprint(fs.Output(), b.String(), "\n") + }) +} diff --git a/vendor/honnef.co/go/tools/lintcmd/config.go b/vendor/honnef.co/go/tools/lintcmd/config.go new file mode 100644 index 0000000..eb38c97 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/config.go @@ -0,0 +1,105 @@ +package lintcmd + +import ( + "bufio" + "errors" + "fmt" + "io" + "strings" + "unicode" +) + +type parseBuildConfigError struct { + line int + err error +} + +func (err parseBuildConfigError) Error() string { return err.err.Error() } + +func parseBuildConfigs(r io.Reader) ([]buildConfig, error) { + var builds []buildConfig + br := bufio.NewReader(r) + i := 0 + for { + line, err := br.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } else { + return nil, err + } + } + line = strings.TrimSpace(line) + if line == "" { + continue + } + name, envs, flags, err := parseBuildConfig(line) + if err != nil { + return nil, parseBuildConfigError{line: i + 1, err: err} + } + + bc := buildConfig{ + Name: name, + Envs: envs, + Flags: flags, + } + builds = append(builds, bc) + + i++ + } + return builds, nil +} + +func parseBuildConfig(line string) (name string, envs []string, flags []string, err error) { + if line == "" { + return "", nil, nil, errors.New("couldn't parse empty build config") + } + if strings.Index(line, ":") == len(line)-1 { + name = line[:len(line)-1] + } else { + before, after, ok := strings.Cut(line, ": ") + if !ok { + return name, envs, flags, errors.New("missing build name") + } + name = before + + var buf []rune + var inQuote bool + args := &envs + for _, r := range strings.TrimSpace(after) { + switch r { + case ' ': + if inQuote { + buf = append(buf, r) + } else if len(buf) != 0 { + if buf[0] == '-' { + args = &flags + } + *args = append(*args, string(buf)) + buf = buf[:0] + } + case '"': + inQuote = !inQuote + default: + buf = append(buf, r) + } + } + + if len(buf) > 0 { + if inQuote { + return "", nil, nil, errors.New("unterminated quoted string") + } + if buf[0] == '-' { + args = &flags + } + *args = append(*args, string(buf)) + } + } + + for _, r := range name { + if !(r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r)) { + return "", nil, nil, fmt.Errorf("invalid build name %q", name) + } + } + return name, envs, flags, nil +} diff --git a/vendor/honnef.co/go/tools/lintcmd/directives.go b/vendor/honnef.co/go/tools/lintcmd/directives.go new file mode 100644 index 0000000..d4f54a0 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/directives.go @@ -0,0 +1,55 @@ +package lintcmd + +import ( + "strings" + + "honnef.co/go/tools/lintcmd/runner" +) + +func parseDirectives(dirs []runner.SerializedDirective) ([]ignore, []diagnostic) { + var ignores []ignore + var diagnostics []diagnostic + + for _, dir := range dirs { + cmd := dir.Command + args := dir.Arguments + switch cmd { + case "ignore", "file-ignore": + if len(args) < 2 { + p := diagnostic{ + Diagnostic: runner.Diagnostic{ + Position: dir.NodePosition, + Message: "malformed linter directive; missing the required reason field?", + Category: "compile", + }, + Severity: severityError, + } + diagnostics = append(diagnostics, p) + continue + } + default: + // unknown directive, ignore + continue + } + checks := strings.Split(args[0], ",") + pos := dir.NodePosition + var ig ignore + switch cmd { + case "ignore": + ig = &lineIgnore{ + File: pos.Filename, + Line: pos.Line, + Checks: makeCaseFoldedStrings(checks), + Pos: dir.DirectivePosition, + } + case "file-ignore": + ig = &fileIgnore{ + File: pos.Filename, + Checks: makeCaseFoldedStrings(checks), + } + } + ignores = append(ignores, ig) + } + + return ignores, diagnostics +} diff --git a/vendor/honnef.co/go/tools/lintcmd/format.go b/vendor/honnef.co/go/tools/lintcmd/format.go new file mode 100644 index 0000000..8a23c3c --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/format.go @@ -0,0 +1,161 @@ +package lintcmd + +import ( + "encoding/json" + "fmt" + "go/token" + "io" + "os" + "path/filepath" + "text/tabwriter" + + "honnef.co/go/tools/analysis/lint" +) + +func shortPath(path string) string { + cwd, err := os.Getwd() + if err != nil { + return path + } + if rel, err := filepath.Rel(cwd, path); err == nil && len(rel) < len(path) { + return rel + } + return path +} + +func relativePositionString(pos token.Position) string { + s := shortPath(pos.Filename) + if pos.IsValid() { + if s != "" { + s += ":" + } + s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) + } + if s == "" { + s = "-" + } + return s +} + +type statter interface { + Stats(total, errors, warnings, ignored int) +} + +type formatter interface { + Format(checks []*lint.Analyzer, diagnostics []diagnostic) +} + +type textFormatter struct { + W io.Writer +} + +func (o textFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) { + for _, p := range ps { + fmt.Fprintf(o.W, "%s: %s\n", relativePositionString(p.Position), p.String()) + for _, r := range p.Related { + fmt.Fprintf(o.W, "\t%s: %s\n", relativePositionString(r.Position), r.Message) + } + } +} + +type nullFormatter struct{} + +func (nullFormatter) Format([]*lint.Analyzer, []diagnostic) {} + +type jsonFormatter struct { + W io.Writer +} + +func (o jsonFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) { + type location struct { + File string `json:"file"` + Line int `json:"line"` + Column int `json:"column"` + } + type related struct { + Location location `json:"location"` + End location `json:"end"` + Message string `json:"message"` + } + + enc := json.NewEncoder(o.W) + for _, p := range ps { + jp := struct { + Code string `json:"code"` + Severity string `json:"severity,omitempty"` + Location location `json:"location"` + End location `json:"end"` + Message string `json:"message"` + Related []related `json:"related,omitempty"` + }{ + Code: p.Category, + Severity: p.Severity.String(), + Location: location{ + File: p.Position.Filename, + Line: p.Position.Line, + Column: p.Position.Column, + }, + End: location{ + File: p.End.Filename, + Line: p.End.Line, + Column: p.End.Column, + }, + Message: p.Message, + } + for _, r := range p.Related { + jp.Related = append(jp.Related, related{ + Location: location{ + File: r.Position.Filename, + Line: r.Position.Line, + Column: r.Position.Column, + }, + End: location{ + File: r.End.Filename, + Line: r.End.Line, + Column: r.End.Column, + }, + Message: r.Message, + }) + } + _ = enc.Encode(jp) + } +} + +type stylishFormatter struct { + W io.Writer + + prevFile string + tw *tabwriter.Writer +} + +func (o *stylishFormatter) Format(_ []*lint.Analyzer, ps []diagnostic) { + for _, p := range ps { + pos := p.Position + if pos.Filename == "" { + pos.Filename = "-" + } + + if pos.Filename != o.prevFile { + if o.prevFile != "" { + o.tw.Flush() + fmt.Fprintln(o.W) + } + fmt.Fprintln(o.W, pos.Filename) + o.prevFile = pos.Filename + o.tw = tabwriter.NewWriter(o.W, 0, 4, 2, ' ', 0) + } + fmt.Fprintf(o.tw, " (%d, %d)\t%s\t%s\n", pos.Line, pos.Column, p.Category, p.Message) + for _, r := range p.Related { + fmt.Fprintf(o.tw, " (%d, %d)\t\t %s\n", r.Position.Line, r.Position.Column, r.Message) + } + } +} + +func (o *stylishFormatter) Stats(total, errors, warnings, ignored int) { + if o.tw != nil { + o.tw.Flush() + fmt.Fprintln(o.W) + } + fmt.Fprintf(o.W, " ✖ %d problems (%d errors, %d warnings, %d ignored)\n", + total, errors, warnings, ignored) +} diff --git a/vendor/honnef.co/go/tools/lintcmd/lint.go b/vendor/honnef.co/go/tools/lintcmd/lint.go new file mode 100644 index 0000000..2147849 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/lint.go @@ -0,0 +1,571 @@ +package lintcmd + +import ( + "crypto/sha256" + "fmt" + "go/token" + "io" + "os" + "os/signal" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + "unicode" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/config" + "honnef.co/go/tools/go/buildid" + "honnef.co/go/tools/go/loader" + "honnef.co/go/tools/lintcmd/cache" + "honnef.co/go/tools/lintcmd/runner" + "honnef.co/go/tools/unused" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/packages" +) + +// A linter lints Go source code. +type linter struct { + analyzers map[caseFoldedString]*lint.Analyzer + cache cache.Cache + opts options +} + +func computeSalt() ([]byte, error) { + p, err := os.Executable() + if err != nil { + return nil, err + } + + if id, err := buildid.ReadFile(p); err == nil { + return []byte(id), nil + } else { + // For some reason we couldn't read the build id from the executable. + // Fall back to hashing the entire executable. + f, err := os.Open(p) + if err != nil { + return nil, err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return nil, err + } + return h.Sum(nil), nil + } +} + +func newLinter(opts options) (*linter, error) { + c, err := cache.Default() + if err != nil { + return nil, err + } + salt, err := computeSalt() + if err != nil { + return nil, fmt.Errorf("could not compute salt for cache: %s", err) + } + cache.SetSalt(salt) + + analyzers := make(map[caseFoldedString]*lint.Analyzer, len(opts.analyzers)) + for _, a := range opts.analyzers { + analyzers[makeCaseFoldedString(a.Analyzer.Name)] = a + } + + return &linter{ + cache: c, + analyzers: analyzers, + opts: opts, + }, nil +} + +type lintResult struct { + // These fields are exported so that we can gob encode them. + + CheckedFiles []string + Diagnostics []diagnostic + Warnings []string +} + +type options struct { + config config.Config + analyzers []*lint.Analyzer + patterns []string + lintTests bool + goVersion string + printAnalyzerMeasurement func(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) +} + +func (l *linter) run(bconf buildConfig) (lintResult, error) { + cfg := &packages.Config{} + if l.opts.lintTests { + cfg.Tests = true + } + + cfg.BuildFlags = bconf.Flags + cfg.Env = append(os.Environ(), bconf.Envs...) + + r, err := runner.New(l.opts.config, l.cache) + if err != nil { + return lintResult{}, err + } + r.GoVersion = l.opts.goVersion + r.Stats.PrintAnalyzerMeasurement = l.opts.printAnalyzerMeasurement + + printStats := func() { + // Individual stats are read atomically, but overall there + // is no synchronisation. For printing rough progress + // information, this doesn't matter. + switch r.Stats.State() { + case runner.StateInitializing: + fmt.Fprintln(os.Stderr, "Status: initializing") + case runner.StateLoadPackageGraph: + fmt.Fprintln(os.Stderr, "Status: loading package graph") + case runner.StateBuildActionGraph: + fmt.Fprintln(os.Stderr, "Status: building action graph") + case runner.StateProcessing: + fmt.Fprintf(os.Stderr, "Packages: %d/%d initial, %d/%d total; Workers: %d/%d\n", + r.Stats.ProcessedInitialPackages(), + r.Stats.InitialPackages(), + r.Stats.ProcessedPackages(), + r.Stats.TotalPackages(), + r.ActiveWorkers(), + r.TotalWorkers(), + ) + case runner.StateFinalizing: + fmt.Fprintln(os.Stderr, "Status: finalizing") + } + } + if len(infoSignals) > 0 { + ch := make(chan os.Signal, 1) + signal.Notify(ch, infoSignals...) + defer signal.Stop(ch) + go func() { + for range ch { + printStats() + } + }() + } + res, err := l.lint(r, cfg, l.opts.patterns) + for i := range res.Diagnostics { + res.Diagnostics[i].BuildName = bconf.Name + } + return res, err +} + +func (l *linter) lint(r *runner.Runner, cfg *packages.Config, patterns []string) (lintResult, error) { + var out lintResult + + as := make([]*analysis.Analyzer, 0, len(l.analyzers)) + for _, a := range l.analyzers { + as = append(as, a.Analyzer) + } + results, err := r.Run(cfg, as, patterns) + if err != nil { + return out, err + } + + if len(results) == 0 { + // TODO(dh): emulate Go's behavior more closely once we have + // access to go list's Match field. + for _, pattern := range patterns { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + } + + analyzerNames := make([]caseFoldedString, 0, len(l.analyzers)) + for name := range l.analyzers { + analyzerNames = append(analyzerNames, name) + } + used := map[unusedKey]bool{} + var unuseds []unusedPair + for _, res := range results { + if len(res.Errors) > 0 && !res.Failed { + panic("package has errors but isn't marked as failed") + } + if res.Failed { + out.Diagnostics = append(out.Diagnostics, failed(res)...) + } else { + if res.Skipped { + out.Warnings = append(out.Warnings, fmt.Sprintf("skipped package %s because it is too large", res.Package)) + continue + } + + if !res.Initial { + continue + } + + out.CheckedFiles = append(out.CheckedFiles, res.Package.GoFiles...) + resChecks := makeCaseFoldedStrings(res.Config.Checks) + allowedAnalyzers := filterAnalyzerNames(analyzerNames, resChecks) + resd, err := res.Load() + if err != nil { + return out, err + } + ps := success(allowedAnalyzers, resd) + filtered, err := filterIgnored(ps, resd, allowedAnalyzers) + if err != nil { + return out, err + } + // OPT move this code into the 'success' function. + for i, diag := range filtered { + a := l.analyzers[makeCaseFoldedString(diag.Category)] + // Some diag.Category don't map to analyzers, such as "staticcheck" + if a != nil { + filtered[i].MergeIf = a.Doc.MergeIf + } + } + out.Diagnostics = append(out.Diagnostics, filtered...) + + for _, obj := range resd.Unused.Used { + // Note: a side-effect of this code is that fields in instantiated structs are handled correctly. Even + // if only an instantiated field is marked as used, we will not flag the generic field, because it has + // the same position as the instance. At some point this won't be necessary anymore because we'll be + // able to make use of the Go 1.19+ Origin methods. + + // FIXME(dh): pick the object whose filename does not include $GOROOT + key := unusedKey{ + pkgPath: res.Package.PkgPath, + base: filepath.Base(obj.Position.Filename), + line: obj.Position.Line, + name: obj.Name, + } + used[key] = true + } + + if allowedAnalyzers[makeCaseFoldedString("U1000")] { + for _, obj := range resd.Unused.Unused { + key := unusedKey{ + pkgPath: res.Package.PkgPath, + base: filepath.Base(obj.Position.Filename), + line: obj.Position.Line, + name: obj.Name, + } + unuseds = append(unuseds, unusedPair{key, obj}) + if _, ok := used[key]; !ok { + used[key] = false + } + } + } + } + } + + for _, uo := range unuseds { + if used[uo.key] { + continue + } + out.Diagnostics = append(out.Diagnostics, diagnostic{ + Diagnostic: runner.Diagnostic{ + Position: uo.obj.DisplayPosition, + Message: fmt.Sprintf("%s %s is unused", uo.obj.Kind, uo.obj.Name), + Category: "U1000", + }, + MergeIf: lint.MergeIfAll, + }) + } + + return out, nil +} + +func filterIgnored( + diagnostics []diagnostic, + res runner.ResultData, + allowedAnalyzers map[caseFoldedString]bool, +) ([]diagnostic, error) { + couldHaveMatched := func(ig *lineIgnore) bool { + for _, c := range ig.Checks { + if c.String() == "u1000" { + // We never want to flag ignores for U1000, + // because U1000 isn't local to a single + // package. For example, an identifier may + // only be used by tests, in which case an + // ignore would only fire when not analyzing + // tests. To avoid spurious "useless ignore" + // warnings, just never flag U1000. + return false + } + + // Even though the runner always runs all analyzers, we + // still only flag unmatched ignores for the set of + // analyzers the user has expressed interest in. That way, + // `staticcheck -checks=SA1000` won't complain about an + // unmatched ignore for an unrelated check. + if allowedAnalyzers[c] { + return true + } + } + + return false + } + + ignores, moreDiagnostics := parseDirectives(res.Directives) + + for _, ig := range ignores { + for i := range diagnostics { + diag := &diagnostics[i] + if ig.match(*diag) { + diag.Severity = severityIgnored + } + } + + if ig, ok := ig.(*lineIgnore); ok && !ig.Matched && couldHaveMatched(ig) { + diag := diagnostic{ + Diagnostic: runner.Diagnostic{ + Position: ig.Pos, + Message: "this linter directive didn't match anything; should it be removed?", + Category: "staticcheck", + }, + } + moreDiagnostics = append(moreDiagnostics, diag) + } + } + + return append(diagnostics, moreDiagnostics...), nil +} + +type ignore interface { + match(diag diagnostic) bool +} + +type lineIgnore struct { + File string + Line int + Checks []caseFoldedString + Matched bool + Pos token.Position +} + +func (li *lineIgnore) match(p diagnostic) bool { + pos := p.Position + if pos.Filename != li.File || pos.Line != li.Line { + return false + } + for _, c := range li.Checks { + if m, _ := filepath.Match(c.String(), makeCaseFoldedString(p.Category).String()); m { + li.Matched = true + return true + } + } + return false +} + +type fileIgnore struct { + File string + Checks []caseFoldedString +} + +func (fi *fileIgnore) match(p diagnostic) bool { + if p.Position.Filename != fi.File { + return false + } + for _, c := range fi.Checks { + if m, _ := filepath.Match(c.String(), makeCaseFoldedString(p.Category).String()); m { + return true + } + } + return false +} + +type severity uint8 + +const ( + severityError severity = iota + severityWarning + severityIgnored +) + +func (s severity) String() string { + switch s { + case severityError: + return "error" + case severityWarning: + return "warning" + case severityIgnored: + return "ignored" + default: + return fmt.Sprintf("Severity(%d)", s) + } +} + +// diagnostic represents a diagnostic in some source code. +type diagnostic struct { + runner.Diagnostic + + // These fields are exported so that we can gob encode them. + Severity severity + MergeIf lint.MergeStrategy + BuildName string +} + +func (p diagnostic) equal(o diagnostic) bool { + return p.Position == o.Position && + p.End == o.End && + p.Message == o.Message && + makeCaseFoldedString(p.Category) == makeCaseFoldedString(o.Category) && + p.Severity == o.Severity && + p.MergeIf == o.MergeIf && + p.BuildName == o.BuildName +} + +func (p *diagnostic) String() string { + if p.BuildName != "" { + return fmt.Sprintf("%s [%s] (%s)", p.Message, p.BuildName, p.Category) + } else { + return fmt.Sprintf("%s (%s)", p.Message, p.Category) + } +} + +func failed(res runner.Result) []diagnostic { + var diagnostics []diagnostic + + for _, e := range res.Errors { + switch e := e.(type) { + case packages.Error: + msg := e.Msg + if len(msg) != 0 && msg[0] == '\n' { + // TODO(dh): See https://github.com/golang/go/issues/32363 + msg = msg[1:] + } + + cat := "compile" + if e.Kind == packages.ParseError { + cat = "config" + } + + var posn token.Position + if e.Pos == "" { + // Under certain conditions (malformed package + // declarations, multiple packages in the same + // directory), go list emits an error on stderr + // instead of JSON. Those errors do not have + // associated position information in + // go/packages.Error, even though the output on + // stderr may contain it. + if p, n, err := parsePos(msg); err == nil { + if abs, err := filepath.Abs(p.Filename); err == nil { + p.Filename = abs + } + posn = p + msg = msg[n+2:] + } + } else { + var err error + posn, _, err = parsePos(e.Pos) + if err != nil { + panic(fmt.Sprintf("internal error: %s", err)) + } + } + diag := diagnostic{ + Diagnostic: runner.Diagnostic{ + Position: posn, + Message: msg, + Category: cat, + }, + Severity: severityError, + } + diagnostics = append(diagnostics, diag) + case error: + diag := diagnostic{ + Diagnostic: runner.Diagnostic{ + Position: token.Position{}, + Message: e.Error(), + Category: "compile", + }, + Severity: severityError, + } + diagnostics = append(diagnostics, diag) + } + } + + return diagnostics +} + +type unusedKey struct { + pkgPath string + base string + line int + name string +} + +type unusedPair struct { + key unusedKey + obj unused.Object +} + +func success(allowedAnalyzers map[caseFoldedString]bool, res runner.ResultData) []diagnostic { + diags := res.Diagnostics + var diagnostics []diagnostic + for _, diag := range diags { + if !allowedAnalyzers[makeCaseFoldedString(diag.Category)] { + continue + } + diagnostics = append(diagnostics, diagnostic{Diagnostic: diag}) + } + return diagnostics +} + +func filterAnalyzerNames(allAnalyzers []caseFoldedString, selection []caseFoldedString) map[caseFoldedString]bool { + allowedChecks := map[caseFoldedString]bool{} + + for _, check := range selection { + b := true + if check.Length() > 1 && check.Index(0) == '-' { + b = false + check = check.Slice(1, -1) + } + if check.String() == "*" || check.String() == "all" { + // Match all + for _, a := range allAnalyzers { + allowedChecks[a] = b + } + } else if strings.HasSuffix(check.String(), "*") { + // Glob + prefix := check.Slice(0, check.Length()-1) + isCat := strings.IndexFunc(prefix.String(), unicode.IsNumber) == -1 + + for _, a := range allAnalyzers { + idx := strings.IndexFunc(a.String(), unicode.IsNumber) + if isCat { + // Glob is S*, which should match S1000 but not SA1000 + cat := a.Slice(0, idx) + if prefix == cat { + allowedChecks[a] = b + } + } else { + // Glob is S1* + if strings.HasPrefix(a.String(), prefix.String()) { + allowedChecks[a] = b + } + } + } + } else { + // Literal check name + allowedChecks[check] = b + } + } + return allowedChecks +} + +// Note that the file name is optional and can be empty because of //line +// directives of the form "//line :1" (but not "//line :1:1"). See +// https://go.dev/issue/24183 and https://staticcheck.dev/issues/1582. +var posRe = regexp.MustCompile(`^(?:(.+?):)?(\d+)(?::(\d+)?)?`) + +func parsePos(pos string) (token.Position, int, error) { + if pos == "-" || pos == "" { + return token.Position{}, 0, nil + } + parts := posRe.FindStringSubmatch(pos) + if parts == nil { + return token.Position{}, 0, fmt.Errorf("malformed position %q", pos) + } + file := parts[1] + line, _ := strconv.Atoi(parts[2]) + col, _ := strconv.Atoi(parts[3]) + return token.Position{ + Filename: file, + Line: line, + Column: col, + }, len(parts[0]), nil +} diff --git a/vendor/honnef.co/go/tools/lintcmd/runner/runner.go b/vendor/honnef.co/go/tools/lintcmd/runner/runner.go new file mode 100644 index 0000000..a963cbe --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/runner/runner.go @@ -0,0 +1,1246 @@ +// Package runner implements a go/analysis runner. It makes heavy use +// of on-disk caching to reduce overall memory usage and to speed up +// repeat runs. +// +// # Public API +// +// A Runner maps a list of analyzers and package patterns to a list of +// results. Results provide access to diagnostics, directives, errors +// encountered, and information about packages. Results explicitly do +// not contain ASTs or type information. All position information is +// returned in the form of token.Position, not token.Pos. All work +// that requires access to the loaded representation of a package has +// to occur inside analyzers. +// +// # Planning and execution +// +// Analyzing packages is split into two phases: planning and +// execution. +// +// During planning, a directed acyclic graph of package dependencies +// is computed. We materialize the full graph so that we can execute +// the graph from the bottom up, without keeping unnecessary data in +// memory during a DFS and with simplified parallel execution. +// +// During execution, leaf nodes (nodes with no outstanding +// dependencies) get executed in parallel, bounded by a semaphore +// sized according to the number of CPUs. Conceptually, this happens +// in a loop, processing new leaf nodes as they appear, until no more +// nodes are left. In the actual implementation, nodes know their +// dependents, and the last dependency of a node to be processed is +// responsible for scheduling its dependent. +// +// The graph is rooted at a synthetic root node. Upon execution of the +// root node, the algorithm terminates. +// +// Analyzing a package repeats the same planning + execution steps, +// but this time on a graph of analyzers for the package. Parallel +// execution of individual analyzers is bounded by the same semaphore +// as executing packages. +// +// # Parallelism +// +// Actions are executed in parallel where the dependency graph allows. +// Overall parallelism is bounded by a semaphore, sized according to +// GOMAXPROCS. Each concurrently processed package takes up a +// token, as does each analyzer – but a package can always execute at +// least one analyzer, using the package's token. +// +// Depending on the overall shape of the graph, there may be GOMAXPROCS +// packages running a single analyzer each, a single package running +// GOMAXPROCS analyzers, or anything in between. +// +// Total memory consumption grows roughly linearly with the number of +// CPUs, while total execution time is inversely proportional to the +// number of CPUs. Overall, parallelism is affected by the shape of +// the dependency graph. A lot of inter-connected packages will see +// less parallelism than a lot of independent packages. +// +// # Caching +// +// The runner caches facts, directives and diagnostics in a +// content-addressable cache that is designed after Go's own cache. +// Additionally, it makes use of Go's export data. +// +// This cache not only speeds up repeat runs, it also reduces peak +// memory usage. When we've analyzed a package, we cache the results +// and drop them from memory. When a dependent needs any of this +// information, or when analysis is complete and we wish to render the +// results, the data gets loaded from disk again. +// +// Data only exists in memory when it is immediately needed, not +// retained for possible future uses. This trades increased CPU usage +// for reduced memory usage. A single dependency may be loaded many +// times over, but it greatly reduces peak memory usage, as an +// arbitrary amount of time may pass between analyzing a dependency +// and its dependent, during which other packages will be processed. +package runner + +// OPT(dh): we could reduce disk storage usage of cached data by +// compressing it, either directly at the cache layer, or by feeding +// compressed data to the cache. Of course doing so may negatively +// affect CPU usage, and there are lower hanging fruit, such as +// needing to cache less data in the first place. + +// OPT(dh): right now, each package is analyzed completely +// independently. Each package loads all of its dependencies from +// export data and cached facts. If we have two packages A and B, +// which both depend on C, and which both get analyzed in parallel, +// then C will be loaded twice. This wastes CPU time and memory. It +// would be nice if we could reuse a single C for the analysis of both +// A and B. +// +// We can't reuse the actual types.Package or facts, because each +// package gets its own token.FileSet. Sharing a global FileSet has +// several drawbacks, including increased memory usage and running the +// risk of running out of FileSet address space. +// +// We could however avoid loading the same raw export data from disk +// twice, as well as deserializing gob data twice. One possible +// solution would be a duplicate-suppressing in-memory cache that +// caches data for a limited amount of time. When the same package +// needs to be loaded twice in close succession, we can reuse work, +// without holding unnecessary data in memory for an extended period +// of time. +// +// We would likely need to do extensive benchmarking to figure out how +// long to keep data around to find a sweet spot where we reduce CPU +// load without increasing memory usage. +// +// We can probably populate the cache after we've analyzed a package, +// on the assumption that it will have to be loaded again in the near +// future. + +import ( + "bytes" + "encoding/gob" + "fmt" + "go/token" + "go/types" + "io" + "maps" + "os" + "reflect" + "runtime" + "sort" + "strings" + "sync/atomic" + "time" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/config" + "honnef.co/go/tools/go/loader" + tsync "honnef.co/go/tools/internal/sync" + "honnef.co/go/tools/lintcmd/cache" + "honnef.co/go/tools/unused" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/types/objectpath" +) + +const sanityCheck = false + +// Diagnostic is like go/analysis.Diagnostic, but with all token.Pos resolved to token.Position. +type Diagnostic struct { + Position token.Position + End token.Position + Category string + Message string + + SuggestedFixes []SuggestedFix + Related []RelatedInformation +} + +// RelatedInformation provides additional context for a diagnostic. +type RelatedInformation struct { + Position token.Position + End token.Position + Message string +} + +type SuggestedFix struct { + Message string + TextEdits []TextEdit +} + +type TextEdit struct { + Position token.Position + End token.Position + NewText []byte +} + +// A Result describes the result of analyzing a single package. +// +// It holds references to cached diagnostics and directives. They can +// be loaded on demand with the Load method. +type Result struct { + Package *loader.PackageSpec + Config config.Config + Initial bool + Skipped bool + + Failed bool + Errors []error + // Action results, path to file + results string + // Results relevant to testing, only set when test mode is enabled, path to file + testData string +} + +type SerializedDirective struct { + Command string + Arguments []string + // The position of the comment + DirectivePosition token.Position + // The position of the node that the comment is attached to + NodePosition token.Position +} + +func serializeDirective(dir lint.Directive, fset *token.FileSet) SerializedDirective { + return SerializedDirective{ + Command: dir.Command, + Arguments: dir.Arguments, + DirectivePosition: report.DisplayPosition(fset, dir.Directive.Pos()), + NodePosition: report.DisplayPosition(fset, dir.Node.Pos()), + } +} + +type ResultData struct { + Directives []SerializedDirective + Diagnostics []Diagnostic + Unused unused.Result +} + +func (r Result) Load() (ResultData, error) { + if r.Failed { + panic("Load called on failed Result") + } + if r.results == "" { + // this package was only a dependency + return ResultData{}, nil + } + f, err := os.Open(r.results) + if err != nil { + return ResultData{}, fmt.Errorf("failed loading result: %w", err) + } + defer f.Close() + var out ResultData + err = gob.NewDecoder(f).Decode(&out) + return out, err +} + +// TestData contains extra information about analysis runs that is only available in test mode. +type TestData struct { + // Facts contains facts produced by analyzers for a package. + // Unlike vetx, this list only contains facts specific to this package, + // not all facts for the transitive closure of dependencies. + Facts []TestFact + // List of files that were part of the package. + Files []string +} + +// LoadTest returns data relevant to testing. +// It should only be called if Runner.TestMode was set to true. +func (r Result) LoadTest() (TestData, error) { + if r.Failed { + panic("Load called on failed Result") + } + if r.results == "" { + // this package was only a dependency + return TestData{}, nil + } + f, err := os.Open(r.testData) + if err != nil { + return TestData{}, fmt.Errorf("failed loading test data: %w", err) + } + defer f.Close() + var out TestData + err = gob.NewDecoder(f).Decode(&out) + return out, err +} + +type action interface { + Deps() []action + Triggers() []action + DecrementPending() bool + MarkFailed() + IsFailed() bool + AddError(error) +} + +type baseAction struct { + // Action description + + deps []action + triggers []action + pending uint32 + + // Action results + + // failed is set to true if the action couldn't be processed. This + // may either be due to an error specific to this action, in + // which case the errors field will be populated, or due to a + // dependency being marked as failed, in which case errors will be + // empty. + failed bool + errors []error +} + +func (act *baseAction) Deps() []action { return act.deps } +func (act *baseAction) Triggers() []action { return act.triggers } +func (act *baseAction) DecrementPending() bool { + return atomic.AddUint32(&act.pending, ^uint32(0)) == 0 +} +func (act *baseAction) MarkFailed() { act.failed = true } +func (act *baseAction) IsFailed() bool { return act.failed } +func (act *baseAction) AddError(err error) { act.errors = append(act.errors, err) } + +// packageAction describes the act of loading a package, fully +// analyzing it, and storing the results. +type packageAction struct { + baseAction + + // Action description + Package *loader.PackageSpec + factsOnly bool + hash cache.ActionID + + // Action results + cfg config.Config + vetx string + results string + testData string + skipped bool +} + +func (act *packageAction) String() string { + return fmt.Sprintf("packageAction(%s)", act.Package) +} + +type objectFact struct { + fact analysis.Fact + // TODO(dh): why do we store the objectpath when producing the + // fact? Is it just for the sanity checking, which compares the + // stored path with a path recomputed from objectFactKey.Obj? + path objectpath.Path +} + +type objectFactKey struct { + Obj types.Object + Type reflect.Type +} + +type packageFactKey struct { + Pkg *types.Package + Type reflect.Type +} + +type gobFact struct { + PkgPath string + ObjPath string + Fact analysis.Fact +} + +// TestFact is a serialization of facts that is specific to the test mode. +type TestFact struct { + ObjectName string + Position token.Position + FactString string + Analyzer string +} + +// analyzerAction describes the act of analyzing a package with a +// single analyzer. +type analyzerAction struct { + baseAction + + // Action description + + Analyzer *analysis.Analyzer + + // Action results + + // We can store actual results here without worrying about memory + // consumption because analyzer actions get garbage collected once + // a package has been fully analyzed. + Result any + Diagnostics []Diagnostic + ObjectFacts map[objectFactKey]objectFact + PackageFacts map[packageFactKey]analysis.Fact + Pass *analysis.Pass +} + +func (act *analyzerAction) String() string { + return fmt.Sprintf("analyzerAction(%s)", act.Analyzer) +} + +// A Runner executes analyzers on packages. +type Runner struct { + Stats Stats + GoVersion string + + // If set to true, Runner will populate results with data relevant to testing analyzers + TestMode bool + + // Config that gets merged with per-package configs + cfg config.Config + cache cache.Cache + semaphore tsync.Semaphore +} + +type subrunner struct { + *Runner + analyzers []*analysis.Analyzer + factAnalyzers []*analysis.Analyzer + analyzerNames string + cache cache.Cache +} + +// New returns a new Runner. +func New(cfg config.Config, c cache.Cache) (*Runner, error) { + return &Runner{ + cfg: cfg, + cache: c, + semaphore: tsync.NewSemaphore(runtime.GOMAXPROCS(0)), + }, nil +} + +func newSubrunner(r *Runner, analyzers []*analysis.Analyzer) *subrunner { + analyzerNames := make([]string, len(analyzers)) + for i, a := range analyzers { + analyzerNames[i] = a.Name + } + sort.Strings(analyzerNames) + + var factAnalyzers []*analysis.Analyzer + for _, a := range analyzers { + if len(a.FactTypes) > 0 { + factAnalyzers = append(factAnalyzers, a) + } + } + return &subrunner{ + Runner: r, + analyzers: analyzers, + factAnalyzers: factAnalyzers, + analyzerNames: strings.Join(analyzerNames, ","), + cache: r.cache, + } +} + +func newPackageActionRoot(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction { + a := newPackageAction(pkg, cache) + a.factsOnly = false + return a +} + +func newPackageAction(pkg *loader.PackageSpec, cache map[*loader.PackageSpec]*packageAction) *packageAction { + if a, ok := cache[pkg]; ok { + return a + } + + a := &packageAction{ + Package: pkg, + factsOnly: true, // will be overwritten by any call to Action + } + cache[pkg] = a + + if len(pkg.Errors) > 0 { + a.errors = make([]error, len(pkg.Errors)) + for i, err := range pkg.Errors { + a.errors[i] = err + } + a.failed = true + + // We don't need to process our imports if this package is + // already broken. + return a + } + + a.deps = make([]action, 0, len(pkg.Imports)) + for _, dep := range pkg.Imports { + depa := newPackageAction(dep, cache) + depa.triggers = append(depa.triggers, a) + a.deps = append(a.deps, depa) + + if depa.failed { + a.failed = true + } + } + // sort dependencies because the list of dependencies is part of + // the cache key + sort.Slice(a.deps, func(i, j int) bool { + return a.deps[i].(*packageAction).Package.ID < a.deps[j].(*packageAction).Package.ID + }) + + a.pending = uint32(len(a.deps)) + + return a +} + +func newAnalyzerAction(an *analysis.Analyzer, cache map[*analysis.Analyzer]*analyzerAction) *analyzerAction { + if a, ok := cache[an]; ok { + return a + } + + a := &analyzerAction{ + Analyzer: an, + ObjectFacts: map[objectFactKey]objectFact{}, + PackageFacts: map[packageFactKey]analysis.Fact{}, + } + cache[an] = a + for _, dep := range an.Requires { + depa := newAnalyzerAction(dep, cache) + depa.triggers = append(depa.triggers, a) + a.deps = append(a.deps, depa) + } + a.pending = uint32(len(a.deps)) + return a +} + +func getCachedFiles(c cache.Cache, ids []cache.ActionID, out []*string) error { + for i, id := range ids { + var err error + *out[i], _, err = cache.GetFile(c, id) + if err != nil { + return err + } + } + return nil +} + +func (r *subrunner) do(act action) error { + a := act.(*packageAction) + defer func() { + r.Stats.finishPackage() + if !a.factsOnly { + r.Stats.finishInitialPackage() + } + }() + + // compute hash of action + a.cfg = a.Package.Config.Merge(r.cfg) + h := cache.NewHash("staticcheck " + a.Package.PkgPath) + + // Note that we do not filter the list of analyzers by the + // package's configuration. We don't allow configuration to + // accidentally break dependencies between analyzers, and it's + // easier to always run all checks and filter the output. This + // also makes cached data more reusable. + + // OPT(dh): not all changes in configuration invalidate cached + // data. specifically, when a.factsOnly == true, we only care + // about checks that produce facts, and settings that affect those + // checks. + + // Config used for constructing the hash; this config doesn't have + // Checks populated, because we always run all checks. + // + // This even works for users who add custom checks, because we include the binary's hash. + hashCfg := a.cfg + hashCfg.Checks = nil + // note that we don't hash staticcheck's version; it is set as the + // salt by a package main. + fmt.Fprintf(h, "cfg %#v\n", hashCfg) + fmt.Fprintf(h, "pkg %x\n", a.Package.Hash) + fmt.Fprintf(h, "analyzers %s\n", r.analyzerNames) + fmt.Fprintf(h, "go %s\n", r.GoVersion) + fmt.Fprintf(h, "env godebug %q\n", os.Getenv("GODEBUG")) + + // OPT(dh): do we actually need to hash vetx? can we not assume + // that for identical inputs, staticcheck will produce identical + // vetx? + for _, dep := range a.deps { + dep := dep.(*packageAction) + vetxHash, err := cache.FileHash(dep.vetx) + if err != nil { + return fmt.Errorf("failed computing hash: %w", err) + } + fmt.Fprintf(h, "vetout %q %x\n", dep.Package.PkgPath, vetxHash) + } + a.hash = cache.ActionID(h.Sum()) + + // try to fetch hashed data + ids := make([]cache.ActionID, 0, 2) + ids = append(ids, cache.Subkey(a.hash, "vetx")) + if !a.factsOnly { + ids = append(ids, cache.Subkey(a.hash, "results")) + if r.TestMode { + ids = append(ids, cache.Subkey(a.hash, "testdata")) + } + } + if err := getCachedFiles(r.cache, ids, []*string{&a.vetx, &a.results, &a.testData}); err != nil { + result, err := r.doUncached(a) + if err != nil { + return err + } + if a.failed { + return nil + } + + a.skipped = result.skipped + + // OPT(dh) instead of collecting all object facts and encoding + // them after analysis finishes, we could encode them as we + // go. however, that would require some locking. + // + // OPT(dh): We could sort gobFacts for more consistent output, + // but it doesn't matter. The hash of a package includes all + // of its files, so whether the vetx hash changes or not, a + // change to a package requires re-analyzing all dependents, + // even if the vetx data stayed the same. See also the note at + // the top of loader/hash.go. + + tf := &bytes.Buffer{} + enc := gob.NewEncoder(tf) + for _, gf := range result.facts { + if err := enc.Encode(gf); err != nil { + return fmt.Errorf("failed gob encoding data: %w", err) + } + } + + a.vetx, err = r.writeCacheReader(a, "vetx", bytes.NewReader(tf.Bytes())) + if err != nil { + return err + } + + if a.factsOnly { + return nil + } + + var out ResultData + out.Directives = make([]SerializedDirective, len(result.dirs)) + for i, dir := range result.dirs { + out.Directives[i] = serializeDirective(dir, result.lpkg.Fset) + } + + out.Diagnostics = result.diags + out.Unused = result.unused + a.results, err = r.writeCacheGob(a, "results", out) + if err != nil { + return err + } + + if r.TestMode { + out := TestData{ + Facts: result.testFacts, + Files: result.lpkg.GoFiles, + } + a.testData, err = r.writeCacheGob(a, "testdata", out) + if err != nil { + return err + } + } + } + return nil +} + +// ActiveWorkers returns the number of currently running workers. +func (r *Runner) ActiveWorkers() int { + return r.semaphore.Len() +} + +// TotalWorkers returns the maximum number of possible workers. +func (r *Runner) TotalWorkers() int { + return r.semaphore.Cap() +} + +func (r *Runner) writeCacheReader(a *packageAction, kind string, rs io.ReadSeeker) (string, error) { + h := cache.Subkey(a.hash, kind) + out, _, err := r.cache.Put(h, rs) + if err != nil { + return "", fmt.Errorf("failed caching data: %w", err) + } + return r.cache.OutputFile(out), nil +} + +func (r *Runner) writeCacheGob(a *packageAction, kind string, data any) (string, error) { + f, err := os.CreateTemp("", "staticcheck") + if err != nil { + return "", err + } + defer f.Close() + os.Remove(f.Name()) + if err := gob.NewEncoder(f).Encode(data); err != nil { + return "", fmt.Errorf("failed gob encoding data: %w", err) + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + return "", err + } + return r.writeCacheReader(a, kind, f) +} + +type packageActionResult struct { + facts []gobFact + diags []Diagnostic + unused unused.Result + dirs []lint.Directive + lpkg *loader.Package + skipped bool + + // Only set when using test mode + testFacts []TestFact +} + +func (r *subrunner) doUncached(a *packageAction) (packageActionResult, error) { + // OPT(dh): for a -> b; c -> b; if both a and b are being + // processed concurrently, we shouldn't load b's export data + // twice. + + pkg, _, err := loader.Load(a.Package, &loader.Options{GoVersion: r.GoVersion}) + if err != nil { + return packageActionResult{}, err + } + + if len(pkg.Errors) > 0 { + // this handles errors that occurred during type-checking the + // package in loader.Load + for _, err := range pkg.Errors { + a.errors = append(a.errors, err) + } + a.failed = true + return packageActionResult{}, nil + } + + if len(pkg.Syntax) == 0 && pkg.PkgPath != "unsafe" { + return packageActionResult{lpkg: pkg, skipped: true}, nil + } + + // OPT(dh): instead of parsing directives twice (twice because + // U1000 depends on the facts.Directives analyzer), reuse the + // existing result + var dirs []lint.Directive + if !a.factsOnly { + dirs = lint.ParseDirectives(pkg.Syntax, pkg.Fset) + } + res, err := r.runAnalyzers(a, pkg) + + return packageActionResult{ + facts: res.facts, + testFacts: res.testFacts, + diags: res.diagnostics, + unused: res.unused, + dirs: dirs, + lpkg: pkg, + }, err +} + +func pkgPaths(root *types.Package) map[string]*types.Package { + out := map[string]*types.Package{} + var dfs func(*types.Package) + dfs = func(pkg *types.Package) { + if _, ok := out[pkg.Path()]; ok { + return + } + out[pkg.Path()] = pkg + for _, imp := range pkg.Imports() { + dfs(imp) + } + } + dfs(root) + return out +} + +func (r *Runner) loadFacts(root *types.Package, dep *packageAction, objFacts map[objectFactKey]objectFact, pkgFacts map[packageFactKey]analysis.Fact) error { + // Load facts of all imported packages + vetx, err := os.Open(dep.vetx) + if err != nil { + return fmt.Errorf("failed loading cached facts: %w", err) + } + defer vetx.Close() + + pathToPkg := pkgPaths(root) + dec := gob.NewDecoder(vetx) + for { + var gf gobFact + err := dec.Decode(&gf) + if err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("failed loading cached facts: %w", err) + } + + pkg, ok := pathToPkg[gf.PkgPath] + if !ok { + continue + } + if gf.ObjPath == "" { + pkgFacts[packageFactKey{ + Pkg: pkg, + Type: reflect.TypeOf(gf.Fact), + }] = gf.Fact + } else { + obj, err := objectpath.Object(pkg, objectpath.Path(gf.ObjPath)) + if err != nil { + continue + } + objFacts[objectFactKey{ + Obj: obj, + Type: reflect.TypeOf(gf.Fact), + }] = objectFact{gf.Fact, objectpath.Path(gf.ObjPath)} + } + } + return nil +} + +func genericHandle(a action, root action, queue chan action, sem *tsync.Semaphore, exec func(a action) error) { + if a == root { + close(queue) + if sem != nil { + sem.Release() + } + return + } + if !a.IsFailed() { + // the action may have already been marked as failed during + // construction of the action graph, for example because of + // unresolved imports. + + for _, dep := range a.Deps() { + if dep.IsFailed() { + // One of our dependencies failed, so mark this package as + // failed and bail. We don't need to record an error for + // this package, the relevant error will have been + // reported by the first package in the chain that failed. + a.MarkFailed() + break + } + } + } + + if !a.IsFailed() { + if err := exec(a); err != nil { + a.MarkFailed() + a.AddError(err) + } + } + if sem != nil { + sem.Release() + } + + for _, t := range a.Triggers() { + if t.DecrementPending() { + queue <- t + } + } +} + +type analyzerRunner struct { + pkg *loader.Package + // object facts of our dependencies; may contain facts of + // analyzers other than the current one + depObjFacts map[objectFactKey]objectFact + // package facts of our dependencies; may contain facts of + // analyzers other than the current one + depPkgFacts map[packageFactKey]analysis.Fact + factsOnly bool + + stats *Stats +} + +func (ar *analyzerRunner) do(act action) error { + a := act.(*analyzerAction) + results := map[*analysis.Analyzer]any{} + // TODO(dh): does this have to be recursive? + for _, dep := range a.deps { + dep := dep.(*analyzerAction) + results[dep.Analyzer] = dep.Result + } + // OPT(dh): cache factTypes, it is the same for all packages for a given analyzer + // + // OPT(dh): do we need the factTypes map? most analyzers have 0-1 + // fact types. iterating over the slice is probably faster than + // indexing a map. + factTypes := map[reflect.Type]struct{}{} + for _, typ := range a.Analyzer.FactTypes { + factTypes[reflect.TypeOf(typ)] = struct{}{} + } + filterFactType := func(typ reflect.Type) bool { + _, ok := factTypes[typ] + return ok + } + a.Pass = &analysis.Pass{ + Analyzer: a.Analyzer, + Fset: ar.pkg.Fset, + Files: ar.pkg.Syntax, + OtherFiles: ar.pkg.OtherFiles, + Pkg: ar.pkg.Types, + TypesInfo: ar.pkg.TypesInfo, + TypesSizes: ar.pkg.TypesSizes, + Report: func(diag analysis.Diagnostic) { + if !ar.factsOnly { + if diag.Category == "" { + diag.Category = a.Analyzer.Name + } + d := Diagnostic{ + Position: report.DisplayPosition(ar.pkg.Fset, diag.Pos), + End: report.DisplayPosition(ar.pkg.Fset, diag.End), + Category: diag.Category, + Message: diag.Message, + } + for _, sugg := range diag.SuggestedFixes { + s := SuggestedFix{ + Message: sugg.Message, + } + for _, edit := range sugg.TextEdits { + s.TextEdits = append(s.TextEdits, TextEdit{ + Position: report.DisplayPosition(ar.pkg.Fset, edit.Pos), + End: report.DisplayPosition(ar.pkg.Fset, edit.End), + NewText: edit.NewText, + }) + } + d.SuggestedFixes = append(d.SuggestedFixes, s) + } + for _, rel := range diag.Related { + d.Related = append(d.Related, RelatedInformation{ + Position: report.DisplayPosition(ar.pkg.Fset, rel.Pos), + End: report.DisplayPosition(ar.pkg.Fset, rel.End), + Message: rel.Message, + }) + } + a.Diagnostics = append(a.Diagnostics, d) + } + }, + ResultOf: results, + ImportObjectFact: func(obj types.Object, fact analysis.Fact) bool { + key := objectFactKey{ + Obj: obj, + Type: reflect.TypeOf(fact), + } + if f, ok := ar.depObjFacts[key]; ok { + reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem()) + return true + } else if f, ok := a.ObjectFacts[key]; ok { + reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f.fact).Elem()) + return true + } + return false + }, + ImportPackageFact: func(pkg *types.Package, fact analysis.Fact) bool { + key := packageFactKey{ + Pkg: pkg, + Type: reflect.TypeOf(fact), + } + if f, ok := ar.depPkgFacts[key]; ok { + reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem()) + return true + } else if f, ok := a.PackageFacts[key]; ok { + reflect.ValueOf(fact).Elem().Set(reflect.ValueOf(f).Elem()) + return true + } + return false + }, + ExportObjectFact: func(obj types.Object, fact analysis.Fact) { + key := objectFactKey{ + Obj: obj, + Type: reflect.TypeOf(fact), + } + path, _ := objectpath.For(obj) + a.ObjectFacts[key] = objectFact{fact, path} + }, + ExportPackageFact: func(fact analysis.Fact) { + key := packageFactKey{ + Pkg: ar.pkg.Types, + Type: reflect.TypeOf(fact), + } + a.PackageFacts[key] = fact + }, + AllPackageFacts: func() []analysis.PackageFact { + out := make([]analysis.PackageFact, 0, len(ar.depPkgFacts)+len(a.PackageFacts)) + for key, fact := range ar.depPkgFacts { + out = append(out, analysis.PackageFact{ + Package: key.Pkg, + Fact: fact, + }) + } + for key, fact := range a.PackageFacts { + out = append(out, analysis.PackageFact{ + Package: key.Pkg, + Fact: fact, + }) + } + return out + }, + AllObjectFacts: func() []analysis.ObjectFact { + out := make([]analysis.ObjectFact, 0, len(ar.depObjFacts)+len(a.ObjectFacts)) + for key, fact := range ar.depObjFacts { + if filterFactType(key.Type) { + out = append(out, analysis.ObjectFact{ + Object: key.Obj, + Fact: fact.fact, + }) + } + } + for key, fact := range a.ObjectFacts { + if filterFactType(key.Type) { + out = append(out, analysis.ObjectFact{ + Object: key.Obj, + Fact: fact.fact, + }) + } + } + return out + }, + } + + t := time.Now() + res, err := a.Analyzer.Run(a.Pass) + ar.stats.measureAnalyzer(a.Analyzer, ar.pkg.PackageSpec, time.Since(t)) + if err != nil { + return err + } + a.Result = res + return nil +} + +type analysisResult struct { + facts []gobFact + diagnostics []Diagnostic + unused unused.Result + + // Only set when using test mode + testFacts []TestFact +} + +func (r *subrunner) runAnalyzers(pkgAct *packageAction, pkg *loader.Package) (analysisResult, error) { + depObjFacts := map[objectFactKey]objectFact{} + depPkgFacts := map[packageFactKey]analysis.Fact{} + + for _, dep := range pkgAct.deps { + if err := r.loadFacts(pkg.Types, dep.(*packageAction), depObjFacts, depPkgFacts); err != nil { + return analysisResult{}, err + } + } + + root := &analyzerAction{} + var analyzers []*analysis.Analyzer + if pkgAct.factsOnly { + // When analyzing non-initial packages, we only care about + // analyzers that produce facts. + analyzers = r.factAnalyzers + } else { + analyzers = r.analyzers + } + + all := map[*analysis.Analyzer]*analyzerAction{} + for _, a := range analyzers { + a := newAnalyzerAction(a, all) + root.deps = append(root.deps, a) + a.triggers = append(a.triggers, root) + } + root.pending = uint32(len(root.deps)) + + ar := &analyzerRunner{ + pkg: pkg, + factsOnly: pkgAct.factsOnly, + depObjFacts: depObjFacts, + depPkgFacts: depPkgFacts, + stats: &r.Stats, + } + queue := make(chan action, len(all)) + for _, a := range all { + if len(a.Deps()) == 0 { + queue <- a + } + } + + // Don't hang if there are no analyzers to run; for example + // because we are analyzing a dependency but have no analyzers + // that produce facts. + if len(all) == 0 { + close(queue) + } + for item := range queue { + b := r.semaphore.AcquireMaybe() + if b { + go genericHandle(item, root, queue, &r.semaphore, ar.do) + } else { + // the semaphore is exhausted; run the analysis under the + // token we've acquired for analyzing the package. + genericHandle(item, root, queue, nil, ar.do) + } + } + + var unusedResult unused.Result + for _, a := range all { + if a != root && a.Analyzer.Name == "U1000" && !a.failed { + // TODO(dh): figure out a clean abstraction, instead of + // special-casing U1000. + unusedResult = a.Result.(unused.Result) + } + + maps.Copy(depObjFacts, a.ObjectFacts) + maps.Copy(depPkgFacts, a.PackageFacts) + } + + // OPT(dh): cull objects not reachable via the exported closure + var testFacts []TestFact + gobFacts := make([]gobFact, 0, len(depObjFacts)+len(depPkgFacts)) + for key, fact := range depObjFacts { + if fact.path == "" { + continue + } + if sanityCheck { + p, _ := objectpath.For(key.Obj) + if p != fact.path { + panic(fmt.Sprintf("got different object paths for %v. old: %q new: %q", key.Obj, fact.path, p)) + } + } + gf := gobFact{ + PkgPath: key.Obj.Pkg().Path(), + ObjPath: string(fact.path), + Fact: fact.fact, + } + gobFacts = append(gobFacts, gf) + } + + for key, fact := range depPkgFacts { + gf := gobFact{ + PkgPath: key.Pkg.Path(), + Fact: fact, + } + gobFacts = append(gobFacts, gf) + } + + if r.TestMode { + for _, a := range all { + for key, fact := range a.ObjectFacts { + tgf := TestFact{ + ObjectName: key.Obj.Name(), + Position: pkg.Fset.Position(key.Obj.Pos()), + FactString: fmt.Sprint(fact.fact), + Analyzer: a.Analyzer.Name, + } + testFacts = append(testFacts, tgf) + } + + for _, fact := range a.PackageFacts { + tgf := TestFact{ + ObjectName: "", + Position: pkg.Fset.Position(pkg.Syntax[0].Pos()), + FactString: fmt.Sprint(fact), + Analyzer: a.Analyzer.Name, + } + testFacts = append(testFacts, tgf) + } + } + } + + var diags []Diagnostic + for _, a := range root.deps { + a := a.(*analyzerAction) + diags = append(diags, a.Diagnostics...) + } + return analysisResult{ + facts: gobFacts, + testFacts: testFacts, + diagnostics: diags, + unused: unusedResult, + }, nil +} + +func registerGobTypes(analyzers []*analysis.Analyzer) { + for _, a := range analyzers { + for _, typ := range a.FactTypes { + // FIXME(dh): use RegisterName so we can work around collisions + // in names. For pointer-types, gob incorrectly qualifies + // type names with the package name, not the import path. + gob.Register(typ) + } + } +} + +func allAnalyzers(analyzers []*analysis.Analyzer) []*analysis.Analyzer { + seen := map[*analysis.Analyzer]struct{}{} + out := make([]*analysis.Analyzer, 0, len(analyzers)) + var dfs func(*analysis.Analyzer) + dfs = func(a *analysis.Analyzer) { + if _, ok := seen[a]; ok { + return + } + seen[a] = struct{}{} + out = append(out, a) + for _, dep := range a.Requires { + dfs(dep) + } + } + for _, a := range analyzers { + dfs(a) + } + return out +} + +// Run loads the packages specified by patterns, runs analyzers on +// them and returns the results. Each result corresponds to a single +// package. Results will be returned for all packages, including +// dependencies. Errors specific to packages will be reported in the +// respective results. +// +// If cfg is nil, a default config will be used. Otherwise, cfg will +// be used, with the exception of the Mode field. +func (r *Runner) Run(cfg *packages.Config, analyzers []*analysis.Analyzer, patterns []string) ([]Result, error) { + analyzers = allAnalyzers(analyzers) + registerGobTypes(analyzers) + + r.Stats.setState(StateLoadPackageGraph) + lpkgs, err := loader.Graph(cfg, patterns...) + if err != nil { + return nil, err + } + r.Stats.setInitialPackages(len(lpkgs)) + + if len(lpkgs) == 0 { + return nil, nil + } + + r.Stats.setState(StateBuildActionGraph) + all := map[*loader.PackageSpec]*packageAction{} + root := &packageAction{} + for _, lpkg := range lpkgs { + a := newPackageActionRoot(lpkg, all) + root.deps = append(root.deps, a) + a.triggers = append(a.triggers, root) + } + root.pending = uint32(len(root.deps)) + + queue := make(chan action) + r.Stats.setTotalPackages(len(all) - 1) + + r.Stats.setState(StateProcessing) + go func() { + for _, a := range all { + if len(a.Deps()) == 0 { + queue <- a + } + } + }() + + sr := newSubrunner(r, analyzers) + for item := range queue { + r.semaphore.Acquire() + go genericHandle(item, root, queue, &r.semaphore, func(act action) error { + return sr.do(act) + }) + } + + r.Stats.setState(StateFinalizing) + out := make([]Result, 0, len(all)) + for _, item := range all { + if item.Package == nil { + continue + } + out = append(out, Result{ + Package: item.Package, + Config: item.cfg, + Initial: !item.factsOnly, + Skipped: item.skipped, + Failed: item.failed, + Errors: item.errors, + results: item.results, + testData: item.testData, + }) + } + return out, nil +} diff --git a/vendor/honnef.co/go/tools/lintcmd/runner/stats.go b/vendor/honnef.co/go/tools/lintcmd/runner/stats.go new file mode 100644 index 0000000..fd7da2f --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/runner/stats.go @@ -0,0 +1,49 @@ +package runner + +import ( + "sync/atomic" + "time" + + "honnef.co/go/tools/go/loader" + + "golang.org/x/tools/go/analysis" +) + +const ( + StateInitializing = iota + StateLoadPackageGraph + StateBuildActionGraph + StateProcessing + StateFinalizing +) + +type Stats struct { + state uint32 + initialPackages uint32 + totalPackages uint32 + processedPackages uint32 + processedInitialPackages uint32 + + // optional function to call every time an analyzer has finished analyzing a package. + PrintAnalyzerMeasurement func(*analysis.Analyzer, *loader.PackageSpec, time.Duration) +} + +func (s *Stats) setState(state uint32) { atomic.StoreUint32(&s.state, state) } +func (s *Stats) State() int { return int(atomic.LoadUint32(&s.state)) } +func (s *Stats) setInitialPackages(n int) { atomic.StoreUint32(&s.initialPackages, uint32(n)) } +func (s *Stats) InitialPackages() int { return int(atomic.LoadUint32(&s.initialPackages)) } +func (s *Stats) setTotalPackages(n int) { atomic.StoreUint32(&s.totalPackages, uint32(n)) } +func (s *Stats) TotalPackages() int { return int(atomic.LoadUint32(&s.totalPackages)) } + +func (s *Stats) finishPackage() { atomic.AddUint32(&s.processedPackages, 1) } +func (s *Stats) finishInitialPackage() { atomic.AddUint32(&s.processedInitialPackages, 1) } +func (s *Stats) ProcessedPackages() int { return int(atomic.LoadUint32(&s.processedPackages)) } +func (s *Stats) ProcessedInitialPackages() int { + return int(atomic.LoadUint32(&s.processedInitialPackages)) +} + +func (s *Stats) measureAnalyzer(analysis *analysis.Analyzer, pkg *loader.PackageSpec, d time.Duration) { + if s.PrintAnalyzerMeasurement != nil { + s.PrintAnalyzerMeasurement(analysis, pkg, d) + } +} diff --git a/vendor/honnef.co/go/tools/lintcmd/sarif.go b/vendor/honnef.co/go/tools/lintcmd/sarif.go new file mode 100644 index 0000000..4e575aa --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/sarif.go @@ -0,0 +1,371 @@ +package lintcmd + +// Notes on GitHub-specific restrictions: +// +// Result.Message needs to either have ID or Text set. Markdown +// gets ignored. Text isn't treated verbatim however: Markdown +// formatting gets stripped, except for links. +// +// GitHub does not display RelatedLocations. The only way to make +// use of them is to link to them (via their ID) in the +// Result.Message. And even then, it will only show the referred +// line of code, not the message. We can duplicate the messages in +// the Result.Message, but we can't even indent them, because +// leading whitespace gets stripped. +// +// GitHub does use the Markdown version of rule help, but it +// renders it the way it renders comments on issues – that is, it +// turns line breaks into hard line breaks, even though it +// shouldn't. +// +// GitHub doesn't make use of the tool's URI or version, nor of +// the help URIs of rules. +// +// There does not seem to be a way of using SARIF for "normal" CI, +// without results showing up as code scanning alerts. Also, a +// SARIF file containing only warnings, no errors, will not fail +// CI by default, but this is configurable. +// GitHub does display some parts of SARIF results in PRs, but +// most of the useful parts of SARIF, such as help text of rules, +// is only accessible via the code scanning alerts, which are only +// accessible by users with write permissions. +// +// Result.Suppressions is being ignored. +// +// +// Notes on other tools +// +// VS Code Sarif viewer +// +// The Sarif viewer in VS Code displays the full message in the +// tabular view, removing newlines. That makes our multi-line +// messages (which we use as a workaround for missing related +// information) very ugly. +// +// Much like GitHub, the Sarif viewer does not make related +// information visible unless we explicitly refer to it in the +// message. +// +// Suggested fixes are not exposed in any way. +// +// It only shows the shortDescription or fullDescription of a +// rule, not its help. We can't put the help in fullDescription, +// because the fullDescription isn't meant to be that long. For +// example, GitHub displays it in a single line, under the +// shortDescription. +// +// VS Code can filter based on Result.Suppressions, but it doesn't +// display our suppression message. Also, by default, suppressed +// results get shown, and the column indicating that a result is +// suppressed is hidden, which makes for a confusing experience. +// +// When a rule has only an ID, no name, VS Code displays a +// prominent dash in place of the name. When the name and ID are +// identical, it prints both. However, we can't make them +// identical, as SARIF requires that either the ID and name are +// different, or that the name is omitted. + +// FIXME(dh): we're currently reporting column information using UTF-8 +// byte offsets, not using Unicode code points or UTF-16, which are +// the only two ways allowed by SARIF. + +// TODO(dh) set properties.tags – we can use different tags for the +// staticcheck, simple, stylecheck and unused checks, so users can +// filter their results + +import ( + "encoding/json" + "fmt" + "net/url" + "os" + "path/filepath" + "regexp" + "strings" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/sarif" +) + +type sarifFormatter struct { + driverName string + driverVersion string + driverWebsite string +} + +func sarifLevel(severity lint.Severity) string { + switch severity { + case lint.SeverityNone: + // no configured severity, default to warning + return "warning" + case lint.SeverityError: + return "error" + case lint.SeverityDeprecated: + return "warning" + case lint.SeverityWarning: + return "warning" + case lint.SeverityInfo: + return "note" + case lint.SeverityHint: + return "note" + default: + // unreachable + return "none" + } +} + +func encodePath(path string) string { + return (&url.URL{Path: path}).EscapedPath() +} + +func sarifURI(path string) string { + u := url.URL{ + Scheme: "file", + Path: path, + } + return u.String() +} + +func sarifArtifactLocation(name string) sarif.ArtifactLocation { + // Ideally we use relative paths so that GitHub can resolve them + name = shortPath(name) + if filepath.IsAbs(name) { + return sarif.ArtifactLocation{ + URI: sarifURI(name), + } + } else { + return sarif.ArtifactLocation{ + URI: encodePath(name), + URIBaseID: "%SRCROOT%", // This is specific to GitHub, + } + } +} + +func sarifFormatText(s string) string { + // GitHub doesn't ignore line breaks, even though it should, so we remove them. + + var out strings.Builder + lines := strings.Split(s, "\n") + for i, line := range lines[:len(lines)-1] { + out.WriteString(line) + if line == "" { + out.WriteString("\n") + } else { + nextLine := lines[i+1] + if nextLine == "" || strings.HasPrefix(line, "> ") || strings.HasPrefix(line, " ") { + out.WriteString("\n") + } else { + out.WriteString(" ") + } + } + } + out.WriteString(lines[len(lines)-1]) + return convertCodeBlocks(out.String()) +} + +func moreCodeFollows(lines []string) bool { + for _, line := range lines { + if line == "" { + continue + } + if strings.HasPrefix(line, " ") { + return true + } else { + return false + } + } + return false +} + +var alpha = regexp.MustCompile(`^[a-zA-Z ]+$`) + +func convertCodeBlocks(text string) string { + var buf strings.Builder + lines := strings.Split(text, "\n") + + inCode := false + empties := 0 + for i, line := range lines { + if inCode { + if !moreCodeFollows(lines[i:]) { + if inCode { + fmt.Fprintln(&buf, "```") + inCode = false + } + } + } + + prevEmpties := empties + if line == "" && !inCode { + empties++ + } else { + empties = 0 + } + + if line == "" { + fmt.Fprintln(&buf) + continue + } + + if strings.HasPrefix(line, " ") { + line = line[4:] + if !inCode { + fmt.Fprintln(&buf, "```go") + inCode = true + } + } + + onlyAlpha := alpha.MatchString(line) + out := line + if !inCode && prevEmpties >= 2 && onlyAlpha { + fmt.Fprintf(&buf, "## %s\n", out) + } else { + fmt.Fprint(&buf, out) + fmt.Fprintln(&buf) + } + } + if inCode { + fmt.Fprintln(&buf, "```") + } + + return buf.String() +} + +func (o *sarifFormatter) Format(checks []*lint.Analyzer, diagnostics []diagnostic) { + // TODO(dh): some diagnostics shouldn't be reported as results. For example, when the user specifies a package on the command line that doesn't exist. + + cwd, _ := os.Getwd() + run := sarif.Run{ + Tool: sarif.Tool{ + Driver: sarif.ToolComponent{ + Name: o.driverName, + Version: o.driverVersion, + InformationURI: o.driverWebsite, + }, + }, + Invocations: []sarif.Invocation{{ + Arguments: os.Args[1:], + WorkingDirectory: sarif.ArtifactLocation{ + URI: sarifURI(cwd), + }, + ExecutionSuccessful: true, + }}, + } + for _, c := range checks { + doc := c.Doc.Compile() + run.Tool.Driver.Rules = append(run.Tool.Driver.Rules, + sarif.ReportingDescriptor{ + // We don't set Name, as Name and ID mustn't be identical. + ID: c.Analyzer.Name, + ShortDescription: sarif.Message{ + Text: doc.Title, + Markdown: doc.TitleMarkdown, + }, + HelpURI: "https://staticcheck.dev/docs/checks#" + c.Analyzer.Name, + // We use our markdown as the plain text version, too. We + // use very little markdown, primarily quotations, + // indented code blocks and backticks. All of these are + // fine as plain text, too. + Help: sarif.Message{ + Text: sarifFormatText(doc.Format(false)), + Markdown: sarifFormatText(doc.FormatMarkdown(false)), + }, + DefaultConfiguration: sarif.ReportingConfiguration{ + // TODO(dh): we could figure out which checks were disabled globally + Enabled: true, + Level: sarifLevel(doc.Severity), + }, + }) + } + + for _, p := range diagnostics { + r := sarif.Result{ + RuleID: p.Category, + Kind: sarif.Fail, + Message: sarif.Message{ + Text: p.Message, + }, + } + r.Locations = []sarif.Location{{ + PhysicalLocation: sarif.PhysicalLocation{ + ArtifactLocation: sarifArtifactLocation(p.Position.Filename), + Region: sarif.Region{ + StartLine: p.Position.Line, + StartColumn: p.Position.Column, + EndLine: p.End.Line, + EndColumn: p.End.Column, + }, + }, + }} + for _, fix := range p.SuggestedFixes { + sfix := sarif.Fix{ + Description: sarif.Message{ + Text: fix.Message, + }, + } + // file name -> replacements + changes := map[string][]sarif.Replacement{} + for _, edit := range fix.TextEdits { + changes[edit.Position.Filename] = append(changes[edit.Position.Filename], sarif.Replacement{ + DeletedRegion: sarif.Region{ + StartLine: edit.Position.Line, + StartColumn: edit.Position.Column, + EndLine: edit.End.Line, + EndColumn: edit.End.Column, + }, + InsertedContent: sarif.ArtifactContent{ + Text: string(edit.NewText), + }, + }) + } + for path, replacements := range changes { + sfix.ArtifactChanges = append(sfix.ArtifactChanges, sarif.ArtifactChange{ + ArtifactLocation: sarifArtifactLocation(path), + Replacements: replacements, + }) + } + r.Fixes = append(r.Fixes, sfix) + } + for i, related := range p.Related { + r.Message.Text += fmt.Sprintf("\n\t[%s](%d)", related.Message, i+1) + + r.RelatedLocations = append(r.RelatedLocations, + sarif.Location{ + ID: i + 1, + Message: &sarif.Message{ + Text: related.Message, + }, + PhysicalLocation: sarif.PhysicalLocation{ + ArtifactLocation: sarifArtifactLocation(related.Position.Filename), + Region: sarif.Region{ + StartLine: related.Position.Line, + StartColumn: related.Position.Column, + EndLine: related.End.Line, + EndColumn: related.End.Column, + }, + }, + }) + } + + if p.Severity == severityIgnored { + // Note that GitHub does not support suppressions, which is why Staticcheck still requires the -show-ignored flag to be set for us to emit ignored diagnostics. + + r.Suppressions = []sarif.Suppression{{ + Kind: "inSource", + // TODO(dh): populate the Justification field + }} + } else { + // We want an empty slice, not nil. SARIF differentiates + // between the two. An empty slice means that the diagnostic + // wasn't suppressed, while nil means that we don't have the + // information available. + r.Suppressions = []sarif.Suppression{} + } + run.Results = append(run.Results, r) + } + + json.NewEncoder(os.Stdout).Encode(sarif.Log{ + Version: sarif.Version, + Schema: sarif.Schema, + Runs: []sarif.Run{run}, + }) +} diff --git a/vendor/honnef.co/go/tools/lintcmd/stats.go b/vendor/honnef.co/go/tools/lintcmd/stats.go new file mode 100644 index 0000000..fa9613b --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/stats.go @@ -0,0 +1,7 @@ +//go:build !aix && !android && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris + +package lintcmd + +import "os" + +var infoSignals = []os.Signal{} diff --git a/vendor/honnef.co/go/tools/lintcmd/stats_bsd.go b/vendor/honnef.co/go/tools/lintcmd/stats_bsd.go new file mode 100644 index 0000000..8704e64 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/stats_bsd.go @@ -0,0 +1,10 @@ +//go:build darwin || dragonfly || freebsd || netbsd || openbsd + +package lintcmd + +import ( + "os" + "syscall" +) + +var infoSignals = []os.Signal{syscall.SIGINFO} diff --git a/vendor/honnef.co/go/tools/lintcmd/stats_posix.go b/vendor/honnef.co/go/tools/lintcmd/stats_posix.go new file mode 100644 index 0000000..2fdb3b7 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/stats_posix.go @@ -0,0 +1,10 @@ +//go:build aix || android || linux || solaris + +package lintcmd + +import ( + "os" + "syscall" +) + +var infoSignals = []os.Signal{syscall.SIGUSR1} diff --git a/vendor/honnef.co/go/tools/lintcmd/version/buildinfo.go b/vendor/honnef.co/go/tools/lintcmd/version/buildinfo.go new file mode 100644 index 0000000..ecf50a1 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/version/buildinfo.go @@ -0,0 +1,44 @@ +package version + +import ( + "fmt" + "runtime/debug" +) + +func printBuildInfo() { + if info, ok := debug.ReadBuildInfo(); ok { + fmt.Println("Main module:") + printModule(&info.Main) + fmt.Println("Dependencies:") + for _, dep := range info.Deps { + printModule(dep) + } + } else { + fmt.Println("Built without Go modules") + } +} + +func buildInfoVersion() (string, bool) { + info, ok := debug.ReadBuildInfo() + if !ok { + return "", false + } + if info.Main.Version == "(devel)" { + return "", false + } + return info.Main.Version, true +} + +func printModule(m *debug.Module) { + fmt.Printf("\t%s", m.Path) + if m.Version != "(devel)" { + fmt.Printf("@%s", m.Version) + } + if m.Sum != "" { + fmt.Printf(" (sum: %s)", m.Sum) + } + if m.Replace != nil { + fmt.Printf(" (replace: %s)", m.Replace.Path) + } + fmt.Println() +} diff --git a/vendor/honnef.co/go/tools/lintcmd/version/version.go b/vendor/honnef.co/go/tools/lintcmd/version/version.go new file mode 100644 index 0000000..3d58cd5 --- /dev/null +++ b/vendor/honnef.co/go/tools/lintcmd/version/version.go @@ -0,0 +1,43 @@ +package version + +import ( + "fmt" + "os" + "path/filepath" + "runtime" +) + +const Version = "2026.2" +const MachineVersion = "0.8.0" + +// version returns a version descriptor and reports whether the +// version is a known release. +func version(human, machine string) (human_, machine_ string, known bool) { + if human != "devel" { + return human, machine, true + } + v, ok := buildInfoVersion() + if ok { + return v, "", false + } + return "devel", "", false +} + +func Print(human, machine string) { + human, machine, release := version(human, machine) + + if release { + fmt.Printf("%s %s (%s)\n", filepath.Base(os.Args[0]), human, machine) + } else if human == "devel" { + fmt.Printf("%s (no version)\n", filepath.Base(os.Args[0])) + } else { + fmt.Printf("%s (devel, %s)\n", filepath.Base(os.Args[0]), human) + } +} + +func Verbose(human, machine string) { + Print(human, machine) + fmt.Println() + fmt.Println("Compiled with Go version:", runtime.Version()) + printBuildInfo() +} diff --git a/vendor/honnef.co/go/tools/pattern/convert.go b/vendor/honnef.co/go/tools/pattern/convert.go new file mode 100644 index 0000000..ae9a0a5 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/convert.go @@ -0,0 +1,243 @@ +package pattern + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" +) + +var astTypes = map[string]reflect.Type{ + "Ellipsis": reflect.TypeFor[ast.Ellipsis](), + "RangeStmt": reflect.TypeFor[ast.RangeStmt](), + "AssignStmt": reflect.TypeFor[ast.AssignStmt](), + "IndexExpr": reflect.TypeFor[ast.IndexExpr](), + "IndexListExpr": reflect.TypeFor[ast.IndexListExpr](), + "Ident": reflect.TypeFor[ast.Ident](), + "ValueSpec": reflect.TypeFor[ast.ValueSpec](), + "GenDecl": reflect.TypeFor[ast.GenDecl](), + "BinaryExpr": reflect.TypeFor[ast.BinaryExpr](), + "ForStmt": reflect.TypeFor[ast.ForStmt](), + "ArrayType": reflect.TypeFor[ast.ArrayType](), + "DeferStmt": reflect.TypeFor[ast.DeferStmt](), + "MapType": reflect.TypeFor[ast.MapType](), + "ReturnStmt": reflect.TypeFor[ast.ReturnStmt](), + "SliceExpr": reflect.TypeFor[ast.SliceExpr](), + "StarExpr": reflect.TypeFor[ast.StarExpr](), + "UnaryExpr": reflect.TypeFor[ast.UnaryExpr](), + "SendStmt": reflect.TypeFor[ast.SendStmt](), + "SelectStmt": reflect.TypeFor[ast.SelectStmt](), + "ImportSpec": reflect.TypeFor[ast.ImportSpec](), + "IfStmt": reflect.TypeFor[ast.IfStmt](), + "GoStmt": reflect.TypeFor[ast.GoStmt](), + "Field": reflect.TypeFor[ast.Field](), + "SelectorExpr": reflect.TypeFor[ast.SelectorExpr](), + "StructType": reflect.TypeFor[ast.StructType](), + "KeyValueExpr": reflect.TypeFor[ast.KeyValueExpr](), + "FuncType": reflect.TypeFor[ast.FuncType](), + "FuncLit": reflect.TypeFor[ast.FuncLit](), + "FuncDecl": reflect.TypeFor[ast.FuncDecl](), + "ChanType": reflect.TypeFor[ast.ChanType](), + "CallExpr": reflect.TypeFor[ast.CallExpr](), + "CaseClause": reflect.TypeFor[ast.CaseClause](), + "CommClause": reflect.TypeFor[ast.CommClause](), + "CompositeLit": reflect.TypeFor[ast.CompositeLit](), + "EmptyStmt": reflect.TypeFor[ast.EmptyStmt](), + "SwitchStmt": reflect.TypeFor[ast.SwitchStmt](), + "TypeSwitchStmt": reflect.TypeFor[ast.TypeSwitchStmt](), + "TypeAssertExpr": reflect.TypeFor[ast.TypeAssertExpr](), + "TypeSpec": reflect.TypeFor[ast.TypeSpec](), + "InterfaceType": reflect.TypeFor[ast.InterfaceType](), + "BranchStmt": reflect.TypeFor[ast.BranchStmt](), + "IncDecStmt": reflect.TypeFor[ast.IncDecStmt](), + "BasicLit": reflect.TypeFor[ast.BasicLit](), +} + +func ASTToNode(node any) Node { + switch node := node.(type) { + case *ast.File: + panic("cannot convert *ast.File to Node") + case nil: + return Nil{} + case string: + return String(node) + case token.Token: + return Token(node) + case *ast.ExprStmt: + return ASTToNode(node.X) + case *ast.BlockStmt: + if node == nil { + return Nil{} + } + return ASTToNode(node.List) + case *ast.FieldList: + if node == nil { + return Nil{} + } + return ASTToNode(node.List) + case *ast.BasicLit: + if node == nil { + return Nil{} + } + case *ast.ParenExpr: + return ASTToNode(node.X) + } + + if node, ok := node.(ast.Node); ok { + name := reflect.TypeOf(node).Elem().Name() + T, ok := structNodes[name] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + + if reflect.ValueOf(node).IsNil() { + return Nil{} + } + v := reflect.ValueOf(node).Elem() + objs := make([]Node, T.NumField()) + for i := 0; i < T.NumField(); i++ { + f := v.FieldByName(T.Field(i).Name) + objs[i] = ASTToNode(f.Interface()) + } + + n, err := populateNode(name, objs, false) + if err != nil { + panic(fmt.Sprintf("internal error: %s", err)) + } + return n + } + + s := reflect.ValueOf(node) + if s.Kind() == reflect.Slice { + if s.Len() == 0 { + return List{} + } + if s.Len() == 1 { + return ASTToNode(s.Index(0).Interface()) + } + + tail := List{} + for i := s.Len() - 1; i >= 0; i-- { + head := ASTToNode(s.Index(i).Interface()) + l := List{ + Head: head, + Tail: tail, + } + tail = l + } + return tail + } + + panic(fmt.Sprintf("internal error: unhandled type %T", node)) +} + +func NodeToAST(node Node, state State) any { + switch node := node.(type) { + case Binding: + v, ok := state[node.Name] + if !ok { + // really we want to return an error here + panic("XXX") + } + switch v := v.(type) { + case types.Object: + return &ast.Ident{Name: v.Name()} + default: + return v + } + case Builtin, Any, Object, Symbol, Not, Or: + panic("XXX") + case List: + if (node == List{}) { + return []ast.Node{} + } + x := []ast.Node{NodeToAST(node.Head, state).(ast.Node)} + x = append(x, NodeToAST(node.Tail, state).([]ast.Node)...) + return x + case Token: + return token.Token(node) + case String: + return string(node) + case Nil: + return nil + } + + name := reflect.TypeOf(node).Name() + T, ok := astTypes[name] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + v := reflect.ValueOf(node) + out := reflect.New(T) + for i := 0; i < T.NumField(); i++ { + fNode := v.FieldByName(T.Field(i).Name) + if (fNode == reflect.Value{}) { + continue + } + fAST := out.Elem().FieldByName(T.Field(i).Name) + switch fAST.Type().Kind() { + case reflect.Slice: + c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state)) + if c.Kind() != reflect.Slice { + // it's a single node in the pattern, we have to wrap + // it in a slice + slice := reflect.MakeSlice(fAST.Type(), 1, 1) + slice.Index(0).Set(c) + c = slice + } + switch fAST.Interface().(type) { + case []ast.Node: + switch cc := c.Interface().(type) { + case []ast.Node: + fAST.Set(c) + case []ast.Expr: + var slice []ast.Node + for _, el := range cc { + slice = append(slice, el) + } + fAST.Set(reflect.ValueOf(slice)) + default: + panic("XXX") + } + case []ast.Expr: + switch cc := c.Interface().(type) { + case []ast.Node: + var slice []ast.Expr + for _, el := range cc { + slice = append(slice, el.(ast.Expr)) + } + fAST.Set(reflect.ValueOf(slice)) + case []ast.Expr: + fAST.Set(c) + default: + panic("XXX") + } + default: + panic("XXX") + } + case reflect.Int: + c := reflect.ValueOf(NodeToAST(fNode.Interface().(Node), state)) + switch c.Kind() { + case reflect.String: + tok, ok := tokensByString[c.Interface().(string)] + if !ok { + // really we want to return an error here + panic("XXX") + } + fAST.SetInt(int64(tok)) + case reflect.Int: + fAST.Set(c) + default: + panic(fmt.Sprintf("internal error: unexpected kind %s", c.Kind())) + } + default: + r := NodeToAST(fNode.Interface().(Node), state) + if r != nil { + fAST.Set(reflect.ValueOf(r)) + } + } + } + + return out.Interface().(ast.Node) +} diff --git a/vendor/honnef.co/go/tools/pattern/doc.go b/vendor/honnef.co/go/tools/pattern/doc.go new file mode 100644 index 0000000..22fe2cf --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/doc.go @@ -0,0 +1,272 @@ +/* +Package pattern implements a simple language for pattern matching Go ASTs. + +# Design decisions and trade-offs + +The language is designed specifically for the task of filtering ASTs +to simplify the implementation of analyses in staticcheck. +It is also intended to be trivial to parse and execute. + +To that end, we make certain decisions that make the language more +suited to its task, while making certain queries infeasible. + +Furthermore, it is fully expected that the majority of analyses will still require ordinary Go code +to further process the filtered AST, to make use of type information and to enforce complex invariants. +It is not our goal to design a scripting language for writing entire checks in. + +# The language + +At its core, patterns are a representation of Go ASTs, allowing for the use of placeholders to enable pattern matching. +Their syntax is inspired by LISP and Haskell, but unlike LISP, the core unit of patterns isn't the list, but the node. +There is a fixed set of nodes, identified by name, and with the exception of the Or node, all nodes have a fixed number of arguments. +In addition to nodes, there are atoms, which represent basic units such as strings or the nil value. + +Pattern matching is implemented via bindings, represented by the Binding node. +A Binding can match nodes and associate them with names, to later recall the nodes. +This allows for expressing "this node must be equal to that node" constraints. + +To simplify writing and reading patterns, a small amount of additional syntax exists on top of nodes and atoms. +This additional syntax doesn't add any new features of its own, it simply provides shortcuts to creating nodes and atoms. + +To show an example of a pattern, first consider this snippet of Go code: + + if x := fn(); x != nil { + for _, v := range x { + println(v, x) + } + } + +The corresponding AST expressed as an idiomatic pattern would look as follows: + + (IfStmt + (AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") [])) + (BinaryExpr (Ident "x") "!=" (Ident "nil")) + (RangeStmt + (Ident "_") (Ident "v") ":=" (Ident "x") + (CallExpr (Ident "println") [(Ident "v") (Ident "x")])) + nil) + +Two things are worth noting about this representation. +First, the [el1 el2 ...] syntax is a short-hand for creating lists. +It is a short-hand for el1:el2:[], which itself is a short-hand for (List el1 (List el2 (List nil nil)). +Second, note the absence of a lot of lists in places that normally accept lists. +For example, assignment assigns a number of right-hands to a number of left-hands, yet our AssignStmt is lacking any form of list. +This is due to the fact that a single node can match a list of exactly one element. +Thus, the two following forms have identical matching behavior: + + (AssignStmt (Ident "x") ":=" (CallExpr (Ident "fn") [])) + (AssignStmt [(Ident "x")] ":=" [(CallExpr (Ident "fn") [])]) + +This section serves as an overview of the language's syntax. +More in-depth explanations of the matching behavior as well as an exhaustive list of node types follows in the coming sections. + +# Pattern matching + +# TODO write about pattern matching + +- inspired by haskell syntax, but much, much simpler and naive + +# Node types + +The language contains two kinds of nodes: those that map to nodes in the AST, and those that implement additional logic. + +Nodes that map directly to AST nodes are named identically to the types in the go/ast package. +What follows is an exhaustive list of these nodes: + + (ArrayType len elt) + (AssignStmt lhs tok rhs) + (BasicLit kind value) + (BinaryExpr x op y) + (BranchStmt tok label) + (CallExpr fun args) + (CaseClause list body) + (ChanType dir value) + (CommClause comm body) + (CompositeLit type elts) + (DeferStmt call) + (Ellipsis elt) + (EmptyStmt) + (Field names type tag) + (ForStmt init cond post body) + (FuncDecl recv name type body) + (FuncLit type body) + (FuncType params results) + (GenDecl specs) + (GoStmt call) + (Ident name) + (IfStmt init cond body else) + (ImportSpec name path) + (IncDecStmt x tok) + (IndexExpr x index) + (InterfaceType methods) + (KeyValueExpr key value) + (MapType key value) + (RangeStmt key value tok x body) + (ReturnStmt results) + (SelectStmt body) + (SelectorExpr x sel) + (SendStmt chan value) + (SliceExpr x low high max) + (StarExpr x) + (StructType fields) + (SwitchStmt init tag body) + (TypeAssertExpr) + (TypeSpec name type) + (TypeSwitchStmt init assign body) + (UnaryExpr op x) + (ValueSpec names type values) + +Additionally, there are the String, Token and nil atoms. +Strings are double-quoted string literals, as in (Ident "someName"). +Tokens are also represented as double-quoted string literals, but are converted to token.Token values in contexts that require tokens, +such as in (BinaryExpr x "<" y), where "<" is transparently converted to token.LSS during matching. +The keyword 'nil' denotes the nil value, which represents the absence of any value. + +We also define the (List head tail) node, which is used to represent sequences of elements as a singly linked list. +The head is a single element, and the tail is the remainder of the list. +For example, + + (List "foo" (List "bar" (List "baz" (List nil nil)))) + +represents a list of three elements, "foo", "bar" and "baz". There is dedicated syntax for writing lists, which looks as follows: + + ["foo" "bar" "baz"] + +This syntax is itself syntactic sugar for the following form: + + "foo":"bar":"baz":[] + +This form is of particular interest for pattern matching, as it allows matching on the head and tail. For example, + + "foo":"bar":_ + +would match any list with at least two elements, where the first two elements are "foo" and "bar". This is equivalent to writing + + (List "foo" (List "bar" _)) + +Note that it is not possible to match from the end of the list. +That is, there is no way to express a query such as "a list of any length where the last element is foo". + +Note that unlike in LISP, nil and empty lists are distinct from one another. +In patterns, with respect to lists, nil is akin to Go's untyped nil. +It will match a nil ast.Node, but it will not match a nil []ast.Expr. Nil will, however, match pointers to named types such as *ast.Ident. +Similarly, lists are akin to Go's +slices. An empty list will match both a nil and an empty []ast.Expr, but it will not match a nil ast.Node. + +Due to the difference between nil and empty lists, an empty list is represented as (List nil nil), i.e. a list with no head or tail. +Similarly, a list of one element is represented as (List el (List nil nil)). Unlike in LISP, it cannot be represented by (List el nil). + +Finally, there are nodes that implement special logic or matching behavior. + +(Any) matches any value. The underscore (_) maps to this node, making the following two forms equivalent: + + (Ident _) + (Ident (Any)) + +(Builtin name) matches a built-in identifier or function by name. +This is a type-aware variant of (Ident name). +Instead of only comparing the name, it resolves the object behind the name and makes sure it's a pre-declared identifier. + +For example, in the following piece of code + + func fn() { + println(true) + true := false + println(true) + } + +the pattern + + (Builtin "true") + +will match exactly once, on the first use of 'true' in the function. +Subsequent occurrences of 'true' no longer refer to the pre-declared identifier. + +(Object name) matches an identifier by name, but yields the +types.Object it refers to. + +(Symbol name) matches ast.Idents and ast.SelectorExprs that refer to a symbol with a given fully qualified name. +For example, "net/url.PathEscape" matches the PathEscape function in the net/url package, +and "(net/url.EscapeError).Error" refers to the Error method on the net/url.EscapeError type, +either on an instance of the type, or on the type itself. + +For example, the following patterns match the following lines of code: + + (CallExpr (Symbol "fmt.Println") _) // pattern 1 + (CallExpr (Symbol "(net/url.EscapeError).Error") _) // pattern 2 + + fmt.Println("hello, world") // matches pattern 1 + var x url.EscapeError + x.Error() // matches pattern 2 + (url.EscapeError).Error(x) // also matches pattern 2 + +(Binding name node) creates or uses a binding. +Bindings work like variable assignments, allowing referring to already matched nodes. +As an example, bindings are necessary to match self-assignment of the form "x = x", +since we need to express that the right-hand side is identical to the left-hand side. + +If a binding's node is not nil, the matcher will attempt to match a node according to the pattern. +If a binding's node is nil, the binding will either recall an existing value, or match the Any node. +It is an error to provide a non-nil node to a binding that has already been bound. + +Referring back to the earlier example, the following pattern will match self-assignment of idents: + + (AssignStmt (Binding "lhs" (Ident _)) "=" (Binding "lhs" nil)) + +Because bindings are a crucial component of pattern matching, there is special syntax for creating and recalling bindings. +Lower-case names refer to bindings. If standing on its own, the name "foo" will be equivalent to (Binding "foo" nil). +If a name is followed by an at-sign (@) then it will create a binding for the node that follows. +Together, this allows us to rewrite the earlier example as follows: + + (AssignStmt lhs@(Ident _) "=" lhs) + +(Or nodes...) is a variadic node that tries matching each node until one succeeds. For example, the following pattern matches all idents of name "foo" or "bar": + + (Ident (Or "foo" "bar")) + +We could also have written + + (Or (Ident "foo") (Ident "bar")) + +and achieved the same result. We can also mix different kinds of nodes: + + (Or (Ident "foo") (CallExpr (Ident "bar") _)) + +When using bindings inside of nodes used inside Or, all or none of the bindings will be bound. +That is, partially matched nodes that ultimately failed to match will not produce any bindings observable outside of the matching attempt. +We can thus write + + (Or (Ident name) (CallExpr name)) + +and 'name' will either be a String if the first option matched, or an Ident or SelectorExpr if the second option matched. + +(Not node) + +The Not node negates a match. For example, (Not (Ident _)) will match all nodes that aren't identifiers. + +ChanDir(0) + +# Automatic unnesting of AST nodes + +The Go AST has several types of nodes that wrap other nodes. +To simplify matching, we automatically unwrap some of these nodes. + +These nodes are ExprStmt (for using expressions in a statement context), +ParenExpr (for parenthesized expressions), +DeclStmt (for declarations in a statement context), +and LabeledStmt (for labeled statements). + +Thus, the query + + (FuncLit _ [(CallExpr _ _)] + +will match a function literal containing a single function call, +even though in the actual Go AST, the CallExpr is nested inside an ExprStmt, +as function bodies are made up of sequences of statements. + +On the flip-side, there is no way to specifically match these wrapper nodes. +For example, there is no way of searching for unnecessary parentheses, like in the following piece of Go code: + + ((x)) += 2 +*/ +package pattern diff --git a/vendor/honnef.co/go/tools/pattern/lexer.go b/vendor/honnef.co/go/tools/pattern/lexer.go new file mode 100644 index 0000000..8ab8f56 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/lexer.go @@ -0,0 +1,257 @@ +package pattern + +import ( + "fmt" + "go/token" + "iter" + "unicode" + "unicode/utf8" +) + +// lex returns the sequence of tokens in the input. +func lex(f *token.File, input string) iter.Seq[item] { + return func(yield func(item) bool) { + lex := &lexer{ + f: f, + input: input, + yield: yield, + } + lex.run() + } +} + +// lexer holds the state of a single [lex] iteration. +type lexer struct { + f *token.File + + input string + start int + pos int + width int + + yield func(item) bool +} + +type itemType int + +const eof = -1 + +const ( + itemError itemType = iota + itemLeftParen + itemRightParen + itemLeftBracket + itemRightBracket + itemTypeName + itemVariable + itemAt + itemColon + itemBlank + itemString + itemEOF +) + +func (typ itemType) String() string { + switch typ { + case itemError: + return "ERROR" + case itemLeftParen: + return "(" + case itemRightParen: + return ")" + case itemLeftBracket: + return "[" + case itemRightBracket: + return "]" + case itemTypeName: + return "TYPE" + case itemVariable: + return "VAR" + case itemAt: + return "@" + case itemColon: + return ":" + case itemBlank: + return "_" + case itemString: + return "STRING" + case itemEOF: + return "EOF" + default: + return fmt.Sprintf("itemType(%d)", typ) + } +} + +type item struct { + typ itemType + val string + pos int +} + +type stateFn func(*lexer) stateFn + +func (l *lexer) run() { + for state := lexStart; state != nil; { + state = state(l) + } +} + +func (l *lexer) emitValue(t itemType, value string) bool { + ok := l.yield(item{t, value, l.start}) + l.start = l.pos + return ok +} + +func (l *lexer) emit(t itemType) bool { + ok := l.yield(item{t, l.input[l.start:l.pos], l.start}) + l.start = l.pos + return ok +} + +func lexStart(l *lexer) stateFn { + switch r := l.next(); { + case r == eof: + _ = l.emit(itemEOF) + return nil + case unicode.IsSpace(r): + l.ignore() + case r == '(': + if !l.emit(itemLeftParen) { + return nil + } + case r == ')': + if !l.emit(itemRightParen) { + return nil + } + case r == '[': + if !l.emit(itemLeftBracket) { + return nil + } + case r == ']': + if !l.emit(itemRightBracket) { + return nil + } + case r == '@': + if !l.emit(itemAt) { + return nil + } + case r == ':': + if !l.emit(itemColon) { + return nil + } + case r == '_': + if !l.emit(itemBlank) { + return nil + } + case r == '"': + l.backup() + return lexString + case unicode.IsUpper(r): + l.backup() + return lexType + case unicode.IsLower(r): + l.backup() + return lexVariable + default: + return l.errorf("unexpected character %c", r) + } + return lexStart +} + +func (l *lexer) next() (r rune) { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + + if r == '\n' { + l.f.AddLine(l.pos) + } + + l.pos += l.width + + return r +} + +func (l *lexer) ignore() { + l.start = l.pos +} + +func (l *lexer) backup() { + l.pos -= l.width +} + +func (l *lexer) errorf(format string, args ...any) stateFn { + // TODO(dh): emit position information in errors + _ = l.yield(item{ + itemError, + fmt.Sprintf(format, args...), + l.start, + }) + return nil +} + +func isAlphaNumeric(r rune) bool { + return r >= '0' && r <= '9' || + r >= 'a' && r <= 'z' || + r >= 'A' && r <= 'Z' +} + +func lexString(l *lexer) stateFn { + l.next() // skip quote + escape := false + + var runes []rune + for { + switch r := l.next(); r { + case eof: + return l.errorf("unterminated string") + case '"': + if !escape { + if !l.emitValue(itemString, string(runes)) { + return nil + } + return lexStart + } else { + runes = append(runes, '"') + escape = false + } + case '\\': + if escape { + runes = append(runes, '\\') + escape = false + } else { + escape = true + } + default: + runes = append(runes, r) + } + } +} + +func lexType(l *lexer) stateFn { + l.next() + for { + if !isAlphaNumeric(l.next()) { + l.backup() + if !l.emit(itemTypeName) { + return nil + } + return lexStart + } + } +} + +func lexVariable(l *lexer) stateFn { + l.next() + for { + if !isAlphaNumeric(l.next()) { + l.backup() + if !l.emit(itemVariable) { + return nil + } + return lexStart + } + } +} diff --git a/vendor/honnef.co/go/tools/pattern/match.go b/vendor/honnef.co/go/tools/pattern/match.go new file mode 100644 index 0000000..1d3eaae --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/match.go @@ -0,0 +1,712 @@ +package pattern + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" +) + +var tokensByString = map[string]Token{ + "INT": Token(token.INT), + "FLOAT": Token(token.FLOAT), + "IMAG": Token(token.IMAG), + "CHAR": Token(token.CHAR), + "STRING": Token(token.STRING), + "+": Token(token.ADD), + "-": Token(token.SUB), + "*": Token(token.MUL), + "/": Token(token.QUO), + "%": Token(token.REM), + "&": Token(token.AND), + "|": Token(token.OR), + "^": Token(token.XOR), + "<<": Token(token.SHL), + ">>": Token(token.SHR), + "&^": Token(token.AND_NOT), + "+=": Token(token.ADD_ASSIGN), + "-=": Token(token.SUB_ASSIGN), + "*=": Token(token.MUL_ASSIGN), + "/=": Token(token.QUO_ASSIGN), + "%=": Token(token.REM_ASSIGN), + "&=": Token(token.AND_ASSIGN), + "|=": Token(token.OR_ASSIGN), + "^=": Token(token.XOR_ASSIGN), + "<<=": Token(token.SHL_ASSIGN), + ">>=": Token(token.SHR_ASSIGN), + "&^=": Token(token.AND_NOT_ASSIGN), + "&&": Token(token.LAND), + "||": Token(token.LOR), + "<-": Token(token.ARROW), + "++": Token(token.INC), + "--": Token(token.DEC), + "==": Token(token.EQL), + "<": Token(token.LSS), + ">": Token(token.GTR), + "=": Token(token.ASSIGN), + "!": Token(token.NOT), + "!=": Token(token.NEQ), + "<=": Token(token.LEQ), + ">=": Token(token.GEQ), + ":=": Token(token.DEFINE), + "...": Token(token.ELLIPSIS), + "IMPORT": Token(token.IMPORT), + "VAR": Token(token.VAR), + "TYPE": Token(token.TYPE), + "CONST": Token(token.CONST), + "BREAK": Token(token.BREAK), + "CONTINUE": Token(token.CONTINUE), + "GOTO": Token(token.GOTO), + "FALLTHROUGH": Token(token.FALLTHROUGH), +} + +func maybeToken(node Node) (Node, bool) { + if node, ok := node.(String); ok { + if tok, ok := tokensByString[string(node)]; ok { + return tok, true + } + return node, false + } + return node, false +} + +func isNil(v any) bool { + if v == nil { + return true + } + if _, ok := v.(Nil); ok { + return true + } + return false +} + +type matcher interface { + Match(*Matcher, any) (any, bool) +} + +type State = map[string]any + +type Matcher struct { + TypesInfo *types.Info + State State + + bindingsMapping []string + + setBindings []uint64 +} + +func (m *Matcher) set(b Binding, value any) { + m.State[b.Name] = value + m.setBindings[len(m.setBindings)-1] |= 1 << b.idx +} + +func (m *Matcher) push() { + m.setBindings = append(m.setBindings, 0) +} + +func (m *Matcher) pop() { + set := m.setBindings[len(m.setBindings)-1] + if set != 0 { + for i := 0; i < len(m.bindingsMapping); i++ { + if (set & (1 << i)) != 0 { + key := m.bindingsMapping[i] + delete(m.State, key) + } + } + } + m.setBindings = m.setBindings[:len(m.setBindings)-1] +} + +func (m *Matcher) merge() { + m.setBindings = m.setBindings[:len(m.setBindings)-1] +} + +func (m *Matcher) Match(a Pattern, b ast.Node) bool { + m.bindingsMapping = a.Bindings + m.State = State{} + m.push() + _, ok := match(m, a.Root, b) + m.merge() + if len(m.setBindings) != 0 { + panic(fmt.Sprintf("%d entries left on the stack, expected none", len(m.setBindings))) + } + return ok +} + +func Match(a Pattern, b ast.Node) (*Matcher, bool) { + m := &Matcher{} + ret := m.Match(a, b) + return m, ret +} + +// Match two items, which may be (Node, AST) or (AST, AST) +func match(m *Matcher, l, r any) (any, bool) { + if _, ok := r.(Node); ok { + panic("Node mustn't be on right side of match") + } + + switch l := l.(type) { + case *ast.ParenExpr: + return match(m, l.X, r) + case *ast.ExprStmt: + return match(m, l.X, r) + case *ast.DeclStmt: + return match(m, l.Decl, r) + case *ast.LabeledStmt: + return match(m, l.Stmt, r) + case *ast.BlockStmt: + return match(m, l.List, r) + case *ast.FieldList: + if l == nil { + return match(m, nil, r) + } else { + return match(m, l.List, r) + } + } + + switch r := r.(type) { + case *ast.ParenExpr: + return match(m, l, r.X) + case *ast.ExprStmt: + return match(m, l, r.X) + case *ast.DeclStmt: + return match(m, l, r.Decl) + case *ast.LabeledStmt: + return match(m, l, r.Stmt) + case *ast.BlockStmt: + if r == nil { + return match(m, l, nil) + } + return match(m, l, r.List) + case *ast.FieldList: + if r == nil { + return match(m, l, nil) + } + return match(m, l, r.List) + case *ast.BasicLit: + if r == nil { + return match(m, l, nil) + } + } + + if l, ok := l.(matcher); ok { + return l.Match(m, r) + } + + if l, ok := l.(Node); ok { + // Matching of pattern with concrete value + return matchNodeAST(m, l, r) + } + + if l == nil || r == nil { + return nil, l == r + } + + { + ln, ok1 := l.(ast.Node) + rn, ok2 := r.(ast.Node) + if ok1 && ok2 { + return matchAST(m, ln, rn) + } + } + + { + obj, ok := l.(types.Object) + if ok { + switch r := r.(type) { + case *ast.Ident: + return obj, obj == m.TypesInfo.ObjectOf(r) + case *ast.SelectorExpr: + return obj, obj == m.TypesInfo.ObjectOf(r.Sel) + default: + return obj, false + } + } + } + + // TODO(dh): the three blocks handling slices can be combined into a single block if we use reflection + + { + ln, ok1 := l.([]ast.Expr) + rn, ok2 := r.([]ast.Expr) + if ok1 || ok2 { + if ok1 && !ok2 { + cast, ok := r.(ast.Expr) + if !ok { + return nil, false + } + rn = []ast.Expr{cast} + } else if !ok1 && ok2 { + cast, ok := l.(ast.Expr) + if !ok { + return nil, false + } + ln = []ast.Expr{cast} + } + + if len(ln) != len(rn) { + return nil, false + } + for i, ll := range ln { + if _, ok := match(m, ll, rn[i]); !ok { + return nil, false + } + } + return r, true + } + } + + { + ln, ok1 := l.([]ast.Stmt) + rn, ok2 := r.([]ast.Stmt) + if ok1 || ok2 { + if ok1 && !ok2 { + cast, ok := r.(ast.Stmt) + if !ok { + return nil, false + } + rn = []ast.Stmt{cast} + } else if !ok1 && ok2 { + cast, ok := l.(ast.Stmt) + if !ok { + return nil, false + } + ln = []ast.Stmt{cast} + } + + if len(ln) != len(rn) { + return nil, false + } + for i, ll := range ln { + if _, ok := match(m, ll, rn[i]); !ok { + return nil, false + } + } + return r, true + } + } + + { + ln, ok1 := l.([]*ast.Field) + rn, ok2 := r.([]*ast.Field) + if ok1 || ok2 { + if ok1 && !ok2 { + cast, ok := r.(*ast.Field) + if !ok { + return nil, false + } + rn = []*ast.Field{cast} + } else if !ok1 && ok2 { + cast, ok := l.(*ast.Field) + if !ok { + return nil, false + } + ln = []*ast.Field{cast} + } + + if len(ln) != len(rn) { + return nil, false + } + for i, ll := range ln { + if _, ok := match(m, ll, rn[i]); !ok { + return nil, false + } + } + return r, true + } + } + + return nil, false +} + +// Match a Node with an AST node +func matchNodeAST(m *Matcher, a Node, b any) (any, bool) { + switch b := b.(type) { + case []ast.Stmt: + // 'a' is not a List or we'd be using its Match + // implementation. + + if len(b) != 1 { + return nil, false + } + return match(m, a, b[0]) + case []ast.Expr: + // 'a' is not a List or we'd be using its Match + // implementation. + + if len(b) != 1 { + return nil, false + } + return match(m, a, b[0]) + case []*ast.Field: + // 'a' is not a List or we'd be using its Match + // implementation + if len(b) != 1 { + return nil, false + } + return match(m, a, b[0]) + case ast.Node: + ra := reflect.ValueOf(a) + rb := reflect.ValueOf(b).Elem() + + if ra.Type().Name() != rb.Type().Name() { + return nil, false + } + + for i := 0; i < ra.NumField(); i++ { + af := ra.Field(i) + fieldName := ra.Type().Field(i).Name + bf := rb.FieldByName(fieldName) + if (bf == reflect.Value{}) { + panic(fmt.Sprintf("internal error: could not find field %s in type %t when comparing with %T", fieldName, b, a)) + } + ai := af.Interface() + bi := bf.Interface() + if ai == nil { + return b, bi == nil + } + if _, ok := match(m, ai.(Node), bi); !ok { + return b, false + } + } + return b, true + case nil: + return nil, a == Nil{} + case string, token.Token: + // 'a' can't be a String, Token, or Binding or we'd be using their Match implementations. + return nil, false + default: + panic(fmt.Sprintf("unhandled type %T", b)) + } +} + +// Match two AST nodes +func matchAST(m *Matcher, a, b ast.Node) (any, bool) { + ra := reflect.ValueOf(a) + rb := reflect.ValueOf(b) + + if ra.Type() != rb.Type() { + return nil, false + } + if ra.IsNil() || rb.IsNil() { + return rb, ra.IsNil() == rb.IsNil() + } + + ra = ra.Elem() + rb = rb.Elem() + for i := 0; i < ra.NumField(); i++ { + af := ra.Field(i) + bf := rb.Field(i) + if af.Type() == rtTokPos || af.Type() == rtObject || af.Type() == rtCommentGroup { + continue + } + + switch af.Kind() { + case reflect.Slice: + if af.Len() != bf.Len() { + return nil, false + } + for j := 0; j < af.Len(); j++ { + if _, ok := match(m, af.Index(j).Interface().(ast.Node), bf.Index(j).Interface().(ast.Node)); !ok { + return nil, false + } + } + case reflect.String: + if af.String() != bf.String() { + return nil, false + } + case reflect.Int: + if af.Int() != bf.Int() { + return nil, false + } + case reflect.Bool: + if af.Bool() != bf.Bool() { + return nil, false + } + case reflect.Pointer, reflect.Interface: + if _, ok := match(m, af.Interface(), bf.Interface()); !ok { + return nil, false + } + default: + panic(fmt.Sprintf("internal error: unhandled kind %s (%T)", af.Kind(), af.Interface())) + } + } + return b, true +} + +func (b Binding) Match(m *Matcher, node any) (any, bool) { + if isNil(b.Node) { + v, ok := m.State[b.Name] + if ok { + // Recall value + return match(m, v, node) + } + // Matching anything + b.Node = Any{} + } + + // Store value + if _, ok := m.State[b.Name]; ok { + panic(fmt.Sprintf("binding already created: %s", b.Name)) + } + new, ret := match(m, b.Node, node) + if ret { + m.set(b, new) + } + return new, ret +} + +func (Any) Match(m *Matcher, node any) (any, bool) { + return node, true +} + +func (l List) Match(m *Matcher, node any) (any, bool) { + v := reflect.ValueOf(node) + if v.Kind() == reflect.Slice { + if isNil(l.Head) { + return node, v.Len() == 0 + } + if v.Len() == 0 { + return nil, false + } + // OPT(dh): don't check the entire tail if head didn't match + _, ok1 := match(m, l.Head, v.Index(0).Interface()) + _, ok2 := match(m, l.Tail, v.Slice(1, v.Len()).Interface()) + return node, ok1 && ok2 + } + // Our empty list does not equal an untyped Go nil. This way, we can + // tell apart an if with no else and an if with an empty else. + return nil, false +} + +func (s String) Match(m *Matcher, node any) (any, bool) { + switch o := node.(type) { + case token.Token: + if tok, ok := maybeToken(s); ok { + return match(m, tok, node) + } + return nil, false + case string: + return o, string(s) == o + case types.TypeAndValue: + return o, o.Value != nil && o.Value.String() == string(s) + default: + return nil, false + } +} + +func (tok Token) Match(m *Matcher, node any) (any, bool) { + o, ok := node.(token.Token) + if !ok { + return nil, false + } + return o, token.Token(tok) == o +} + +func (Nil) Match(m *Matcher, node any) (any, bool) { + if isNil(node) { + return nil, true + } + v := reflect.ValueOf(node) + switch v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + return nil, v.IsNil() + default: + return nil, false + } +} + +func (builtin Builtin) Match(m *Matcher, node any) (any, bool) { + r, ok := match(m, Ident(builtin), node) + if !ok { + return nil, false + } + ident := r.(*ast.Ident) + obj := m.TypesInfo.ObjectOf(ident) + if obj != types.Universe.Lookup(ident.Name) { + return nil, false + } + return ident, true +} + +func (obj Object) Match(m *Matcher, node any) (any, bool) { + r, ok := match(m, Ident(obj), node) + if !ok { + return nil, false + } + ident := r.(*ast.Ident) + + id := m.TypesInfo.ObjectOf(ident) + _, ok = match(m, obj.Name, ident.Name) + return id, ok +} + +func (fn Symbol) Match(m *Matcher, node any) (any, bool) { + var name string + var obj types.Object + + base := []Node{ + Ident{Any{}}, + SelectorExpr{Any{}, Any{}}, + } + p := Or{ + Nodes: append(base, + IndexExpr{Or{Nodes: base}, Any{}}, + IndexListExpr{Or{Nodes: base}, Any{}})} + + r, ok := match(m, p, node) + if !ok { + return nil, false + } + + fun := r.(ast.Expr) + switch idx := fun.(type) { + case *ast.IndexExpr: + fun = idx.X + case *ast.IndexListExpr: + fun = idx.X + } + + switch fun := ast.Unparen(fun).(type) { + case *ast.Ident: + obj = m.TypesInfo.ObjectOf(fun) + case *ast.SelectorExpr: + obj = m.TypesInfo.ObjectOf(fun.Sel) + default: + panic("unreachable") + } + switch obj := obj.(type) { + case *types.Func: + // OPT(dh): optimize this similar to code.FuncName + name = obj.FullName() + case *types.Builtin: + name = obj.Name() + case *types.TypeName: + origObj := obj + for { + if obj.Parent() != obj.Pkg().Scope() { + return nil, false + } + name = types.TypeString(obj.Type(), nil) + _, ok = match(m, fn.Name, name) + if ok || !obj.IsAlias() { + return origObj, ok + } else { + // FIXME(dh): we should peel away one layer of alias at a time; this is blocked on + // github.com/golang/go/issues/66559 + switch typ := types.Unalias(obj.Type()).(type) { + case interface{ Obj() *types.TypeName }: + obj = typ.Obj() + case *types.Basic: + return match(m, fn.Name, typ.Name()) + default: + return nil, false + } + } + } + case *types.Const, *types.Var: + if obj.Pkg() == nil { + return nil, false + } + if obj.Parent() != obj.Pkg().Scope() { + return nil, false + } + name = fmt.Sprintf("%s.%s", obj.Pkg().Path(), obj.Name()) + default: + return nil, false + } + + _, ok = match(m, fn.Name, name) + return obj, ok +} + +func (or Or) Match(m *Matcher, node any) (any, bool) { + for _, opt := range or.Nodes { + m.push() + if ret, ok := match(m, opt, node); ok { + m.merge() + return ret, true + } else { + m.pop() + } + } + return nil, false +} + +func (not Not) Match(m *Matcher, node any) (any, bool) { + _, ok := match(m, not.Node, node) + if ok { + return nil, false + } + return node, true +} + +var integerLiteralQ = MustParse(`(Or (BasicLit "INT" _) (UnaryExpr (Or "+" "-") (IntegerLiteral _)))`) + +func (lit IntegerLiteral) Match(m *Matcher, node any) (any, bool) { + matched, ok := match(m, integerLiteralQ.Root, node) + if !ok { + return nil, false + } + tv, ok := m.TypesInfo.Types[matched.(ast.Expr)] + if !ok { + return nil, false + } + if tv.Value == nil { + return nil, false + } + _, ok = match(m, lit.Value, tv) + return matched, ok +} + +func (texpr TrulyConstantExpression) Match(m *Matcher, node any) (any, bool) { + expr, ok := node.(ast.Expr) + if !ok { + return nil, false + } + tv, ok := m.TypesInfo.Types[expr] + if !ok { + return nil, false + } + if tv.Value == nil { + return nil, false + } + truly := true + ast.Inspect(expr, func(node ast.Node) bool { + if _, ok := node.(*ast.Ident); ok { + truly = false + return false + } + return true + }) + if !truly { + return nil, false + } + _, ok = match(m, texpr.Value, tv) + return expr, ok +} + +var ( + // Types of fields in go/ast structs that we want to skip + rtTokPos = reflect.TypeFor[token.Pos]() + //lint:ignore SA1019 It's deprecated, but we still want to skip the field. + rtObject = reflect.TypeFor[*ast.Object]() + rtCommentGroup = reflect.TypeFor[*ast.CommentGroup]() +) + +var ( + _ matcher = Binding{} + _ matcher = Any{} + _ matcher = List{} + _ matcher = String("") + _ matcher = Token(0) + _ matcher = Nil{} + _ matcher = Builtin{} + _ matcher = Object{} + _ matcher = Symbol{} + _ matcher = Or{} + _ matcher = Not{} + _ matcher = IntegerLiteral{} + _ matcher = TrulyConstantExpression{} +) diff --git a/vendor/honnef.co/go/tools/pattern/parser.go b/vendor/honnef.co/go/tools/pattern/parser.go new file mode 100644 index 0000000..e9af702 --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/parser.go @@ -0,0 +1,714 @@ +package pattern + +import ( + "errors" + "fmt" + "go/ast" + "go/token" + "iter" + "reflect" + "strings" +) + +type Pattern struct { + Root Node + // EntryNodes contains instances of ast.Node that could potentially + // initiate a successful match of the pattern. + EntryNodes []ast.Node + + // SymbolsPattern is a pattern consisting or Any, Or, And, and IndexSymbol, + // that can be used to implement fast rejection of whole packages using + // typeindex. + SymbolsPattern Node + + // If non-empty, all possible candidate nodes for this pattern can be found + // by finding all call expressions for this list of symbols. + RootCallSymbols []IndexSymbol + + // Mapping from binding index to binding name + Bindings []string +} + +func MustParse(s string) Pattern { + p := &Parser{AllowTypeInfo: true} + pat, err := p.Parse(s) + if err != nil { + panic(err) + } + return pat +} + +func symbolToIndexSymbol(name string) IndexSymbol { + if len(name) == 0 { + return IndexSymbol{} + } + if name[0] == '(' { + end := strings.IndexAny(name, ")") + // Ensure there's a ), and also that there are at least two more + // characters after it, for a dot and an identifier. + if end == -1 || end > len(name)-2 { + return IndexSymbol{} + } + pathAndType := strings.TrimPrefix(name[1:end], "*") + dot := strings.LastIndex(pathAndType, ".") + if dot == -1 { + return IndexSymbol{} + } + path := pathAndType[:dot] + typ := pathAndType[dot+1:] + ident := name[end+2:] + return IndexSymbol{path, typ, ident} + } else { + dot := strings.LastIndex(name, ".") + if dot == -1 { + return IndexSymbol{"", "", name} + } + path := name[:dot] + ident := name[dot+1:] + return IndexSymbol{path, "", ident} + } +} + +func collectSymbols(node Node, inSymbol bool) Node { + and := func(c Node, out *And) { + switch cc := c.(type) { + case And: + out.Nodes = append(out.Nodes, cc.Nodes...) + case Any: + case nil: + default: + out.Nodes = append(out.Nodes, c) + } + } + + switch node := node.(type) { + case Or: + s := Or{} + for _, el := range node.Nodes { + c := collectSymbols(el, inSymbol) + switch cc := c.(type) { + case Or: + s.Nodes = append(s.Nodes, cc.Nodes...) + case Any: + return Any{} + case nil: + default: + s.Nodes = append(s.Nodes, c) + } + } + switch len(s.Nodes) { + case 0: + return nil + case 1: + return s.Nodes[0] + default: + return s + } + case Not, Token, nil: + return Any{} + case Symbol: + return collectSymbols(node.Name, true) + case String: + if !inSymbol { + return Any{} + } + // In logically correct patterns, all Strings that are children of + // Symbols describe the names of symbols. + return symbolToIndexSymbol(string(node)) + case Binding: + return collectSymbols(node.Node, inSymbol) + case Any: + return Any{} + case List: + var out And + and(collectSymbols(node.Head, inSymbol), &out) + and(collectSymbols(node.Tail, inSymbol), &out) + switch len(out.Nodes) { + case 0: + return Any{} + case 1: + return out.Nodes[0] + default: + return out + } + default: + var out And + rv := reflect.ValueOf(node) + for i := range rv.NumField() { + c := collectSymbols(rv.Field(i).Interface().(Node), inSymbol) + and(c, &out) + } + switch len(out.Nodes) { + case 0: + return Any{} + case 1: + return out.Nodes[0] + default: + return out + } + } +} + +func collectRootCallSymbols(node Node) []IndexSymbol { + root, ok := node.(CallExpr) + if !ok { + return nil + } + + var names []String + var handleSymName func(name Node) bool + handleSymName = func(name Node) bool { + switch name := name.(type) { + case String: + names = append(names, name) + case Or: + for _, node := range name.Nodes { + if name, ok := node.(String); ok { + names = append(names, name) + } else { + return false + } + } + case Binding: + return handleSymName(name.Node) + default: + return false + } + return true + } + var handleRootFun func(node Node) bool + handleRootFun = func(node Node) bool { + switch fun := node.(type) { + case Binding: + return handleRootFun(fun.Node) + case Symbol: + return handleSymName(fun.Name) + case Or: + for _, node := range fun.Nodes { + if sym, ok := node.(Symbol); !ok || !handleSymName(sym.Name) { + return false + } + } + return true + default: + return false + } + } + if !handleRootFun(root.Fun) { + return nil + } + + out := make([]IndexSymbol, len(names)) + for i, name := range names { + out[i] = symbolToIndexSymbol(string(name)) + } + return out +} + +func collectEntryNodes(node Node, m map[reflect.Type]struct{}) { + switch node := node.(type) { + case Or: + for _, el := range node.Nodes { + collectEntryNodes(el, m) + } + case Not: + collectEntryNodes(node.Node, m) + case Binding: + collectEntryNodes(node.Node, m) + case Nil, nil: + // this branch is reached via bindings + for _, T := range allTypes { + m[T] = struct{}{} + } + default: + Ts, ok := nodeToASTTypes[reflect.TypeOf(node)] + if !ok { + panic(fmt.Sprintf("internal error: unhandled type %T", node)) + } + for _, T := range Ts { + m[T] = struct{}{} + } + } +} + +var allTypes = []reflect.Type{ + reflect.TypeFor[*ast.RangeStmt](), + reflect.TypeFor[*ast.AssignStmt](), + reflect.TypeFor[*ast.IndexExpr](), + reflect.TypeFor[*ast.Ident](), + reflect.TypeFor[*ast.ValueSpec](), + reflect.TypeFor[*ast.GenDecl](), + reflect.TypeFor[*ast.BinaryExpr](), + reflect.TypeFor[*ast.ForStmt](), + reflect.TypeFor[*ast.ArrayType](), + reflect.TypeFor[*ast.DeferStmt](), + reflect.TypeFor[*ast.MapType](), + reflect.TypeFor[*ast.ReturnStmt](), + reflect.TypeFor[*ast.SliceExpr](), + reflect.TypeFor[*ast.StarExpr](), + reflect.TypeFor[*ast.UnaryExpr](), + reflect.TypeFor[*ast.SendStmt](), + reflect.TypeFor[*ast.SelectStmt](), + reflect.TypeFor[*ast.ImportSpec](), + reflect.TypeFor[*ast.IfStmt](), + reflect.TypeFor[*ast.GoStmt](), + reflect.TypeFor[*ast.Field](), + reflect.TypeFor[*ast.SelectorExpr](), + reflect.TypeFor[*ast.StructType](), + reflect.TypeFor[*ast.KeyValueExpr](), + reflect.TypeFor[*ast.FuncType](), + reflect.TypeFor[*ast.FuncLit](), + reflect.TypeFor[*ast.FuncDecl](), + reflect.TypeFor[*ast.ChanType](), + reflect.TypeFor[*ast.CallExpr](), + reflect.TypeFor[*ast.CaseClause](), + reflect.TypeFor[*ast.CommClause](), + reflect.TypeFor[*ast.CompositeLit](), + reflect.TypeFor[*ast.EmptyStmt](), + reflect.TypeFor[*ast.SwitchStmt](), + reflect.TypeFor[*ast.TypeSwitchStmt](), + reflect.TypeFor[*ast.TypeAssertExpr](), + reflect.TypeFor[*ast.TypeSpec](), + reflect.TypeFor[*ast.InterfaceType](), + reflect.TypeFor[*ast.BranchStmt](), + reflect.TypeFor[*ast.IncDecStmt](), + reflect.TypeFor[*ast.BasicLit](), +} + +var nodeToASTTypes = map[reflect.Type][]reflect.Type{ + reflect.TypeFor[String](): nil, + reflect.TypeFor[Token](): nil, + reflect.TypeFor[List](): {reflect.TypeFor[*ast.BlockStmt](), reflect.TypeFor[*ast.FieldList]()}, + reflect.TypeFor[Builtin](): {reflect.TypeFor[*ast.Ident]()}, + reflect.TypeFor[Object](): {reflect.TypeFor[*ast.Ident]()}, + reflect.TypeFor[Symbol](): {reflect.TypeFor[*ast.Ident](), reflect.TypeFor[*ast.SelectorExpr]()}, + reflect.TypeFor[Any](): allTypes, + reflect.TypeFor[RangeStmt](): {reflect.TypeFor[*ast.RangeStmt]()}, + reflect.TypeFor[AssignStmt](): {reflect.TypeFor[*ast.AssignStmt]()}, + reflect.TypeFor[IndexExpr](): {reflect.TypeFor[*ast.IndexExpr]()}, + reflect.TypeFor[Ident](): {reflect.TypeFor[*ast.Ident]()}, + reflect.TypeFor[ValueSpec](): {reflect.TypeFor[*ast.ValueSpec]()}, + reflect.TypeFor[GenDecl](): {reflect.TypeFor[*ast.GenDecl]()}, + reflect.TypeFor[BinaryExpr](): {reflect.TypeFor[*ast.BinaryExpr]()}, + reflect.TypeFor[ForStmt](): {reflect.TypeFor[*ast.ForStmt]()}, + reflect.TypeFor[ArrayType](): {reflect.TypeFor[*ast.ArrayType]()}, + reflect.TypeFor[DeferStmt](): {reflect.TypeFor[*ast.DeferStmt]()}, + reflect.TypeFor[MapType](): {reflect.TypeFor[*ast.MapType]()}, + reflect.TypeFor[ReturnStmt](): {reflect.TypeFor[*ast.ReturnStmt]()}, + reflect.TypeFor[SliceExpr](): {reflect.TypeFor[*ast.SliceExpr]()}, + reflect.TypeFor[StarExpr](): {reflect.TypeFor[*ast.StarExpr]()}, + reflect.TypeFor[UnaryExpr](): {reflect.TypeFor[*ast.UnaryExpr]()}, + reflect.TypeFor[SendStmt](): {reflect.TypeFor[*ast.SendStmt]()}, + reflect.TypeFor[SelectStmt](): {reflect.TypeFor[*ast.SelectStmt]()}, + reflect.TypeFor[ImportSpec](): {reflect.TypeFor[*ast.ImportSpec]()}, + reflect.TypeFor[IfStmt](): {reflect.TypeFor[*ast.IfStmt]()}, + reflect.TypeFor[GoStmt](): {reflect.TypeFor[*ast.GoStmt]()}, + reflect.TypeFor[Field](): {reflect.TypeFor[*ast.Field]()}, + reflect.TypeFor[SelectorExpr](): {reflect.TypeFor[*ast.SelectorExpr]()}, + reflect.TypeFor[StructType](): {reflect.TypeFor[*ast.StructType]()}, + reflect.TypeFor[KeyValueExpr](): {reflect.TypeFor[*ast.KeyValueExpr]()}, + reflect.TypeFor[FuncType](): {reflect.TypeFor[*ast.FuncType]()}, + reflect.TypeFor[FuncLit](): {reflect.TypeFor[*ast.FuncLit]()}, + reflect.TypeFor[FuncDecl](): {reflect.TypeFor[*ast.FuncDecl]()}, + reflect.TypeFor[ChanType](): {reflect.TypeFor[*ast.ChanType]()}, + reflect.TypeFor[CallExpr](): {reflect.TypeFor[*ast.CallExpr]()}, + reflect.TypeFor[CaseClause](): {reflect.TypeFor[*ast.CaseClause]()}, + reflect.TypeFor[CommClause](): {reflect.TypeFor[*ast.CommClause]()}, + reflect.TypeFor[CompositeLit](): {reflect.TypeFor[*ast.CompositeLit]()}, + reflect.TypeFor[EmptyStmt](): {reflect.TypeFor[*ast.EmptyStmt]()}, + reflect.TypeFor[SwitchStmt](): {reflect.TypeFor[*ast.SwitchStmt]()}, + reflect.TypeFor[TypeSwitchStmt](): {reflect.TypeFor[*ast.TypeSwitchStmt]()}, + reflect.TypeFor[TypeAssertExpr](): {reflect.TypeFor[*ast.TypeAssertExpr]()}, + reflect.TypeFor[TypeSpec](): {reflect.TypeFor[*ast.TypeSpec]()}, + reflect.TypeFor[InterfaceType](): {reflect.TypeFor[*ast.InterfaceType]()}, + reflect.TypeFor[BranchStmt](): {reflect.TypeFor[*ast.BranchStmt]()}, + reflect.TypeFor[IncDecStmt](): {reflect.TypeFor[*ast.IncDecStmt]()}, + reflect.TypeFor[BasicLit](): {reflect.TypeFor[*ast.BasicLit]()}, + reflect.TypeFor[IntegerLiteral](): {reflect.TypeFor[*ast.BasicLit](), reflect.TypeFor[*ast.UnaryExpr]()}, + reflect.TypeFor[TrulyConstantExpression](): allTypes, // this is an over-approximation, which is fine +} + +var requiresTypeInfo = map[string]bool{ + "Symbol": true, + "Builtin": true, + "Object": true, + "IntegerLiteral": true, + "TrulyConstantExpression": true, +} + +type Parser struct { + // Allow nodes that rely on type information + AllowTypeInfo bool + + f *token.File + cur item + last *item + nextItem func() (item, bool) + + bindings map[string]int +} + +func (p *Parser) bindingIndex(name string) int { + if p.bindings == nil { + p.bindings = map[string]int{} + } + if idx, ok := p.bindings[name]; ok { + return idx + } + idx := len(p.bindings) + p.bindings[name] = idx + return idx +} + +func (p *Parser) Parse(s string) (Pattern, error) { + f := token.NewFileSet().AddFile("", -1, len(s)) + + // Run the lexer iterator as a coroutine. + // The parser will call 'next' to consume each item. + // After the parser returns, we must call 'stop' to + // terminate the coroutine. + next, stop := iter.Pull(lex(f, s)) + defer stop() + + p.cur = item{} + p.last = nil + p.f = f + p.nextItem = next + + // Parse. + root, err := p.node() + if err != nil { + return Pattern{}, err + } + // Consume final EOF token. + if item, ok := next(); !ok || item.typ != itemEOF { + return Pattern{}, fmt.Errorf("unexpected token %s after end of pattern", item.typ) + } + + if len(p.bindings) > 64 { + return Pattern{}, errors.New("encountered more than 64 bindings") + } + + bindings := make([]string, len(p.bindings)) + for name, idx := range p.bindings { + bindings[idx] = name + } + + _, isSymbol := root.(Symbol) + sym := collectSymbols(root, isSymbol) + rootSyms := collectRootCallSymbols(root) + relevantMap := map[reflect.Type]struct{}{} + collectEntryNodes(root, relevantMap) + relevantNodes := make([]ast.Node, 0, len(relevantMap)) + for k := range relevantMap { + relevantNodes = append(relevantNodes, reflect.Zero(k).Interface().(ast.Node)) + } + return Pattern{ + Root: root, + EntryNodes: relevantNodes, + SymbolsPattern: sym, + RootCallSymbols: rootSyms, + Bindings: bindings, + }, nil +} + +func (p *Parser) next() item { + if p.last != nil { + n := *p.last + p.last = nil + return n + } + var ok bool + p.cur, ok = p.nextItem() + if !ok { + p.cur = item{typ: eof} + } + return p.cur +} + +func (p *Parser) rewind() { + p.last = &p.cur +} + +func (p *Parser) peek() item { + n := p.next() + p.rewind() + return n +} + +func (p *Parser) accept(typ itemType) (item, bool) { + n := p.next() + if n.typ == typ { + return n, true + } + p.rewind() + return item{}, false +} + +func (p *Parser) unexpectedToken(valid string) error { + if p.cur.typ == itemError { + return fmt.Errorf("error lexing input: %s", p.cur.val) + } + var got string + switch p.cur.typ { + case itemTypeName, itemVariable, itemString: + got = p.cur.val + default: + got = "'" + p.cur.typ.String() + "'" + } + + pos := p.f.Position(token.Pos(p.cur.pos)) + return fmt.Errorf("%s: expected %s, found %s", pos, valid, got) +} + +func (p *Parser) node() (Node, error) { + if _, ok := p.accept(itemLeftParen); !ok { + return nil, p.unexpectedToken("'('") + } + typ, ok := p.accept(itemTypeName) + if !ok { + return nil, p.unexpectedToken("Node type") + } + + var objs []Node + for { + if _, ok := p.accept(itemRightParen); ok { + break + } else { + p.rewind() + obj, err := p.object() + if err != nil { + return nil, err + } + objs = append(objs, obj) + } + } + + node, err := p.populateNode(typ.val, objs) + if err != nil { + return nil, err + } + if node, ok := node.(Binding); ok { + node.idx = p.bindingIndex(node.Name) + } + return node, nil +} + +func populateNode(typ string, objs []Node, allowTypeInfo bool) (Node, error) { + T, ok := structNodes[typ] + if !ok { + return nil, fmt.Errorf("unknown node %s", typ) + } + + if !allowTypeInfo && requiresTypeInfo[typ] { + return nil, fmt.Errorf("Node %s requires type information", typ) + } + + pv := reflect.New(T) + v := pv.Elem() + + if v.NumField() == 1 { + f := v.Field(0) + if f.Type().Kind() == reflect.Slice { + // Variadic node + f.Set(reflect.AppendSlice(f, reflect.ValueOf(objs))) + return v.Interface().(Node), nil + } + } + + n := -1 + for i := 0; i < T.NumField(); i++ { + if !T.Field(i).IsExported() { + break + } + n = i + } + + if len(objs) != n+1 { + return nil, fmt.Errorf("tried to initialize node %s with %d values, expected %d", typ, len(objs), n+1) + } + + for i := 0; i < v.NumField(); i++ { + if !T.Field(i).IsExported() { + break + } + f := v.Field(i) + if f.Kind() == reflect.String { + if obj, ok := objs[i].(String); ok { + f.Set(reflect.ValueOf(string(obj))) + } else { + return nil, fmt.Errorf("first argument of (Binding name node) must be string, but got %s", objs[i]) + } + } else { + f.Set(reflect.ValueOf(objs[i])) + } + } + return v.Interface().(Node), nil +} + +func (p *Parser) populateNode(typ string, objs []Node) (Node, error) { + return populateNode(typ, objs, p.AllowTypeInfo) +} + +var structNodes = map[string]reflect.Type{ + "Any": reflect.TypeFor[Any](), + "Ellipsis": reflect.TypeFor[Ellipsis](), + "List": reflect.TypeFor[List](), + "Binding": reflect.TypeFor[Binding](), + "RangeStmt": reflect.TypeFor[RangeStmt](), + "AssignStmt": reflect.TypeFor[AssignStmt](), + "IndexExpr": reflect.TypeFor[IndexExpr](), + "Ident": reflect.TypeFor[Ident](), + "Builtin": reflect.TypeFor[Builtin](), + "ValueSpec": reflect.TypeFor[ValueSpec](), + "GenDecl": reflect.TypeFor[GenDecl](), + "BinaryExpr": reflect.TypeFor[BinaryExpr](), + "ForStmt": reflect.TypeFor[ForStmt](), + "ArrayType": reflect.TypeFor[ArrayType](), + "DeferStmt": reflect.TypeFor[DeferStmt](), + "MapType": reflect.TypeFor[MapType](), + "ReturnStmt": reflect.TypeFor[ReturnStmt](), + "SliceExpr": reflect.TypeFor[SliceExpr](), + "StarExpr": reflect.TypeFor[StarExpr](), + "UnaryExpr": reflect.TypeFor[UnaryExpr](), + "SendStmt": reflect.TypeFor[SendStmt](), + "SelectStmt": reflect.TypeFor[SelectStmt](), + "ImportSpec": reflect.TypeFor[ImportSpec](), + "IfStmt": reflect.TypeFor[IfStmt](), + "GoStmt": reflect.TypeFor[GoStmt](), + "Field": reflect.TypeFor[Field](), + "SelectorExpr": reflect.TypeFor[SelectorExpr](), + "StructType": reflect.TypeFor[StructType](), + "KeyValueExpr": reflect.TypeFor[KeyValueExpr](), + "FuncType": reflect.TypeFor[FuncType](), + "FuncLit": reflect.TypeFor[FuncLit](), + "FuncDecl": reflect.TypeFor[FuncDecl](), + "ChanType": reflect.TypeFor[ChanType](), + "CallExpr": reflect.TypeFor[CallExpr](), + "CaseClause": reflect.TypeFor[CaseClause](), + "CommClause": reflect.TypeFor[CommClause](), + "CompositeLit": reflect.TypeFor[CompositeLit](), + "EmptyStmt": reflect.TypeFor[EmptyStmt](), + "SwitchStmt": reflect.TypeFor[SwitchStmt](), + "TypeSwitchStmt": reflect.TypeFor[TypeSwitchStmt](), + "TypeAssertExpr": reflect.TypeFor[TypeAssertExpr](), + "TypeSpec": reflect.TypeFor[TypeSpec](), + "InterfaceType": reflect.TypeFor[InterfaceType](), + "BranchStmt": reflect.TypeFor[BranchStmt](), + "IncDecStmt": reflect.TypeFor[IncDecStmt](), + "BasicLit": reflect.TypeFor[BasicLit](), + "Object": reflect.TypeFor[Object](), + "Symbol": reflect.TypeFor[Symbol](), + "Or": reflect.TypeFor[Or](), + "Not": reflect.TypeFor[Not](), + "IntegerLiteral": reflect.TypeFor[IntegerLiteral](), + "TrulyConstantExpression": reflect.TypeFor[TrulyConstantExpression](), +} + +func (p *Parser) object() (Node, error) { + n := p.next() + switch n.typ { + case itemLeftParen: + p.rewind() + node, err := p.node() + if err != nil { + return node, err + } + if p.peek().typ == itemColon { + p.next() + tail, err := p.object() + if err != nil { + return node, err + } + return List{Head: node, Tail: tail}, nil + } + return node, nil + case itemLeftBracket: + p.rewind() + return p.array() + case itemVariable: + v := n + if v.val == "nil" { + return Nil{}, nil + } + var b Binding + if _, ok := p.accept(itemAt); ok { + o, err := p.node() + if err != nil { + return nil, err + } + b = Binding{ + Name: v.val, + Node: o, + idx: p.bindingIndex(v.val), + } + } else { + p.rewind() + b = Binding{ + Name: v.val, + idx: p.bindingIndex(v.val), + } + } + if p.peek().typ == itemColon { + p.next() + tail, err := p.object() + if err != nil { + return b, err + } + return List{Head: b, Tail: tail}, nil + } + return b, nil + case itemBlank: + if p.peek().typ == itemColon { + p.next() + tail, err := p.object() + if err != nil { + return Any{}, err + } + return List{Head: Any{}, Tail: tail}, nil + } + return Any{}, nil + case itemString: + return String(n.val), nil + default: + return nil, p.unexpectedToken("object") + } +} + +func (p *Parser) array() (Node, error) { + if _, ok := p.accept(itemLeftBracket); !ok { + return nil, p.unexpectedToken("'['") + } + + var objs []Node + for { + if _, ok := p.accept(itemRightBracket); ok { + break + } else { + p.rewind() + obj, err := p.object() + if err != nil { + return nil, err + } + objs = append(objs, obj) + } + } + + tail := List{} + for i := len(objs) - 1; i >= 0; i-- { + l := List{ + Head: objs[i], + Tail: tail, + } + tail = l + } + return tail, nil +} + +/* +Node ::= itemLeftParen itemTypeName Object* itemRightParen +Object ::= Node | Array | Binding | itemVariable | itemBlank | itemString +Array := itemLeftBracket Object* itemRightBracket +Array := Object itemColon Object +Binding ::= itemVariable itemAt Node +*/ diff --git a/vendor/honnef.co/go/tools/pattern/pattern.go b/vendor/honnef.co/go/tools/pattern/pattern.go new file mode 100644 index 0000000..107f95b --- /dev/null +++ b/vendor/honnef.co/go/tools/pattern/pattern.go @@ -0,0 +1,551 @@ +package pattern + +import ( + "fmt" + "go/token" + "reflect" + "strings" +) + +var ( + _ Node = Ellipsis{} + _ Node = Binding{} + _ Node = RangeStmt{} + _ Node = AssignStmt{} + _ Node = IndexExpr{} + _ Node = IndexListExpr{} + _ Node = Ident{} + _ Node = Builtin{} + _ Node = String("") + _ Node = Any{} + _ Node = ValueSpec{} + _ Node = List{} + _ Node = GenDecl{} + _ Node = BinaryExpr{} + _ Node = ForStmt{} + _ Node = ArrayType{} + _ Node = DeferStmt{} + _ Node = MapType{} + _ Node = ReturnStmt{} + _ Node = SliceExpr{} + _ Node = StarExpr{} + _ Node = UnaryExpr{} + _ Node = SendStmt{} + _ Node = SelectStmt{} + _ Node = ImportSpec{} + _ Node = IfStmt{} + _ Node = GoStmt{} + _ Node = Field{} + _ Node = SelectorExpr{} + _ Node = StructType{} + _ Node = KeyValueExpr{} + _ Node = FuncType{} + _ Node = FuncLit{} + _ Node = FuncDecl{} + _ Node = Token(0) + _ Node = ChanType{} + _ Node = CallExpr{} + _ Node = CaseClause{} + _ Node = CommClause{} + _ Node = CompositeLit{} + _ Node = EmptyStmt{} + _ Node = SwitchStmt{} + _ Node = TypeSwitchStmt{} + _ Node = TypeAssertExpr{} + _ Node = TypeSpec{} + _ Node = InterfaceType{} + _ Node = BranchStmt{} + _ Node = IncDecStmt{} + _ Node = BasicLit{} + _ Node = Nil{} + _ Node = Object{} + _ Node = Symbol{} + _ Node = Not{} + _ Node = Or{} + _ Node = IntegerLiteral{} + _ Node = TrulyConstantExpression{} +) + +type Symbol struct { + Name Node +} + +type Token token.Token + +type Nil struct { +} + +type Ellipsis struct { + Elt Node +} + +type IncDecStmt struct { + X Node + Tok Node +} + +type BranchStmt struct { + Tok Node + Label Node +} + +type InterfaceType struct { + Methods Node +} + +type TypeSpec struct { + Name Node + Type Node +} + +type TypeAssertExpr struct { + X Node + Type Node +} + +type TypeSwitchStmt struct { + Init Node + Assign Node + Body Node +} + +type SwitchStmt struct { + Init Node + Tag Node + Body Node +} + +type EmptyStmt struct { +} + +type CompositeLit struct { + Type Node + Elts Node +} + +type CommClause struct { + Comm Node + Body Node +} + +type CaseClause struct { + List Node + Body Node +} + +type CallExpr struct { + Fun Node + Args Node + // XXX handle ellipsis +} + +// TODO(dh): add a ChanDir node, and a way of instantiating it. + +type ChanType struct { + Dir Node + Value Node +} + +type FuncDecl struct { + Recv Node + Name Node + Type Node + Body Node +} + +type FuncLit struct { + Type Node + Body Node +} + +type FuncType struct { + Params Node + Results Node +} + +type KeyValueExpr struct { + Key Node + Value Node +} + +type StructType struct { + Fields Node +} + +type SelectorExpr struct { + X Node + Sel Node +} + +type Field struct { + Names Node + Type Node + Tag Node +} + +type GoStmt struct { + Call Node +} + +type IfStmt struct { + Init Node + Cond Node + Body Node + Else Node +} + +type ImportSpec struct { + Name Node + Path Node +} + +type SelectStmt struct { + Body Node +} + +type ArrayType struct { + Len Node + Elt Node +} + +type DeferStmt struct { + Call Node +} + +type MapType struct { + Key Node + Value Node +} + +type ReturnStmt struct { + Results Node +} + +type SliceExpr struct { + X Node + Low Node + High Node + Max Node +} + +type StarExpr struct { + X Node +} + +type UnaryExpr struct { + Op Node + X Node +} + +type SendStmt struct { + Chan Node + Value Node +} + +type Binding struct { + Name string + Node Node + + idx int +} + +type RangeStmt struct { + Key Node + Value Node + Tok Node + X Node + Body Node +} + +type AssignStmt struct { + Lhs Node + Tok Node + Rhs Node +} + +type IndexExpr struct { + X Node + Index Node +} + +type IndexListExpr struct { + X Node + Indices Node +} + +type Node interface { + String() string + isNode() +} + +type Ident struct { + Name Node +} + +type Object struct { + Name Node +} + +type Builtin struct { + Name Node +} + +type String string + +type Any struct{} + +type ValueSpec struct { + Names Node + Type Node + Values Node +} + +type List struct { + Head Node + Tail Node +} + +type GenDecl struct { + Tok Node + Specs Node +} + +type BasicLit struct { + Kind Node + Value Node +} + +// An IntegerLiteral is a constant expression made up of only integer basic literals and the "+" and "-" unary operators. +// That is, 0, -4, -+42 are all integer literals, but 1 + 2 is not. +type IntegerLiteral struct { + Value Node +} + +type BinaryExpr struct { + X Node + Op Node + Y Node +} + +type ForStmt struct { + Init Node + Cond Node + Post Node + Body Node +} + +type Or struct { + Nodes []Node +} + +type And struct { + Nodes []Node +} + +type Not struct { + Node Node +} + +// A TrulyConstantExpression is a constant expression that does not make use of any identifiers. +// It is constant even under varying build tags. +type TrulyConstantExpression struct { + Value Node +} + +type IndexSymbol struct { + Path string + Type string + Ident string +} + +func stringify(n Node) string { + v := reflect.ValueOf(n) + var parts []string + parts = append(parts, v.Type().Name()) + for i := 0; i < v.NumField(); i++ { + parts = append(parts, fmt.Sprintf("%s", v.Field(i))) + } + return "(" + strings.Join(parts, " ") + ")" +} + +func (stmt AssignStmt) String() string { return stringify(stmt) } +func (expr IndexExpr) String() string { return stringify(expr) } +func (expr IndexListExpr) String() string { return stringify(expr) } +func (id Ident) String() string { return stringify(id) } +func (spec ValueSpec) String() string { return stringify(spec) } +func (decl GenDecl) String() string { return stringify(decl) } +func (lit BasicLit) String() string { return stringify(lit) } +func (expr BinaryExpr) String() string { return stringify(expr) } +func (stmt ForStmt) String() string { return stringify(stmt) } +func (stmt RangeStmt) String() string { return stringify(stmt) } +func (typ ArrayType) String() string { return stringify(typ) } +func (stmt DeferStmt) String() string { return stringify(stmt) } +func (typ MapType) String() string { return stringify(typ) } +func (stmt ReturnStmt) String() string { return stringify(stmt) } +func (expr SliceExpr) String() string { return stringify(expr) } +func (expr StarExpr) String() string { return stringify(expr) } +func (expr UnaryExpr) String() string { return stringify(expr) } +func (stmt SendStmt) String() string { return stringify(stmt) } +func (spec ImportSpec) String() string { return stringify(spec) } +func (stmt SelectStmt) String() string { return stringify(stmt) } +func (stmt IfStmt) String() string { return stringify(stmt) } +func (stmt IncDecStmt) String() string { return stringify(stmt) } +func (stmt GoStmt) String() string { return stringify(stmt) } +func (field Field) String() string { return stringify(field) } +func (expr SelectorExpr) String() string { return stringify(expr) } +func (typ StructType) String() string { return stringify(typ) } +func (expr KeyValueExpr) String() string { return stringify(expr) } +func (typ FuncType) String() string { return stringify(typ) } +func (lit FuncLit) String() string { return stringify(lit) } +func (decl FuncDecl) String() string { return stringify(decl) } +func (stmt BranchStmt) String() string { return stringify(stmt) } +func (expr CallExpr) String() string { return stringify(expr) } +func (clause CaseClause) String() string { return stringify(clause) } +func (typ ChanType) String() string { return stringify(typ) } +func (clause CommClause) String() string { return stringify(clause) } +func (lit CompositeLit) String() string { return stringify(lit) } +func (stmt EmptyStmt) String() string { return stringify(stmt) } +func (typ InterfaceType) String() string { return stringify(typ) } +func (stmt SwitchStmt) String() string { return stringify(stmt) } +func (expr TypeAssertExpr) String() string { return stringify(expr) } +func (spec TypeSpec) String() string { return stringify(spec) } +func (stmt TypeSwitchStmt) String() string { return stringify(stmt) } +func (nil Nil) String() string { return "nil" } +func (builtin Builtin) String() string { return stringify(builtin) } +func (obj Object) String() string { return stringify(obj) } +func (fn Symbol) String() string { return stringify(fn) } +func (el Ellipsis) String() string { return stringify(el) } +func (not Not) String() string { return stringify(not) } +func (lit IntegerLiteral) String() string { return stringify(lit) } +func (expr TrulyConstantExpression) String() string { return stringify(expr) } +func (sym IndexSymbol) String() string { + return fmt.Sprintf("(IndexSymbol %q %q %q)", sym.Path, sym.Type, sym.Ident) +} + +func (or Or) String() string { + var s strings.Builder + s.WriteString("(Or") + for _, node := range or.Nodes { + s.WriteString(" ") + s.WriteString(node.String()) + } + s.WriteString(")") + return s.String() +} + +func (and And) String() string { + var s strings.Builder + s.WriteString("(And") + for _, node := range and.Nodes { + s.WriteString(" ") + s.WriteString(node.String()) + } + s.WriteString(")") + return s.String() +} + +func isProperList(l List) bool { + if l.Head == nil && l.Tail == nil { + return true + } + switch tail := l.Tail.(type) { + case nil: + return false + case List: + return isProperList(tail) + default: + return false + } +} + +func (l List) String() string { + if l.Head == nil && l.Tail == nil { + return "[]" + } + + if isProperList(l) { + // pretty-print the list + var objs []string + for l.Head != nil { + objs = append(objs, l.Head.String()) + l = l.Tail.(List) + } + return fmt.Sprintf("[%s]", strings.Join(objs, " ")) + } + + return fmt.Sprintf("%s:%s", l.Head, l.Tail) +} + +func (bind Binding) String() string { + if bind.Node == nil { + return bind.Name + } + return fmt.Sprintf("%s@%s", bind.Name, bind.Node) +} + +func (s String) String() string { return fmt.Sprintf("%q", string(s)) } + +func (tok Token) String() string { + return fmt.Sprintf("%q", strings.ToUpper(token.Token(tok).String())) +} + +func (Any) String() string { return "_" } + +func (AssignStmt) isNode() {} +func (IndexExpr) isNode() {} +func (IndexListExpr) isNode() {} +func (Ident) isNode() {} +func (ValueSpec) isNode() {} +func (GenDecl) isNode() {} +func (BasicLit) isNode() {} +func (BinaryExpr) isNode() {} +func (ForStmt) isNode() {} +func (RangeStmt) isNode() {} +func (ArrayType) isNode() {} +func (DeferStmt) isNode() {} +func (MapType) isNode() {} +func (ReturnStmt) isNode() {} +func (SliceExpr) isNode() {} +func (StarExpr) isNode() {} +func (UnaryExpr) isNode() {} +func (SendStmt) isNode() {} +func (ImportSpec) isNode() {} +func (SelectStmt) isNode() {} +func (IfStmt) isNode() {} +func (IncDecStmt) isNode() {} +func (GoStmt) isNode() {} +func (Field) isNode() {} +func (SelectorExpr) isNode() {} +func (StructType) isNode() {} +func (KeyValueExpr) isNode() {} +func (FuncType) isNode() {} +func (FuncLit) isNode() {} +func (FuncDecl) isNode() {} +func (BranchStmt) isNode() {} +func (CallExpr) isNode() {} +func (CaseClause) isNode() {} +func (ChanType) isNode() {} +func (CommClause) isNode() {} +func (CompositeLit) isNode() {} +func (EmptyStmt) isNode() {} +func (InterfaceType) isNode() {} +func (SwitchStmt) isNode() {} +func (TypeAssertExpr) isNode() {} +func (TypeSpec) isNode() {} +func (TypeSwitchStmt) isNode() {} +func (Nil) isNode() {} +func (Builtin) isNode() {} +func (Object) isNode() {} +func (Symbol) isNode() {} +func (Ellipsis) isNode() {} +func (Or) isNode() {} +func (And) isNode() {} +func (List) isNode() {} +func (String) isNode() {} +func (Token) isNode() {} +func (Any) isNode() {} +func (Binding) isNode() {} +func (Not) isNode() {} +func (IntegerLiteral) isNode() {} +func (TrulyConstantExpression) isNode() {} +func (IndexSymbol) isNode() {} diff --git a/vendor/honnef.co/go/tools/printf/fuzz.go b/vendor/honnef.co/go/tools/printf/fuzz.go new file mode 100644 index 0000000..cf6310a --- /dev/null +++ b/vendor/honnef.co/go/tools/printf/fuzz.go @@ -0,0 +1,11 @@ +//go:build gofuzz + +package printf + +func Fuzz(data []byte) int { + _, err := Parse(string(data)) + if err == nil { + return 1 + } + return 0 +} diff --git a/vendor/honnef.co/go/tools/printf/printf.go b/vendor/honnef.co/go/tools/printf/printf.go new file mode 100644 index 0000000..b4d4bac --- /dev/null +++ b/vendor/honnef.co/go/tools/printf/printf.go @@ -0,0 +1,198 @@ +// Package printf implements a parser for fmt.Printf-style format +// strings. +// +// It parses verbs according to the following syntax: +// +// Numeric -> '0'-'9' +// Letter -> 'a'-'z' | 'A'-'Z' +// Index -> '[' Numeric+ ']' +// Star -> '*' +// Star -> Index '*' +// +// Precision -> Numeric+ | Star +// Width -> Numeric+ | Star +// +// WidthAndPrecision -> Width '.' Precision +// WidthAndPrecision -> Width '.' +// WidthAndPrecision -> Width +// WidthAndPrecision -> '.' Precision +// WidthAndPrecision -> '.' +// +// Flag -> '+' | '-' | '#' | ' ' | '0' +// Verb -> Letter | '%' +// +// Input -> '%' [ Flag+ ] [ WidthAndPrecision ] [ Index ] Verb +package printf + +import ( + "errors" + "regexp" + "strconv" + "strings" +) + +// ErrInvalid is returned for invalid format strings or verbs. +var ErrInvalid = errors.New("invalid format string") + +type Verb struct { + Letter rune + Flags string + + Width Argument + Precision Argument + // Which value in the argument list the verb uses. + // -1 denotes the next argument, + // values > 0 denote explicit arguments. + // The value 0 denotes that no argument is consumed. This is the case for %%. + Value int + + Raw string +} + +// Argument is an implicit or explicit width or precision. +type Argument interface { + isArgument() +} + +// The Default value, when no width or precision is provided. +type Default struct{} + +// Zero is the implicit zero value. +// This value may only appear for precisions in format strings like %6.f +type Zero struct{} + +// Star is a * value, which may either refer to the next argument (Index == -1) or an explicit argument. +type Star struct{ Index int } + +// A Literal value, such as 6 in %6d. +type Literal int + +func (Default) isArgument() {} +func (Zero) isArgument() {} +func (Star) isArgument() {} +func (Literal) isArgument() {} + +// Parse parses f and returns a list of actions. +// An action may either be a literal string, or a Verb. +func Parse(f string) ([]any, error) { + var out []any + for len(f) > 0 { + if f[0] == '%' { + v, n, err := ParseVerb(f) + if err != nil { + return nil, err + } + f = f[n:] + out = append(out, v) + } else { + n := strings.IndexByte(f, '%') + if n > -1 { + out = append(out, f[:n]) + f = f[n:] + } else { + out = append(out, f) + f = "" + } + } + } + + return out, nil +} + +func atoi(s string) int { + n, _ := strconv.Atoi(s) + return n +} + +// ParseVerb parses the verb at the beginning of f. +// It returns the verb, how much of the input was consumed, and an error, if any. +func ParseVerb(f string) (Verb, int, error) { + if len(f) < 2 { + return Verb{}, 0, ErrInvalid + } + const ( + flags = 1 + + width = 2 + widthStar = 3 + widthIndex = 5 + + dot = 6 + prec = 7 + precStar = 8 + precIndex = 10 + + verbIndex = 11 + verb = 12 + ) + + m := re.FindStringSubmatch(f) + if m == nil { + return Verb{}, 0, ErrInvalid + } + + v := Verb{ + Letter: []rune(m[verb])[0], + Flags: m[flags], + Raw: m[0], + } + + if m[width] != "" { + // Literal width + v.Width = Literal(atoi(m[width])) + } else if m[widthStar] != "" { + // Star width + if m[widthIndex] != "" { + v.Width = Star{atoi(m[widthIndex])} + } else { + v.Width = Star{-1} + } + } else { + // Default width + v.Width = Default{} + } + + if m[dot] == "" { + // default precision + v.Precision = Default{} + } else { + if m[prec] != "" { + // Literal precision + v.Precision = Literal(atoi(m[prec])) + } else if m[precStar] != "" { + // Star precision + if m[precIndex] != "" { + v.Precision = Star{atoi(m[precIndex])} + } else { + v.Precision = Star{-1} + } + } else { + // Zero precision + v.Precision = Zero{} + } + } + + if m[verb] == "%" { + v.Value = 0 + } else if m[verbIndex] != "" { + v.Value = atoi(m[verbIndex]) + } else { + v.Value = -1 + } + + return v, len(m[0]), nil +} + +const ( + flags = `([+#0 -]*)` + verb = `([a-zA-Z%])` + index = `(?:\[([0-9]+)\])` + star = `((` + index + `)?\*)` + width1 = `([0-9]+)` + width2 = star + width = `(?:` + width1 + `|` + width2 + `)` + precision = width + widthAndPrecision = `(?:(?:` + width + `)?(?:(\.)(?:` + precision + `)?)?)` +) + +var re = regexp.MustCompile(`^%` + flags + widthAndPrecision + `?` + index + `?` + verb) diff --git a/vendor/honnef.co/go/tools/quickfix/analysis.go b/vendor/honnef.co/go/tools/quickfix/analysis.go new file mode 100644 index 0000000..b877a91 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/analysis.go @@ -0,0 +1,34 @@ +// Code generated by generate.go. DO NOT EDIT. + +package quickfix + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/quickfix/qf1001" + "honnef.co/go/tools/quickfix/qf1002" + "honnef.co/go/tools/quickfix/qf1003" + "honnef.co/go/tools/quickfix/qf1004" + "honnef.co/go/tools/quickfix/qf1005" + "honnef.co/go/tools/quickfix/qf1006" + "honnef.co/go/tools/quickfix/qf1007" + "honnef.co/go/tools/quickfix/qf1008" + "honnef.co/go/tools/quickfix/qf1009" + "honnef.co/go/tools/quickfix/qf1010" + "honnef.co/go/tools/quickfix/qf1011" + "honnef.co/go/tools/quickfix/qf1012" +) + +var Analyzers = []*lint.Analyzer{ + qf1001.SCAnalyzer, + qf1002.SCAnalyzer, + qf1003.SCAnalyzer, + qf1004.SCAnalyzer, + qf1005.SCAnalyzer, + qf1006.SCAnalyzer, + qf1007.SCAnalyzer, + qf1008.SCAnalyzer, + qf1009.SCAnalyzer, + qf1010.SCAnalyzer, + qf1011.SCAnalyzer, + qf1012.SCAnalyzer, +} diff --git a/vendor/honnef.co/go/tools/quickfix/doc.go b/vendor/honnef.co/go/tools/quickfix/doc.go new file mode 100644 index 0000000..16d67b3 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/doc.go @@ -0,0 +1,9 @@ +//go:generate go run ../generate.go + +// Package quickfix contains analyzes that implement code refactorings. +// None of these analyzers produce diagnostics that have to be followed. +// Most of the time, they only provide alternative ways of doing things, +// requiring users to make informed decisions. +// +// None of these analyzes should fail a build, and they are likely useless in CI as a whole. +package quickfix diff --git a/vendor/honnef.co/go/tools/quickfix/qf1001/qf1001.go b/vendor/honnef.co/go/tools/quickfix/qf1001/qf1001.go new file mode 100644 index 0000000..b338dba --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1001/qf1001.go @@ -0,0 +1,123 @@ +package qf1001 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1001", + Run: CheckDeMorgan, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Apply De Morgan's law", + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var demorganQ = pattern.MustParse(`(UnaryExpr "!" expr@(BinaryExpr _ _ _))`) + +func CheckDeMorgan(pass *analysis.Pass) (any, error) { + // TODO(dh): support going in the other direction, e.g. turning `!a && !b && !c` into `!(a || b || c)` + + // hasFloats reports whether any subexpression is of type float. + hasFloats := func(expr ast.Expr) bool { + found := false + ast.Inspect(expr, func(node ast.Node) bool { + if expr, ok := node.(ast.Expr); ok { + if typ := pass.TypesInfo.TypeOf(expr); typ != nil { + if basic, ok := typ.Underlying().(*types.Basic); ok { + if (basic.Info() & types.IsFloat) != 0 { + found = true + return false + } + } + } + } + return true + }) + return found + } + + for c := range code.Cursor(pass).Preorder((*ast.UnaryExpr)(nil)) { + node := c.Node() + matcher, ok := code.Match(pass, demorganQ, node) + if !ok { + continue + } + + expr := matcher.State["expr"].(ast.Expr) + + // be extremely conservative when it comes to floats + if hasFloats(expr) { + continue + } + + n := astutil.NegateDeMorgan(expr, false) + nr := astutil.NegateDeMorgan(expr, true) + nc, ok := astutil.CopyExpr(n) + if !ok { + continue + } + ns := astutil.SimplifyParentheses(nc) + nrc, ok := astutil.CopyExpr(nr) + if !ok { + continue + } + nrs := astutil.SimplifyParentheses(nrc) + + var bn, bnr, bns, bnrs string + switch c.Parent().Node().(type) { + case *ast.BinaryExpr, *ast.IfStmt, *ast.ForStmt, *ast.SwitchStmt: + // Always add parentheses for if, for and switch. If + // they're unnecessary, go/printer will strip them when + // the whole file gets formatted. + + bn = report.Render(pass, &ast.ParenExpr{X: n}) + bnr = report.Render(pass, &ast.ParenExpr{X: nr}) + bns = report.Render(pass, &ast.ParenExpr{X: ns}) + bnrs = report.Render(pass, &ast.ParenExpr{X: nrs}) + + default: + // TODO are there other types where we don't want to strip parentheses? + bn = report.Render(pass, n) + bnr = report.Render(pass, nr) + bns = report.Render(pass, ns) + bnrs = report.Render(pass, nrs) + } + + // Note: we cannot compare the ASTs directly, because + // simplifyParentheses might have rebalanced trees without + // affecting the rendered form. + var fixes []analysis.SuggestedFix + fixes = append(fixes, edit.Fix("Apply De Morgan's law", edit.ReplaceWithString(node, bn))) + if bn != bns { + fixes = append(fixes, edit.Fix("Apply De Morgan's law & simplify", edit.ReplaceWithString(node, bns))) + } + if bn != bnr { + fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively", edit.ReplaceWithString(node, bnr))) + if bnr != bnrs { + fixes = append(fixes, edit.Fix("Apply De Morgan's law recursively & simplify", edit.ReplaceWithString(node, bnrs))) + } + } + + report.Report(pass, node, "could apply De Morgan's law", report.Fixes(fixes...)) + } + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go b/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go new file mode 100644 index 0000000..dd52216 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1002/qf1002.go @@ -0,0 +1,146 @@ +package qf1002 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1002", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Convert untagged switch to tagged switch", + Text: ` +An untagged switch that compares a single variable against a series of +values can be replaced with a tagged switch.`, + Before: ` +switch { +case x == 1 || x == 2, x == 3: + ... +case x == 4: + ... +default: + ... +}`, + + After: ` +switch x { +case 1, 2, 3: + ... +case 4: + ... +default: + ... +}`, + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + swtch := node.(*ast.SwitchStmt) + if swtch.Tag != nil || len(swtch.Body.List) == 0 { + return + } + + pairs := make([][]*ast.BinaryExpr, len(swtch.Body.List)) + for i, stmt := range swtch.Body.List { + stmt := stmt.(*ast.CaseClause) + for _, cond := range stmt.List { + if !findSwitchPairs(pass, cond, &pairs[i]) { + return + } + } + } + + var x ast.Expr + for _, pair := range pairs { + if len(pair) == 0 { + continue + } + if x == nil { + x = pair[0].X + } else { + if !astutil.Equal(x, pair[0].X) { + return + } + } + } + if x == nil { + // the switch only has a default case + if len(pairs) > 1 { + panic("found more than one case clause with no pairs") + } + return + } + + edits := make([]analysis.TextEdit, 0, len(swtch.Body.List)+1) + for i, stmt := range swtch.Body.List { + stmt := stmt.(*ast.CaseClause) + if stmt.List == nil { + continue + } + + var values []string + for _, binexpr := range pairs[i] { + y := binexpr.Y + if p, ok := y.(*ast.ParenExpr); ok { + y = p.X + } + values = append(values, report.Render(pass, y)) + } + + edits = append(edits, edit.ReplaceWithString(edit.Range{stmt.List[0].Pos(), stmt.Colon}, strings.Join(values, ", "))) + } + pos := swtch.Body.Lbrace + edits = append(edits, edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, x))) + report.Report(pass, swtch, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)), + report.Fixes(edit.Fix("Replace with tagged switch", edits...)), + report.ShortRange()) + } + + code.Preorder(pass, fn, (*ast.SwitchStmt)(nil)) + return nil, nil +} + +func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool { + binexpr, ok := ast.Unparen(expr).(*ast.BinaryExpr) + if !ok { + return false + } + switch binexpr.Op { + case token.EQL: + if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) { + return false + } + // syntactic identity should suffice. we do not allow side + // effects in the case clauses, so there should be no way for + // values to change. + if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) { + return false + } + *pairs = append(*pairs, binexpr) + return true + case token.LOR: + return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs) + default: + return false + } +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go b/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go new file mode 100644 index 0000000..7958795 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1003/qf1003.go @@ -0,0 +1,205 @@ +package qf1003 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1003", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Convert if/else-if chain to tagged switch", + Text: ` +A series of if/else-if checks comparing the same variable against +values can be replaced with a tagged switch.`, + Before: ` +if x == 1 || x == 2 { + ... +} else if x == 3 { + ... +} else { + ... +}`, + + After: ` +switch x { +case 1, 2: + ... +case 3: + ... +default: + ... +}`, + Since: "2021.1", + Severity: lint.SeverityInfo, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { +nodeLoop: + for c := range code.Cursor(pass).Preorder((*ast.IfStmt)(nil)) { + node := c.Node() + if _, ok := c.Parent().Node().(*ast.IfStmt); ok { + // this if statement is part of an if-else chain + continue + } + ifstmt := node.(*ast.IfStmt) + + m := map[ast.Expr][]*ast.BinaryExpr{} + for item := ifstmt; item != nil; { + if item.Init != nil { + continue nodeLoop + } + if item.Body == nil { + continue nodeLoop + } + + skip := false + ast.Inspect(item.Body, func(node ast.Node) bool { + if branch, ok := node.(*ast.BranchStmt); ok && branch.Tok != token.GOTO { + skip = true + return false + } + return true + }) + if skip { + continue nodeLoop + } + + var pairs []*ast.BinaryExpr + if !findSwitchPairs(pass, item.Cond, &pairs) { + continue nodeLoop + } + m[item.Cond] = pairs + switch els := item.Else.(type) { + case *ast.IfStmt: + item = els + case *ast.BlockStmt, nil: + item = nil + default: + panic(fmt.Sprintf("unreachable: %T", els)) + } + } + + var x ast.Expr + for _, pair := range m { + if len(pair) == 0 { + continue + } + if x == nil { + x = pair[0].X + } else { + if !astutil.Equal(x, pair[0].X) { + continue nodeLoop + } + } + } + if x == nil { + // shouldn't happen + continue nodeLoop + } + + // We require at least two 'if' to make this suggestion, to + // avoid clutter in the editor. + if len(m) < 2 { + continue nodeLoop + } + + // Note that we insert the switch statement as the first text edit instead of the last one so that gopls has an + // easier time converting it to an LSP-conforming edit. + // + // Specifically: + // > Text edits ranges must never overlap, that means no part of the original + // > document must be manipulated by more than one edit. However, it is + // > possible that multiple edits have the same start position: multiple + // > inserts, or any number of inserts followed by a single remove or replace + // > edit. If multiple inserts have the same position, the order in the array + // > defines the order in which the inserted strings appear in the resulting + // > text. + // + // See https://go.dev/issue/63930 + // + // FIXME this edit forces the first case to begin in column 0 because we ignore indentation. try to fix that. + edits := []analysis.TextEdit{edit.ReplaceWithString(edit.Range{ifstmt.If, ifstmt.If}, fmt.Sprintf("switch %s {\n", report.Render(pass, x)))} + for item := ifstmt; item != nil; { + var end token.Pos + if item.Else != nil { + end = item.Else.Pos() + } else { + // delete up to but not including the closing brace. + end = item.Body.Rbrace + } + + var conds []string + for _, cond := range m[item.Cond] { + y := cond.Y + if p, ok := y.(*ast.ParenExpr); ok { + y = p.X + } + conds = append(conds, report.Render(pass, y)) + } + sconds := strings.Join(conds, ", ") + edits = append(edits, + edit.ReplaceWithString(edit.Range{item.If, item.Body.Lbrace + 1}, "case "+sconds+":"), + edit.Delete(edit.Range{item.Body.Rbrace, end})) + + switch els := item.Else.(type) { + case *ast.IfStmt: + item = els + case *ast.BlockStmt: + edits = append(edits, edit.ReplaceWithString(edit.Range{els.Lbrace, els.Lbrace + 1}, "default:")) + item = nil + case nil: + item = nil + default: + panic(fmt.Sprintf("unreachable: %T", els)) + } + } + report.Report(pass, ifstmt, fmt.Sprintf("could use tagged switch on %s", report.Render(pass, x)), + report.Fixes(edit.Fix("Replace with tagged switch", edits...)), + report.ShortRange()) + } + return nil, nil +} + +func findSwitchPairs(pass *analysis.Pass, expr ast.Expr, pairs *[]*ast.BinaryExpr) bool { + binexpr, ok := ast.Unparen(expr).(*ast.BinaryExpr) + if !ok { + return false + } + switch binexpr.Op { + case token.EQL: + if code.MayHaveSideEffects(pass, binexpr.X, nil) || code.MayHaveSideEffects(pass, binexpr.Y, nil) { + return false + } + // syntactic identity should suffice. we do not allow side + // effects in the case clauses, so there should be no way for + // values to change. + if len(*pairs) > 0 && !astutil.Equal(binexpr.X, (*pairs)[0].X) { + return false + } + *pairs = append(*pairs, binexpr) + return true + case token.LOR: + return findSwitchPairs(pass, binexpr.X, pairs) && findSwitchPairs(pass, binexpr.Y, pairs) + default: + return false + } +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go b/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go new file mode 100644 index 0000000..bde8c67 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1004/qf1004.go @@ -0,0 +1,65 @@ +package qf1004 + +import ( + "fmt" + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + typeindexanalyzer "honnef.co/go/tools/internal/xtools-internal/analysis/typeindex" + "honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1004", + Run: run, + Requires: []*analysis.Analyzer{typeindexanalyzer.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \'strings.ReplaceAll\' instead of \'strings.Replace\' with \'n == -1\'`, + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var fns = []struct { + path string + name string + replacement string +}{ + {"strings", "Replace", "strings.ReplaceAll"}, + {"strings", "SplitN", "strings.Split"}, + {"strings", "SplitAfterN", "strings.SplitAfter"}, + {"bytes", "Replace", "bytes.ReplaceAll"}, + {"bytes", "SplitN", "bytes.Split"}, + {"bytes", "SplitAfterN", "bytes.SplitAfter"}, +} + +func run(pass *analysis.Pass) (any, error) { + // XXX respect minimum Go version + + // FIXME(dh): create proper suggested fix for renamed import + + index := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index) + for _, fn := range fns { + for c := range index.Calls(index.Object(fn.path, fn.name)) { + call := c.Node().(*ast.CallExpr) + if op, ok := call.Args[len(call.Args)-1].(*ast.UnaryExpr); ok && op.Op == token.SUB { + if lit, ok := op.X.(*ast.BasicLit); ok && lit.Value == "1" { + report.Report(pass, call.Fun, fmt.Sprintf("could use %s instead", fn.replacement), + report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead", fn.replacement), + edit.ReplaceWithString(call.Fun, fn.replacement), + edit.Delete(op)))) + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1005/qf1005.go b/vendor/honnef.co/go/tools/quickfix/qf1005/qf1005.go new file mode 100644 index 0000000..048a1db --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1005/qf1005.go @@ -0,0 +1,111 @@ +package qf1005 + +import ( + "go/ast" + "go/constant" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1005", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Expand call to \'math.Pow\'`, + Text: `Some uses of \'math.Pow\' can be simplified to basic multiplication.`, + Before: `math.Pow(x, 2)`, + After: `x * x`, + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var mathPowQ = pattern.MustParse(`(CallExpr (Symbol "math.Pow") [x (IntegerLiteral n)])`) + +func run(pass *analysis.Pass) (any, error) { + for node, matcher := range code.Matches(pass, mathPowQ) { + x := matcher.State["x"].(ast.Expr) + if code.MayHaveSideEffects(pass, x, nil) { + continue + } + n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value)) + if !ok { + continue + } + + needConversion := false + if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil { + info := types.Info{ + Types: map[ast.Expr]types.TypeAndValue{}, + } + + // determine if the constant expression would have type float64 if used on its own + if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil { + // This should not happen + continue + } + if T, ok := info.Types[x].Type.(*types.Basic); ok { + if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 { + needConversion = true + } + } else { + needConversion = true + } + } + + var replacement ast.Expr + switch n { + case 0: + replacement = &ast.BasicLit{ + Kind: token.FLOAT, + Value: "1.0", + } + case 1: + replacement = x + case 2, 3: + r := &ast.BinaryExpr{ + X: x, + Op: token.MUL, + Y: x, + } + for i := 3; i <= int(n); i++ { + r = &ast.BinaryExpr{ + X: r, + Op: token.MUL, + Y: x, + } + } + + rc, ok := astutil.CopyExpr(r) + if !ok { + continue + } + replacement = astutil.SimplifyParentheses(rc) + default: + continue + } + if needConversion && n != 0 { + replacement = &ast.CallExpr{ + Fun: &ast.Ident{Name: "float64"}, + Args: []ast.Expr{replacement}, + } + } + report.Report(pass, node, "could expand call to math.Pow", + report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1006/qf1006.go b/vendor/honnef.co/go/tools/quickfix/qf1006/qf1006.go new file mode 100644 index 0000000..66d5fc5 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1006/qf1006.go @@ -0,0 +1,61 @@ +package qf1006 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1006", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Lift \'if\'+\'break\' into loop condition`, + Before: ` +for { + if done { + break + } + ... +}`, + + After: ` +for !done { + ... +}`, + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkForLoopIfBreak = pattern.MustParse(`(ForStmt nil nil nil if@(IfStmt nil cond (BranchStmt "BREAK" nil) nil):_)`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkForLoopIfBreak) { + pos := node.Pos() + token.Pos(len("for")) + r := astutil.NegateDeMorgan(m.State["cond"].(ast.Expr), false) + + // FIXME(dh): we're leaving behind an empty line when we + // delete the old if statement. However, we can't just delete + // an additional character, in case there closing curly brace + // is followed by a comment, or Windows newlines. + report.Report(pass, m.State["if"].(ast.Node), "could lift into loop condition", + report.Fixes(edit.Fix("Lift into loop condition", + edit.ReplaceWithString(edit.Range{pos, pos}, " "+report.Render(pass, r)), + edit.Delete(m.State["if"].(ast.Node))))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1007/qf1007.go b/vendor/honnef.co/go/tools/quickfix/qf1007/qf1007.go new file mode 100644 index 0000000..ab3be19 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1007/qf1007.go @@ -0,0 +1,92 @@ +package qf1007 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1007", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Merge conditional assignment into variable declaration", + Before: ` +x := false +if someCondition { + x = true +}`, + After: `x := someCondition`, + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkConditionalAssignmentQ = pattern.MustParse(`(AssignStmt x@(Object _) ":=" assign@(Builtin b@(Or "true" "false")))`) +var checkConditionalAssignmentIfQ = pattern.MustParse(`(IfStmt nil cond [(AssignStmt x@(Object _) "=" (Builtin b@(Or "true" "false")))] nil)`) + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + var body *ast.BlockStmt + switch node := node.(type) { + case *ast.FuncDecl: + body = node.Body + case *ast.FuncLit: + body = node.Body + default: + panic("unreachable") + } + if body == nil { + return + } + + stmts := body.List + if len(stmts) < 2 { + return + } + for i, first := range stmts[:len(stmts)-1] { + second := stmts[i+1] + m1, ok := code.Match(pass, checkConditionalAssignmentQ, first) + if !ok { + continue + } + m2, ok := code.Match(pass, checkConditionalAssignmentIfQ, second) + if !ok { + continue + } + if m1.State["x"] != m2.State["x"] { + continue + } + if m1.State["b"] == m2.State["b"] { + continue + } + + v := m2.State["cond"].(ast.Expr) + if m1.State["b"] == "true" { + v = &ast.UnaryExpr{ + Op: token.NOT, + X: v, + } + } + report.Report(pass, first, "could merge conditional assignment into variable declaration", + report.Fixes(edit.Fix("Merge conditional assignment into variable declaration", + edit.ReplaceWithNode(pass.Fset, m1.State["assign"].(ast.Node), v), + edit.Delete(second)))) + } + } + code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1008/qf1008.go b/vendor/honnef.co/go/tools/quickfix/qf1008/qf1008.go new file mode 100644 index 0000000..e71d637 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1008/qf1008.go @@ -0,0 +1,147 @@ +package qf1008 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1008", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Omit embedded fields from selector expression", + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + type Selector struct { + Node *ast.SelectorExpr + X ast.Expr + Fields []*ast.Ident + } + + // extractSelectors extracts uninterrupted sequences of selector expressions. + // For example, for a.b.c().d.e[0].f.g three sequences will be returned: (X=a, X.b.c), (X=a.b.c(), X.d.e), and (X=a.b.c().d.e[0], X.f.g) + // + // It returns nil if the provided selector expression is not the root of a set of sequences. + // For example, for a.b.c, if node is b.c, no selectors will be returned. + extractSelectors := func(expr *ast.SelectorExpr) []Selector { + path, _ := astutil.PathEnclosingInterval(code.File(pass, expr), expr.Pos(), expr.Pos()) + for i := len(path) - 1; i >= 0; i-- { + if el, ok := path[i].(*ast.SelectorExpr); ok { + if el != expr { + // this expression is a subset of the entire chain, don't look at it. + return nil + } + break + } + } + + inChain := false + var out []Selector + for _, el := range path { + if expr, ok := el.(*ast.SelectorExpr); ok { + if !inChain { + inChain = true + out = append(out, Selector{X: expr.X}) + } + sel := &out[len(out)-1] + sel.Fields = append(sel.Fields, expr.Sel) + sel.Node = expr + } else if inChain { + inChain = false + } + } + return out + } + + fn := func(node ast.Node) { + expr := node.(*ast.SelectorExpr) + + if _, ok := expr.X.(*ast.SelectorExpr); !ok { + // Avoid the expensive call to PathEnclosingInterval for the common 1-level deep selector, which cannot be shortened. + return + } + + sels := extractSelectors(expr) + if len(sels) == 0 { + return + } + + for _, sel := range sels { + fieldLoop: + for base, fields := pass.TypesInfo.TypeOf(sel.X), sel.Fields; len(fields) >= 2; base, fields = pass.TypesInfo.ObjectOf(fields[0]).Type(), fields[1:] { + hop1 := fields[0] + hop2 := fields[1] + + // the selector expression might be a qualified identifier, which cannot be simplified + if base == types.Typ[types.Invalid] { + continue fieldLoop + } + + // Check if we can skip a field in the chain of selectors. + // We can skip a field 'b' if a.b.c and a.c resolve to the same object and take the same path. + // + // We set addressable to true unconditionally because we've already successfully type-checked the program, + // which means either the selector doesn't need addressability, or it is addressable. + leftObj, leftLeg, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop1.Name) + + // We can't skip fields that aren't embedded + if !leftObj.(*types.Var).Embedded() { + continue fieldLoop + } + + directObj, directPath, _ := types.LookupFieldOrMethod(base, true, pass.Pkg, hop2.Name) + + // Fail fast if omitting the embedded field leads to a different object + if directObj != pass.TypesInfo.ObjectOf(hop2) { + continue fieldLoop + } + + _, rightLeg, _ := types.LookupFieldOrMethod(leftObj.Type(), true, pass.Pkg, hop2.Name) + + // Fail fast if the paths are obviously different + if len(directPath) != len(leftLeg)+len(rightLeg) { + continue fieldLoop + } + + // Make sure that omitting the embedded field will take the same path to the final object. + // Multiple paths involving different fields may lead to the same type-checker object, causing different runtime behavior. + for i := range directPath { + if i < len(leftLeg) { + if leftLeg[i] != directPath[i] { + continue fieldLoop + } + } else { + if rightLeg[i-len(leftLeg)] != directPath[i] { + continue fieldLoop + } + } + } + + e := edit.Delete(edit.Range{hop1.Pos(), hop2.Pos()}) + report.Report(pass, hop1, fmt.Sprintf("could remove embedded field %q from selector", hop1.Name), + report.Fixes(edit.Fix(fmt.Sprintf("Remove embedded field %q from selector", hop1.Name), e))) + } + } + } + code.Preorder(pass, fn, (*ast.SelectorExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1009/qf1009.go b/vendor/honnef.co/go/tools/quickfix/qf1009/qf1009.go new file mode 100644 index 0000000..d16e67c --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1009/qf1009.go @@ -0,0 +1,51 @@ +package qf1009 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1009", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \'time.Time.Equal\' instead of \'==\' operator`, + Since: "2021.1", + Severity: lint.SeverityInfo, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var timeEqualR = pattern.MustParse(`(CallExpr (SelectorExpr lhs (Ident "Equal")) rhs)`) + +func run(pass *analysis.Pass) (any, error) { + // FIXME(dh): create proper suggested fix for renamed import + + fn := func(node ast.Node) { + expr := node.(*ast.BinaryExpr) + if expr.Op != token.EQL { + return + } + if !code.IsOfTypeWithName(pass, expr.X, "time.Time") || !code.IsOfTypeWithName(pass, expr.Y, "time.Time") { + return + } + report.Report(pass, node, "probably want to use time.Time.Equal instead", + report.Fixes(edit.Fix("Use time.Time.Equal method", + edit.ReplaceWithPattern(pass.Fset, node, timeEqualR, pattern.State{"lhs": expr.X, "rhs": expr.Y})))) + } + code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1010/qf1010.go b/vendor/honnef.co/go/tools/quickfix/qf1010/qf1010.go new file mode 100644 index 0000000..1942bc0 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1010/qf1010.go @@ -0,0 +1,75 @@ +package qf1010 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1010", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: "Convert slice of bytes to string when printing it", + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var byteSlicePrintingQ = pattern.MustParse(` + (Or + (CallExpr + (Symbol (Or + "fmt.Print" + "fmt.Println" + "fmt.Sprint" + "fmt.Sprintln" + "log.Fatal" + "log.Fatalln" + "log.Panic" + "log.Panicln" + "log.Print" + "log.Println" + "(*log.Logger).Fatal" + "(*log.Logger).Fatalln" + "(*log.Logger).Panic" + "(*log.Logger).Panicln" + "(*log.Logger).Print" + "(*log.Logger).Println")) args) + + (CallExpr (Symbol (Or + "fmt.Fprint" + "fmt.Fprintln")) _:args))`) + +var byteSlicePrintingR = pattern.MustParse(`(CallExpr (Ident "string") [arg])`) + +func run(pass *analysis.Pass) (any, error) { + for _, m := range code.Matches(pass, byteSlicePrintingQ) { + args := m.State["args"].([]ast.Expr) + for _, arg := range args { + if !code.IsOfStringConvertibleByteSlice(pass, arg) { + continue + } + if types.Implements(pass.TypesInfo.TypeOf(arg), knowledge.Interfaces["fmt.Stringer"]) { + continue + } + + fix := edit.Fix("Convert argument to string", edit.ReplaceWithPattern(pass.Fset, arg, byteSlicePrintingR, pattern.State{"arg": arg})) + report.Report(pass, arg, "could convert argument to string", report.Fixes(fix)) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/quickfix/qf1011/qf1011.go b/vendor/honnef.co/go/tools/quickfix/qf1011/qf1011.go new file mode 100644 index 0000000..f77fdb9 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1011/qf1011.go @@ -0,0 +1,21 @@ +package qf1011 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/sharedcheck" +) + +func init() { + SCAnalyzer.Analyzer.Name = "QF1011" +} + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: sharedcheck.RedundantTypeInDeclarationChecker("could", true), + Doc: &lint.RawDocumentation{ + Title: "Omit redundant type from variable declaration", + Since: "2021.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer diff --git a/vendor/honnef.co/go/tools/quickfix/qf1012/qf1012.go b/vendor/honnef.co/go/tools/quickfix/qf1012/qf1012.go new file mode 100644 index 0000000..45784d9 --- /dev/null +++ b/vendor/honnef.co/go/tools/quickfix/qf1012/qf1012.go @@ -0,0 +1,128 @@ +package qf1012 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "QF1012", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \'fmt.Fprintf(x, ...)\' instead of \'x.Write(fmt.Sprintf(...))\'`, + Since: "2022.1", + Severity: lint.SeverityHint, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkWriteBytesSprintfQ = pattern.MustParse(` + (CallExpr + (SelectorExpr recv (Ident "Write")) + (CallExpr (ArrayType nil (Ident "byte")) + (CallExpr + fn@(Or + (Symbol "fmt.Sprint") + (Symbol "fmt.Sprintf") + (Symbol "fmt.Sprintln")) + args) + ))`) + + checkWriteStringSprintfQ = pattern.MustParse(` + (CallExpr + (SelectorExpr recv (Ident "WriteString")) + (CallExpr + fn@(Or + (Symbol "fmt.Sprint") + (Symbol "fmt.Sprintf") + (Symbol "fmt.Sprintln")) + args))`) +) + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + getRecv := func(m *pattern.Matcher) (ast.Expr, types.Type) { + recv := m.State["recv"].(ast.Expr) + recvT := pass.TypesInfo.TypeOf(recv) + + // Use *N, not N, for the interface check if N + // is a named non-interface type, since the pointer + // has a larger method set (https://staticcheck.dev/issues/1097). + // We assume the receiver expression is addressable + // since otherwise the code wouldn't compile. + if _, ok := types.Unalias(recvT).(*types.Named); ok && !types.IsInterface(recvT) { + recvT = types.NewPointer(recvT) + recv = &ast.UnaryExpr{Op: token.AND, X: recv} + + } + return recv, recvT + } + + if m, ok := code.Match(pass, checkWriteBytesSprintfQ, node); ok { + recv, recvT := getRecv(m) + if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) { + return + } + + name := m.State["fn"].(*types.Func).Name() + newName := "F" + strings.TrimPrefix(name, "S") + msg := fmt.Sprintf("Use fmt.%s(...) instead of Write([]byte(fmt.%s(...)))", newName, name) + + args := m.State["args"].([]ast.Expr) + fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("fmt"), + Sel: ast.NewIdent(newName), + }, + Args: append([]ast.Expr{recv}, args...), + })) + report.Report(pass, node, msg, report.Fixes(fix)) + } else if m, ok := code.Match(pass, checkWriteStringSprintfQ, node); ok { + recv, recvT := getRecv(m) + if !types.Implements(recvT, knowledge.Interfaces["io.StringWriter"]) { + return + } + // The type needs to implement both StringWriter and Writer. + // If it doesn't implement Writer, then we cannot pass it to fmt.Fprint. + if !types.Implements(recvT, knowledge.Interfaces["io.Writer"]) { + return + } + + name := m.State["fn"].(*types.Func).Name() + newName := "F" + strings.TrimPrefix(name, "S") + msg := fmt.Sprintf("Use fmt.%s(...) instead of WriteString(fmt.%s(...))", newName, name) + + args := m.State["args"].([]ast.Expr) + fix := edit.Fix(msg, edit.ReplaceWithNode(pass.Fset, node, &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("fmt"), + Sel: ast.NewIdent(newName), + }, + Args: append([]ast.Expr{recv}, args...), + })) + report.Report(pass, node, msg, report.Fixes(fix)) + } + } + if !code.CouldMatchAny(pass, checkWriteBytesSprintfQ, checkWriteStringSprintfQ) { + return nil, nil + } + code.Preorder(pass, fn, (*ast.CallExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/sarif/sarif.go b/vendor/honnef.co/go/tools/sarif/sarif.go new file mode 100644 index 0000000..1c7b1d4 --- /dev/null +++ b/vendor/honnef.co/go/tools/sarif/sarif.go @@ -0,0 +1,138 @@ +package sarif + +const Version = "2.1.0" +const Schema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" + +type Log struct { + Version string `json:"version"` + Schema string `json:"$schema"` + Runs []Run `json:"runs"` +} + +type Run struct { + Tool Tool `json:"tool"` + Results []Result `json:"results,omitempty"` + Invocations []Invocation `json:"invocations,omitempty"` + Artifacts []Artifact `json:"artifacts,omitempty"` +} + +type Artifact struct { + Location ArtifactLocation `json:"location"` + Length int `json:"length"` + SourceLanguage string `json:"sourceLanguage"` + Roles []string `json:"roles"` + Encoding string `json:"encoding"` +} + +const ( + AnalysisTarget = "analysisTarget" + UTF8 = "UTF-8" + Fail = "fail" + Warning = "warning" + Error = "error" + Note = "note" + None = "none" +) + +type Hash struct { + Sha256 string `json:"sha-256"` +} + +type Tool struct { + Driver ToolComponent `json:"driver"` +} + +type Invocation struct { + CommandLine string `json:"commandLine,omitempty"` + Arguments []string `json:"arguments,omitempty"` + WorkingDirectory ArtifactLocation `json:"workingDirectory,omitzero"` + ExecutionSuccessful bool `json:"executionSuccessful"` +} + +type ToolComponent struct { + Name string `json:"name,omitempty"` + Version string `json:"version,omitempty"` + SemanticVersion string `json:"semanticVersion,omitempty"` + InformationURI string `json:"informationUri,omitempty"` + Rules []ReportingDescriptor `json:"rules,omitempty"` +} + +type ReportingDescriptor struct { + ID string `json:"id"` + ShortDescription Message `json:"shortDescription"` + // FullDescription Message `json:"fullDescription"` + Help Message `json:"help"` + HelpURI string `json:"helpUri,omitempty"` + DefaultConfiguration ReportingConfiguration `json:"defaultConfiguration"` +} + +type ReportingConfiguration struct { + Enabled bool `json:"enabled"` + Level string `json:"level,omitempty"` + Parameters map[string]any `json:"parameters,omitempty"` +} + +type Result struct { + RuleID string `json:"ruleId"` + // RuleIndex int `json:"ruleIndex"` + Kind string `json:"kind"` + Level string `json:"level,omitempty"` + Message Message `json:"message"` + Locations []Location `json:"locations,omitempty"` + RelatedLocations []Location `json:"relatedLocations,omitempty"` + Fixes []Fix `json:"fixes,omitempty"` + Suppressions []Suppression `json:"suppressions"` +} + +type Suppression struct { + Kind string `json:"kind"` + Justification string `json:"justification"` +} + +type Fix struct { + Description Message `json:"description"` + ArtifactChanges []ArtifactChange `json:"artifactChanges"` +} + +type ArtifactChange struct { + ArtifactLocation ArtifactLocation `json:"artifactLocation"` + Replacements []Replacement `json:"replacements"` +} + +type Replacement struct { + DeletedRegion Region `json:"deletedRegion"` + InsertedContent ArtifactContent `json:"insertedContent"` +} + +type ArtifactContent struct { + Text string `json:"text"` +} + +type Message struct { + Text string `json:"text,omitempty"` + Markdown string `json:"markdown,omitempty"` +} + +type Location struct { + ID int `json:"id,omitempty"` + Message *Message `json:"message,omitempty"` + PhysicalLocation PhysicalLocation `json:"physicalLocation"` +} + +type PhysicalLocation struct { + ArtifactLocation ArtifactLocation `json:"artifactLocation"` + Region Region `json:"region"` +} + +type ArtifactLocation struct { + URI string `json:"uri,omitempty"` + Index int `json:"index,omitempty"` + URIBaseID string `json:"uriBaseId,omitempty"` +} + +type Region struct { + StartLine int `json:"startLine"` + StartColumn int `json:"startColumn"` + EndLine int `json:"endLine,omitempty"` + EndColumn int `json:"endColumn,omitempty"` +} diff --git a/vendor/honnef.co/go/tools/simple/analysis.go b/vendor/honnef.co/go/tools/simple/analysis.go new file mode 100644 index 0000000..9512d2a --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/analysis.go @@ -0,0 +1,80 @@ +// Code generated by generate.go. DO NOT EDIT. + +package simple + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/simple/s1000" + "honnef.co/go/tools/simple/s1001" + "honnef.co/go/tools/simple/s1002" + "honnef.co/go/tools/simple/s1003" + "honnef.co/go/tools/simple/s1004" + "honnef.co/go/tools/simple/s1005" + "honnef.co/go/tools/simple/s1006" + "honnef.co/go/tools/simple/s1007" + "honnef.co/go/tools/simple/s1008" + "honnef.co/go/tools/simple/s1009" + "honnef.co/go/tools/simple/s1010" + "honnef.co/go/tools/simple/s1011" + "honnef.co/go/tools/simple/s1012" + "honnef.co/go/tools/simple/s1016" + "honnef.co/go/tools/simple/s1017" + "honnef.co/go/tools/simple/s1018" + "honnef.co/go/tools/simple/s1019" + "honnef.co/go/tools/simple/s1020" + "honnef.co/go/tools/simple/s1021" + "honnef.co/go/tools/simple/s1023" + "honnef.co/go/tools/simple/s1024" + "honnef.co/go/tools/simple/s1025" + "honnef.co/go/tools/simple/s1028" + "honnef.co/go/tools/simple/s1029" + "honnef.co/go/tools/simple/s1030" + "honnef.co/go/tools/simple/s1031" + "honnef.co/go/tools/simple/s1032" + "honnef.co/go/tools/simple/s1033" + "honnef.co/go/tools/simple/s1034" + "honnef.co/go/tools/simple/s1035" + "honnef.co/go/tools/simple/s1036" + "honnef.co/go/tools/simple/s1037" + "honnef.co/go/tools/simple/s1038" + "honnef.co/go/tools/simple/s1039" + "honnef.co/go/tools/simple/s1040" +) + +var Analyzers = []*lint.Analyzer{ + s1000.SCAnalyzer, + s1001.SCAnalyzer, + s1002.SCAnalyzer, + s1003.SCAnalyzer, + s1004.SCAnalyzer, + s1005.SCAnalyzer, + s1006.SCAnalyzer, + s1007.SCAnalyzer, + s1008.SCAnalyzer, + s1009.SCAnalyzer, + s1010.SCAnalyzer, + s1011.SCAnalyzer, + s1012.SCAnalyzer, + s1016.SCAnalyzer, + s1017.SCAnalyzer, + s1018.SCAnalyzer, + s1019.SCAnalyzer, + s1020.SCAnalyzer, + s1021.SCAnalyzer, + s1023.SCAnalyzer, + s1024.SCAnalyzer, + s1025.SCAnalyzer, + s1028.SCAnalyzer, + s1029.SCAnalyzer, + s1030.SCAnalyzer, + s1031.SCAnalyzer, + s1032.SCAnalyzer, + s1033.SCAnalyzer, + s1034.SCAnalyzer, + s1035.SCAnalyzer, + s1036.SCAnalyzer, + s1037.SCAnalyzer, + s1038.SCAnalyzer, + s1039.SCAnalyzer, + s1040.SCAnalyzer, +} diff --git a/vendor/honnef.co/go/tools/simple/doc.go b/vendor/honnef.co/go/tools/simple/doc.go new file mode 100644 index 0000000..c04bc24 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/doc.go @@ -0,0 +1,6 @@ +//go:generate go run ../generate.go + +// Package simple contains analyzes that simplify code. +// All suggestions made by these analyzes are intended to result in objectively simpler code, +// and following their advice is recommended. +package simple diff --git a/vendor/honnef.co/go/tools/simple/s1000/s1000.go b/vendor/honnef.co/go/tools/simple/s1000/s1000.go new file mode 100644 index 0000000..3e4adf9 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1000/s1000.go @@ -0,0 +1,71 @@ +package s1000 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1000", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use plain channel send or receive instead of single-case select`, + Text: `Select statements with a single case can be replaced with a simple +send or receive.`, + Before: ` +select { +case x := <-ch: + fmt.Println(x) +}`, + After: ` +x := <-ch +fmt.Println(x) +`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkSingleCaseSelectQ1 = pattern.MustParse(` + (ForStmt + nil nil nil + select@(SelectStmt + (CommClause + (Or + (UnaryExpr "<-" _) + (AssignStmt _ _ (UnaryExpr "<-" _))) + _)))`) + checkSingleCaseSelectQ2 = pattern.MustParse(`(SelectStmt (CommClause _ _))`) +) + +func run(pass *analysis.Pass) (any, error) { + seen := map[ast.Node]struct{}{} + fn := func(node ast.Node) { + if m, ok := code.Match(pass, checkSingleCaseSelectQ1, node); ok { + seen[m.State["select"].(ast.Node)] = struct{}{} + report.Report(pass, node, "should use for range instead of for { select {} }", report.FilterGenerated()) + } else if _, ok := code.Match(pass, checkSingleCaseSelectQ2, node); ok { + if _, ok := seen[node]; !ok { + report.Report(pass, node, "should use a simple channel send/receive instead of select with a single case", + report.ShortRange(), + report.FilterGenerated()) + } + } + } + code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.SelectStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1001/s1001.go b/vendor/honnef.co/go/tools/simple/s1001/s1001.go new file mode 100644 index 0000000..ae026e2 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1001/s1001.go @@ -0,0 +1,190 @@ +package s1001 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1001", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Replace for loop with call to copy`, + Text: ` +Use \'copy()\' for copying elements from one slice to another. For +arrays of identical size, you can use simple assignment.`, + Before: ` +for i, x := range src { + dst[i] = x +}`, + After: `copy(dst, src)`, + Since: "2017.1", + // MergeIfAll because the types of src and dst might be different under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkLoopCopyQ = pattern.MustParse(` + (Or + (RangeStmt + key@(Ident _) value@(Ident _) ":=" src + [(AssignStmt (IndexExpr dst key) "=" value)]) + (RangeStmt + key@(Ident _) nil ":=" src + [(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]) + (ForStmt + (AssignStmt key@(Ident _) ":=" (IntegerLiteral "0")) + (BinaryExpr key "<" (CallExpr (Symbol "len") [src])) + (IncDecStmt key "++") + [(AssignStmt (IndexExpr dst key) "=" (IndexExpr src key))]))`) +) + +func run(pass *analysis.Pass) (any, error) { + // TODO revisit once range doesn't require a structural type + + isInvariant := func(k, v types.Object, node ast.Expr) bool { + if code.MayHaveSideEffects(pass, node, nil) { + return false + } + invariant := true + ast.Inspect(node, func(node ast.Node) bool { + if node, ok := node.(*ast.Ident); ok { + obj := pass.TypesInfo.ObjectOf(node) + if obj == k || obj == v { + // don't allow loop bodies like 'a[i][i] = v' + invariant = false + return false + } + } + return true + }) + return invariant + } + + var elType func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) + elType = func(T types.Type) (el types.Type, isArray bool, isArrayPointer bool, ok bool) { + switch typ := T.Underlying().(type) { + case *types.Slice: + return typ.Elem(), false, false, true + case *types.Array: + return typ.Elem(), true, false, true + case *types.Pointer: + el, isArray, _, ok = elType(typ.Elem()) + return el, isArray, true, ok + default: + return nil, false, false, false + } + } + + for node, m := range code.Matches(pass, checkLoopCopyQ) { + src := m.State["src"].(ast.Expr) + dst := m.State["dst"].(ast.Expr) + + k := pass.TypesInfo.ObjectOf(m.State["key"].(*ast.Ident)) + var v types.Object + if value, ok := m.State["value"]; ok { + v = pass.TypesInfo.ObjectOf(value.(*ast.Ident)) + } + if !isInvariant(k, v, dst) { + continue + } + if !isInvariant(k, v, src) { + // For example: 'for i := range foo()' + continue + } + + Tsrc := pass.TypesInfo.TypeOf(src) + Tdst := pass.TypesInfo.TypeOf(dst) + TsrcElem, TsrcArray, TsrcPointer, ok := elType(Tsrc) + if !ok { + continue + } + if TsrcPointer { + Tsrc = Tsrc.Underlying().(*types.Pointer).Elem() + } + TdstElem, TdstArray, TdstPointer, ok := elType(Tdst) + if !ok { + continue + } + if TdstPointer { + Tdst = Tdst.Underlying().(*types.Pointer).Elem() + } + + if !types.Identical(TsrcElem, TdstElem) { + continue + } + + if TsrcArray && TdstArray && types.Identical(Tsrc, Tdst) { + if TsrcPointer { + src = &ast.StarExpr{ + X: src, + } + } + if TdstPointer { + dst = &ast.StarExpr{ + X: dst, + } + } + r := &ast.AssignStmt{ + Lhs: []ast.Expr{dst}, + Rhs: []ast.Expr{src}, + Tok: token.ASSIGN, + } + + report.Report(pass, node, "should copy arrays using assignment instead of using a loop", + report.FilterGenerated(), + report.ShortRange(), + report.Fixes(edit.Fix("Replace loop with assignment", edit.ReplaceWithNode(pass.Fset, node, r)))) + } else { + tv, err := types.Eval(pass.Fset, pass.Pkg, node.Pos(), "copy") + if err == nil && tv.IsBuiltin() { + to := "to" + from := "from" + src := m.State["src"].(ast.Expr) + if TsrcArray { + from = "from[:]" + src = &ast.SliceExpr{ + X: src, + } + } + dst := m.State["dst"].(ast.Expr) + if TdstArray { + to = "to[:]" + dst = &ast.SliceExpr{ + X: dst, + } + } + + r := &ast.CallExpr{ + Fun: &ast.Ident{Name: "copy"}, + Args: []ast.Expr{dst, src}, + } + opts := []report.Option{ + report.ShortRange(), + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace loop with call to copy()", edit.ReplaceWithNode(pass.Fset, node, r))), + } + report.Report(pass, node, fmt.Sprintf("should use copy(%s, %s) instead of a loop", to, from), opts...) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1002/s1002.go b/vendor/honnef.co/go/tools/simple/s1002/s1002.go new file mode 100644 index 0000000..7d7d8b8 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1002/s1002.go @@ -0,0 +1,88 @@ +package s1002 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1002", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Omit comparison with boolean constant`, + Before: `if x == true {}`, + After: `if x {}`, + Since: "2017.1", + // MergeIfAll because 'true' might not be the builtin constant under all build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + if code.IsInTest(pass, node) { + return + } + + expr := node.(*ast.BinaryExpr) + if expr.Op != token.EQL && expr.Op != token.NEQ { + return + } + x := code.IsBoolConst(pass, expr.X) + y := code.IsBoolConst(pass, expr.Y) + if !x && !y { + return + } + var other ast.Expr + var val bool + if x { + val = code.BoolConst(pass, expr.X) + other = expr.Y + } else { + val = code.BoolConst(pass, expr.Y) + other = expr.X + } + + ok := typeutil.All(pass.TypesInfo.TypeOf(other), func(term *types.Term) bool { + basic, ok := term.Type().Underlying().(*types.Basic) + return ok && basic.Kind() == types.Bool + }) + if !ok { + return + } + op := "" + if (expr.Op == token.EQL && !val) || (expr.Op == token.NEQ && val) { + op = "!" + } + r := op + report.Render(pass, other) + l1 := len(r) + r = strings.TrimLeft(r, "!") + if (l1-len(r))%2 == 1 { + r = "!" + r + } + report.Report(pass, expr, fmt.Sprintf("should omit comparison to bool constant, can be simplified to %s", r), + report.FilterGenerated(), + report.Fixes(edit.Fix("Simplify bool comparison", edit.ReplaceWithString(expr, r)))) + } + code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1003/s1003.go b/vendor/honnef.co/go/tools/simple/s1003/s1003.go new file mode 100644 index 0000000..0bd1e02 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1003/s1003.go @@ -0,0 +1,118 @@ +package s1003 + +import ( + "fmt" + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1003", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Replace call to \'strings.Index\' with \'strings.Contains\'`, + Before: `if strings.Index(x, y) != -1 {}`, + After: `if strings.Contains(x, y) {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // map of value to token to bool value + allowed := map[int64]map[token.Token]bool{ + -1: {token.GTR: true, token.NEQ: true, token.EQL: false}, + 0: {token.GEQ: true, token.LSS: false}, + } + fn := func(node ast.Node) { + expr := node.(*ast.BinaryExpr) + switch expr.Op { + case token.GEQ, token.GTR, token.NEQ, token.LSS, token.EQL: + default: + return + } + + value, ok := code.ExprToInt(pass, expr.Y) + if !ok { + return + } + + allowedOps, ok := allowed[value] + if !ok { + return + } + b, ok := allowedOps[expr.Op] + if !ok { + return + } + + call, ok := expr.X.(*ast.CallExpr) + if !ok { + return + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return + } + pkgIdent, ok := sel.X.(*ast.Ident) + if !ok { + return + } + funIdent := sel.Sel + if pkgIdent.Name != "strings" && pkgIdent.Name != "bytes" { + return + } + + var r ast.Expr + switch funIdent.Name { + case "IndexRune": + r = &ast.SelectorExpr{ + X: pkgIdent, + Sel: &ast.Ident{Name: "ContainsRune"}, + } + case "IndexAny": + r = &ast.SelectorExpr{ + X: pkgIdent, + Sel: &ast.Ident{Name: "ContainsAny"}, + } + case "Index": + r = &ast.SelectorExpr{ + X: pkgIdent, + Sel: &ast.Ident{Name: "Contains"}, + } + default: + return + } + + r = &ast.CallExpr{ + Fun: r, + Args: call.Args, + } + if !b { + r = &ast.UnaryExpr{ + Op: token.NOT, + X: r, + } + } + + report.Report(pass, node, fmt.Sprintf("should use %s instead", report.Render(pass, r)), + report.FilterGenerated(), + report.Fixes(edit.Fix(fmt.Sprintf("Simplify use of %s", report.Render(pass, call.Fun)), edit.ReplaceWithNode(pass.Fset, node, r)))) + } + code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1004/s1004.go b/vendor/honnef.co/go/tools/simple/s1004/s1004.go new file mode 100644 index 0000000..1299683 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1004/s1004.go @@ -0,0 +1,65 @@ +package s1004 + +import ( + "fmt" + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1004", + Run: CheckBytesCompare, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Replace call to \'bytes.Compare\' with \'bytes.Equal\'`, + Before: `if bytes.Compare(x, y) == 0 {}`, + After: `if bytes.Equal(x, y) {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkBytesCompareQ = pattern.MustParse(`(BinaryExpr (CallExpr (Symbol "bytes.Compare") args) op@(Or "==" "!=") (IntegerLiteral "0"))`) + checkBytesCompareRe = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args)`) + checkBytesCompareRn = pattern.MustParse(`(UnaryExpr "!" (CallExpr (SelectorExpr (Ident "bytes") (Ident "Equal")) args))`) +) + +func CheckBytesCompare(pass *analysis.Pass) (any, error) { + if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" { + // the bytes package is free to use bytes.Compare as it sees fit + return nil, nil + } + for node, m := range code.Matches(pass, checkBytesCompareQ) { + args := report.RenderArgs(pass, m.State["args"].([]ast.Expr)) + prefix := "" + if m.State["op"].(token.Token) == token.NEQ { + prefix = "!" + } + + var fix analysis.SuggestedFix + switch tok := m.State["op"].(token.Token); tok { + case token.EQL: + fix = edit.Fix("Simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRe, m.State)) + case token.NEQ: + fix = edit.Fix("Simplify use of bytes.Compare", edit.ReplaceWithPattern(pass.Fset, node, checkBytesCompareRn, m.State)) + default: + panic(fmt.Sprintf("unexpected token %v", tok)) + } + report.Report(pass, node, fmt.Sprintf("should use %sbytes.Equal(%s) instead", prefix, args), report.FilterGenerated(), report.Fixes(fix)) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1005/s1005.go b/vendor/honnef.co/go/tools/simple/s1005/s1005.go new file mode 100644 index 0000000..bd3dcbd --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1005/s1005.go @@ -0,0 +1,108 @@ +package s1005 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1005", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Drop unnecessary use of the blank identifier`, + Text: `In many cases, assigning to the blank identifier is unnecessary.`, + Before: ` +for _ = range s {} +_ = <-ch`, + After: ` +for range s{} +<-ch`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkUnnecessaryBlankQ1 = pattern.MustParse(` + (AssignStmt + [_ (Ident "_")] + _ + (UnaryExpr "<-" _)) `) + checkUnnecessaryBlankQ2 = pattern.MustParse(` + (AssignStmt + (Ident "_") _ recv@(UnaryExpr "<-" _))`) +) + +func run(pass *analysis.Pass) (any, error) { + fn1 := func(node ast.Node) { + // We don't check for 'x, _ = m[k]', which might be used to indicate + // that one knows that there might be no entry and that one doesn't + // care. + + if _, ok := code.Match(pass, checkUnnecessaryBlankQ1, node); ok { + r := *node.(*ast.AssignStmt) + r.Lhs = r.Lhs[0:1] + report.Report(pass, node, "unnecessary assignment to the blank identifier", + report.FilterGenerated(), + report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.ReplaceWithNode(pass.Fset, node, &r)))) + } else if m, ok := code.Match(pass, checkUnnecessaryBlankQ2, node); ok { + report.Report(pass, node, "unnecessary assignment to the blank identifier", + report.FilterGenerated(), + report.Fixes(edit.Fix("Simplify channel receive operation", edit.ReplaceWithNode(pass.Fset, node, m.State["recv"].(ast.Node))))) + } + } + + fn3 := func(node ast.Node) { + rs := node.(*ast.RangeStmt) + + if _, ok := pass.TypesInfo.TypeOf(rs.X).Underlying().(*types.Signature); ok { + // iteration variables are not optional with rangefunc + return + } + + // for _ + if rs.Value == nil && astutil.IsBlank(rs.Key) { + report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier", + report.FilterGenerated(), + report.MinimumLanguageVersion("go1.4"), + report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1})))) + } + + // for _, _ + if astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) { + // FIXME we should mark both key and value + report.Report(pass, rs.Key, "unnecessary assignment to the blank identifier", + report.FilterGenerated(), + report.MinimumLanguageVersion("go1.4"), + report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.Pos(), rs.TokPos + 1})))) + } + + // for x, _ + if !astutil.IsBlank(rs.Key) && astutil.IsBlank(rs.Value) { + report.Report(pass, rs.Value, "unnecessary assignment to the blank identifier", + report.FilterGenerated(), + report.MinimumLanguageVersion("go1.4"), + report.Fixes(edit.Fix("Remove assignment to blank identifier", edit.Delete(edit.Range{rs.Key.End(), rs.Value.End()})))) + } + } + + code.Preorder(pass, fn1, (*ast.AssignStmt)(nil)) + code.Preorder(pass, fn3, (*ast.RangeStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1006/s1006.go b/vendor/honnef.co/go/tools/simple/s1006/s1006.go new file mode 100644 index 0000000..faa58a6 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1006/s1006.go @@ -0,0 +1,46 @@ +package s1006 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1006", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \"for { ... }\" for infinite loops`, + Text: `For infinite loops, using \'for { ... }\' is the most idiomatic choice.`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + loop := node.(*ast.ForStmt) + if loop.Init != nil || loop.Post != nil { + return + } + if !code.IsBoolConst(pass, loop.Cond) || !code.BoolConst(pass, loop.Cond) { + return + } + report.Report(pass, loop, "should use for {} instead of for true {}", + report.ShortRange(), + report.FilterGenerated()) + } + code.Preorder(pass, fn, (*ast.ForStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1007/s1007.go b/vendor/honnef.co/go/tools/simple/s1007/s1007.go new file mode 100644 index 0000000..a92a2bd --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1007/s1007.go @@ -0,0 +1,78 @@ +package s1007 + +import ( + "fmt" + "go/ast" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1007", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Simplify regular expression by using raw string literal`, + Text: `Raw string literals use backticks instead of quotation marks and do not support +any escape sequences. This means that the backslash can be used +freely, without the need of escaping. + +Since regular expressions have their own escape sequences, raw strings +can improve their readability.`, + Before: `regexp.Compile("\\A(\\w+) profile: total \\d+\\n\\z")`, + After: "regexp.Compile(`\\A(\\w+) profile: total \\d+\\n\\z`)", + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +// TODO(dominikh): support string concat, maybe support constants +var query = pattern.MustParse(`(CallExpr (Symbol fn@(Or "regexp.MustCompile" "regexp.Compile")) [lit@(BasicLit "STRING" _)])`) + +func run(pass *analysis.Pass) (any, error) { +outer: + for _, m := range code.Matches(pass, query) { + lit := m.State["lit"].(*ast.BasicLit) + val := lit.Value + if lit.Value[0] != '"' { + // already a raw string + continue + } + if !strings.Contains(val, `\\`) { + continue + } + if strings.Contains(val, "`") { + continue + } + + bs := false + for _, c := range val { + if !bs && c == '\\' { + bs = true + continue + } + if bs && c == '\\' { + bs = false + continue + } + if bs { + // backslash followed by non-backslash -> escape sequence + continue outer + } + } + + report.Report(pass, lit, fmt.Sprintf("should use raw string (`...`) with %s to avoid having to escape twice", m.State["fn"]), report.FilterGenerated()) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1008/s1008.go b/vendor/honnef.co/go/tools/simple/s1008/s1008.go new file mode 100644 index 0000000..e945e3f --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1008/s1008.go @@ -0,0 +1,177 @@ +package s1008 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1008", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Simplify returning boolean expression`, + Before: ` +if { + return true +} +return false`, + After: `return `, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkIfReturnQIf = pattern.MustParse(`(IfStmt nil cond [(ReturnStmt [ret@(Builtin (Or "true" "false"))])] nil)`) + checkIfReturnQRet = pattern.MustParse(`(ReturnStmt [ret@(Builtin (Or "true" "false"))])`) +) + +func run(pass *analysis.Pass) (any, error) { + var cm ast.CommentMap + fn := func(node ast.Node) { + if f, ok := node.(*ast.File); ok { + cm = ast.NewCommentMap(pass.Fset, f, f.Comments) + return + } + + block := node.(*ast.BlockStmt) + l := len(block.List) + if l < 2 { + return + } + n1, n2 := block.List[l-2], block.List[l-1] + + if len(block.List) >= 3 { + if _, ok := block.List[l-3].(*ast.IfStmt); ok { + // Do not flag a series of if statements + return + } + } + m1, ok := code.Match(pass, checkIfReturnQIf, n1) + if !ok { + return + } + m2, ok := code.Match(pass, checkIfReturnQRet, n2) + if !ok { + return + } + + if op, ok := m1.State["cond"].(*ast.BinaryExpr); ok { + switch op.Op { + case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: + default: + return + } + } + + ret1 := m1.State["ret"].(*ast.Ident) + ret2 := m2.State["ret"].(*ast.Ident) + if ret1.Name == ret2.Name { + // we want the function to return true and false, not the + // same value both times. + return + } + + hasComments := func(n ast.Node) bool { + cmf := cm.Filter(n) + for _, groups := range cmf { + for _, group := range groups { + for _, cmt := range group.List { + if strings.HasPrefix(cmt.Text, "//@ diag") { + // Staticcheck test cases use comments to mark + // expected diagnostics. Ignore these comments so we + // can test this check. + continue + } + return true + } + } + } + return false + } + + // Don't flag if either branch is commented + if hasComments(n1) || hasComments(n2) { + return + } + + cond := m1.State["cond"].(ast.Expr) + origCond := cond + if ret1.Name == "false" { + cond = negate(pass, cond) + } + report.Report(pass, n1, + fmt.Sprintf("should use 'return %s' instead of 'if %s { return %s }; return %s'", + report.Render(pass, cond), + report.Render(pass, origCond), report.Render(pass, ret1), report.Render(pass, ret2)), + report.FilterGenerated()) + } + code.Preorder(pass, fn, (*ast.File)(nil), (*ast.BlockStmt)(nil)) + return nil, nil +} + +func negate(pass *analysis.Pass, expr ast.Expr) ast.Expr { + switch expr := expr.(type) { + case *ast.BinaryExpr: + out := *expr + switch expr.Op { + case token.EQL: + out.Op = token.NEQ + case token.LSS: + out.Op = token.GEQ + case token.GTR: + // Some builtins never return negative ints; "len(x) <= 0" should be "len(x) == 0". + if call, ok := expr.X.(*ast.CallExpr); ok && + code.IsCallToAny(pass, call, "len", "cap", "copy") && + code.IsIntegerLiteral(pass, expr.Y, constant.MakeInt64(0)) { + out.Op = token.EQL + } else { + out.Op = token.LEQ + } + case token.NEQ: + out.Op = token.EQL + case token.LEQ: + out.Op = token.GTR + case token.GEQ: + out.Op = token.LSS + } + return &out + case *ast.Ident, *ast.CallExpr, *ast.IndexExpr, *ast.StarExpr: + return &ast.UnaryExpr{ + Op: token.NOT, + X: expr, + } + case *ast.UnaryExpr: + if expr.Op == token.NOT { + return expr.X + } + return &ast.UnaryExpr{ + Op: token.NOT, + X: expr, + } + default: + return &ast.UnaryExpr{ + Op: token.NOT, + X: &ast.ParenExpr{ + X: expr, + }, + } + } +} diff --git a/vendor/honnef.co/go/tools/simple/s1009/s1009.go b/vendor/honnef.co/go/tools/simple/s1009/s1009.go new file mode 100644 index 0000000..fda7ec0 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1009/s1009.go @@ -0,0 +1,180 @@ +package s1009 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1009", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Omit redundant nil check on slices, maps, and channels`, + Text: `The \'len\' function is defined for all slices, maps, and +channels, even nil ones, which have a length of zero. It is not necessary to +check for nil before checking that their length is not zero.`, + Before: `if x != nil && len(x) != 0 {}`, + After: `if len(x) != 0 {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(` + (BinaryExpr + (BinaryExpr + x + lhsOp@(Or "==" "!=") + nilly) + outerOp@(Or "&&" "||") + (BinaryExpr + (CallExpr (Builtin "len") [x]) + rhsOp + k))`) + +// run checks for the following redundant nil-checks: +// +// if x == nil || len(x) == 0 {} +// if x == nil || len(x) < N {} (where N != 0) +// if x == nil || len(x) <= N {} +// if x != nil && len(x) != 0 {} +// if x != nil && len(x) == N {} (where N != 0) +// if x != nil && len(x) > N {} +// if x != nil && len(x) >= N {} (where N != 0) +func run(pass *analysis.Pass) (any, error) { + isConstZero := func(expr ast.Expr) (isConst bool, isZero bool) { + _, ok := expr.(*ast.BasicLit) + if ok { + return true, code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) + } + id, ok := expr.(*ast.Ident) + if !ok { + return false, false + } + c, ok := pass.TypesInfo.ObjectOf(id).(*types.Const) + if !ok { + return false, false + } + return true, c.Val().Kind() == constant.Int && c.Val().String() == "0" + } + + for node, m := range code.Matches(pass, query) { + x := m.State["x"].(ast.Expr) + outerOp := m.State["outerOp"].(token.Token) + lhsOp := m.State["lhsOp"].(token.Token) + rhsOp := m.State["rhsOp"].(token.Token) + nilly := m.State["nilly"].(ast.Expr) + k := m.State["k"].(ast.Expr) + eqNil := outerOp == token.LOR + + if code.MayHaveSideEffects(pass, x, nil) { + continue + } + + if eqNil && lhsOp != token.EQL { + continue + } + if !eqNil && lhsOp != token.NEQ { + continue + } + if !code.IsNil(pass, nilly) { + continue + } + isConst, isZero := isConstZero(k) + if !isConst { + continue + } + + if eqNil { + switch rhsOp { + case token.EQL: + // avoid false positive for "xx == nil || len(xx) == " + if !isZero { + continue + } + case token.LEQ: + // ok + case token.LSS: + // avoid false positive for "xx == nil || len(xx) < 0" + if isZero { + continue + } + default: + continue + } + } else { + switch rhsOp { + case token.EQL: + // avoid false positive for "xx != nil && len(xx) == 0" + if isZero { + continue + } + case token.GEQ: + // avoid false positive for "xx != nil && len(xx) >= 0" + if isZero { + continue + } + case token.NEQ: + // avoid false positive for "xx != nil && len(xx) != " + if !isZero { + continue + } + case token.GTR: + // ok + default: + continue + } + } + + // finally check that xx type is one of array, slice, map or chan + // this is to prevent false positive in case if xx is a pointer to an array + typ := pass.TypesInfo.TypeOf(x) + var nilType string + ok := typeutil.All(typ, func(term *types.Term) bool { + switch term.Type().Underlying().(type) { + case *types.Slice: + nilType = "nil slices" + return true + case *types.Map: + nilType = "nil maps" + return true + case *types.Chan: + nilType = "nil channels" + return true + case *types.Pointer: + return false + case *types.TypeParam: + return false + default: + lint.ExhaustiveTypeSwitch(term.Type().Underlying()) + return false + } + }) + if !ok { + continue + } + + report.Report(pass, node, + fmt.Sprintf("should omit nil check; len() for %s is defined as zero", nilType), + report.FilterGenerated()) + } + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1010/s1010.go b/vendor/honnef.co/go/tools/simple/s1010/s1010.go new file mode 100644 index 0000000..899a654 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1010/s1010.go @@ -0,0 +1,44 @@ +package s1010 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1010", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Omit default slice index`, + Text: `When slicing, the second index defaults to the length of the value, +making \'s[n:len(s)]\' and \'s[n:]\' equivalent.`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkSlicingQ = pattern.MustParse(`(SliceExpr x@(Object _) low (CallExpr (Builtin "len") [x]) nil)`) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, checkSlicingQ) { + expr := node.(*ast.SliceExpr) + report.Report(pass, expr.High, + "should omit second index in slice, s[a:len(s)] is identical to s[a:]", + report.FilterGenerated(), + report.Fixes(edit.Fix("Simplify slice expression", edit.Delete(expr.High)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1011/s1011.go b/vendor/honnef.co/go/tools/simple/s1011/s1011.go new file mode 100644 index 0000000..50d2549 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1011/s1011.go @@ -0,0 +1,137 @@ +package s1011 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/facts/purity" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1011", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer, purity.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Use a single \'append\' to concatenate two slices`, + Before: ` +for _, e := range y { + x = append(x, e) +} + +for i := range y { + x = append(x, y[i]) +} + +for i := range y { + v := y[i] + x = append(x, v) +}`, + + After: ` +x = append(x, y...) +x = append(x, y...) +x = append(x, y...)`, + Since: "2017.1", + // MergeIfAll because y might not be a slice under all build tags. + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkLoopAppendQ = pattern.MustParse(` +(Or + (RangeStmt + (Ident "_") + val@(Object _) + _ + x + [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]) + (RangeStmt + idx@(Object _) + nil + _ + x + [(AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs (IndexExpr x idx)])])]) + (RangeStmt + idx@(Object _) + nil + _ + x + [(AssignStmt val@(Object _) ":=" (IndexExpr x idx)) + (AssignStmt [lhs] "=" [(CallExpr (Builtin "append") [lhs val])])]))`) + +func run(pass *analysis.Pass) (any, error) { + pure := pass.ResultOf[purity.Analyzer].(purity.Result) + + for node, m := range code.Matches(pass, checkLoopAppendQ) { + if val, ok := m.State["val"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), val) { + continue + } + + if m.State["idx"] != nil && code.MayHaveSideEffects(pass, m.State["x"].(ast.Expr), pure) { + // When using an index-based loop, x gets evaluated repeatedly and thus should be pure. + // This doesn't matter for value-based loops, because x only gets evaluated once. + continue + } + + if idx, ok := m.State["idx"].(types.Object); ok && code.RefersTo(pass, m.State["lhs"].(ast.Expr), idx) { + // The lhs mustn't refer to the index loop variable. + continue + } + + if code.MayHaveSideEffects(pass, m.State["lhs"].(ast.Expr), pure) { + // The lhs may be dynamic and return different values on each iteration. For example: + // + // func bar() map[int][]int { /* return one of several maps */ } + // + // func foo(x []int, y [][]int) { + // for i := range x { + // bar()[0] = append(bar()[0], x[i]) + // } + // } + // + // The dynamic nature of the lhs might also affect the value of the index. + continue + } + + src := pass.TypesInfo.TypeOf(m.State["x"].(ast.Expr)) + dst := pass.TypesInfo.TypeOf(m.State["lhs"].(ast.Expr)) + if !types.Identical(src, dst) { + continue + } + + r := &ast.AssignStmt{ + Lhs: []ast.Expr{m.State["lhs"].(ast.Expr)}, + Tok: token.ASSIGN, + Rhs: []ast.Expr{ + &ast.CallExpr{ + Fun: &ast.Ident{Name: "append"}, + Args: []ast.Expr{ + m.State["lhs"].(ast.Expr), + m.State["x"].(ast.Expr), + }, + Ellipsis: 1, + }, + }, + } + + report.Report(pass, node, fmt.Sprintf("should replace loop with %s", report.Render(pass, r)), + report.ShortRange(), + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace loop with call to append", edit.ReplaceWithNode(pass.Fset, node, r)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1012/s1012.go b/vendor/honnef.co/go/tools/simple/s1012/s1012.go new file mode 100644 index 0000000..33edd55 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1012/s1012.go @@ -0,0 +1,46 @@ +package s1012 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1012", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Replace \'time.Now().Sub(x)\' with \'time.Since(x)\'`, + Text: `The \'time.Since\' helper has the same effect as using \'time.Now().Sub(x)\' +but is easier to read.`, + Before: `time.Now().Sub(x)`, + After: `time.Since(x)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkTimeSinceQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (Symbol "time.Now") []) (Symbol "(time.Time).Sub")) [arg])`) + checkTimeSinceR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Since")) [arg])`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkTimeSinceQ) { + edits := code.EditMatch(pass, node, m, checkTimeSinceR) + report.Report(pass, node, "should use time.Since instead of time.Now().Sub", + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace with call to time.Since", edits...))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1016/s1016.go b/vendor/honnef.co/go/tools/simple/s1016/s1016.go new file mode 100644 index 0000000..9f0ab30 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1016/s1016.go @@ -0,0 +1,193 @@ +package s1016 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "go/version" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1016", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use a type conversion instead of manually copying struct fields`, + Text: `Two struct types with identical fields can be converted between each +other. In older versions of Go, the fields had to have identical +struct tags. Since Go 1.8, however, struct tags are ignored during +conversions. It is thus not necessary to manually copy every field +individually.`, + Before: ` +var x T1 +y := T2{ + Field1: x.Field1, + Field2: x.Field2, +}`, + After: ` +var x T1 +y := T2(x)`, + Since: "2017.1", + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // TODO(dh): support conversions between type parameters + fn := func(c inspector.Cursor) { + node := c.Node() + if unary, ok := c.Parent().Node().(*ast.UnaryExpr); ok && unary.Op == token.AND { + // Do not suggest type conversion between pointers + return + } + + lit := node.(*ast.CompositeLit) + var typ1 types.Type + var named1 *types.Named + switch typ := pass.TypesInfo.TypeOf(lit.Type).(type) { + case *types.Named: + typ1 = typ + named1 = typ + case *types.Alias: + ua := types.Unalias(typ) + if n, ok := ua.(*types.Named); ok { + typ1 = typ + named1 = n + } + } + if typ1 == nil { + return + } + s1, ok := typ1.Underlying().(*types.Struct) + if !ok { + return + } + + var typ2 types.Type + var named2 *types.Named + var ident *ast.Ident + getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) { + sel, ok := expr.(*ast.SelectorExpr) + if !ok { + return nil, nil, false + } + ident, ok := sel.X.(*ast.Ident) + if !ok { + return nil, nil, false + } + typ := pass.TypesInfo.TypeOf(sel.X) + return typ, ident, typ != nil + } + if len(lit.Elts) == 0 { + return + } + if s1.NumFields() != len(lit.Elts) { + return + } + for i, elt := range lit.Elts { + var t types.Type + var id *ast.Ident + var ok bool + switch elt := elt.(type) { + case *ast.SelectorExpr: + t, id, ok = getSelType(elt) + if !ok { + return + } + if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name { + return + } + case *ast.KeyValueExpr: + var sel *ast.SelectorExpr + sel, ok = elt.Value.(*ast.SelectorExpr) + if !ok { + return + } + + if elt.Key.(*ast.Ident).Name != sel.Sel.Name { + return + } + t, id, ok = getSelType(elt.Value) + } + if !ok { + return + } + // All fields must be initialized from the same object + if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) { + return + } + switch t := t.(type) { + case *types.Named: + typ2 = t + named2 = t + case *types.Alias: + if n, ok := types.Unalias(t).(*types.Named); ok { + typ2 = t + named2 = n + } + } + if typ2 == nil { + return + } + ident = id + } + + if typ2 == nil { + return + } + + if named1.Obj().Pkg() != named2.Obj().Pkg() { + // Do not suggest type conversions between different + // packages. Types in different packages might only match + // by coincidence. Furthermore, if the dependency ever + // adds more fields to its type, it could break the code + // that relies on the type conversion to work. + return + } + + s2, ok := typ2.Underlying().(*types.Struct) + if !ok { + return + } + if typ1 == typ2 { + return + } + if version.Compare(code.LanguageVersion(pass, node), "go1.8") >= 0 { + if !types.IdenticalIgnoreTags(s1, s2) { + return + } + } else { + if !types.Identical(s1, s2) { + return + } + } + + r := &ast.CallExpr{ + Fun: lit.Type, + Args: []ast.Expr{ident}, + } + report.Report(pass, node, + fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, types.TypeString(typ2, types.RelativeTo(pass.Pkg)), types.TypeString(typ1, types.RelativeTo(pass.Pkg))), + report.FilterGenerated(), + report.Fixes(edit.Fix("Use type conversion", edit.ReplaceWithNode(pass.Fset, node, r)))) + } + for c := range code.Cursor(pass).Preorder((*ast.CompositeLit)(nil)) { + fn(c) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1017/s1017.go b/vendor/honnef.co/go/tools/simple/s1017/s1017.go new file mode 100644 index 0000000..cae41eb --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1017/s1017.go @@ -0,0 +1,239 @@ +package s1017 + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1017", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Replace manual trimming with \'strings.TrimPrefix\'`, + Text: `Instead of using \'strings.HasPrefix\' and manual slicing, use the +\'strings.TrimPrefix\' function. If the string doesn't start with the +prefix, the original string will be returned. Using \'strings.TrimPrefix\' +reduces complexity, and avoids common bugs, such as off-by-one +mistakes.`, + Before: ` +if strings.HasPrefix(str, prefix) { + str = str[len(prefix):] +}`, + After: `str = strings.TrimPrefix(str, prefix)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + sameNonDynamic := func(node1, node2 ast.Node) bool { + if reflect.TypeOf(node1) != reflect.TypeOf(node2) { + return false + } + + switch node1 := node1.(type) { + case *ast.Ident: + return pass.TypesInfo.ObjectOf(node1) == pass.TypesInfo.ObjectOf(node2.(*ast.Ident)) + case *ast.SelectorExpr, *ast.IndexExpr: + return astutil.Equal(node1, node2) + case *ast.BasicLit: + return astutil.Equal(node1, node2) + } + return false + } + + isLenOnIdent := func(fn ast.Expr, ident ast.Expr) bool { + call, ok := fn.(*ast.CallExpr) + if !ok { + return false + } + if !code.IsCallTo(pass, call, "len") { + return false + } + if len(call.Args) != 1 { + return false + } + return sameNonDynamic(call.Args[knowledge.Arg("len.v")], ident) + } + + seen := make(map[ast.Node]struct{}) + fn := func(node ast.Node) { + var pkg string + var fun string + + ifstmt := node.(*ast.IfStmt) + if ifstmt.Init != nil { + return + } + if ifstmt.Else != nil { + seen[ifstmt.Else] = struct{}{} + return + } + if _, ok := seen[ifstmt]; ok { + return + } + if len(ifstmt.Body.List) != 1 { + return + } + condCall, ok := ifstmt.Cond.(*ast.CallExpr) + if !ok { + return + } + + condCallName := code.CallName(pass, condCall) + switch condCallName { + case "strings.HasPrefix": + pkg = "strings" + fun = "HasPrefix" + case "strings.HasSuffix": + pkg = "strings" + fun = "HasSuffix" + case "strings.Contains": + pkg = "strings" + fun = "Contains" + case "bytes.HasPrefix": + pkg = "bytes" + fun = "HasPrefix" + case "bytes.HasSuffix": + pkg = "bytes" + fun = "HasSuffix" + case "bytes.Contains": + pkg = "bytes" + fun = "Contains" + default: + return + } + + assign, ok := ifstmt.Body.List[0].(*ast.AssignStmt) + if !ok { + return + } + if assign.Tok != token.ASSIGN { + return + } + if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { + return + } + if !sameNonDynamic(condCall.Args[0], assign.Lhs[0]) { + return + } + + switch rhs := assign.Rhs[0].(type) { + case *ast.CallExpr: + if len(rhs.Args) < 2 || !sameNonDynamic(condCall.Args[0], rhs.Args[0]) || !sameNonDynamic(condCall.Args[1], rhs.Args[1]) { + return + } + + rhsName := code.CallName(pass, rhs) + if condCallName == "strings.HasPrefix" && rhsName == "strings.TrimPrefix" || + condCallName == "strings.HasSuffix" && rhsName == "strings.TrimSuffix" || + condCallName == "strings.Contains" && rhsName == "strings.Replace" || + condCallName == "bytes.HasPrefix" && rhsName == "bytes.TrimPrefix" || + condCallName == "bytes.HasSuffix" && rhsName == "bytes.TrimSuffix" || + condCallName == "bytes.Contains" && rhsName == "bytes.Replace" { + report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s", rhsName), report.FilterGenerated()) + } + case *ast.SliceExpr: + slice := rhs + if !ok { + return + } + if slice.Slice3 { + return + } + if !sameNonDynamic(slice.X, condCall.Args[0]) { + return + } + + validateOffset := func(off ast.Expr) bool { + switch off := off.(type) { + case *ast.CallExpr: + return isLenOnIdent(off, condCall.Args[1]) + case *ast.BasicLit: + if pkg != "strings" { + return false + } + if _, ok := condCall.Args[1].(*ast.BasicLit); !ok { + // Only allow manual slicing with an integer + // literal if the second argument to HasPrefix + // was a string literal. + return false + } + s, ok1 := code.ExprToString(pass, condCall.Args[1]) + n, ok2 := code.ExprToInt(pass, off) + if !ok1 || !ok2 || n != int64(len(s)) { + return false + } + return true + default: + return false + } + } + + switch fun { + case "HasPrefix": + // TODO(dh) We could detect a High that is len(s), but another + // rule will already flag that, anyway. + if slice.High != nil { + return + } + if !validateOffset(slice.Low) { + return + } + case "HasSuffix": + if slice.Low != nil { + n, ok := code.ExprToInt(pass, slice.Low) + if !ok || n != 0 { + return + } + } + switch index := slice.High.(type) { + case *ast.BinaryExpr: + if index.Op != token.SUB { + return + } + if !isLenOnIdent(index.X, condCall.Args[0]) { + return + } + if !validateOffset(index.Y) { + return + } + default: + return + } + default: + return + } + + var replacement string + switch fun { + case "HasPrefix": + replacement = "TrimPrefix" + case "HasSuffix": + replacement = "TrimSuffix" + } + report.Report(pass, ifstmt, fmt.Sprintf("should replace this if statement with an unconditional %s.%s", pkg, replacement), + report.ShortRange(), + report.FilterGenerated()) + } + } + code.Preorder(pass, fn, (*ast.IfStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1018/s1018.go b/vendor/honnef.co/go/tools/simple/s1018/s1018.go new file mode 100644 index 0000000..98e3975 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1018/s1018.go @@ -0,0 +1,78 @@ +package s1018 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1018", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Use \"copy\" for sliding elements`, + Text: `\'copy()\' permits using the same source and destination slice, even with +overlapping ranges. This makes it ideal for sliding elements in a +slice.`, + + Before: ` +for i := 0; i < n; i++ { + bs[i] = bs[offset+i] +}`, + After: `copy(bs[:n], bs[offset:])`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkLoopSlideQ = pattern.MustParse(` + (ForStmt + (AssignStmt initvar@(Ident _) _ (IntegerLiteral "0")) + (BinaryExpr initvar "<" limit@(Ident _)) + (IncDecStmt initvar "++") + [(AssignStmt + (IndexExpr slice@(Ident _) initvar) + "=" + (IndexExpr slice (BinaryExpr offset@(Ident _) "+" initvar)))])`) + checkLoopSlideR = pattern.MustParse(` + (CallExpr + (Ident "copy") + [(SliceExpr slice nil limit nil) + (SliceExpr slice offset nil nil)])`) +) + +func run(pass *analysis.Pass) (any, error) { + // TODO(dh): detect bs[i+offset] in addition to bs[offset+i] + // TODO(dh): consider merging this function with LintLoopCopy + // TODO(dh): detect length that is an expression, not a variable name + // TODO(dh): support sliding to a different offset than the beginning of the slice + + for node, m := range code.Matches(pass, checkLoopSlideQ) { + typ := pass.TypesInfo.TypeOf(m.State["slice"].(*ast.Ident)) + // The pattern probably needs a core type, but All is fine, too. Either way we only accept slices. + if !typeutil.All(typ, typeutil.IsSlice) { + continue + } + + edits := code.EditMatch(pass, node, m, checkLoopSlideR) + report.Report(pass, node, "should use copy() instead of loop for sliding slice elements", + report.ShortRange(), + report.FilterGenerated(), + report.Fixes(edit.Fix("Use copy() instead of loop", edits...))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1019/s1019.go b/vendor/honnef.co/go/tools/simple/s1019/s1019.go new file mode 100644 index 0000000..03e2b10 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1019/s1019.go @@ -0,0 +1,71 @@ +package s1019 + +import ( + "fmt" + "go/ast" + "go/types" + "path/filepath" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1019", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Simplify \"make\" call by omitting redundant arguments`, + Text: `The \"make\" function has default values for the length and capacity +arguments. For channels, the length defaults to zero, and for slices, +the capacity defaults to the length.`, + Since: "2017.1", + // MergeIfAll because the type might be different under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkMakeLenCapQ1 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size@(IntegerLiteral "0")])`) + checkMakeLenCapQ2 = pattern.MustParse(`(CallExpr (Builtin "make") [typ size size])`) +) + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + if pass.Pkg.Path() == "runtime_test" && filepath.Base(pass.Fset.Position(node.Pos()).Filename) == "map_test.go" { + // special case of runtime tests testing map creation + return + } + if m, ok := code.Match(pass, checkMakeLenCapQ1, node); ok { + T := m.State["typ"].(ast.Expr) + size := m.State["size"].(ast.Node) + + if _, ok := typeutil.CoreType(pass.TypesInfo.TypeOf(T)).Underlying().(*types.Chan); ok { + report.Report(pass, size, fmt.Sprintf("should use make(%s) instead", report.Render(pass, T)), report.FilterGenerated()) + } + + } else if m, ok := code.Match(pass, checkMakeLenCapQ2, node); ok { + // TODO(dh): don't consider sizes identical if they're + // dynamic. for example: make(T, <-ch, <-ch). + T := m.State["typ"].(ast.Expr) + size := m.State["size"].(ast.Node) + report.Report(pass, size, + fmt.Sprintf("should use make(%s, %s) instead", report.Render(pass, T), report.Render(pass, size)), + report.FilterGenerated()) + } + } + code.Preorder(pass, fn, (*ast.CallExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1020/s1020.go b/vendor/honnef.co/go/tools/simple/s1020/s1020.go new file mode 100644 index 0000000..1983556 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1020/s1020.go @@ -0,0 +1,74 @@ +package s1020 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1020", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Omit redundant nil check in type assertion`, + Before: `if _, ok := i.(T); ok && i != nil {}`, + After: `if _, ok := i.(T); ok {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkAssertNotNilFn1Q = pattern.MustParse(` + (IfStmt + (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr assert@(Object _) _)]) + (Or + (BinaryExpr ok "&&" (BinaryExpr assert "!=" (Builtin "nil"))) + (BinaryExpr (BinaryExpr assert "!=" (Builtin "nil")) "&&" ok)) + _ + _)`) + checkAssertNotNilFn2Q = pattern.MustParse(` + (IfStmt + nil + (BinaryExpr lhs@(Object _) "!=" (Builtin "nil")) + [ + ifstmt@(IfStmt + (AssignStmt [(Ident "_") ok@(Object _)] _ [(TypeAssertExpr lhs _)]) + ok + _ + nil) + ] + nil)`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkAssertNotNilFn1Q) { + assert := m.State["assert"].(types.Object) + assign := m.State["ok"].(types.Object) + report.Report(pass, node, fmt.Sprintf("when %s is true, %s can't be nil", assign.Name(), assert.Name()), + report.ShortRange(), + report.FilterGenerated()) + } + for _, m := range code.Matches(pass, checkAssertNotNilFn2Q) { + ifstmt := m.State["ifstmt"].(*ast.IfStmt) + lhs := m.State["lhs"].(types.Object) + assignIdent := m.State["ok"].(types.Object) + report.Report(pass, ifstmt, fmt.Sprintf("when %s is true, %s can't be nil", assignIdent.Name(), lhs.Name()), + report.ShortRange(), + report.FilterGenerated()) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1021/s1021.go b/vendor/honnef.co/go/tools/simple/s1021/s1021.go new file mode 100644 index 0000000..14f32f0 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1021/s1021.go @@ -0,0 +1,118 @@ +package s1021 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1021", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Merge variable declaration and assignment`, + Before: ` +var x uint +x = 1`, + After: `var x uint = 1`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + hasMultipleAssignments := func(root ast.Node, ident *ast.Ident) bool { + num := 0 + ast.Inspect(root, func(node ast.Node) bool { + if num >= 2 { + return false + } + assign, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + for _, lhs := range assign.Lhs { + if oident, ok := lhs.(*ast.Ident); ok { + if pass.TypesInfo.ObjectOf(oident) == pass.TypesInfo.ObjectOf(ident) { + num++ + } + } + } + + return true + }) + return num >= 2 + } + fn := func(node ast.Node) { + block := node.(*ast.BlockStmt) + if len(block.List) < 2 { + return + } + for i, stmt := range block.List[:len(block.List)-1] { + _ = i + decl, ok := stmt.(*ast.DeclStmt) + if !ok { + continue + } + gdecl, ok := decl.Decl.(*ast.GenDecl) + if !ok || gdecl.Tok != token.VAR || len(gdecl.Specs) != 1 { + continue + } + vspec, ok := gdecl.Specs[0].(*ast.ValueSpec) + if !ok || len(vspec.Names) != 1 || len(vspec.Values) != 0 { + continue + } + + assign, ok := block.List[i+1].(*ast.AssignStmt) + if !ok || assign.Tok != token.ASSIGN { + continue + } + if len(assign.Lhs) != 1 || len(assign.Rhs) != 1 { + continue + } + ident, ok := assign.Lhs[0].(*ast.Ident) + if !ok { + continue + } + if pass.TypesInfo.ObjectOf(vspec.Names[0]) != pass.TypesInfo.ObjectOf(ident) { + continue + } + + if code.RefersTo(pass, assign.Rhs[0], pass.TypesInfo.ObjectOf(ident)) { + continue + } + if hasMultipleAssignments(block, ident) { + continue + } + + r := &ast.GenDecl{ + Specs: []ast.Spec{ + &ast.ValueSpec{ + Names: vspec.Names, + Values: []ast.Expr{assign.Rhs[0]}, + Type: vspec.Type, + }, + }, + Tok: gdecl.Tok, + } + report.Report(pass, decl, "should merge variable declaration with assignment on next line", + report.FilterGenerated(), + report.Fixes(edit.Fix("Merge declaration with assignment", edit.ReplaceWithNode(pass.Fset, edit.Range{decl.Pos(), assign.End()}, r)))) + } + } + code.Preorder(pass, fn, (*ast.BlockStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1023/s1023.go b/vendor/honnef.co/go/tools/simple/s1023/s1023.go new file mode 100644 index 0000000..f106d31 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1023/s1023.go @@ -0,0 +1,79 @@ +package s1023 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1023", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Omit redundant control flow`, + Text: `Functions that have no return value do not need a return statement as +the final statement of the function. + +Switches in Go do not have automatic fallthrough, unlike languages +like C. It is not necessary to have a break statement as the final +statement in a case block.`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn1 := func(node ast.Node) { + clause := node.(*ast.CaseClause) + if len(clause.Body) < 2 { + return + } + branch, ok := clause.Body[len(clause.Body)-1].(*ast.BranchStmt) + if !ok || branch.Tok != token.BREAK || branch.Label != nil { + return + } + report.Report(pass, branch, "redundant break statement", report.FilterGenerated()) + } + fn2 := func(node ast.Node) { + var ret *ast.FieldList + var body *ast.BlockStmt + switch x := node.(type) { + case *ast.FuncDecl: + ret = x.Type.Results + body = x.Body + case *ast.FuncLit: + ret = x.Type.Results + body = x.Body + default: + lint.ExhaustiveTypeSwitch(node) + } + // if the func has results, a return can't be redundant. + // similarly, if there are no statements, there can be + // no return. + if ret != nil || body == nil || len(body.List) < 1 { + return + } + rst, ok := body.List[len(body.List)-1].(*ast.ReturnStmt) + if !ok { + return + } + // we don't need to check rst.Results as we already + // checked x.Type.Results to be nil. + report.Report(pass, rst, "redundant return statement", report.FilterGenerated()) + } + code.Preorder(pass, fn1, (*ast.CaseClause)(nil)) + code.Preorder(pass, fn2, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1024/s1024.go b/vendor/honnef.co/go/tools/simple/s1024/s1024.go new file mode 100644 index 0000000..82d3730 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1024/s1024.go @@ -0,0 +1,55 @@ +package s1024 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1024", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Replace \'x.Sub(time.Now())\' with \'time.Until(x)\'`, + Text: `The \'time.Until\' helper has the same effect as using \'x.Sub(time.Now())\' +but is easier to read.`, + Before: `x.Sub(time.Now())`, + After: `time.Until(x)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkTimeUntilQ = pattern.MustParse(`(CallExpr (Symbol "(time.Time).Sub") [(CallExpr (Symbol "time.Now") [])])`) + checkTimeUntilR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Until")) [arg])`) +) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, checkTimeUntilQ) { + if sel, ok := node.(*ast.CallExpr).Fun.(*ast.SelectorExpr); ok { + r := pattern.NodeToAST(checkTimeUntilR.Root, map[string]any{"arg": sel.X}).(ast.Node) + report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())", + report.FilterGenerated(), + report.MinimumStdlibVersion("go1.8"), + report.Fixes(edit.Fix("Replace with call to time.Until", edit.ReplaceWithNode(pass.Fset, node, r)))) + } else { + report.Report(pass, node, "should use time.Until instead of t.Sub(time.Now())", + report.MinimumStdlibVersion("go1.8"), + report.FilterGenerated()) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1025/s1025.go b/vendor/honnef.co/go/tools/simple/s1025/s1025.go new file mode 100644 index 0000000..9260789 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1025/s1025.go @@ -0,0 +1,154 @@ +package s1025 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/exp/typeparams" + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1025", + Run: run, + Requires: append([]*analysis.Analyzer{ + buildir.Analyzer, + generated.Analyzer, + }, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Don't use \'fmt.Sprintf("%s", x)\' unnecessarily`, + Text: `In many instances, there are easier and more efficient ways of getting +a value's string representation. Whenever a value's underlying type is +a string already, or the type has a String method, they should be used +directly. + +Given the following shared definitions + + type T1 string + type T2 int + + func (T2) String() string { return "Hello, world" } + + var x string + var y T1 + var z T2 + +we can simplify + + fmt.Sprintf("%s", x) + fmt.Sprintf("%s", y) + fmt.Sprintf("%s", z) + +to + + x + string(y) + z.String() +`, + Since: "2017.1", + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkRedundantSprintfQ = pattern.MustParse(`(CallExpr (Symbol "fmt.Sprintf") [format arg])`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkRedundantSprintfQ) { + format := m.State["format"].(ast.Expr) + arg := m.State["arg"].(ast.Expr) + // TODO(dh): should we really support named constants here? + // shouldn't we only look for string literals? to avoid false + // positives via build tags? + if s, ok := code.ExprToString(pass, format); !ok || s != "%s" { + continue + } + typ := pass.TypesInfo.TypeOf(arg) + if typeparams.IsTypeParam(typ) { + continue + } + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + + if typeutil.IsTypeWithName(typ, "reflect.Value") { + // printing with %s produces output different from using + // the String method + continue + } + + if isFormatter(typ, &irpkg.Prog.MethodSets) { + // the type may choose to handle %s in arbitrary ways + continue + } + + if types.Implements(typ, knowledge.Interfaces["fmt.Stringer"]) { + replacement := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: arg, + Sel: &ast.Ident{Name: "String"}, + }, + } + report.Report(pass, node, "should use String() instead of fmt.Sprintf", + report.Fixes(edit.Fix("Replace with call to String method", edit.ReplaceWithNode(pass.Fset, node, replacement)))) + } else if types.Unalias(typ) == types.Universe.Lookup("string").Type() { + report.Report(pass, node, "the argument is already a string, there's no need to use fmt.Sprintf", + report.FilterGenerated(), + report.Fixes(edit.Fix("Remove unnecessary call to fmt.Sprintf", edit.ReplaceWithNode(pass.Fset, node, arg)))) + } else if typ.Underlying() == types.Universe.Lookup("string").Type() { + replacement := &ast.CallExpr{ + Fun: &ast.Ident{Name: "string"}, + Args: []ast.Expr{arg}, + } + report.Report(pass, node, "the argument's underlying type is a string, should use a simple conversion instead of fmt.Sprintf", + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement)))) + } else if code.IsOfStringConvertibleByteSlice(pass, arg) { + replacement := &ast.CallExpr{ + Fun: &ast.Ident{Name: "string"}, + Args: []ast.Expr{arg}, + } + report.Report(pass, node, "the argument's underlying type is a slice of bytes, should use a simple conversion instead of fmt.Sprintf", + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace with conversion to string", edit.ReplaceWithNode(pass.Fset, node, replacement)))) + } + + } + return nil, nil +} + +func isFormatter(T types.Type, msCache *typeutil.MethodSetCache) bool { + // TODO(dh): this function also exists in staticcheck/lint.go – deduplicate. + + ms := msCache.MethodSet(T) + sel := ms.Lookup(nil, "Format") + if sel == nil { + return false + } + fn, ok := sel.Obj().(*types.Func) + if !ok { + // should be unreachable + return false + } + sig := fn.Type().(*types.Signature) + if sig.Params().Len() != 2 { + return false + } + // TODO(dh): check the types of the arguments for more + // precision + if sig.Results().Len() != 0 { + return false + } + return true +} diff --git a/vendor/honnef.co/go/tools/simple/s1028/s1028.go b/vendor/honnef.co/go/tools/simple/s1028/s1028.go new file mode 100644 index 0000000..45da204 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1028/s1028.go @@ -0,0 +1,45 @@ +package s1028 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1028", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Simplify error construction with \'fmt.Errorf\'`, + Before: `errors.New(fmt.Sprintf(...))`, + After: `fmt.Errorf(...)`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkErrorsNewSprintfQ = pattern.MustParse(`(CallExpr (Symbol "errors.New") [(CallExpr (Symbol "fmt.Sprintf") args)])`) + checkErrorsNewSprintfR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "fmt") (Ident "Errorf")) args)`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkErrorsNewSprintfQ) { + edits := code.EditMatch(pass, node, m, checkErrorsNewSprintfR) + // TODO(dh): the suggested fix may leave an unused import behind + report.Report(pass, node, "should use fmt.Errorf(...) instead of errors.New(fmt.Sprintf(...))", + report.FilterGenerated(), + report.Fixes(edit.Fix("Use fmt.Errorf", edits...))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1029/s1029.go b/vendor/honnef.co/go/tools/simple/s1029/s1029.go new file mode 100644 index 0000000..1bf3f00 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1029/s1029.go @@ -0,0 +1,31 @@ +package s1029 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/internal/sharedcheck" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1029", + Run: sharedcheck.CheckRangeStringRunes, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Range over the string directly`, + Text: `Ranging over a string will yield byte offsets and runes. If the offset +isn't used, this is functionally equivalent to converting the string +to a slice of runes and ranging over that. Ranging directly over the +string will be more performant, however, as it avoids allocating a new +slice, the size of which depends on the length of the string.`, + Before: `for _, r := range []rune(s) {}`, + After: `for _, r := range s {}`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer diff --git a/vendor/honnef.co/go/tools/simple/s1030/s1030.go b/vendor/honnef.co/go/tools/simple/s1030/s1030.go new file mode 100644 index 0000000..44c8820 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1030/s1030.go @@ -0,0 +1,87 @@ +package s1030 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1030", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \'bytes.Buffer.String\' or \'bytes.Buffer.Bytes\'`, + Text: `\'bytes.Buffer\' has both a \'String\' and a \'Bytes\' method. It is almost never +necessary to use \'string(buf.Bytes())\' or \'[]byte(buf.String())\' – simply +use the other method. + +The only exception to this are map lookups. Due to a compiler optimization, +\'m[string(buf.Bytes())]\' is more efficient than \'m[buf.String()]\'. +`, + Since: "2017.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkBytesBufferConversionsQ = pattern.MustParse(`(CallExpr _ [(CallExpr sel@(SelectorExpr recv _) [])])`) + checkBytesBufferConversionsRs = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "String")) [])`) + checkBytesBufferConversionsRb = pattern.MustParse(`(CallExpr (SelectorExpr recv (Ident "Bytes")) [])`) +) + +func run(pass *analysis.Pass) (any, error) { + if pass.Pkg.Path() == "bytes" || pass.Pkg.Path() == "bytes_test" { + // The bytes package can use itself however it wants + return nil, nil + } + fn := func(c inspector.Cursor) { + node := c.Node() + m, ok := code.Match(pass, checkBytesBufferConversionsQ, node) + if !ok { + return + } + call := node.(*ast.CallExpr) + sel := m.State["sel"].(*ast.SelectorExpr) + + typ := pass.TypesInfo.TypeOf(call.Fun) + if types.Unalias(typ) == types.Universe.Lookup("string").Type() && code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).Bytes") { + if _, ok := c.Parent().Node().(*ast.IndexExpr); ok { + // Don't flag m[string(buf.Bytes())] – thanks to a + // compiler optimization, this is actually faster than + // m[buf.String()] + return + } + + report.Report(pass, call, fmt.Sprintf("should use %v.String() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)), + report.FilterGenerated(), + report.Fixes(edit.Fix("Simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRs, m.State)))) + } else if typ, ok := types.Unalias(typ).(*types.Slice); ok && + types.Unalias(typ.Elem()) == types.Universe.Lookup("byte").Type() && + code.IsCallTo(pass, call.Args[0], "(*bytes.Buffer).String") { + report.Report(pass, call, fmt.Sprintf("should use %v.Bytes() instead of %v", report.Render(pass, sel.X), report.Render(pass, call)), + report.FilterGenerated(), + report.Fixes(edit.Fix("Simplify conversion", edit.ReplaceWithPattern(pass.Fset, node, checkBytesBufferConversionsRb, m.State)))) + } + + } + for c := range code.Cursor(pass).Preorder((*ast.CallExpr)(nil)) { + fn(c) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1031/s1031.go b/vendor/honnef.co/go/tools/simple/s1031/s1031.go new file mode 100644 index 0000000..4be0171 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1031/s1031.go @@ -0,0 +1,71 @@ +package s1031 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1031", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Omit redundant nil check around loop`, + Text: `You can use range on nil slices and maps, the loop will simply never +execute. This makes an additional nil check around the loop +unnecessary.`, + Before: ` +if s != nil { + for _, x := range s { + ... + } +}`, + After: ` +for _, x := range s { + ... +}`, + Since: "2017.1", + // MergeIfAll because x might be a channel under some build tags. + // you shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkNilCheckAroundRangeQ = pattern.MustParse(` + (IfStmt + nil + (BinaryExpr x@(Object _) "!=" (Builtin "nil")) + [(RangeStmt _ _ _ x _)] + nil)`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkNilCheckAroundRangeQ) { + ok := typeutil.All(m.State["x"].(types.Object).Type(), func(term *types.Term) bool { + switch term.Type().Underlying().(type) { + case *types.Slice, *types.Map: + return true + case *types.TypeParam, *types.Chan, *types.Pointer, *types.Signature: + return false + default: + lint.ExhaustiveTypeSwitch(term.Type().Underlying()) + return false + } + }) + if ok { + report.Report(pass, node, "unnecessary nil check around range", report.ShortRange(), report.FilterGenerated()) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1032/s1032.go b/vendor/honnef.co/go/tools/simple/s1032/s1032.go new file mode 100644 index 0000000..6684db8 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1032/s1032.go @@ -0,0 +1,128 @@ +package s1032 + +import ( + "go/ast" + "go/token" + "sort" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1032", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use \'sort.Ints(x)\', \'sort.Float64s(x)\', and \'sort.Strings(x)\'`, + Text: `The \'sort.Ints\', \'sort.Float64s\' and \'sort.Strings\' functions are easier to +read than \'sort.Sort(sort.IntSlice(x))\', \'sort.Sort(sort.Float64Slice(x))\' +and \'sort.Sort(sort.StringSlice(x))\'.`, + Before: `sort.Sort(sort.StringSlice(x))`, + After: `sort.Strings(x)`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func isPermissibleSort(pass *analysis.Pass, node ast.Node) bool { + call := node.(*ast.CallExpr) + typeconv, ok := call.Args[0].(*ast.CallExpr) + if !ok { + return true + } + + sel, ok := typeconv.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + name := code.SelectorName(pass, sel) + switch name { + case "sort.IntSlice", "sort.Float64Slice", "sort.StringSlice": + default: + return true + } + + return false +} + +func run(pass *analysis.Pass) (any, error) { + type Error struct { + node ast.Node + msg string + } + var allErrors []Error + fn := func(node ast.Node) { + var body *ast.BlockStmt + switch node := node.(type) { + case *ast.FuncLit: + body = node.Body + case *ast.FuncDecl: + body = node.Body + default: + lint.ExhaustiveTypeSwitch(node) + } + if body == nil { + return + } + + var errors []Error + permissible := false + fnSorts := func(node ast.Node) bool { + if permissible { + return false + } + if !code.IsCallTo(pass, node, "sort.Sort") { + return true + } + if isPermissibleSort(pass, node) { + permissible = true + return false + } + call := node.(*ast.CallExpr) + // isPermissibleSort guarantees that this type assertion will succeed + typeconv := call.Args[knowledge.Arg("sort.Sort.data")].(*ast.CallExpr) + sel := typeconv.Fun.(*ast.SelectorExpr) + name := code.SelectorName(pass, sel) + + switch name { + case "sort.IntSlice": + errors = append(errors, Error{node, "should use sort.Ints(...) instead of sort.Sort(sort.IntSlice(...))"}) + case "sort.Float64Slice": + errors = append(errors, Error{node, "should use sort.Float64s(...) instead of sort.Sort(sort.Float64Slice(...))"}) + case "sort.StringSlice": + errors = append(errors, Error{node, "should use sort.Strings(...) instead of sort.Sort(sort.StringSlice(...))"}) + } + return true + } + ast.Inspect(body, fnSorts) + + if permissible { + return + } + allErrors = append(allErrors, errors...) + } + code.Preorder(pass, fn, (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)) + sort.Slice(allErrors, func(i, j int) bool { + return allErrors[i].node.Pos() < allErrors[j].node.Pos() + }) + var prev token.Pos + for _, err := range allErrors { + if err.node.Pos() == prev { + continue + } + prev = err.node.Pos() + report.Report(pass, err.node, err.msg, report.FilterGenerated()) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1033/s1033.go b/vendor/honnef.co/go/tools/simple/s1033/s1033.go new file mode 100644 index 0000000..f6704f4 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1033/s1033.go @@ -0,0 +1,50 @@ +package s1033 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1033", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Unnecessary guard around call to \"delete\"`, + Text: `Calling \'delete\' on a nil map is a no-op.`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkGuardedDeleteQ = pattern.MustParse(` + (IfStmt + (AssignStmt + [(Ident "_") ok@(Ident _)] + ":=" + (IndexExpr m key)) + ok + [call@(CallExpr (Builtin "delete") [m key])] + nil)`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkGuardedDeleteQ) { + report.Report(pass, node, "unnecessary guard around call to delete", + report.ShortRange(), + report.FilterGenerated(), + report.Fixes(edit.Fix("Remove guard", edit.ReplaceWithNode(pass.Fset, node, m.State["call"].(ast.Node))))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1034/s1034.go b/vendor/honnef.co/go/tools/simple/s1034/s1034.go new file mode 100644 index 0000000..9eb9531 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1034/s1034.go @@ -0,0 +1,113 @@ +package s1034 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1034", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Use result of type assertion to simplify cases`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkSimplifyTypeSwitchQ = pattern.MustParse(` + (TypeSwitchStmt + nil + expr@(TypeAssertExpr ident@(Ident _) _) + body)`) + checkSimplifyTypeSwitchR = pattern.MustParse(`(AssignStmt ident ":=" expr)`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkSimplifyTypeSwitchQ) { + stmt := node.(*ast.TypeSwitchStmt) + expr := m.State["expr"].(ast.Node) + ident := m.State["ident"].(*ast.Ident) + + x := pass.TypesInfo.ObjectOf(ident) + var allOffenders []*ast.TypeAssertExpr + canSuggestFix := true + for _, clause := range stmt.Body.List { + clause := clause.(*ast.CaseClause) + if len(clause.List) != 1 { + continue + } + hasUnrelatedAssertion := false + var offenders []*ast.TypeAssertExpr + ast.Inspect(clause, func(node ast.Node) bool { + assert2, ok := node.(*ast.TypeAssertExpr) + if !ok { + return true + } + ident, ok := assert2.X.(*ast.Ident) + if !ok { + hasUnrelatedAssertion = true + return false + } + if pass.TypesInfo.ObjectOf(ident) != x { + hasUnrelatedAssertion = true + return false + } + + if !types.Identical(pass.TypesInfo.TypeOf(clause.List[0]), pass.TypesInfo.TypeOf(assert2.Type)) { + hasUnrelatedAssertion = true + return false + } + offenders = append(offenders, assert2) + return true + }) + if !hasUnrelatedAssertion { + // don't flag cases that have other type assertions + // unrelated to the one in the case clause. often + // times, this is done for symmetry, when two + // different values have to be asserted to the same + // type. + allOffenders = append(allOffenders, offenders...) + } + canSuggestFix = canSuggestFix && !hasUnrelatedAssertion + } + if len(allOffenders) != 0 { + var opts []report.Option + for _, offender := range allOffenders { + opts = append(opts, report.Related(offender, "could eliminate this type assertion")) + } + opts = append(opts, report.FilterGenerated()) + + msg := fmt.Sprintf("assigning the result of this type assertion to a variable (switch %s := %s.(type)) could eliminate type assertions in switch cases", + report.Render(pass, ident), report.Render(pass, ident)) + if canSuggestFix { + var edits []analysis.TextEdit + edits = append(edits, edit.ReplaceWithPattern(pass.Fset, expr, checkSimplifyTypeSwitchR, m.State)) + for _, offender := range allOffenders { + edits = append(edits, edit.ReplaceWithNode(pass.Fset, offender, offender.X)) + } + opts = append(opts, report.Fixes(edit.Fix("Simplify type switch", edits...))) + report.Report(pass, expr, msg, opts...) + } else { + report.Report(pass, expr, msg, opts...) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1035/s1035.go b/vendor/honnef.co/go/tools/simple/s1035/s1035.go new file mode 100644 index 0000000..969502a --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1035/s1035.go @@ -0,0 +1,54 @@ +package s1035 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1035", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Redundant call to \'net/http.CanonicalHeaderKey\' in method call on \'net/http.Header\'`, + Text: ` +The methods on \'net/http.Header\', namely \'Add\', \'Del\', \'Get\' +and \'Set\', already canonicalize the given header name.`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(` + (CallExpr + (Symbol + callName@(Or + "(net/http.Header).Add" + "(net/http.Header).Del" + "(net/http.Header).Get" + "(net/http.Header).Set")) + arg@(CallExpr (Symbol "net/http.CanonicalHeaderKey") _):_)`) + +func run(pass *analysis.Pass) (any, error) { + for _, m := range code.Matches(pass, query) { + arg := m.State["arg"].(*ast.CallExpr) + report.Report(pass, m.State["arg"].(ast.Expr), + fmt.Sprintf("calling net/http.CanonicalHeaderKey on the 'key' argument of %s is redundant", m.State["callName"].(string)), + report.FilterGenerated(), + report.Fixes(edit.Fix("Remove call to CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, arg, arg.Args[0])))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1036/s1036.go b/vendor/honnef.co/go/tools/simple/s1036/s1036.go new file mode 100644 index 0000000..2d07874 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1036/s1036.go @@ -0,0 +1,88 @@ +package s1036 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1036", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Unnecessary guard around map access`, + + Text: ` +When accessing a map key that doesn't exist yet, one receives a zero +value. Often, the zero value is a suitable value, for example when +using append or doing integer math. + +The following + + if _, ok := m["foo"]; ok { + m["foo"] = append(m["foo"], "bar") + } else { + m["foo"] = []string{"bar"} + } + +can be simplified to + + m["foo"] = append(m["foo"], "bar") + +and + + if _, ok := m2["k"]; ok { + m2["k"] += 4 + } else { + m2["k"] = 4 + } + +can be simplified to + + m["k"] += 4 +`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkUnnecessaryGuardQ = pattern.MustParse(` + (Or + (IfStmt + (AssignStmt [(Ident "_") ok@(Ident _)] ":=" indexexpr@(IndexExpr _ _)) + ok + set@(AssignStmt indexexpr "=" (CallExpr (Builtin "append") indexexpr:values)) + (AssignStmt indexexpr "=" (CompositeLit _ values))) + (IfStmt + (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _)) + ok + set@(AssignStmt indexexpr "+=" value) + (AssignStmt indexexpr "=" value)) + (IfStmt + (AssignStmt [(Ident "_") ok] ":=" indexexpr@(IndexExpr _ _)) + ok + set@(IncDecStmt indexexpr "++") + (AssignStmt indexexpr "=" (IntegerLiteral "1"))))`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkUnnecessaryGuardQ) { + if code.MayHaveSideEffects(pass, m.State["indexexpr"].(ast.Expr), nil) { + continue + } + report.Report(pass, node, "unnecessary guard around map access", + report.ShortRange(), + report.Fixes(edit.Fix("Simplify map access", edit.ReplaceWithNode(pass.Fset, node, m.State["set"].(ast.Node))))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1037/s1037.go b/vendor/honnef.co/go/tools/simple/s1037/s1037.go new file mode 100644 index 0000000..a067c1a --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1037/s1037.go @@ -0,0 +1,55 @@ +package s1037 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1037", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Elaborate way of sleeping`, + Text: `Using a select statement with a single case receiving +from the result of \'time.After\' is a very elaborate way of sleeping that +can much simpler be expressed with a simple call to time.Sleep.`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkElaborateSleepQ = pattern.MustParse(`(SelectStmt (CommClause (UnaryExpr "<-" (CallExpr (Symbol "time.After") [arg])) body))`) + checkElaborateSleepR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "time") (Ident "Sleep")) [arg])`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkElaborateSleepQ) { + if body, ok := m.State["body"].([]ast.Stmt); ok && len(body) == 0 { + report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping", + report.ShortRange(), + report.FilterGenerated(), + report.Fixes(edit.Fix("Use time.Sleep", edit.ReplaceWithPattern(pass.Fset, node, checkElaborateSleepR, m.State)))) + } else { + // TODO(dh): we could make a suggested fix if the body + // doesn't declare or shadow any identifiers + report.Report(pass, node, "should use time.Sleep instead of elaborate way of sleeping", + report.ShortRange(), + report.FilterGenerated()) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1038/s1038.go b/vendor/honnef.co/go/tools/simple/s1038/s1038.go new file mode 100644 index 0000000..7987a56 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1038/s1038.go @@ -0,0 +1,190 @@ +package s1038 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1038", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: "Unnecessarily complex way of printing formatted string", + Text: `Instead of using \'fmt.Print(fmt.Sprintf(...))\', one can use \'fmt.Printf(...)\'.`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkPrintSprintQ = pattern.MustParse(` + (Or + (CallExpr + fn@(Or + (Symbol "fmt.Print") + (Symbol "fmt.Sprint") + (Symbol "fmt.Println") + (Symbol "fmt.Sprintln")) + [(CallExpr (Symbol "fmt.Sprintf") f:_)]) + (CallExpr + fn@(Or + (Symbol "fmt.Fprint") + (Symbol "fmt.Fprintln")) + [_ (CallExpr (Symbol "fmt.Sprintf") f:_)]))`) + + checkTestingErrorSprintfQ = pattern.MustParse(` + (CallExpr + sel@(SelectorExpr + recv + (Ident + name@(Or + "Error" + "Fatal" + "Fatalln" + "Log" + "Panic" + "Panicln" + "Print" + "Println" + "Skip"))) + [(CallExpr (Symbol "fmt.Sprintf") args)])`) + + checkLogSprintfQ = pattern.MustParse(` + (CallExpr + (Symbol + (Or + "log.Fatal" + "log.Fatalln" + "log.Panic" + "log.Panicln" + "log.Print" + "log.Println")) + [(CallExpr (Symbol "fmt.Sprintf") args)])`) + + checkSprintfMapping = map[string]struct { + recv string + alternative string + }{ + "(*testing.common).Error": {"(*testing.common)", "Errorf"}, + "(testing.TB).Error": {"(testing.TB)", "Errorf"}, + "(*testing.common).Fatal": {"(*testing.common)", "Fatalf"}, + "(testing.TB).Fatal": {"(testing.TB)", "Fatalf"}, + "(*testing.common).Log": {"(*testing.common)", "Logf"}, + "(testing.TB).Log": {"(testing.TB)", "Logf"}, + "(*testing.common).Skip": {"(*testing.common)", "Skipf"}, + "(testing.TB).Skip": {"(testing.TB)", "Skipf"}, + "(*log.Logger).Fatal": {"(*log.Logger)", "Fatalf"}, + "(*log.Logger).Fatalln": {"(*log.Logger)", "Fatalf"}, + "(*log.Logger).Panic": {"(*log.Logger)", "Panicf"}, + "(*log.Logger).Panicln": {"(*log.Logger)", "Panicf"}, + "(*log.Logger).Print": {"(*log.Logger)", "Printf"}, + "(*log.Logger).Println": {"(*log.Logger)", "Printf"}, + "log.Fatal": {"", "log.Fatalf"}, + "log.Fatalln": {"", "log.Fatalf"}, + "log.Panic": {"", "log.Panicf"}, + "log.Panicln": {"", "log.Panicf"}, + "log.Print": {"", "log.Printf"}, + "log.Println": {"", "log.Printf"}, + } +) + +func run(pass *analysis.Pass) (any, error) { + fmtPrintf := func(node ast.Node) { + m, ok := code.Match(pass, checkPrintSprintQ, node) + if !ok { + return + } + + name := m.State["fn"].(*types.Func).Name() + var msg string + switch name { + case "Print", "Fprint", "Sprint": + newname := name + "f" + msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...))", newname, name) + case "Println", "Fprintln", "Sprintln": + if _, ok := m.State["f"].(*ast.BasicLit); !ok { + // This may be an instance of + // fmt.Println(fmt.Sprintf(arg, ...)) where arg is an + // externally provided format string and the caller + // cannot guarantee that the format string ends with a + // newline. + return + } + newname := name[:len(name)-2] + "f" + msg = fmt.Sprintf("should use fmt.%s instead of fmt.%s(fmt.Sprintf(...)) (but don't forget the newline)", newname, name) + } + report.Report(pass, node, msg, + report.FilterGenerated()) + } + + methSprintf := func(node ast.Node) { + m, ok := code.Match(pass, checkTestingErrorSprintfQ, node) + if !ok { + return + } + mapped, ok := checkSprintfMapping[code.CallName(pass, node.(*ast.CallExpr))] + if !ok { + return + } + + // Ensure that Errorf/Fatalf refer to the right method + recvTV, ok := pass.TypesInfo.Types[m.State["recv"].(ast.Expr)] + if !ok { + return + } + obj, _, _ := types.LookupFieldOrMethod(recvTV.Type, recvTV.Addressable(), nil, mapped.alternative) + f, ok := obj.(*types.Func) + if !ok { + return + } + if typeutil.FuncName(f) != mapped.recv+"."+mapped.alternative { + return + } + + alt := &ast.SelectorExpr{ + X: m.State["recv"].(ast.Expr), + Sel: &ast.Ident{Name: mapped.alternative}, + } + report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", report.Render(pass, alt), report.Render(pass, m.State["sel"].(*ast.SelectorExpr)))) + } + + pkgSprintf := func(node ast.Node) { + _, ok := code.Match(pass, checkLogSprintfQ, node) + if !ok { + return + } + callName := code.CallName(pass, node.(*ast.CallExpr)) + mapped, ok := checkSprintfMapping[callName] + if !ok { + return + } + report.Report(pass, node, fmt.Sprintf("should use %s(...) instead of %s(fmt.Sprintf(...))", mapped.alternative, callName)) + } + + fn := func(node ast.Node) { + fmtPrintf(node) + // TODO(dh): add suggested fixes + methSprintf(node) + pkgSprintf(node) + } + if !code.CouldMatchAny(pass, checkLogSprintfQ, checkPrintSprintQ, checkTestingErrorSprintfQ) { + return nil, nil + } + code.Preorder(pass, fn, (*ast.CallExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1039/s1039.go b/vendor/honnef.co/go/tools/simple/s1039/s1039.go new file mode 100644 index 0000000..8167046 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1039/s1039.go @@ -0,0 +1,66 @@ +package s1039 + +import ( + "fmt" + "go/ast" + "go/types" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1039", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Unnecessary use of \'fmt.Sprint\'`, + Text: ` +Calling \'fmt.Sprint\' with a single string argument is unnecessary +and identical to using the string directly.`, + Since: "2020.1", + // MergeIfAll because s might not be a string under all build tags. + // you shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkSprintLiteralQ = pattern.MustParse(` + (CallExpr + fn@(Or + (Symbol "fmt.Sprint") + (Symbol "fmt.Sprintf")) + [lit@(BasicLit "STRING" _)])`) + +func run(pass *analysis.Pass) (any, error) { + // We only flag calls with string literals, not expressions of + // type string, because some people use fmt.Sprint(s) as a pattern + // for copying strings, which may be useful when extracting a small + // substring from a large string. + + for node, m := range code.Matches(pass, checkSprintLiteralQ) { + callee := m.State["fn"].(*types.Func) + lit := m.State["lit"].(*ast.BasicLit) + if callee.Name() == "Sprintf" { + if strings.ContainsRune(lit.Value, '%') { + // This might be a format string + continue + } + } + report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()), + report.FilterGenerated(), + report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/simple/s1040/s1040.go b/vendor/honnef.co/go/tools/simple/s1040/s1040.go new file mode 100644 index 0000000..62cc8c7 --- /dev/null +++ b/vendor/honnef.co/go/tools/simple/s1040/s1040.go @@ -0,0 +1,73 @@ +package s1040 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "S1040", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Type assertion to current type", + Text: `The type assertion \'x.(SomeInterface)\', when \'x\' already has type +\'SomeInterface\', can only fail if \'x\' is nil. Usually, this is +left-over code from when \'x\' had a different type and you can safely +delete the type assertion. If you want to check that \'x\' is not nil, +consider being explicit and using an actual \'if x == nil\' comparison +instead of relying on the type assertion panicking.`, + Since: "2021.1", + // MergeIfAll because x might have different types under different build tags. + // You shouldn't write code like that… + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + expr := node.(*ast.TypeAssertExpr) + if expr.Type == nil { + // skip type switches + // + // TODO(dh): we could flag type switches, too, when a case + // statement has the same type as expr.X – however, + // depending on the location of that case, it might behave + // identically to a default branch. we need to think + // carefully about the instances we want to flag. We also + // have to take nil interface values into consideration. + // + // It might make more sense to extend SA4020 to handle + // this. + return + } + t1 := pass.TypesInfo.TypeOf(expr.Type) + t2 := pass.TypesInfo.TypeOf(expr.X) + if types.IsInterface(t1) && types.Identical(t1, t2) { + report.Report(pass, expr, + fmt.Sprintf("type assertion to the same type: %s already has type %s", report.Render(pass, expr.X), report.Render(pass, expr.Type)), + report.FilterGenerated()) + } + } + + // TODO(dh): add suggested fixes. we need different fixes depending on the context: + // - assignment with 1 or 2 lhs + // - assignment to blank identifiers (as the first, second or both lhs) + // - initializers in if statements, with the same variations as above + + code.Preorder(pass, fn, (*ast.TypeAssertExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/analysis.go b/vendor/honnef.co/go/tools/staticcheck/analysis.go new file mode 100644 index 0000000..81ab81c --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/analysis.go @@ -0,0 +1,200 @@ +// Code generated by generate.go. DO NOT EDIT. + +package staticcheck + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/staticcheck/sa1000" + "honnef.co/go/tools/staticcheck/sa1001" + "honnef.co/go/tools/staticcheck/sa1002" + "honnef.co/go/tools/staticcheck/sa1003" + "honnef.co/go/tools/staticcheck/sa1004" + "honnef.co/go/tools/staticcheck/sa1005" + "honnef.co/go/tools/staticcheck/sa1006" + "honnef.co/go/tools/staticcheck/sa1007" + "honnef.co/go/tools/staticcheck/sa1008" + "honnef.co/go/tools/staticcheck/sa1010" + "honnef.co/go/tools/staticcheck/sa1011" + "honnef.co/go/tools/staticcheck/sa1012" + "honnef.co/go/tools/staticcheck/sa1013" + "honnef.co/go/tools/staticcheck/sa1014" + "honnef.co/go/tools/staticcheck/sa1015" + "honnef.co/go/tools/staticcheck/sa1016" + "honnef.co/go/tools/staticcheck/sa1017" + "honnef.co/go/tools/staticcheck/sa1018" + "honnef.co/go/tools/staticcheck/sa1019" + "honnef.co/go/tools/staticcheck/sa1020" + "honnef.co/go/tools/staticcheck/sa1021" + "honnef.co/go/tools/staticcheck/sa1023" + "honnef.co/go/tools/staticcheck/sa1024" + "honnef.co/go/tools/staticcheck/sa1025" + "honnef.co/go/tools/staticcheck/sa1026" + "honnef.co/go/tools/staticcheck/sa1027" + "honnef.co/go/tools/staticcheck/sa1028" + "honnef.co/go/tools/staticcheck/sa1029" + "honnef.co/go/tools/staticcheck/sa1030" + "honnef.co/go/tools/staticcheck/sa1031" + "honnef.co/go/tools/staticcheck/sa1032" + "honnef.co/go/tools/staticcheck/sa2000" + "honnef.co/go/tools/staticcheck/sa2001" + "honnef.co/go/tools/staticcheck/sa2002" + "honnef.co/go/tools/staticcheck/sa2003" + "honnef.co/go/tools/staticcheck/sa3000" + "honnef.co/go/tools/staticcheck/sa3001" + "honnef.co/go/tools/staticcheck/sa4000" + "honnef.co/go/tools/staticcheck/sa4001" + "honnef.co/go/tools/staticcheck/sa4003" + "honnef.co/go/tools/staticcheck/sa4004" + "honnef.co/go/tools/staticcheck/sa4005" + "honnef.co/go/tools/staticcheck/sa4006" + "honnef.co/go/tools/staticcheck/sa4008" + "honnef.co/go/tools/staticcheck/sa4009" + "honnef.co/go/tools/staticcheck/sa4010" + "honnef.co/go/tools/staticcheck/sa4011" + "honnef.co/go/tools/staticcheck/sa4012" + "honnef.co/go/tools/staticcheck/sa4013" + "honnef.co/go/tools/staticcheck/sa4014" + "honnef.co/go/tools/staticcheck/sa4015" + "honnef.co/go/tools/staticcheck/sa4016" + "honnef.co/go/tools/staticcheck/sa4017" + "honnef.co/go/tools/staticcheck/sa4018" + "honnef.co/go/tools/staticcheck/sa4019" + "honnef.co/go/tools/staticcheck/sa4020" + "honnef.co/go/tools/staticcheck/sa4021" + "honnef.co/go/tools/staticcheck/sa4022" + "honnef.co/go/tools/staticcheck/sa4023" + "honnef.co/go/tools/staticcheck/sa4024" + "honnef.co/go/tools/staticcheck/sa4025" + "honnef.co/go/tools/staticcheck/sa4026" + "honnef.co/go/tools/staticcheck/sa4027" + "honnef.co/go/tools/staticcheck/sa4028" + "honnef.co/go/tools/staticcheck/sa4029" + "honnef.co/go/tools/staticcheck/sa4030" + "honnef.co/go/tools/staticcheck/sa4031" + "honnef.co/go/tools/staticcheck/sa4032" + "honnef.co/go/tools/staticcheck/sa5000" + "honnef.co/go/tools/staticcheck/sa5001" + "honnef.co/go/tools/staticcheck/sa5002" + "honnef.co/go/tools/staticcheck/sa5003" + "honnef.co/go/tools/staticcheck/sa5004" + "honnef.co/go/tools/staticcheck/sa5005" + "honnef.co/go/tools/staticcheck/sa5007" + "honnef.co/go/tools/staticcheck/sa5008" + "honnef.co/go/tools/staticcheck/sa5009" + "honnef.co/go/tools/staticcheck/sa5010" + "honnef.co/go/tools/staticcheck/sa5012" + "honnef.co/go/tools/staticcheck/sa6000" + "honnef.co/go/tools/staticcheck/sa6001" + "honnef.co/go/tools/staticcheck/sa6002" + "honnef.co/go/tools/staticcheck/sa6003" + "honnef.co/go/tools/staticcheck/sa6005" + "honnef.co/go/tools/staticcheck/sa6006" + "honnef.co/go/tools/staticcheck/sa9001" + "honnef.co/go/tools/staticcheck/sa9002" + "honnef.co/go/tools/staticcheck/sa9003" + "honnef.co/go/tools/staticcheck/sa9004" + "honnef.co/go/tools/staticcheck/sa9005" + "honnef.co/go/tools/staticcheck/sa9006" + "honnef.co/go/tools/staticcheck/sa9007" + "honnef.co/go/tools/staticcheck/sa9008" + "honnef.co/go/tools/staticcheck/sa9009" + "honnef.co/go/tools/staticcheck/sa9010" +) + +var Analyzers = []*lint.Analyzer{ + sa1000.SCAnalyzer, + sa1001.SCAnalyzer, + sa1002.SCAnalyzer, + sa1003.SCAnalyzer, + sa1004.SCAnalyzer, + sa1005.SCAnalyzer, + sa1006.SCAnalyzer, + sa1007.SCAnalyzer, + sa1008.SCAnalyzer, + sa1010.SCAnalyzer, + sa1011.SCAnalyzer, + sa1012.SCAnalyzer, + sa1013.SCAnalyzer, + sa1014.SCAnalyzer, + sa1015.SCAnalyzer, + sa1016.SCAnalyzer, + sa1017.SCAnalyzer, + sa1018.SCAnalyzer, + sa1019.SCAnalyzer, + sa1020.SCAnalyzer, + sa1021.SCAnalyzer, + sa1023.SCAnalyzer, + sa1024.SCAnalyzer, + sa1025.SCAnalyzer, + sa1026.SCAnalyzer, + sa1027.SCAnalyzer, + sa1028.SCAnalyzer, + sa1029.SCAnalyzer, + sa1030.SCAnalyzer, + sa1031.SCAnalyzer, + sa1032.SCAnalyzer, + sa2000.SCAnalyzer, + sa2001.SCAnalyzer, + sa2002.SCAnalyzer, + sa2003.SCAnalyzer, + sa3000.SCAnalyzer, + sa3001.SCAnalyzer, + sa4000.SCAnalyzer, + sa4001.SCAnalyzer, + sa4003.SCAnalyzer, + sa4004.SCAnalyzer, + sa4005.SCAnalyzer, + sa4006.SCAnalyzer, + sa4008.SCAnalyzer, + sa4009.SCAnalyzer, + sa4010.SCAnalyzer, + sa4011.SCAnalyzer, + sa4012.SCAnalyzer, + sa4013.SCAnalyzer, + sa4014.SCAnalyzer, + sa4015.SCAnalyzer, + sa4016.SCAnalyzer, + sa4017.SCAnalyzer, + sa4018.SCAnalyzer, + sa4019.SCAnalyzer, + sa4020.SCAnalyzer, + sa4021.SCAnalyzer, + sa4022.SCAnalyzer, + sa4023.SCAnalyzer, + sa4024.SCAnalyzer, + sa4025.SCAnalyzer, + sa4026.SCAnalyzer, + sa4027.SCAnalyzer, + sa4028.SCAnalyzer, + sa4029.SCAnalyzer, + sa4030.SCAnalyzer, + sa4031.SCAnalyzer, + sa4032.SCAnalyzer, + sa5000.SCAnalyzer, + sa5001.SCAnalyzer, + sa5002.SCAnalyzer, + sa5003.SCAnalyzer, + sa5004.SCAnalyzer, + sa5005.SCAnalyzer, + sa5007.SCAnalyzer, + sa5008.SCAnalyzer, + sa5009.SCAnalyzer, + sa5010.SCAnalyzer, + sa5012.SCAnalyzer, + sa6000.SCAnalyzer, + sa6001.SCAnalyzer, + sa6002.SCAnalyzer, + sa6003.SCAnalyzer, + sa6005.SCAnalyzer, + sa6006.SCAnalyzer, + sa9001.SCAnalyzer, + sa9002.SCAnalyzer, + sa9003.SCAnalyzer, + sa9004.SCAnalyzer, + sa9005.SCAnalyzer, + sa9006.SCAnalyzer, + sa9007.SCAnalyzer, + sa9008.SCAnalyzer, + sa9009.SCAnalyzer, + sa9010.SCAnalyzer, +} diff --git a/vendor/honnef.co/go/tools/staticcheck/doc.go b/vendor/honnef.co/go/tools/staticcheck/doc.go new file mode 100644 index 0000000..462acee --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/doc.go @@ -0,0 +1,5 @@ +//go:generate go run ../generate.go + +// Package staticcheck contains analyzes that find bugs and performance issues. +// Barring the rare false positive, any code flagged by these analyzes needs to be fixed. +package staticcheck diff --git a/vendor/honnef.co/go/tools/staticcheck/fakejson/encode.go b/vendor/honnef.co/go/tools/staticcheck/fakejson/encode.go new file mode 100644 index 0000000..13e3182 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/fakejson/encode.go @@ -0,0 +1,370 @@ +// Copyright 2010 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. + +// This file contains a modified copy of the encoding/json encoder. +// All dynamic behavior has been removed, and reflecttion has been replaced with go/types. +// This allows us to statically find unmarshable types +// with the same rules for tags, shadowing and addressability as encoding/json. +// This is used for SA1026. + +package fakejson + +import ( + "go/types" + "sort" + "strings" + "unicode" + + "golang.org/x/exp/typeparams" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/staticcheck/fakereflect" +) + +// parseTag splits a struct field's json tag into its name and +// comma-separated options. +func parseTag(tag string) string { + if before, _, ok := strings.Cut(tag, ","); ok { + return before + } + return tag +} + +func Marshal(v types.Type) *UnsupportedTypeError { + enc := encoder{} + return enc.newTypeEncoder(fakereflect.TypeAndCanAddr{Type: v}, "x") +} + +// An UnsupportedTypeError is returned by Marshal when attempting +// to encode an unsupported value type. +type UnsupportedTypeError struct { + Type types.Type + Path string +} + +type encoder struct { + // TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know + // if this is actually required for correctness. + seenCanAddr typeutil.Map[struct{}] + seenCantAddr typeutil.Map[struct{}] +} + +func (enc *encoder) newTypeEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError { + var m *typeutil.Map[struct{}] + if t.CanAddr() { + m = &enc.seenCanAddr + } else { + m = &enc.seenCantAddr + } + if _, ok := m.At(t.Type); ok { + return nil + } + m.Set(t.Type, struct{}{}) + + if t.Implements(knowledge.Interfaces["encoding/json.Marshaler"]) { + return nil + } + if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding/json.Marshaler"]) { + return nil + } + if t.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + if !t.IsPtr() && t.CanAddr() && fakereflect.PtrTo(t).Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + + switch t.Type.Underlying().(type) { + case *types.Basic, *types.Interface: + return nil + case *types.Struct: + return enc.typeFields(t, stack) + case *types.Map: + return enc.newMapEncoder(t, stack) + case *types.Slice: + return enc.newSliceEncoder(t, stack) + case *types.Array: + return enc.newArrayEncoder(t, stack) + case *types.Pointer: + // we don't have to express the pointer dereference in the path; x.f is syntactic sugar for (*x).f + return enc.newTypeEncoder(t.Elem(), stack) + default: + return &UnsupportedTypeError{t.Type, stack} + } +} + +func (enc *encoder) newMapEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError { + if typeparams.IsTypeParam(t.Key().Type) { + // We don't know enough about the concrete instantiation to say much about the key. The only time we could make + // a definite "this key is bad" statement is if the type parameter is constrained by type terms, none of which + // are tilde terms, none of which are a basic type. In all other cases, the key might implement TextMarshaler. + // It doesn't seem worth checking for that one single case. + return enc.newTypeEncoder(t.Elem(), stack+"[k]") + } + + switch t.Key().Type.Underlying().(type) { + case *types.Basic: + default: + if !t.Key().Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return &UnsupportedTypeError{ + Type: t.Type, + Path: stack, + } + } + } + return enc.newTypeEncoder(t.Elem(), stack+"[k]") +} + +func (enc *encoder) newSliceEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError { + // Byte slices get special treatment; arrays don't. + basic, ok := t.Elem().Type.Underlying().(*types.Basic) + if ok && basic.Kind() == types.Uint8 { + p := fakereflect.PtrTo(t.Elem()) + if !p.Implements(knowledge.Interfaces["encoding/json.Marshaler"]) && !p.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + } + return enc.newArrayEncoder(t, stack) +} + +func (enc *encoder) newArrayEncoder(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError { + return enc.newTypeEncoder(t.Elem(), stack+"[0]") +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + case !unicode.IsLetter(c) && !unicode.IsDigit(c): + return false + } + } + return true +} + +func typeByIndex(t fakereflect.TypeAndCanAddr, index []int) fakereflect.TypeAndCanAddr { + for _, i := range index { + if t.IsPtr() { + t = t.Elem() + } + t = t.Field(i).Type + } + return t +} + +func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string { + var path strings.Builder + for _, i := range index { + if t.IsPtr() { + t = t.Elem() + } + path.WriteString("." + t.Field(i).Name) + t = t.Field(i).Type + } + return path.String() +} + +// A field represents a single field found in a struct. +type field struct { + name string + + tag bool + index []int + typ fakereflect.TypeAndCanAddr +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that JSON should recognize for the given type. +// The algorithm is breadth-first search over the set of structs to include - the top struct +// and then any reachable anonymous structs. +func (enc *encoder) typeFields(t fakereflect.TypeAndCanAddr, stack string) *UnsupportedTypeError { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + var count, nextCount map[fakereflect.TypeAndCanAddr]int + + // Types already visited at an earlier level. + visited := map[fakereflect.TypeAndCanAddr]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[fakereflect.TypeAndCanAddr]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.Anonymous { + t := sf.Type + if t.IsPtr() { + t = t.Elem() + } + if !sf.IsExported() && !t.IsStruct() { + // Ignore embedded fields of unexported non-struct types. + continue + } + // Do not ignore embedded fields of unexported struct types + // since they may have exported fields. + } else if !sf.IsExported() { + // Ignore unexported non-embedded fields. + continue + } + tag := sf.Tag.Get("json") + if tag == "-" { + continue + } + name := parseTag(tag) + if !isValidTag(name) { + name = "" + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.IsPtr() { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || !ft.IsStruct() { + tagged := name != "" + if name == "" { + name = sf.Name + } + field := field{ + name: name, + tag: tagged, + index: index, + typ: ft, + } + + fields = append(fields, field) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + next = append(next, field{name: ft.Name(), index: index, typ: ft}) + } + } + } + } + + sort.Slice(fields, func(i, j int) bool { + x := fields + // sort field by name, breaking ties with depth, then + // breaking ties with "name came from json tag", then + // breaking ties with index sequence. + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) + }) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with JSON tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + for i := range fields { + f := &fields[i] + err := enc.newTypeEncoder(typeByIndex(t, f.index), stack+pathByIndex(t, f.index)) + if err != nil { + return err + } + } + return nil +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// JSON tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order, then by presence of tag. + // That means that the first field is the dominant one. We need only check + // for error cases: two fields at top level, either both tagged or neither tagged. + if len(fields) > 1 && len(fields[0].index) == len(fields[1].index) && fields[0].tag == fields[1].tag { + return field{}, false + } + return fields[0], true +} diff --git a/vendor/honnef.co/go/tools/staticcheck/fakereflect/fakereflect.go b/vendor/honnef.co/go/tools/staticcheck/fakereflect/fakereflect.go new file mode 100644 index 0000000..5fdf15b --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/fakereflect/fakereflect.go @@ -0,0 +1,131 @@ +package fakereflect + +import ( + "fmt" + "go/types" + "reflect" +) + +type TypeAndCanAddr struct { + Type types.Type + canAddr bool +} + +type StructField struct { + Index []int + Name string + Anonymous bool + Tag reflect.StructTag + f *types.Var + Type TypeAndCanAddr +} + +func (sf StructField) IsExported() bool { return sf.f.Exported() } + +func (t TypeAndCanAddr) Field(i int) StructField { + st := t.Type.Underlying().(*types.Struct) + f := st.Field(i) + return StructField{ + f: f, + Index: []int{i}, + Name: f.Name(), + Anonymous: f.Anonymous(), + Tag: reflect.StructTag(st.Tag(i)), + Type: TypeAndCanAddr{ + Type: f.Type(), + canAddr: t.canAddr, + }, + } +} + +func (t TypeAndCanAddr) FieldByIndex(index []int) StructField { + f := t.Field(index[0]) + for _, idx := range index[1:] { + f = f.Type.Field(idx) + } + f.Index = index + return f +} + +func PtrTo(t TypeAndCanAddr) TypeAndCanAddr { + // Note that we don't care about canAddr here because it's irrelevant to all uses of PtrTo + return TypeAndCanAddr{Type: types.NewPointer(t.Type)} +} + +func (t TypeAndCanAddr) CanAddr() bool { return t.canAddr } + +func (t TypeAndCanAddr) Implements(ityp *types.Interface) bool { + return types.Implements(t.Type, ityp) +} + +func (t TypeAndCanAddr) IsSlice() bool { + _, ok := t.Type.Underlying().(*types.Slice) + return ok +} + +func (t TypeAndCanAddr) IsArray() bool { + _, ok := t.Type.Underlying().(*types.Array) + return ok +} + +func (t TypeAndCanAddr) IsPtr() bool { + _, ok := t.Type.Underlying().(*types.Pointer) + return ok +} + +func (t TypeAndCanAddr) IsInterface() bool { + _, ok := t.Type.Underlying().(*types.Interface) + return ok +} + +func (t TypeAndCanAddr) IsStruct() bool { + _, ok := t.Type.Underlying().(*types.Struct) + return ok +} + +func (t TypeAndCanAddr) Name() string { + named, ok := types.Unalias(t.Type).(*types.Named) + if !ok { + return "" + } + return named.Obj().Name() +} + +func (t TypeAndCanAddr) NumField() int { + return t.Type.Underlying().(*types.Struct).NumFields() +} + +func (t TypeAndCanAddr) String() string { + return t.Type.String() +} + +func (t TypeAndCanAddr) Key() TypeAndCanAddr { + return TypeAndCanAddr{Type: t.Type.Underlying().(*types.Map).Key()} +} + +func (t TypeAndCanAddr) Elem() TypeAndCanAddr { + switch typ := t.Type.Underlying().(type) { + case *types.Pointer: + return TypeAndCanAddr{ + Type: typ.Elem(), + canAddr: true, + } + case *types.Slice: + return TypeAndCanAddr{ + Type: typ.Elem(), + canAddr: true, + } + case *types.Array: + return TypeAndCanAddr{ + Type: typ.Elem(), + canAddr: t.canAddr, + } + case *types.Map: + return TypeAndCanAddr{ + Type: typ.Elem(), + canAddr: false, + } + default: + panic(fmt.Sprintf("unhandled type %T", typ)) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/fakexml/marshal.go b/vendor/honnef.co/go/tools/staticcheck/fakexml/marshal.go new file mode 100644 index 0000000..9c71e86 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/fakexml/marshal.go @@ -0,0 +1,376 @@ +// Copyright 2011 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. + +// This file contains a modified copy of the encoding/xml encoder. +// All dynamic behavior has been removed, and reflecttion has been replaced with go/types. +// This allows us to statically find unmarshable types +// with the same rules for tags, shadowing and addressability as encoding/xml. +// This is used for SA1026 and SA5008. + +// NOTE(dh): we do not check CanInterface in various places, which means we'll accept more marshaler implementations than encoding/xml does. This will lead to a small amount of false negatives. + +package fakexml + +import ( + "fmt" + "go/types" + "strings" + + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/staticcheck/fakereflect" +) + +func Marshal(v types.Type) error { + return NewEncoder().Encode(v) +} + +type Encoder struct { + // TODO we track addressable and non-addressable instances separately out of an abundance of caution. We don't know + // if this is actually required for correctness. + seenCanAddr typeutil.Map[struct{}] + seenCantAddr typeutil.Map[struct{}] +} + +func NewEncoder() *Encoder { + e := &Encoder{} + return e +} + +func (enc *Encoder) Encode(v types.Type) error { + rv := fakereflect.TypeAndCanAddr{Type: v} + return enc.marshalValue(rv, nil, nil, "x") +} + +func implementsMarshaler(v fakereflect.TypeAndCanAddr) bool { + t := v.Type + obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXML") + if obj == nil { + return false + } + fn, ok := obj.(*types.Func) + if !ok { + return false + } + params := fn.Type().(*types.Signature).Params() + if params.Len() != 2 { + return false + } + if !typeutil.IsPointerToTypeWithName(params.At(0).Type(), "encoding/xml.Encoder") { + return false + } + if !typeutil.IsTypeWithName(params.At(1).Type(), "encoding/xml.StartElement") { + return false + } + rets := fn.Type().(*types.Signature).Results() + if rets.Len() != 1 { + return false + } + if !typeutil.IsTypeWithName(rets.At(0).Type(), "error") { + return false + } + return true +} + +func implementsMarshalerAttr(v fakereflect.TypeAndCanAddr) bool { + t := v.Type + obj, _, _ := types.LookupFieldOrMethod(t, false, nil, "MarshalXMLAttr") + if obj == nil { + return false + } + fn, ok := obj.(*types.Func) + if !ok { + return false + } + params := fn.Type().(*types.Signature).Params() + if params.Len() != 1 { + return false + } + if !typeutil.IsTypeWithName(params.At(0).Type(), "encoding/xml.Name") { + return false + } + rets := fn.Type().(*types.Signature).Results() + if rets.Len() != 2 { + return false + } + if !typeutil.IsTypeWithName(rets.At(0).Type(), "encoding/xml.Attr") { + return false + } + if !typeutil.IsTypeWithName(rets.At(1).Type(), "error") { + return false + } + return true +} + +type CyclicTypeError struct { + Type types.Type + Path string +} + +func (err *CyclicTypeError) Error() string { + return "cyclic type" +} + +// marshalValue writes one or more XML elements representing val. +// If val was obtained from a struct field, finfo must have its details. +func (e *Encoder) marshalValue(val fakereflect.TypeAndCanAddr, finfo *fieldInfo, startTemplate *StartElement, stack string) error { + var m *typeutil.Map[struct{}] + if val.CanAddr() { + m = &e.seenCanAddr + } else { + m = &e.seenCantAddr + } + if _, ok := m.At(val.Type); ok { + return nil + } + m.Set(val.Type, struct{}{}) + + // Drill into interfaces and pointers. + seen := map[fakereflect.TypeAndCanAddr]struct{}{} + for val.IsInterface() || val.IsPtr() { + if val.IsInterface() { + return nil + } + val = val.Elem() + if _, ok := seen[val]; ok { + // Loop in type graph, e.g. 'type P *P' + return &CyclicTypeError{val.Type, stack} + } + seen[val] = struct{}{} + } + + // Check for marshaler. + if implementsMarshaler(val) { + return nil + } + if val.CanAddr() { + pv := fakereflect.PtrTo(val) + if implementsMarshaler(pv) { + return nil + } + } + + // Check for text marshaler. + if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + if val.CanAddr() { + pv := fakereflect.PtrTo(val) + if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + } + + // Slices and arrays iterate over the elements. They do not have an enclosing tag. + if (val.IsSlice() || val.IsArray()) && !isByteArray(val) && !isByteSlice(val) { + if err := e.marshalValue(val.Elem(), finfo, startTemplate, stack+"[0]"); err != nil { + return err + } + return nil + } + + tinfo, err := getTypeInfo(val) + if err != nil { + return err + } + + // Create start element. + // Precedence for the XML element name is: + // 0. startTemplate + // 1. XMLName field in underlying struct; + // 2. field name/tag in the struct field; and + // 3. type name + var start StartElement + + if startTemplate != nil { + start.Name = startTemplate.Name + start.Attr = append(start.Attr, startTemplate.Attr...) + } else if tinfo.xmlname != nil { + xmlname := tinfo.xmlname + if xmlname.name != "" { + start.Name.Space, start.Name.Local = xmlname.xmlns, xmlname.name + } + } + + // Attributes + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr == 0 { + continue + } + fv := finfo.value(val) + + name := Name{Space: finfo.xmlns, Local: finfo.name} + if err := e.marshalAttr(&start, name, fv, stack+pathByIndex(val, finfo.idx)); err != nil { + return err + } + } + + if val.IsStruct() { + return e.marshalStruct(tinfo, val, stack) + } else { + return e.marshalSimple(val, stack) + } +} + +func isSlice(v fakereflect.TypeAndCanAddr) bool { + _, ok := v.Type.Underlying().(*types.Slice) + return ok +} + +func isByteSlice(v fakereflect.TypeAndCanAddr) bool { + slice, ok := v.Type.Underlying().(*types.Slice) + if !ok { + return false + } + basic, ok := slice.Elem().Underlying().(*types.Basic) + if !ok { + return false + } + return basic.Kind() == types.Uint8 +} + +func isByteArray(v fakereflect.TypeAndCanAddr) bool { + slice, ok := v.Type.Underlying().(*types.Array) + if !ok { + return false + } + basic, ok := slice.Elem().Underlying().(*types.Basic) + if !ok { + return false + } + return basic.Kind() == types.Uint8 +} + +// marshalAttr marshals an attribute with the given name and value, adding to start.Attr. +func (e *Encoder) marshalAttr(start *StartElement, name Name, val fakereflect.TypeAndCanAddr, stack string) error { + if implementsMarshalerAttr(val) { + return nil + } + + if val.CanAddr() { + pv := fakereflect.PtrTo(val) + if implementsMarshalerAttr(pv) { + return nil + } + } + + if val.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + + if val.CanAddr() { + pv := fakereflect.PtrTo(val) + if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + return nil + } + } + + // Dereference or skip nil pointer + if val.IsPtr() { + val = val.Elem() + } + + // Walk slices. + if isSlice(val) && !isByteSlice(val) { + if err := e.marshalAttr(start, name, val.Elem(), stack+"[0]"); err != nil { + return err + } + return nil + } + + if typeutil.IsTypeWithName(val.Type, "encoding/xml.Attr") { + return nil + } + + return e.marshalSimple(val, stack) +} + +func (e *Encoder) marshalSimple(val fakereflect.TypeAndCanAddr, stack string) error { + switch val.Type.Underlying().(type) { + case *types.Basic, *types.Interface: + return nil + case *types.Slice, *types.Array: + basic, ok := val.Elem().Type.Underlying().(*types.Basic) + if !ok || basic.Kind() != types.Uint8 { + return &UnsupportedTypeError{val.Type, stack} + } + return nil + default: + return &UnsupportedTypeError{val.Type, stack} + } +} + +func indirect(vf fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr { + for vf.IsPtr() { + vf = vf.Elem() + } + return vf +} + +func pathByIndex(t fakereflect.TypeAndCanAddr, index []int) string { + var path strings.Builder + for _, i := range index { + if t.IsPtr() { + t = t.Elem() + } + path.WriteString("." + t.Field(i).Name) + t = t.Field(i).Type + } + return path.String() +} + +func (e *Encoder) marshalStruct(tinfo *typeInfo, val fakereflect.TypeAndCanAddr, stack string) error { + for i := range tinfo.fields { + finfo := &tinfo.fields[i] + if finfo.flags&fAttr != 0 { + continue + } + vf := finfo.value(val) + + switch finfo.flags & fMode { + case fCDATA, fCharData: + if vf.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + continue + } + if vf.CanAddr() { + pv := fakereflect.PtrTo(vf) + if pv.Implements(knowledge.Interfaces["encoding.TextMarshaler"]) { + continue + } + } + continue + + case fComment: + vf = indirect(vf) + if !(isByteSlice(vf) || isByteArray(vf)) { + return fmt.Errorf("xml: bad type for comment field of %s", val) + } + continue + + case fInnerXML: + vf = indirect(vf) + if t, ok := vf.Type.(*types.Slice); (ok && types.Identical(t.Elem(), types.Typ[types.Byte])) || types.Identical(vf.Type, types.Typ[types.String]) { + continue + } + + case fElement, fElement | fAny: + } + if err := e.marshalValue(vf, finfo, nil, stack+pathByIndex(val, finfo.idx)); err != nil { + return err + } + } + return nil +} + +// UnsupportedTypeError is returned when Marshal encounters a type +// that cannot be converted into XML. +type UnsupportedTypeError struct { + Type types.Type + Path string +} + +func (e *UnsupportedTypeError) Error() string { + return fmt.Sprintf("xml: unsupported type %s, via %s ", e.Type, e.Path) +} diff --git a/vendor/honnef.co/go/tools/staticcheck/fakexml/typeinfo.go b/vendor/honnef.co/go/tools/staticcheck/fakexml/typeinfo.go new file mode 100644 index 0000000..bf5c73b --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/fakexml/typeinfo.go @@ -0,0 +1,381 @@ +// Copyright 2011 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 fakexml + +import ( + "fmt" + "strconv" + "strings" + "sync" + + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/staticcheck/fakereflect" +) + +// typeInfo holds details for the xml representation of a type. +type typeInfo struct { + xmlname *fieldInfo + fields []fieldInfo +} + +// fieldInfo holds details for the xml representation of a single field. +type fieldInfo struct { + idx []int + name string + xmlns string + flags fieldFlags + parents []string +} + +type fieldFlags int + +const ( + fElement fieldFlags = 1 << iota + fAttr + fCDATA + fCharData + fInnerXML + fComment + fAny + + fOmitEmpty + + fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny + + xmlName = "XMLName" +) + +func (f fieldFlags) String() string { + switch f { + case fAttr: + return "attr" + case fCDATA: + return "cdata" + case fCharData: + return "chardata" + case fInnerXML: + return "innerxml" + case fComment: + return "comment" + case fAny: + return "any" + case fOmitEmpty: + return "omitempty" + case fAny | fAttr: + return "any,attr" + default: + return strconv.Itoa(int(f)) + } +} + +var tinfoMap sync.Map // map[reflect.Type]*typeInfo + +// getTypeInfo returns the typeInfo structure with details necessary +// for marshaling and unmarshaling typ. +func getTypeInfo(typ fakereflect.TypeAndCanAddr) (*typeInfo, error) { + if ti, ok := tinfoMap.Load(typ); ok { + return ti.(*typeInfo), nil + } + + tinfo := &typeInfo{} + if typ.IsStruct() && !typeutil.IsTypeWithName(typ.Type, "encoding/xml.Name") { + n := typ.NumField() + for i := range n { + f := typ.Field(i) + if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" { + continue // Private field + } + + // For embedded structs, embed its fields. + if f.Anonymous { + t := f.Type + if t.IsPtr() { + t = t.Elem() + } + if t.IsStruct() { + inner, err := getTypeInfo(t) + if err != nil { + return nil, err + } + if tinfo.xmlname == nil { + tinfo.xmlname = inner.xmlname + } + for _, finfo := range inner.fields { + finfo.idx = append([]int{i}, finfo.idx...) + if err := addFieldInfo(typ, tinfo, &finfo); err != nil { + return nil, err + } + } + continue + } + } + + finfo, err := StructFieldInfo(f) + if err != nil { + return nil, err + } + + if f.Name == xmlName { + tinfo.xmlname = finfo + continue + } + + // Add the field if it doesn't conflict with other fields. + if err := addFieldInfo(typ, tinfo, finfo); err != nil { + return nil, err + } + } + } + + ti, _ := tinfoMap.LoadOrStore(typ, tinfo) + return ti.(*typeInfo), nil +} + +// StructFieldInfo builds and returns a fieldInfo for f. +func StructFieldInfo(f fakereflect.StructField) (*fieldInfo, error) { + finfo := &fieldInfo{idx: f.Index} + + // Split the tag from the xml namespace if necessary. + tag := f.Tag.Get("xml") + if i := strings.Index(tag, " "); i >= 0 { + finfo.xmlns, tag = tag[:i], tag[i+1:] + } + + // Parse flags. + tokens := strings.Split(tag, ",") + if len(tokens) == 1 { + finfo.flags = fElement + } else { + tag = tokens[0] + for _, flag := range tokens[1:] { + switch flag { + case "attr": + finfo.flags |= fAttr + case "cdata": + finfo.flags |= fCDATA + case "chardata": + finfo.flags |= fCharData + case "innerxml": + finfo.flags |= fInnerXML + case "comment": + finfo.flags |= fComment + case "any": + finfo.flags |= fAny + case "omitempty": + finfo.flags |= fOmitEmpty + } + } + + // Validate the flags used. + switch mode := finfo.flags & fMode; mode { + case 0: + finfo.flags |= fElement + case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr: + if f.Name == xmlName { + return nil, fmt.Errorf("cannot use option %s on XMLName field", mode) + } else if tag != "" && mode != fAttr { + return nil, fmt.Errorf("cannot specify name together with option ,%s", mode) + } + default: + // This will also catch multiple modes in a single field. + return nil, fmt.Errorf("invalid combination of options: %q", f.Tag.Get("xml")) + } + if finfo.flags&fMode == fAny { + finfo.flags |= fElement + } + if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 { + return nil, fmt.Errorf("can only use omitempty on elements and attributes") + } + } + + // Use of xmlns without a name is not allowed. + if finfo.xmlns != "" && tag == "" { + return nil, fmt.Errorf("namespace without name: %q", f.Tag.Get("xml")) + } + + if f.Name == xmlName { + // The XMLName field records the XML element name. Don't + // process it as usual because its name should default to + // empty rather than to the field name. + finfo.name = tag + return finfo, nil + } + + if tag == "" { + // If the name part of the tag is completely empty, get + // default from XMLName of underlying struct if feasible, + // or field name otherwise. + if xmlname := lookupXMLName(f.Type); xmlname != nil { + finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name + } else { + finfo.name = f.Name + } + return finfo, nil + } + + // Prepare field name and parents. + parents := strings.Split(tag, ">") + if parents[0] == "" { + parents[0] = f.Name + } + if parents[len(parents)-1] == "" { + return nil, fmt.Errorf("trailing '>'") + } + finfo.name = parents[len(parents)-1] + if len(parents) > 1 { + if (finfo.flags & fElement) == 0 { + return nil, fmt.Errorf("%s chain not valid with %s flag", tag, strings.Join(tokens[1:], ",")) + } + finfo.parents = parents[:len(parents)-1] + } + + // If the field type has an XMLName field, the names must match + // so that the behavior of both marshaling and unmarshaling + // is straightforward and unambiguous. + if finfo.flags&fElement != 0 { + ftyp := f.Type + xmlname := lookupXMLName(ftyp) + if xmlname != nil && xmlname.name != finfo.name { + return nil, fmt.Errorf("name %q conflicts with name %q in %s.XMLName", finfo.name, xmlname.name, ftyp) + } + } + return finfo, nil +} + +// lookupXMLName returns the fieldInfo for typ's XMLName field +// in case it exists and has a valid xml field tag, otherwise +// it returns nil. +func lookupXMLName(typ fakereflect.TypeAndCanAddr) (xmlname *fieldInfo) { + seen := map[fakereflect.TypeAndCanAddr]struct{}{} + for typ.IsPtr() { + typ = typ.Elem() + if _, ok := seen[typ]; ok { + // Loop in type graph, e.g. 'type P *P' + return nil + } + seen[typ] = struct{}{} + } + if !typ.IsStruct() { + return nil + } + for i, n := 0, typ.NumField(); i < n; i++ { + f := typ.Field(i) + if f.Name != xmlName { + continue + } + finfo, err := StructFieldInfo(f) + if err == nil && finfo.name != "" { + return finfo + } + // Also consider errors as a non-existent field tag + // and let getTypeInfo itself report the error. + break + } + return nil +} + +// addFieldInfo adds finfo to tinfo.fields if there are no +// conflicts, or if conflicts arise from previous fields that were +// obtained from deeper embedded structures than finfo. In the latter +// case, the conflicting entries are dropped. +// A conflict occurs when the path (parent + name) to a field is +// itself a prefix of another path, or when two paths match exactly. +// It is okay for field paths to share a common, shorter prefix. +func addFieldInfo(typ fakereflect.TypeAndCanAddr, tinfo *typeInfo, newf *fieldInfo) error { + var conflicts []int +Loop: + // First, figure all conflicts. Most working code will have none. + for i := range tinfo.fields { + oldf := &tinfo.fields[i] + if oldf.flags&fMode != newf.flags&fMode { + continue + } + if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns { + continue + } + minl := min(len(newf.parents), len(oldf.parents)) + for p := range minl { + if oldf.parents[p] != newf.parents[p] { + continue Loop + } + } + if len(oldf.parents) > len(newf.parents) { + if oldf.parents[len(newf.parents)] == newf.name { + conflicts = append(conflicts, i) + } + } else if len(oldf.parents) < len(newf.parents) { + if newf.parents[len(oldf.parents)] == oldf.name { + conflicts = append(conflicts, i) + } + } else { + if newf.name == oldf.name { + conflicts = append(conflicts, i) + } + } + } + // Without conflicts, add the new field and return. + if conflicts == nil { + tinfo.fields = append(tinfo.fields, *newf) + return nil + } + + // If any conflict is shallower, ignore the new field. + // This matches the Go field resolution on embedding. + for _, i := range conflicts { + if len(tinfo.fields[i].idx) < len(newf.idx) { + return nil + } + } + + // Otherwise, if any of them is at the same depth level, it's an error. + for _, i := range conflicts { + oldf := &tinfo.fields[i] + if len(oldf.idx) == len(newf.idx) { + f1 := typ.FieldByIndex(oldf.idx) + f2 := typ.FieldByIndex(newf.idx) + return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")} + } + } + + // Otherwise, the new field is shallower, and thus takes precedence, + // so drop the conflicting fields from tinfo and append the new one. + for c := len(conflicts) - 1; c >= 0; c-- { + i := conflicts[c] + copy(tinfo.fields[i:], tinfo.fields[i+1:]) + tinfo.fields = tinfo.fields[:len(tinfo.fields)-1] + } + tinfo.fields = append(tinfo.fields, *newf) + return nil +} + +// A TagPathError represents an error in the unmarshaling process +// caused by the use of field tags with conflicting paths. +type TagPathError struct { + Struct fakereflect.TypeAndCanAddr + Field1, Tag1 string + Field2, Tag2 string +} + +func (e *TagPathError) Error() string { + return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2) +} + +// value returns v's field value corresponding to finfo. +// It's equivalent to v.FieldByIndex(finfo.idx), but when passed +// initNilPointers, it initializes and dereferences pointers as necessary. +// When passed dontInitNilPointers and a nil pointer is reached, the function +// returns a zero reflect.Value. +func (finfo *fieldInfo) value(v fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr { + for i, x := range finfo.idx { + if i > 0 { + t := v + if t.IsPtr() && t.Elem().IsStruct() { + v = v.Elem() + } + } + v = v.Field(x).Type + } + return v +} diff --git a/vendor/honnef.co/go/tools/staticcheck/fakexml/xml.go b/vendor/honnef.co/go/tools/staticcheck/fakexml/xml.go new file mode 100644 index 0000000..0f8fb27 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/fakexml/xml.go @@ -0,0 +1,33 @@ +// Copyright 2009 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 fakexml + +// References: +// Annotated XML spec: https://www.xml.com/axml/testaxml.htm +// XML name spaces: https://www.w3.org/TR/REC-xml-names/ + +// TODO(rsc): +// Test error handling. + +// A Name represents an XML name (Local) annotated +// with a name space identifier (Space). +// In tokens returned by Decoder.Token, the Space identifier +// is given as a canonical URL, not the short prefix used +// in the document being parsed. +type Name struct { + Space, Local string +} + +// An Attr represents an attribute in an XML element (Name=Value). +type Attr struct { + Name Name + Value string +} + +// A StartElement represents an XML start element. +type StartElement struct { + Name Name + Attr []Attr +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1000/sa1000.go b/vendor/honnef.co/go/tools/staticcheck/sa1000/sa1000.go new file mode 100644 index 0000000..3a1356a --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1000/sa1000.go @@ -0,0 +1,46 @@ +package sa1000 + +import ( + "go/constant" + "regexp" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1000", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid regular expression`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "regexp.MustCompile": check, + "regexp.Compile": check, + "regexp.Match": check, + "regexp.MatchReader": check, + "regexp.MatchString": check, +} + +func check(call *callcheck.Call) { + arg := call.Args[0] + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil { + s := constant.StringVal(c.Value) + if _, err := regexp.Compile(s); err != nil { + arg.Invalid(err.Error()) + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1001/sa1001.go b/vendor/honnef.co/go/tools/staticcheck/sa1001/sa1001.go new file mode 100644 index 0000000..4e8e4ff --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1001/sa1001.go @@ -0,0 +1,83 @@ +package sa1001 + +import ( + "go/ast" + htmltemplate "html/template" + "strings" + texttemplate "text/template" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1001", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid template`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(` + (CallExpr + (Symbol + name@(Or + "(*text/template.Template).Parse" + "(*html/template.Template).Parse")) + [s])`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, query) { + name := m.State["name"].(string) + var kind string + switch name { + case "(*text/template.Template).Parse": + kind = "text" + case "(*html/template.Template).Parse": + kind = "html" + } + + call := node.(*ast.CallExpr) + sel := call.Fun.(*ast.SelectorExpr) + if !code.IsCallToAny(pass, sel.X, "text/template.New", "html/template.New") { + // TODO(dh): this is a cheap workaround for templates with + // different delims. A better solution with less false + // negatives would use data flow analysis to see where the + // template comes from and where it has been + continue + } + + s, ok := code.ExprToString(pass, m.State["s"].(ast.Expr)) + if !ok { + continue + } + var err error + switch kind { + case "text": + _, err = texttemplate.New("").Parse(s) + case "html": + _, err = htmltemplate.New("").Parse(s) + } + if err != nil { + // TODO(dominikh): whitelist other parse errors, if any + if strings.Contains(err.Error(), "unexpected") || + strings.Contains(err.Error(), "bad character") { + report.Report(pass, call.Args[knowledge.Arg("(*text/template.Template).Parse.text")], err.Error()) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go b/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go new file mode 100644 index 0000000..4f7159a --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1002/sa1002.go @@ -0,0 +1,48 @@ +package sa1002 + +import ( + "go/constant" + "strings" + "time" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1002", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid format in \'time.Parse\'`, + Text: `\'time.Parse\' requires a layout string that uses Go's reference time: +\"Mon Jan 2 15:04:05 MST 2006\". The layout must represent this date and time +exactly. See https://pkg.go.dev/time#pkg-constants for layout examples.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "time.Parse": func(call *callcheck.Call) { + arg := call.Args[knowledge.Arg("time.Parse.layout")] + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil { + s := constant.StringVal(c.Value) + s = strings.Replace(s, "_", " ", -1) + s = strings.Replace(s, "Z", "-", -1) + _, err := time.Parse(s, s) + if err != nil { + arg.Invalid(err.Error()) + } + } + }, +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1003/sa1003.go b/vendor/honnef.co/go/tools/staticcheck/sa1003/sa1003.go new file mode 100644 index 0000000..087f031 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1003/sa1003.go @@ -0,0 +1,92 @@ +package sa1003 + +import ( + "fmt" + "go/types" + "go/version" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1003", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkEncodingBinaryRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Unsupported argument to functions in \'encoding/binary\'`, + Text: `The \'encoding/binary\' package can only serialize types with known sizes. +This precludes the use of the \'int\' and \'uint\' types, as their sizes +differ on different architectures. Furthermore, it doesn't support +serializing maps, channels, strings, or functions. + +Before Go 1.8, \'bool\' wasn't supported, either.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkEncodingBinaryRules = map[string]callcheck.Check{ + "encoding/binary.Write": func(call *callcheck.Call) { + arg := call.Args[knowledge.Arg("encoding/binary.Write.data")] + if !CanBinaryMarshal(call.Pass, call.Parent, arg.Value) { + arg.Invalid(fmt.Sprintf("value of type %s cannot be used with binary.Write", arg.Value.Value.Type())) + } + }, +} + +func CanBinaryMarshal(pass *analysis.Pass, node code.Positioner, v callcheck.Value) bool { + typ := v.Value.Type().Underlying() + if ttyp, ok := typ.(*types.Pointer); ok { + typ = ttyp.Elem().Underlying() + } + if ttyp, ok := types.Unalias(typ).(interface { + Elem() types.Type + }); ok { + if _, ok := ttyp.(*types.Pointer); !ok { + typ = ttyp.Elem() + } + } + + return validEncodingBinaryType(pass, node, typ) +} + +func validEncodingBinaryType(pass *analysis.Pass, node code.Positioner, typ types.Type) bool { + typ = typ.Underlying() + switch typ := typ.(type) { + case *types.Basic: + switch typ.Kind() { + case types.Uint8, types.Uint16, types.Uint32, types.Uint64, + types.Int8, types.Int16, types.Int32, types.Int64, + types.Float32, types.Float64, types.Complex64, types.Complex128, types.Invalid: + return true + case types.Bool: + return version.Compare(code.StdlibVersion(pass, node), "go1.8") >= 0 + } + return false + case *types.Struct: + n := typ.NumFields() + for i := range n { + if !validEncodingBinaryType(pass, node, typ.Field(i).Type()) { + return false + } + } + return true + case *types.Array: + return validEncodingBinaryType(pass, node, typ.Elem()) + case *types.Interface: + // we can't determine if it's a valid type or not + return true + } + return false +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1004/sa1004.go b/vendor/honnef.co/go/tools/staticcheck/sa1004/sa1004.go new file mode 100644 index 0000000..b3dee16 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1004/sa1004.go @@ -0,0 +1,72 @@ +package sa1004 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1004", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Suspiciously small untyped constant in \'time.Sleep\'`, + Text: `The \'time\'.Sleep function takes a \'time.Duration\' as its only argument. +Durations are expressed in nanoseconds. Thus, calling \'time.Sleep(1)\' +will sleep for 1 nanosecond. This is a common source of bugs, as sleep +functions in other languages often accept seconds or milliseconds. + +The \'time\' package provides constants such as \'time.Second\' to express +large durations. These can be combined with arithmetic to express +arbitrary durations, for example \'5 * time.Second\' for 5 seconds. + +If you truly meant to sleep for a tiny amount of time, use +\'n * time.Nanosecond\' to signal to Staticcheck that you did mean to sleep +for some amount of nanoseconds.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkTimeSleepConstantPatternQ = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`) + checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`) + checkTimeSleepConstantPatternRs = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`) +) + +func run(pass *analysis.Pass) (any, error) { + for _, m := range code.Matches(pass, checkTimeSleepConstantPatternQ) { + n, ok := constant.Int64Val(m.State["value"].(types.TypeAndValue).Value) + if !ok { + continue + } + if n == 0 || n > 120 { + // time.Sleep(0) is a seldom used pattern in concurrency + // tests. >120 might be intentional. 120 was chosen + // because the user could've meant 2 minutes. + continue + } + + lit := m.State["lit"].(ast.Node) + report.Report(pass, lit, + fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes( + edit.Fix("Explicitly use nanoseconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit})), + edit.Fix("Use seconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit})))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1005/sa1005.go b/vendor/honnef.co/go/tools/staticcheck/sa1005/sa1005.go new file mode 100644 index 0000000..65edb51 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1005/sa1005.go @@ -0,0 +1,65 @@ +package sa1005 + +import ( + "go/ast" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1005", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid first argument to \'exec.Command\'`, + Text: `\'os/exec\' runs programs directly (using variants of the fork and exec +system calls on Unix systems). This shouldn't be confused with running +a command in a shell. The shell will allow for features such as input +redirection, pipes, and general scripting. The shell is also +responsible for splitting the user's input into a program name and its +arguments. For example, the equivalent to + + ls / /tmp + +would be + + exec.Command("ls", "/", "/tmp") + +If you want to run a command in a shell, consider using something like +the following – but be aware that not all systems, particularly +Windows, will have a \'/bin/sh\' program: + + exec.Command("/bin/sh", "-c", "ls | grep Awesome")`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(`(CallExpr (Symbol "os/exec.Command") arg1:_)`) + +func run(pass *analysis.Pass) (any, error) { + for _, m := range code.Matches(pass, query) { + arg1 := m.State["arg1"].(ast.Expr) + val, ok := code.ExprToString(pass, arg1) + if !ok { + continue + } + if !strings.Contains(val, " ") || strings.Contains(val, `\`) || strings.Contains(val, "/") { + continue + } + report.Report(pass, arg1, + "first argument to exec.Command looks like a shell command, but a program name or path are expected") + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1006/sa1006.go b/vendor/honnef.co/go/tools/staticcheck/sa1006/sa1006.go new file mode 100644 index 0000000..c613808 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1006/sa1006.go @@ -0,0 +1,115 @@ +package sa1006 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1006", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `\'Printf\' with dynamic first argument and no further arguments`, + Text: `Using \'fmt.Printf\' with a dynamic first argument can lead to unexpected +output. The first argument is a format string, where certain character +combinations have special meaning. If, for example, a user were to +enter a string such as + + Interest rate: 5% + +and you printed it with + + fmt.Printf(s) + +it would lead to the following output: + + Interest rate: 5%!(NOVERB). + +Similarly, forming the first parameter via string concatenation with +user input should be avoided for the same reason. When printing user +input, either use a variant of \'fmt.Print\', or use the \'%s\' Printf verb +and pass the string as an argument.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query1 = pattern.MustParse(` + (CallExpr + (Symbol + name@(Or + "fmt.Errorf" + "fmt.Printf" + "fmt.Sprintf" + "log.Fatalf" + "log.Panicf" + "log.Printf" + "(*log.Logger).Printf" + "(*testing.common).Logf" + "(*testing.common).Errorf" + "(*testing.common).Fatalf" + "(*testing.common).Skipf" + "(testing.TB).Logf" + "(testing.TB).Errorf" + "(testing.TB).Fatalf" + "(testing.TB).Skipf")) + format:[]) +`) + +var query2 = pattern.MustParse(`(CallExpr (Symbol "fmt.Fprintf") _:format:[])`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, query1, query2) { + call := node.(*ast.CallExpr) + name, ok := m.State["name"].(string) + if !ok { + name = "fmt.Fprintf" + } + + arg := m.State["format"].(ast.Expr) + switch arg.(type) { + case *ast.CallExpr, *ast.Ident: + default: + continue + } + + if _, ok := pass.TypesInfo.TypeOf(arg).(*types.Tuple); ok { + // the called function returns multiple values and got + // splatted into the call. for all we know, it is + // returning good arguments. + continue + } + + var alt string + if name == "fmt.Errorf" { + // The alternative to fmt.Errorf isn't fmt.Error but errors.New + alt = "errors.New" + } else { + // This can be either a function call like log.Printf or a method call with an + // arbitrarily complex selector, such as foo.bar[0].Printf. In either case, + // all we have to do is remove the final 'f' from the existing call.Fun + // expression. + alt = report.Render(pass, call.Fun) + alt = alt[:len(alt)-1] + } + report.Report(pass, call, + "printf-style function with dynamic format string and no further arguments should use print-style function instead", + report.Fixes(edit.Fix(fmt.Sprintf("Use %s instead of %s", alt, name), edit.ReplaceWithString(call.Fun, alt)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1007/sa1007.go b/vendor/honnef.co/go/tools/staticcheck/sa1007/sa1007.go new file mode 100644 index 0000000..8aa6958 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1007/sa1007.go @@ -0,0 +1,43 @@ +package sa1007 + +import ( + "fmt" + "go/constant" + "net/url" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1007", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid URL in \'net/url.Parse\'`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "net/url.Parse": func(call *callcheck.Call) { + arg := call.Args[knowledge.Arg("net/url.Parse.rawurl")] + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil { + s := constant.StringVal(c.Value) + _, err := url.Parse(s) + if err != nil { + arg.Invalid(fmt.Sprintf("%q is not a valid URL: %s", s, err)) + } + } + }, +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1008/sa1008.go b/vendor/honnef.co/go/tools/staticcheck/sa1008/sa1008.go new file mode 100644 index 0000000..3e0d1bb --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1008/sa1008.go @@ -0,0 +1,111 @@ +package sa1008 + +import ( + "fmt" + "go/ast" + "net/http" + "strconv" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1008", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Non-canonical key in \'http.Header\' map`, + Text: `Keys in \'http.Header\' maps are canonical, meaning they follow a specific +combination of uppercase and lowercase letters. Methods such as +\'http.Header.Add\' and \'http.Header.Del\' convert inputs into this canonical +form before manipulating the map. + +When manipulating \'http.Header\' maps directly, as opposed to using the +provided methods, care should be taken to stick to canonical form in +order to avoid inconsistencies. The following piece of code +demonstrates one such inconsistency: + + h := http.Header{} + h["etag"] = []string{"1234"} + h.Add("etag", "5678") + fmt.Println(h) + + // Output: + // map[Etag:[5678] etag:[1234]] + +The easiest way of obtaining the canonical form of a key is to use +\'http.CanonicalHeaderKey\'.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node, push bool) bool { + if !push { + return false + } + if assign, ok := node.(*ast.AssignStmt); ok { + // TODO(dh): This risks missing some Header reads, for + // example in `h1["foo"] = h2["foo"]` – these edge + // cases are probably rare enough to ignore for now. + for _, expr := range assign.Lhs { + op, ok := expr.(*ast.IndexExpr) + if !ok { + continue + } + if code.IsOfTypeWithName(pass, op.X, "net/http.Header") { + return false + } + } + return true + } + op, ok := node.(*ast.IndexExpr) + if !ok { + return true + } + if !code.IsOfTypeWithName(pass, op.X, "net/http.Header") { + return true + } + s, ok := code.ExprToString(pass, op.Index) + if !ok { + return true + } + canonical := http.CanonicalHeaderKey(s) + if s == canonical { + return true + } + var fix analysis.SuggestedFix + switch op.Index.(type) { + case *ast.BasicLit: + fix = edit.Fix("Canonicalize header key", edit.ReplaceWithString(op.Index, strconv.Quote(canonical))) + case *ast.Ident: + call := &ast.CallExpr{ + Fun: edit.Selector("http", "CanonicalHeaderKey"), + Args: []ast.Expr{op.Index}, + } + fix = edit.Fix("Wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call)) + } + msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s) + if fix.Message != "" { + report.Report(pass, op, msg, report.Fixes(fix)) + } else { + report.Report(pass, op, msg) + } + return true + } + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1010/sa1010.go b/vendor/honnef.co/go/tools/staticcheck/sa1010/sa1010.go new file mode 100644 index 0000000..2ad6b1f --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1010/sa1010.go @@ -0,0 +1,53 @@ +package sa1010 + +import ( + "fmt" + "go/constant" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1010", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkRegexpFindAllRules), + }, + Doc: &lint.RawDocumentation{ + Title: `\'(*regexp.Regexp).FindAll\' called with \'n == 0\', which will always return zero results`, + Text: `If \'n >= 0\', the function returns at most \'n\' matches/submatches. To +return all results, specify a negative number.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkRegexpFindAllRules = map[string]callcheck.Check{ + "(*regexp.Regexp).FindAll": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllIndex": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllString": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllStringIndex": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllStringSubmatch": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllStringSubmatchIndex": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllSubmatch": RepeatZeroTimes("a FindAll method", 1), + "(*regexp.Regexp).FindAllSubmatchIndex": RepeatZeroTimes("a FindAll method", 1), +} + +func RepeatZeroTimes(name string, arg int) callcheck.Check { + return func(call *callcheck.Call) { + arg := call.Args[arg] + if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int { + if v, ok := constant.Int64Val(k.Value); ok && v == 0 { + arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name)) + } + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1011/sa1011.go b/vendor/honnef.co/go/tools/staticcheck/sa1011/sa1011.go new file mode 100644 index 0000000..5b86c6d --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1011/sa1011.go @@ -0,0 +1,47 @@ +package sa1011 + +import ( + "go/constant" + "unicode/utf8" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1011", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkUTF8CutsetRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Various methods in the \"strings\" package expect valid UTF-8, but invalid input is provided`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkUTF8CutsetRules = map[string]callcheck.Check{ + "strings.IndexAny": check, + "strings.LastIndexAny": check, + "strings.ContainsAny": check, + "strings.Trim": check, + "strings.TrimLeft": check, + "strings.TrimRight": check, +} + +func check(call *callcheck.Call) { + arg := call.Args[1] + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.String); c != nil { + s := constant.StringVal(c.Value) + if !utf8.ValidString(s) { + arg.Invalid("argument is not a valid UTF-8 encoded string") + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go b/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go new file mode 100644 index 0000000..b04fd8c --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1012/sa1012.go @@ -0,0 +1,68 @@ +package sa1012 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1012", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `A nil \'context.Context\' is being passed to a function, consider using \'context.TODO\' instead`, + Text: `The context package prohibits the use of a \'nil\' context. +If no parent context is available, a new context should be used, +e.g. \'context.TODO\' or \'context.Background\'.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Symbol _) (Builtin "nil"):_)`) + +func run(pass *analysis.Pass) (any, error) { + todo := &ast.CallExpr{ + Fun: edit.Selector("context", "TODO"), + } + bg := &ast.CallExpr{ + Fun: edit.Selector("context", "Background"), + } + for node, m := range code.Matches(pass, checkNilContextQ) { + call := node.(*ast.CallExpr) + fun, ok := m.State["fun"].(*types.Func) + if !ok { + // it might also be a builtin + continue + } + sig := fun.Type().(*types.Signature) + if sig.Params().Len() == 0 { + // Our CallExpr might've matched a method expression, like + // (*T).Foo(nil) – here, nil isn't the first argument of + // the Foo method, but the method receiver. + continue + } + if !typeutil.IsTypeWithName(sig.Params().At(0).Type(), "context.Context") { + continue + } + report.Report(pass, call.Args[0], + "do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes( + edit.Fix("Use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)), + edit.Fix("Use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1013/sa1013.go b/vendor/honnef.co/go/tools/staticcheck/sa1013/sa1013.go new file mode 100644 index 0000000..cbcbe4e --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1013/sa1013.go @@ -0,0 +1,47 @@ +package sa1013 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1013", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `\'io.Seeker.Seek\' is being called with the whence constant as the first argument, but it should be the second`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkSeekerQ = pattern.MustParse(`(CallExpr fun@(SelectorExpr _ (Ident "Seek")) [arg1@(SelectorExpr _ (Symbol (Or "io.SeekStart" "io.SeekCurrent" "io.SeekEnd"))) arg2])`) + checkSeekerR = pattern.MustParse(`(CallExpr fun [arg2 arg1])`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkSeekerQ) { + if !code.IsMethod(pass, m.State["fun"].(*ast.SelectorExpr), "Seek", knowledge.Signatures["(io.Seeker).Seek"]) { + continue + } + edits := code.EditMatch(pass, node, m, checkSeekerR) + report.Report(pass, node, "the first argument of io.Seeker is the offset, but an io.Seek* constant is being used instead", + report.Fixes(edit.Fix("Swap arguments", edits...))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go b/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go new file mode 100644 index 0000000..528aa33 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1014/sa1014.go @@ -0,0 +1,56 @@ +package sa1014 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1014", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkUnmarshalPointerRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Non-pointer value passed to \'Unmarshal\' or \'Decode\'`, + Text: `Functions such as \'encoding/json.Unmarshal\' and +\'(*encoding/json.Decoder).Decode\' require a pointer to the value that should +be populated. Passing a non-pointer value results in the function returning an +error at runtime, as it cannot modify the target value.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkUnmarshalPointerRules = map[string]callcheck.Check{ + "encoding/xml.Unmarshal": unmarshalPointer("xml.Unmarshal", 1), + "(*encoding/xml.Decoder).Decode": unmarshalPointer("Decode", 0), + "(*encoding/xml.Decoder).DecodeElement": unmarshalPointer("DecodeElement", 0), + "encoding/json.Unmarshal": unmarshalPointer("json.Unmarshal", 1), + "(*encoding/json.Decoder).Decode": unmarshalPointer("Decode", 0), +} + +func unmarshalPointer(name string, arg int) callcheck.Check { + return func(call *callcheck.Call) { + if !Pointer(call.Args[arg].Value) { + call.Args[arg].Invalid(fmt.Sprintf("%s expects to unmarshal into a pointer, but the provided value is not a pointer", name)) + } + } +} + +func Pointer(v callcheck.Value) bool { + switch v.Value.Type().Underlying().(type) { + case *types.Pointer, *types.Interface: + return true + } + return false +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1015/sa1015.go b/vendor/honnef.co/go/tools/staticcheck/sa1015/sa1015.go new file mode 100644 index 0000000..a416c34 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1015/sa1015.go @@ -0,0 +1,69 @@ +package sa1015 + +import ( + "go/token" + "go/version" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1015", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Using \'time.Tick\' in a way that will leak. Consider using \'time.NewTicker\', and only use \'time.Tick\' in tests, commands and endless functions`, + + Text: `Before Go 1.23, \'time.Ticker\'s had to be closed to be able to be garbage +collected. Since \'time.Tick\' doesn't make it possible to close the underlying +ticker, using it repeatedly would leak memory. + +Go 1.23 fixes this by allowing tickers to be collected even if they weren't closed.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + if fn.Pos() == token.NoPos || version.Compare(code.StdlibVersion(pass, fn), "go1.23") >= 0 { + // Beginning with Go 1.23, the GC is able to collect unreferenced, unclosed + // tickers, which makes time.Tick safe(r) to use. + // + // When we don't have a valid position, we err on the side of false negatives. + // This shouldn't actually lead to any false negatives, as no functions + // without valid positions (such as the synthesized init function) should be + // able to use time.Tick. + continue + } + + if code.IsMainLike(pass) || code.IsInTest(pass, fn) { + continue + } + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + call, ok := ins.(*ir.Call) + if !ok || !irutil.IsCallTo(call.Common(), "time.Tick") { + continue + } + if !irutil.Terminates(call.Parent()) { + continue + } + report.Report(pass, call, "using time.Tick leaks the underlying ticker, consider using it only in endless functions, tests and the main package, and use time.NewTicker here") + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1016/sa1016.go b/vendor/honnef.co/go/tools/staticcheck/sa1016/sa1016.go new file mode 100644 index 0000000..bf89f6c --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1016/sa1016.go @@ -0,0 +1,115 @@ +package sa1016 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1016", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Trapping a signal that cannot be trapped`, + Text: `Not all signals can be intercepted by a process. Specifically, on +UNIX-like systems, the \'syscall.SIGKILL\' and \'syscall.SIGSTOP\' signals are +never passed to the process, but instead handled directly by the +kernel. It is therefore pointless to try and handle these signals.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(` + (CallExpr + (Symbol + (Or + "os/signal.Ignore" + "os/signal.Notify" + "os/signal.Reset")) + _)`) + +func run(pass *analysis.Pass) (any, error) { + isSignal := func(pass *analysis.Pass, expr ast.Expr, name string) bool { + if expr, ok := expr.(*ast.SelectorExpr); ok { + return code.SelectorName(pass, expr) == name + } else { + return false + } + } + + for node := range code.Matches(pass, query) { + call := node.(*ast.CallExpr) + hasSigterm := false + for _, arg := range call.Args { + if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") { + arg = conv.Args[0] + } + + if isSignal(pass, arg, "syscall.SIGTERM") { + hasSigterm = true + break + } + + } + for i, arg := range call.Args { + if conv, ok := arg.(*ast.CallExpr); ok && isSignal(pass, conv.Fun, "os.Signal") { + arg = conv.Args[0] + } + + if isSignal(pass, arg, "os.Kill") || isSignal(pass, arg, "syscall.SIGKILL") { + var fixes []analysis.SuggestedFix + if !hasSigterm { + nargs := make([]ast.Expr, len(call.Args)) + for j, a := range call.Args { + if i == j { + nargs[j] = edit.Selector("syscall", "SIGTERM") + } else { + nargs[j] = a + } + } + ncall := *call + ncall.Args = nargs + fixes = append(fixes, edit.Fix(fmt.Sprintf("Use syscall.SIGTERM instead of %s", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall))) + } + nargs := make([]ast.Expr, 0, len(call.Args)) + for j, a := range call.Args { + if i == j { + continue + } + nargs = append(nargs, a) + } + ncall := *call + ncall.Args = nargs + fixes = append(fixes, edit.Fix(fmt.Sprintf("Remove %s from list of arguments", report.Render(pass, arg)), edit.ReplaceWithNode(pass.Fset, call, &ncall))) + report.Report(pass, arg, fmt.Sprintf("%s cannot be trapped (did you mean syscall.SIGTERM?)", report.Render(pass, arg)), report.Fixes(fixes...)) + } + if isSignal(pass, arg, "syscall.SIGSTOP") { + nargs := make([]ast.Expr, 0, len(call.Args)-1) + for j, a := range call.Args { + if i == j { + continue + } + nargs = append(nargs, a) + } + ncall := *call + ncall.Args = nargs + report.Report(pass, arg, "syscall.SIGSTOP cannot be trapped", report.Fixes(edit.Fix("Remove syscall.SIGSTOP from list of arguments", edit.ReplaceWithNode(pass.Fset, call, &ncall)))) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1017/sa1017.go b/vendor/honnef.co/go/tools/staticcheck/sa1017/sa1017.go new file mode 100644 index 0000000..f200d99 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1017/sa1017.go @@ -0,0 +1,64 @@ +package sa1017 + +import ( + "go/constant" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1017", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Channels used with \'os/signal.Notify\' should be buffered`, + Text: `The \'os/signal\' package uses non-blocking channel sends when delivering +signals. If the receiving end of the channel isn't ready and the +channel is either unbuffered or full, the signal will be dropped. To +avoid missing signals, the channel should be buffered and of the +appropriate size. For a channel used for notification of just one +signal value, a buffer of size 1 is sufficient.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "os/signal.Notify": func(call *callcheck.Call) { + arg := call.Args[knowledge.Arg("os/signal.Notify.c")] + if isUnbufferedChannel(arg.Value) { + arg.Invalid("the channel used with signal.Notify should be buffered") + } + }, +} + +func isUnbufferedChannel(v callcheck.Value) bool { + // TODO(dh): this check of course misses many cases of unbuffered + // channels, such as any in phi or sigma nodes. We'll eventually + // replace this function. + val := v.Value + if ct, ok := val.(*ir.ChangeType); ok { + val = ct.X + } + mk, ok := val.(*ir.MakeChan) + if !ok { + return false + } + if k, ok := mk.Size.(*ir.Const); ok && k.Value.Kind() == constant.Int { + if v, ok := constant.Int64Val(k.Value); ok && v == 0 { + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1018/sa1018.go b/vendor/honnef.co/go/tools/staticcheck/sa1018/sa1018.go new file mode 100644 index 0000000..9981910 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1018/sa1018.go @@ -0,0 +1,47 @@ +package sa1018 + +import ( + "fmt" + "go/constant" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1018", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `\'strings.Replace\' called with \'n == 0\', which does nothing`, + Text: `With \'n == 0\', zero instances will be replaced. To replace all +instances, use a negative number, or use \'strings.ReplaceAll\'.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "strings.Replace": check("strings.Replace", 3), + "bytes.Replace": check("bytes.Replace", 3), +} + +func check(name string, arg int) callcheck.Check { + return func(call *callcheck.Call) { + arg := call.Args[arg] + if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value.Kind() == constant.Int { + if v, ok := constant.Int64Val(k.Value); ok && v == 0 { + arg.Invalid(fmt.Sprintf("calling %s with n == 0 will return no results, did you mean -1?", name)) + } + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go b/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go new file mode 100644 index 0000000..0fb9b30 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1019/sa1019.go @@ -0,0 +1,246 @@ +package sa1019 + +import ( + "fmt" + "go/ast" + "go/types" + "go/version" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/deprecated" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1019", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, deprecated.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Using a deprecated function, variable, constant or field`, + Since: "2017.1", + Severity: lint.SeverityDeprecated, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func formatGoVersion(s string) string { + return "Go " + strings.TrimPrefix(s, "go") +} + +func run(pass *analysis.Pass) (any, error) { + deprs := pass.ResultOf[deprecated.Analyzer].(deprecated.Result) + + // Selectors can appear outside of function literals, e.g. when + // declaring package level variables. + + isStdlibPath := func(path string) bool { + // Modules with no dot in the first path element are reserved for the standard library and tooling. + // This is the best we can currently do. + // Nobody tells us which import paths are part of the standard library. + // + // We check the entire path instead of just the first path element, because the standard library doesn't contain paths with any dots, anyway. + + return !strings.Contains(path, ".") + } + + handleDeprecation := func(depr *deprecated.IsDeprecated, node ast.Node, deprecatedObjName string, pkgPath string, tfn types.Object) { + std, ok := knowledge.StdlibDeprecations[deprecatedObjName] + if !ok && isStdlibPath(pkgPath) { + // Deprecated object in the standard library, but we don't know the details of the deprecation. + // Don't flag it at all, to avoid flagging an object that was deprecated in 1.N when targeting 1.N-1. + // See https://staticcheck.dev/issues/1108 for the background on this. + return + } + if ok { + // In the past, we made use of the AlternativeAvailableSince field. If a function was deprecated in Go + // 1.6 and an alternative had been available in Go 1.0, then we'd recommend using the alternative even + // if targeting Go 1.2. The idea was to suggest writing future-proof code by using already-existing + // alternatives. This had a major flaw, however: the user would need to use at least Go 1.6 for + // Staticcheck to know that the function had been deprecated. Thus, targeting Go 1.2 and using Go 1.2 + // would behave differently from targeting Go 1.2 and using Go 1.6. This is especially a problem if the + // user tries to ignore the warning. Depending on the Go version in use, the ignore directive may or may + // not match, causing a warning of its own. + // + // To avoid this issue, we no longer try to be smart. We now only compare the targeted version against + // the version that deprecated an object. + // + // Unfortunately, this issue also applies to AlternativeAvailableSince == DeprecatedNeverUse. Even though it + // is only applied to seriously flawed API, such as broken cryptography, users may wish to ignore those + // warnings. + // + // See also https://staticcheck.dev/issues/1318. + if version.Compare(code.StdlibVersion(pass, node), std.DeprecatedSince) == -1 { + return + } + } + + if tfn != nil { + if _, ok := deprs.Objects[tfn]; ok { + // functions that are deprecated may use deprecated + // symbols + return + } + } + + if ok { + switch std.AlternativeAvailableSince { + case knowledge.DeprecatedNeverUse: + report.Report(pass, node, + fmt.Sprintf("%s has been deprecated since %s because it shouldn't be used: %s", + deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg)) + case std.DeprecatedSince, knowledge.DeprecatedUseNoLonger: + report.Report(pass, node, + fmt.Sprintf("%s has been deprecated since %s: %s", + deprecatedObjName, formatGoVersion(std.DeprecatedSince), depr.Msg)) + default: + report.Report(pass, node, + fmt.Sprintf("%s has been deprecated since %s and an alternative has been available since %s: %s", + deprecatedObjName, formatGoVersion(std.DeprecatedSince), formatGoVersion(std.AlternativeAvailableSince), depr.Msg)) + } + } else { + report.Report(pass, node, fmt.Sprintf("%s is deprecated: %s", deprecatedObjName, depr.Msg)) + } + } + + var tfn types.Object + stack := 0 + + checkIdentObj := func(sel *ast.SelectorExpr) bool { + obj := pass.TypesInfo.ObjectOf(sel.Sel) + + if obj_, ok := obj.(*types.Func); ok { + obj = obj_.Origin() + } + if obj.Pkg() == nil { + return true + } + if obj.Pkg() == pass.Pkg { + // A package is allowed to use its own deprecated objects + return true + } + + // A package "foo" has two related packages "foo_test" and "foo.test", for external tests and the package main + // generated by 'go test' respectively. "foo_test" can import and use "foo", "foo.test" imports and uses "foo" + // and "foo_test". + + if strings.TrimSuffix(pass.Pkg.Path(), "_test") == obj.Pkg().Path() { + // foo_test (the external tests of foo) can use objects from foo. + return true + } + if strings.TrimSuffix(pass.Pkg.Path(), ".test") == obj.Pkg().Path() { + // foo.test (the main package of foo's tests) can use objects from foo. + return true + } + if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(obj.Pkg().Path(), "_test") { + // foo.test (the main package of foo's tests) can use objects from foo's external tests. + return true + } + + node := ast.Node(sel) + if pass.TypesInfo.Types[sel.X].IsType() { + node = sel.Sel + } + if depr, ok := deprs.Objects[obj]; ok { + handleDeprecation(depr, node, code.SelectorName(pass, sel), obj.Pkg().Path(), tfn) + } + return true + } + + fn := func(node ast.Node, push bool) bool { + if !push { + stack-- + return false + } + stack++ + if stack == 1 { + tfn = nil + } + if fn, ok := node.(*ast.FuncDecl); ok { + tfn = pass.TypesInfo.ObjectOf(fn.Name) + } + + switch v := node.(type) { + // FIXME(dh): this misses dot-imported objects + case *ast.SelectorExpr: + return checkIdentObj(v) + + case *ast.CompositeLit: + litType := pass.TypesInfo.Types[v.Type] + if !litType.IsType() { + // This is probably unreachable. + return true + } + if _, ok := litType.Type.Underlying().(*types.Struct); !ok { + // We don't want to look at expressions in map initializers, for + // example. + return true + } + for _, elt := range v.Elts { + kv, ok := elt.(*ast.KeyValueExpr) + if !ok { + return true + } + key, ok := kv.Key.(*ast.Ident) + if !ok { + // This is probably unreachable, since we're looking at keys + // in a struct initializer. + return true + } + sel := &ast.SelectorExpr{X: v.Type, Sel: key} + checkIdentObj(sel) + } + } + return true + } + + fn2 := func(node ast.Node) { + spec := node.(*ast.ImportSpec) + var imp *types.Package + if spec.Name != nil { + imp = pass.TypesInfo.ObjectOf(spec.Name).(*types.PkgName).Imported() + } else { + imp = pass.TypesInfo.Implicits[spec].(*types.PkgName).Imported() + } + + p := spec.Path.Value + path := p[1 : len(p)-1] + if depr, ok := deprs.Packages[imp]; ok { + if path == "github.com/golang/protobuf/proto" { + gen, ok := code.Generator(pass, spec.Path.Pos()) + if ok && gen == generated.ProtocGenGo { + return + } + } + + if strings.TrimSuffix(pass.Pkg.Path(), "_test") == path { + // foo_test can import foo + return + } + if strings.TrimSuffix(pass.Pkg.Path(), ".test") == path { + // foo.test can import foo + return + } + if strings.TrimSuffix(pass.Pkg.Path(), ".test") == strings.TrimSuffix(path, "_test") { + // foo.test can import foo_test + return + } + + handleDeprecation(depr, spec.Path, path, path, nil) + } + } + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes(nil, fn) + code.Preorder(pass, fn2, (*ast.ImportSpec)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go b/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go new file mode 100644 index 0000000..9d6256a --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1020/sa1020.go @@ -0,0 +1,99 @@ +package sa1020 + +import ( + "go/constant" + "net" + "strconv" + "strings" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1020", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkListenAddressRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Using an invalid host:port pair with a \'net.Listen\'-related function`, + Text: `Functions such as \'net.Listen\', \'net.ListenTCP\', and similar, +expect a valid network address in the form of host:port. The host, the port, +or both, can be omitted, e.g. \'localhost:8080\', \':8080\' or \':\' are valid +host:port pairs. +See https://pkg.go.dev/net#Listen for the full documentation.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkListenAddressRules = map[string]callcheck.Check{ + "net/http.ListenAndServe": checkValidHostPort(0), + "net/http.ListenAndServeTLS": checkValidHostPort(0), +} + +func checkValidHostPort(arg int) callcheck.Check { + return func(call *callcheck.Call) { + if !ValidHostPort(call.Args[arg].Value) { + const MsgInvalidHostPort = "invalid port or service name in host:port pair" + call.Args[arg].Invalid(MsgInvalidHostPort) + } + } +} + +func ValidHostPort(v callcheck.Value) bool { + if k := callcheck.ExtractConstExpectKind(v, constant.String); k != nil { + s := constant.StringVal(k.Value) + if s == "" { + return true + } + _, port, err := net.SplitHostPort(s) + if err != nil { + return false + } + // TODO(dh): check hostname + if !validatePort(port) { + return false + } + } + return true +} + +func validateServiceName(s string) bool { + if len(s) < 1 || len(s) > 15 { + return false + } + if s[0] == '-' || s[len(s)-1] == '-' { + return false + } + if strings.Contains(s, "--") { + return false + } + hasLetter := false + for _, r := range s { + if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') { + hasLetter = true + continue + } + if r >= '0' && r <= '9' { + continue + } + return false + } + return hasLetter +} + +func validatePort(s string) bool { + n, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return validateServiceName(s) + } + return n >= 0 && n <= 65535 +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1021/sa1021.go b/vendor/honnef.co/go/tools/staticcheck/sa1021/sa1021.go new file mode 100644 index 0000000..95fc960 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1021/sa1021.go @@ -0,0 +1,49 @@ +package sa1021 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1021", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Using \'bytes.Equal\' to compare two \'net.IP\'`, + Text: `A \'net.IP\' stores an IPv4 or IPv6 address as a slice of bytes. The +length of the slice for an IPv4 address, however, can be either 4 or +16 bytes long, using different ways of representing IPv4 addresses. In +order to correctly compare two \'net.IP\'s, the \'net.IP.Equal\' method should +be used, as it takes both representations into account.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "bytes.Equal": func(call *callcheck.Call) { + if isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.a")].Value, "net.IP") && + isConvertedFrom(call.Args[knowledge.Arg("bytes.Equal.b")].Value, "net.IP") { + call.Invalid("use net.IP.Equal to compare net.IPs, not bytes.Equal") + } + }, +} + +// ConvertedFrom reports whether value v was converted from type typ. +func isConvertedFrom(v callcheck.Value, typ string) bool { + change, ok := v.Value.(*ir.ChangeType) + return ok && types.TypeString(types.Unalias(change.X.Type()), nil) == typ +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1023/sa1023.go b/vendor/honnef.co/go/tools/staticcheck/sa1023/sa1023.go new file mode 100644 index 0000000..22d21e2 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1023/sa1023.go @@ -0,0 +1,72 @@ +package sa1023 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1023", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Modifying the buffer in an \'io.Writer\' implementation`, + Text: `\'Write\' must not modify the slice data, even temporarily.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // TODO(dh): this might be a good candidate for taint analysis. + // Taint the argument as MUST_NOT_MODIFY, then propagate that + // through functions like bytes.Split + + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + sig := fn.Signature + if fn.Name() != "Write" || sig.Recv() == nil { + continue + } + if !types.Identical(sig, knowledge.Signatures["(io.Writer).Write"]) { + continue + } + + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + switch ins := ins.(type) { + case *ir.Store: + addr, ok := ins.Addr.(*ir.IndexAddr) + if !ok { + continue + } + if addr.X != fn.Params[1] { + continue + } + report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily") + case *ir.Call: + if !irutil.IsCallTo(ins.Common(), "append") { + continue + } + if ins.Common().Args[0] != fn.Params[1] { + continue + } + report.Report(pass, ins, "io.Writer.Write must not modify the provided buffer, not even temporarily") + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1024/sa1024.go b/vendor/honnef.co/go/tools/staticcheck/sa1024/sa1024.go new file mode 100644 index 0000000..2ff4896 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1024/sa1024.go @@ -0,0 +1,75 @@ +package sa1024 + +import ( + "go/constant" + "sort" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1024", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `A string cutset contains duplicate characters`, + Text: `The \'strings.TrimLeft\' and \'strings.TrimRight\' functions take cutsets, not +prefixes. A cutset is treated as a set of characters to remove from a +string. For example, + + strings.TrimLeft("42133word", "1234") + +will result in the string \'"word"\' – any characters that are 1, 2, 3 or +4 are cut from the left of the string. + +In order to remove one string from another, use \'strings.TrimPrefix\' instead.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "strings.Trim": check, + "strings.TrimLeft": check, + "strings.TrimRight": check, +} + +func check(call *callcheck.Call) { + arg := call.Args[1] + if !isUniqueStringCutset(arg.Value) { + const MsgNonUniqueCutset = "cutset contains duplicate characters" + arg.Invalid(MsgNonUniqueCutset) + } +} + +func isUniqueStringCutset(v callcheck.Value) bool { + if c := callcheck.ExtractConstExpectKind(v, constant.String); c != nil { + s := constant.StringVal(c.Value) + rs := runeSlice(s) + if len(rs) < 2 { + return true + } + sort.Sort(rs) + for i, r := range rs[1:] { + if rs[i] == r { + return false + } + } + } + return true +} + +type runeSlice []rune + +func (rs runeSlice) Len() int { return len(rs) } +func (rs runeSlice) Less(i int, j int) bool { return rs[i] < rs[j] } +func (rs runeSlice) Swap(i int, j int) { rs[i], rs[j] = rs[j], rs[i] } diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go b/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go new file mode 100644 index 0000000..2ac361a --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1025/sa1025.go @@ -0,0 +1,92 @@ +package sa1025 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1025", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `It is not possible to use \'(*time.Timer).Reset\''s return value correctly`, + Since: "2019.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + call, ok := ins.(*ir.Call) + if !ok { + continue + } + if !irutil.IsCallTo(call.Common(), "(*time.Timer).Reset") { + continue + } + refs := call.Referrers() + if refs == nil { + continue + } + for _, ref := range *refs { + ifstmt, ok := ref.(*ir.If) + if !ok { + continue + } + + found := false + for _, succ := range ifstmt.Block().Succs { + if len(succ.Preds) != 1 { + // Merge point, not a branch in the + // syntactical sense. + + // FIXME(dh): this is broken for if + // statements a la "if x || y" + continue + } + irutil.Walk(succ, func(b *ir.BasicBlock) bool { + if !succ.Dominates(b) { + // We've reached the end of the branch + return false + } + for _, ins := range b.Instrs { + // TODO(dh): we should check that we're receiving from the + // channel of a time.Timer to further reduce false + // positives. Not a key priority, considering the rarity + // of Reset and the tiny likeliness of a false positive + // + // We intentionally don't handle aliases here, because + // we're only interested in time.Timer.C. + if ins, ok := ins.(*ir.Recv); ok && types.TypeString(ins.Chan.Type(), nil) == "<-chan time.Time" { + found = true + return false + } + } + return true + }) + } + + if found { + report.Report(pass, call, "it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring") + } + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go b/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go new file mode 100644 index 0000000..0aa99fc --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1026/sa1026.go @@ -0,0 +1,79 @@ +package sa1026 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/staticcheck/fakejson" + "honnef.co/go/tools/staticcheck/fakexml" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1026", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Cannot marshal channels or functions`, + Since: "2019.2", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "encoding/json.Marshal": checkJSON, + "encoding/json.MarshalIndent": checkJSON, + "encoding/xml.Marshal": checkXML, + "encoding/xml.MarshalIndent": checkXML, + "(*encoding/json.Encoder).Encode": checkJSON, + "(*encoding/xml.Encoder).Encode": checkXML, +} + +func checkJSON(call *callcheck.Call) { + arg := call.Args[0] + T := arg.Value.Value.Type() + if err := fakejson.Marshal(T); err != nil { + typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg)) + if err.Path == "x" { + arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ)) + } else { + arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path)) + } + } +} + +func checkXML(call *callcheck.Call) { + arg := call.Args[0] + T := arg.Value.Value.Type() + if err := fakexml.Marshal(T); err != nil { + switch err := err.(type) { + case *fakexml.UnsupportedTypeError: + typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg)) + if err.Path == "x" { + arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s", typ)) + } else { + arg.Invalid(fmt.Sprintf("trying to marshal unsupported type %s, via %s", typ, err.Path)) + } + case *fakexml.CyclicTypeError: + typ := types.TypeString(err.Type, types.RelativeTo(call.Parent.Pkg.Pkg)) + if err.Path == "x" { + arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s", typ)) + } else { + arg.Invalid(fmt.Sprintf("trying to marshal cyclic type %s, via %s", typ, err.Path)) + } + case *fakexml.TagPathError: + // Vet does a better job at reporting this error, because it can flag the actual struct tags, not just the call to Marshal + default: + // These errors get reported by SA5008 instead, which can flag the actual fields, independently of calls to xml.Marshal + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1027/sa1027.go b/vendor/honnef.co/go/tools/staticcheck/sa1027/sa1027.go new file mode 100644 index 0000000..95633c4 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1027/sa1027.go @@ -0,0 +1,76 @@ +package sa1027 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1027", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkAtomicAlignment), + }, + Doc: &lint.RawDocumentation{ + Title: `Atomic access to 64-bit variable must be 64-bit aligned`, + Text: `On ARM, x86-32, and 32-bit MIPS, it is the caller's responsibility to +arrange for 64-bit alignment of 64-bit words accessed atomically. The +first word in a variable or in an allocated struct, array, or slice +can be relied upon to be 64-bit aligned. + +You can use the structlayout tool to inspect the alignment of fields +in a struct.`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkAtomicAlignment = map[string]callcheck.Check{ + "sync/atomic.AddInt64": checkAtomicAlignmentImpl, + "sync/atomic.AddUint64": checkAtomicAlignmentImpl, + "sync/atomic.CompareAndSwapInt64": checkAtomicAlignmentImpl, + "sync/atomic.CompareAndSwapUint64": checkAtomicAlignmentImpl, + "sync/atomic.LoadInt64": checkAtomicAlignmentImpl, + "sync/atomic.LoadUint64": checkAtomicAlignmentImpl, + "sync/atomic.StoreInt64": checkAtomicAlignmentImpl, + "sync/atomic.StoreUint64": checkAtomicAlignmentImpl, + "sync/atomic.SwapInt64": checkAtomicAlignmentImpl, + "sync/atomic.SwapUint64": checkAtomicAlignmentImpl, +} + +func checkAtomicAlignmentImpl(call *callcheck.Call) { + sizes := call.Pass.TypesSizes + if sizes.Sizeof(types.Typ[types.Uintptr]) != 4 { + // Not running on a 32-bit platform + return + } + v, ok := irutil.Flatten(call.Args[0].Value.Value).(*ir.FieldAddr) + if !ok { + // TODO(dh): also check indexing into arrays and slices + return + } + T := v.X.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Struct) + fields := make([]*types.Var, 0, T.NumFields()) + for i := 0; i < T.NumFields() && i <= v.Field; i++ { + fields = append(fields, T.Field(i)) + } + + off := sizes.Offsetsof(fields)[v.Field] + if off%8 != 0 { + msg := fmt.Sprintf("address of non 64-bit aligned field %s passed to %s", + T.Field(v.Field).Name(), + irutil.CallName(call.Instr.Common())) + call.Invalid(msg) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1028/sa1028.go b/vendor/honnef.co/go/tools/staticcheck/sa1028/sa1028.go new file mode 100644 index 0000000..5ac1216 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1028/sa1028.go @@ -0,0 +1,57 @@ +package sa1028 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1028", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `\'sort.Slice\' can only be used on slices`, + Text: `The first argument of \'sort.Slice\' must be a slice.`, + Since: "2020.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "sort.Slice": check, + "sort.SliceIsSorted": check, + "sort.SliceStable": check, +} + +func check(call *callcheck.Call) { + c := call.Instr.Common().StaticCallee() + arg := call.Args[0] + + T := arg.Value.Value.Type().Underlying() + switch T.(type) { + case *types.Interface: + // we don't know. + // TODO(dh): if the value is a phi node we can look at its edges + if k, ok := arg.Value.Value.(*ir.Const); ok && k.Value == nil { + // literal nil, e.g. sort.Sort(nil, ...) + arg.Invalid(fmt.Sprintf("cannot call %s on nil literal", c)) + } + case *types.Slice: + // this is fine + default: + // this is not fine + arg.Invalid(fmt.Sprintf("%s must only be called on slices, was called on %s", c, T)) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1029/sa1029.go b/vendor/honnef.co/go/tools/staticcheck/sa1029/sa1029.go new file mode 100644 index 0000000..c981baa --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1029/sa1029.go @@ -0,0 +1,61 @@ +package sa1029 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1029", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkWithValueKeyRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Inappropriate key in call to \'context.WithValue\'`, + Text: `The provided key must be comparable and should not be +of type \'string\' or any other built-in type to avoid collisions between +packages using context. Users of \'WithValue\' should define their own +types for keys. + +To avoid allocating when assigning to an \'interface{}\', +context keys often have concrete type \'struct{}\'. Alternatively, +exported context key variables' static type should be a pointer or +interface.`, + Since: "2020.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkWithValueKeyRules = map[string]callcheck.Check{ + "context.WithValue": checkWithValueKey, +} + +func checkWithValueKey(call *callcheck.Call) { + arg := call.Args[1] + T := arg.Value.Value.Type() + if typ, ok := types.Unalias(T).(*types.Basic); ok { + if _, ok := T.(*types.Alias); ok { + arg.Invalid( + fmt.Sprintf("should not use built-in type %s (via alias %s) as key for value; define your own type to avoid collisions", typ, types.TypeString(T, types.RelativeTo(call.Pass.Pkg)))) + } else { + arg.Invalid( + fmt.Sprintf("should not use built-in type %s as key for value; define your own type to avoid collisions", typ)) + } + } + // TODO(dh): we should probably flag all anonymous structs, as they all risk collisions + if s, ok := T.(*types.Struct); ok && s.NumFields() == 0 { + arg.Invalid("should not use empty anonymous struct as key for value; define your own type to avoid collisions") + } else if !types.Comparable(T) { + arg.Invalid(fmt.Sprintf("keys used with context.WithValue must be comparable, but type %s is not comparable", T)) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go b/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go new file mode 100644 index 0000000..0506938 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1030/sa1030.go @@ -0,0 +1,134 @@ +package sa1030 + +import ( + "fmt" + "go/constant" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1030", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid argument in call to a \'strconv\' function`, + Text: `This check validates the format, number base and bit size arguments of +the various parsing and formatting functions in \'strconv\'.`, + Since: "2021.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "strconv.ParseComplex": func(call *callcheck.Call) { + validateComplexBitSize(call.Args[knowledge.Arg("strconv.ParseComplex.bitSize")]) + }, + "strconv.ParseFloat": func(call *callcheck.Call) { + validateFloatBitSize(call.Args[knowledge.Arg("strconv.ParseFloat.bitSize")]) + }, + "strconv.ParseInt": func(call *callcheck.Call) { + validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseInt.bitSize")], 0, 64) + validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseInt.base")]) + }, + "strconv.ParseUint": func(call *callcheck.Call) { + validateContinuousBitSize(call.Args[knowledge.Arg("strconv.ParseUint.bitSize")], 0, 64) + validateIntBaseAllowZero(call.Args[knowledge.Arg("strconv.ParseUint.base")]) + }, + + "strconv.FormatComplex": func(call *callcheck.Call) { + validateComplexFormat(call.Args[knowledge.Arg("strconv.FormatComplex.fmt")]) + validateComplexBitSize(call.Args[knowledge.Arg("strconv.FormatComplex.bitSize")]) + }, + "strconv.FormatFloat": func(call *callcheck.Call) { + validateFloatFormat(call.Args[knowledge.Arg("strconv.FormatFloat.fmt")]) + validateFloatBitSize(call.Args[knowledge.Arg("strconv.FormatFloat.bitSize")]) + }, + "strconv.FormatInt": func(call *callcheck.Call) { + validateIntBase(call.Args[knowledge.Arg("strconv.FormatInt.base")]) + }, + "strconv.FormatUint": func(call *callcheck.Call) { + validateIntBase(call.Args[knowledge.Arg("strconv.FormatUint.base")]) + }, + + "strconv.AppendFloat": func(call *callcheck.Call) { + validateFloatFormat(call.Args[knowledge.Arg("strconv.AppendFloat.fmt")]) + validateFloatBitSize(call.Args[knowledge.Arg("strconv.AppendFloat.bitSize")]) + }, + "strconv.AppendInt": func(call *callcheck.Call) { + validateIntBase(call.Args[knowledge.Arg("strconv.AppendInt.base")]) + }, + "strconv.AppendUint": func(call *callcheck.Call) { + validateIntBase(call.Args[knowledge.Arg("strconv.AppendUint.base")]) + }, +} + +func validateDiscreetBitSize(arg *callcheck.Argument, size1 int, size2 int) { + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil { + val, _ := constant.Int64Val(c.Value) + if val != int64(size1) && val != int64(size2) { + arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be either %d or %d", size1, size2)) + } + } +} + +func validateComplexBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 64, 128) } +func validateFloatBitSize(arg *callcheck.Argument) { validateDiscreetBitSize(arg, 32, 64) } + +func validateContinuousBitSize(arg *callcheck.Argument, min int, max int) { + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil { + val, _ := constant.Int64Val(c.Value) + if val < int64(min) || val > int64(max) { + arg.Invalid(fmt.Sprintf("'bitSize' argument is invalid, must be within %d and %d", min, max)) + } + } +} + +func validateIntBase(arg *callcheck.Argument) { + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil { + val, _ := constant.Int64Val(c.Value) + if val < 2 { + arg.Invalid("'base' must not be smaller than 2") + } + if val > 36 { + arg.Invalid("'base' must not be larger than 36") + } + } +} + +func validateIntBaseAllowZero(arg *callcheck.Argument) { + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil { + val, _ := constant.Int64Val(c.Value) + if val < 2 && val != 0 { + arg.Invalid("'base' must not be smaller than 2, unless it is 0") + } + if val > 36 { + arg.Invalid("'base' must not be larger than 36") + } + } +} + +func validateComplexFormat(arg *callcheck.Argument) { + validateFloatFormat(arg) +} + +func validateFloatFormat(arg *callcheck.Argument) { + if c := callcheck.ExtractConstExpectKind(arg.Value, constant.Int); c != nil { + val, _ := constant.Int64Val(c.Value) + switch val { + case 'b', 'e', 'E', 'f', 'g', 'G', 'x', 'X': + default: + arg.Invalid(fmt.Sprintf("'fmt' argument is invalid: unknown format %q", rune(val))) + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1031/sa1031.go b/vendor/honnef.co/go/tools/staticcheck/sa1031/sa1031.go new file mode 100644 index 0000000..52ba4dc --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1031/sa1031.go @@ -0,0 +1,81 @@ +package sa1031 + +import ( + "go/constant" + "go/token" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1031", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkEncodeRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Overlapping byte slices passed to an encoder`, + Text: `In an encoding function of the form \'Encode(dst, src)\', \'dst\' and +\'src\' were found to reference the same memory. This can result in +\'src\' bytes being overwritten before they are read, when the encoder +writes more than one byte per \'src\' byte.`, + Since: "2024.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkEncodeRules = map[string]callcheck.Check{ + "encoding/ascii85.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/ascii85.Encode.dst"), knowledge.Arg("encoding/ascii85.Encode.src")), + "(*encoding/base32.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base32.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base32.Encoding).Encode.src")), + "(*encoding/base64.Encoding).Encode": checkNonOverlappingDstSrc(knowledge.Arg("(*encoding/base64.Encoding).Encode.dst"), knowledge.Arg("(*encoding/base64.Encoding).Encode.src")), + "encoding/hex.Encode": checkNonOverlappingDstSrc(knowledge.Arg("encoding/hex.Encode.dst"), knowledge.Arg("encoding/hex.Encode.src")), +} + +func checkNonOverlappingDstSrc(dstArg, srcArg int) callcheck.Check { + return func(call *callcheck.Call) { + dst := call.Args[dstArg] + src := call.Args[srcArg] + _, dstConst := irutil.Flatten(dst.Value.Value).(*ir.Const) + _, srcConst := irutil.Flatten(src.Value.Value).(*ir.Const) + if dstConst || srcConst { + // one of the arguments is nil, therefore overlap is not possible + return + } + if dst.Value == src.Value { + // simple case of f(b, b) + dst.Invalid("overlapping dst and src") + return + } + dstSlice, ok := irutil.Flatten(dst.Value.Value).(*ir.Slice) + if !ok { + return + } + srcSlice, ok := irutil.Flatten(src.Value.Value).(*ir.Slice) + if !ok { + return + } + if irutil.Flatten(dstSlice.X) != irutil.Flatten(srcSlice.X) { + // differing underlying arrays, all is well + return + } + l1 := irutil.Flatten(dstSlice.Low) + l2 := irutil.Flatten(srcSlice.Low) + c1, ok1 := l1.(*ir.Const) + c2, ok2 := l2.(*ir.Const) + if l1 == l2 || (ok1 && ok2 && constant.Compare(c1.Value, token.EQL, c2.Value)) { + // dst and src are the same slice, and have the same lower bound + dst.Invalid("overlapping dst and src") + return + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa1032/sa1032.go b/vendor/honnef.co/go/tools/staticcheck/sa1032/sa1032.go new file mode 100644 index 0000000..2ea6038 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa1032/sa1032.go @@ -0,0 +1,79 @@ +package sa1032 + +import ( + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA1032", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Wrong order of arguments to \'errors.Is\'`, + Text: ` +The first argument of the function \'errors.Is\' is the error +that we have and the second argument is the error we're trying to match against. +For example: + + if errors.Is(err, io.EOF) { ... } + +This check detects some cases where the two arguments have been swapped. It +flags any calls where the first argument is referring to a package-level error +variable, such as + + if errors.Is(io.EOF, err) { /* this is wrong */ }`, + Since: "2024.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "errors.Is": validateIs, +} + +func validateIs(call *callcheck.Call) { + if len(call.Args) != 2 { + return + } + + global := func(arg *callcheck.Argument) *ir.Global { + v, ok := arg.Value.Value.(*ir.Load) + if !ok { + return nil + } + g, _ := v.X.(*ir.Global) + return g + } + + x, y := call.Args[0], call.Args[1] + gx := global(x) + if gx == nil { + return + } + + if pkgx := gx.Package().Pkg; pkgx != nil && pkgx.Path() != call.Pass.Pkg.Path() { + // x is a global that's not in this package + + if gy := global(y); gy != nil { + if pkgy := gy.Package().Pkg; pkgy != nil && pkgy.Path() != call.Pass.Pkg.Path() { + // Both arguments refer to globals that aren't in this package. This can + // genuinely happen for external tests that check that one error "is" + // another one. net/http's external tests, for example, do + // `errors.Is(http.ErrNotSupported, errors.ErrUnsupported)`. + return + } + } + + call.Invalid("arguments have the wrong order") + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go b/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go new file mode 100644 index 0000000..721f269 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa2000/sa2000.go @@ -0,0 +1,47 @@ +package sa2000 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA2000", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `\'(*sync.WaitGroup).Add\' called inside the goroutine, leading to a race condition`, + Text: `\'(*sync.WaitGroup).Add\' must be called before starting the goroutine +it is meant to wait for. Calling \'Add\' inside the goroutine creates a race +condition between the call to \'Add\' and the call to \'Wait\'.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkWaitgroupAddQ = pattern.MustParse(` + (GoStmt + (CallExpr + (FuncLit + _ + call@(CallExpr (Symbol "(*sync.WaitGroup).Add") _):_) _))`) + +func run(pass *analysis.Pass) (any, error) { + for _, m := range code.Matches(pass, checkWaitgroupAddQ) { + call := m.State["call"].(ast.Node) + report.Report(pass, call, fmt.Sprintf("should call %s before starting the goroutine to avoid a race", report.Render(pass, call))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go b/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go new file mode 100644 index 0000000..5e3f83e --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa2001/sa2001.go @@ -0,0 +1,110 @@ +package sa2001 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA2001", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Empty critical section, did you mean to defer the unlock?`, + Text: `Empty critical sections of the kind + + mu.Lock() + mu.Unlock() + +are very often a typo, and the following was intended instead: + + mu.Lock() + defer mu.Unlock() + +Do note that sometimes empty critical sections can be useful, as a +form of signaling to wait on another goroutine. Many times, there are +simpler ways of achieving the same effect. When that isn't the case, +the code should be amply commented to avoid confusion. Combining such +comments with a \'//lint:ignore\' directive can be used to suppress this +rare false positive.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + if pass.Pkg.Path() == "sync_test" { + // exception for the sync package's tests + return nil, nil + } + + // Initially it might seem like this check would be easier to + // implement using IR. After all, we're only checking for two + // consecutive method calls. In reality, however, there may be any + // number of other instructions between the lock and unlock, while + // still constituting an empty critical section. For example, + // given `m.x().Lock(); m.x().Unlock()`, there will be a call to + // x(). In the AST-based approach, this has a tiny potential for a + // false positive (the second call to x might be doing work that + // is protected by the mutex). In an IR-based approach, however, + // it would miss a lot of real bugs. + + mutexParams := func(s ast.Stmt) (x ast.Expr, funcName string, ok bool) { + expr, ok := s.(*ast.ExprStmt) + if !ok { + return nil, "", false + } + call, ok := ast.Unparen(expr.X).(*ast.CallExpr) + if !ok { + return nil, "", false + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + return nil, "", false + } + + fn, ok := pass.TypesInfo.ObjectOf(sel.Sel).(*types.Func) + if !ok { + return nil, "", false + } + sig := fn.Type().(*types.Signature) + if sig.Params().Len() != 0 || sig.Results().Len() != 0 { + return nil, "", false + } + + return sel.X, fn.Name(), true + } + + fn := func(node ast.Node) { + block := node.(*ast.BlockStmt) + if len(block.List) < 2 { + return + } + for i := range block.List[:len(block.List)-1] { + sel1, method1, ok1 := mutexParams(block.List[i]) + sel2, method2, ok2 := mutexParams(block.List[i+1]) + + if !ok1 || !ok2 || report.Render(pass, sel1) != report.Render(pass, sel2) { + continue + } + if (method1 == "Lock" && method2 == "Unlock") || + (method1 == "RLock" && method2 == "RUnlock") { + report.Report(pass, block.List[i+1], "empty critical section") + } + } + } + code.Preorder(pass, fn, (*ast.BlockStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa2002/sa2002.go b/vendor/honnef.co/go/tools/staticcheck/sa2002/sa2002.go new file mode 100644 index 0000000..5641dd8 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa2002/sa2002.go @@ -0,0 +1,93 @@ +package sa2002 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA2002", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Called \'testing.T.FailNow\' or \'SkipNow\' in a goroutine, which isn't allowed`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + gostmt, ok := ins.(*ir.Go) + if !ok { + continue + } + var fn *ir.Function + switch val := gostmt.Call.Value.(type) { + case *ir.Function: + fn = val + case *ir.MakeClosure: + fn = val.Fn.(*ir.Function) + default: + continue + } + if fn.Blocks == nil { + continue + } + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + call, ok := ins.(*ir.Call) + if !ok { + continue + } + if call.Call.IsInvoke() { + continue + } + callee := call.Call.StaticCallee() + if callee == nil { + continue + } + recv := callee.Signature.Recv() + if recv == nil { + continue + } + if !typeutil.IsPointerToTypeWithName(recv.Type(), "testing.common") { + continue + } + fn, ok := call.Call.StaticCallee().Object().(*types.Func) + if !ok { + continue + } + name := fn.Name() + switch name { + case "FailNow", "Fatal", "Fatalf", "SkipNow", "Skip", "Skipf": + default: + continue + } + // TODO(dh): don't report multiple diagnostics + // for multiple calls to T.Fatal, but do + // collect all of them as related information + report.Report(pass, gostmt, fmt.Sprintf("the goroutine calls T.%s, which must be called in the same goroutine as the test", name), + report.Related(call, fmt.Sprintf("call to T.%s", name))) + } + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go b/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go new file mode 100644 index 0000000..c6fb7f0 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa2003/sa2003.go @@ -0,0 +1,99 @@ +package sa2003 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA2003", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Deferred \'Lock\' right after locking, likely meant to defer \'Unlock\' instead`, + Text: `Deferring a call to \'Lock\' immediately after locking is almost always +a typo. For example: + + mu.Lock() + defer mu.Lock() + +While this does not strictly guarantee a deadlock depending on how the +surrounding code is structured, it is highly likely to be a mistake. +The intended code was likely this: + + mu.Lock() + defer mu.Unlock()`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, block := range fn.Blocks { + instrs := block.Instrs + if len(instrs) < 2 { + continue + } + for i, ins := range instrs[:len(instrs)-1] { + call, ok := ins.(*ir.Call) + if !ok { + continue + } + if !irutil.IsCallToAny(call.Common(), "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { + continue + } + nins, ok := instrs[i+1].(*ir.Defer) + if !ok { + continue + } + if !irutil.IsCallToAny(&nins.Call, "(*sync.Mutex).Lock", "(*sync.RWMutex).RLock") { + continue + } + if call.Common().Args[0] != nins.Call.Args[0] { + continue + } + name := shortCallName(call.Common()) + alt := "" + switch name { + case "Lock": + alt = "Unlock" + case "RLock": + alt = "RUnlock" + } + report.Report(pass, nins, fmt.Sprintf("deferring %s right after having locked already; did you mean to defer %s?", name, alt)) + } + } + } + return nil, nil +} + +func shortCallName(call *ir.CallCommon) string { + if call.IsInvoke() { + return "" + } + switch v := call.Value.(type) { + case *ir.Function: + fn, ok := v.Object().(*types.Func) + if !ok { + return "" + } + return fn.Name() + case *ir.Builtin: + return v.Name() + } + return "" +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa3000/sa3000.go b/vendor/honnef.co/go/tools/staticcheck/sa3000/sa3000.go new file mode 100644 index 0000000..69d4dc1 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa3000/sa3000.go @@ -0,0 +1,118 @@ +package sa3000 + +import ( + "go/ast" + "go/types" + "go/version" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA3000", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `\'TestMain\' doesn't call \'os.Exit\', hiding test failures`, + Text: `Test executables (and in turn \"go test\") exit with a non-zero status +code if any tests failed. When specifying your own \'TestMain\' function, +it is your responsibility to arrange for this, by calling \'os.Exit\' with +the correct code. The correct code is returned by \'(*testing.M).Run\', so +the usual way of implementing \'TestMain\' is to end it with +\'os.Exit(m.Run())\'.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + var ( + fnmain ast.Node + callsExit bool + callsRun bool + arg types.Object + ) + fn := func(node ast.Node, push bool) bool { + if !push { + if fnmain != nil && node == fnmain { + if !callsExit && callsRun { + report.Report(pass, fnmain, "TestMain should call os.Exit to set exit code") + } + fnmain = nil + callsExit = false + callsRun = false + arg = nil + } + return true + } + + switch node := node.(type) { + case *ast.FuncDecl: + if fnmain != nil { + return true + } + if !isTestMain(pass, node) { + return false + } + if version.Compare(code.StdlibVersion(pass, node), "go1.15") >= 0 { + // Beginning with Go 1.15, the test framework will call + // os.Exit for us. + return false + } + fnmain = node + arg = pass.TypesInfo.ObjectOf(node.Type.Params.List[0].Names[0]) + return true + case *ast.CallExpr: + if code.IsCallTo(pass, node, "os.Exit") { + callsExit = true + return false + } + sel, ok := node.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + ident, ok := sel.X.(*ast.Ident) + if !ok { + return true + } + if arg != pass.TypesInfo.ObjectOf(ident) { + return true + } + if sel.Sel.Name == "Run" { + callsRun = true + return false + } + return true + default: + lint.ExhaustiveTypeSwitch(node) + return true + } + } + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.FuncDecl)(nil), (*ast.CallExpr)(nil)}, fn) + return nil, nil +} + +func isTestMain(pass *analysis.Pass, decl *ast.FuncDecl) bool { + if decl.Name.Name != "TestMain" { + return false + } + if len(decl.Type.Params.List) != 1 { + return false + } + arg := decl.Type.Params.List[0] + if len(arg.Names) != 1 { + return false + } + return code.IsOfPointerToTypeWithName(pass, arg.Type, "testing.M") +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa3001/sa3001.go b/vendor/honnef.co/go/tools/staticcheck/sa3001/sa3001.go new file mode 100644 index 0000000..eebd13b --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa3001/sa3001.go @@ -0,0 +1,47 @@ +package sa3001 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA3001", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Assigning to \'b.N\' in benchmarks distorts the results`, + Text: `The testing package dynamically sets \'b.N\' to improve the reliability of +benchmarks and uses it in computations to determine the duration of a +single operation. Benchmark code must not alter \'b.N\' as this would +falsify results.`, + Since: "2017.1", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(`(AssignStmt sel@(SelectorExpr selX (Ident "N")) "=" [_] )`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, query) { + assign := node.(*ast.AssignStmt) + if !code.IsOfPointerToTypeWithName(pass, m.State["selX"].(ast.Expr), "testing.B") { + continue + } + report.Report(pass, assign, + fmt.Sprintf("should not assign to %s", report.Render(pass, m.State["sel"]))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go b/vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go new file mode 100644 index 0000000..bec2afd --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4000/sa4000.go @@ -0,0 +1,217 @@ +package sa4000 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "reflect" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/edge" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4000", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Binary operator has identical expressions on both sides`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + var isFloat func(T types.Type) bool + isFloat = func(T types.Type) bool { + tset := typeutil.NewTypeSet(T) + if len(tset.Terms) == 0 { + // no terms, so floats are a possibility + return true + } + return tset.Any(func(term *types.Term) bool { + switch typ := term.Type().Underlying().(type) { + case *types.Basic: + kind := typ.Kind() + return kind == types.Float32 || kind == types.Float64 + case *types.Array: + return isFloat(typ.Elem()) + case *types.Struct: + for field := range typ.Fields() { + if isFloat(field.Type()) { + return true + } + } + return false + default: + return false + } + }) + } + + // TODO(dh): this check ignores the existence of side-effects and + // happily flags fn() == fn() – so far, we've had only two complains + // about false positives, and it's caught several bugs in real + // code. + // + // We special case functions from the math/rand package. Someone ran + // into the following false positive: "rand.Intn(2) - rand.Intn(2), which I wrote to generate values {-1, 0, 1} with {0.25, 0.5, 0.25} probability." + + skipComparableCheck := func(c inspector.Cursor) bool { + op, ok := c.Node().(*ast.BinaryExpr) + if !ok { + return false + } + if clit, ok := op.X.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 { + return false + } + if clit, ok := op.Y.(*ast.CompositeLit); !ok || len(clit.Elts) != 0 { + return false + } + + // TODO(dh): we should probably skip ParenExprs, but users should + // probably not use unnecessary ParenExprs. + vspec, ok := c.Parent().Node().(*ast.ValueSpec) + if !ok { + return false + } + e, i := c.ParentEdge() + if e != edge.ValueSpec_Values { + return false + } + if vspec.Names[i].Name == "_" { + // `var _ = T{} == T{}` is permitted, as a compile-time + // check that T implements comparable. + return true + } + return false + } + + for c := range code.Cursor(pass).Preorder((*ast.BinaryExpr)(nil)) { + node := c.Node() + op := node.(*ast.BinaryExpr) + switch op.Op { + case token.EQL, token.NEQ: + if skipComparableCheck(c) { + continue + } + case token.SUB, token.QUO, token.AND, token.REM, token.OR, token.XOR, token.AND_NOT, + token.LAND, token.LOR, token.LSS, token.GTR, token.LEQ, token.GEQ: + default: + // For some ops, such as + and *, it can make sense to + // have identical operands + continue + } + + if isFloat(pass.TypesInfo.TypeOf(op.X)) { + // 'float float' makes sense for several operators. + // We've tried keeping an exact list of operators to allow, but floats keep surprising us. Let's just give up instead. + continue + } + + if reflect.TypeOf(op.X) != reflect.TypeOf(op.Y) { + continue + } + if report.Render(pass, op.X) != report.Render(pass, op.Y) { + continue + } + l1, ok1 := op.X.(*ast.BasicLit) + l2, ok2 := op.Y.(*ast.BasicLit) + if ok1 && ok2 && l1.Kind == token.INT && l2.Kind == l1.Kind && l1.Value == "0" && l2.Value == l1.Value && code.IsGenerated(pass, l1.Pos()) { + // cgo generates the following function call: + // _cgoCheckPointer(_cgoBase0, 0 == 0) – it uses 0 == 0 + // instead of true in case the user shadowed the + // identifier. Ideally we'd restrict this exception to + // calls of _cgoCheckPointer, but it's not worth the + // hassle of keeping track of the stack. + // are very rare to begin with, and we're mostly checking + // for them to catch typos such as 1 == 1 where the user + // meant to type i == 1. The odds of a false negative for + // 0 == 0 are slim. + continue + } + + if expr, ok := op.X.(*ast.CallExpr); ok { + call := code.CallName(pass, expr) + switch call { + case "math/rand.Int", + "math/rand.Int31", + "math/rand.Int31n", + "math/rand.Int63", + "math/rand.Int63n", + "math/rand.Intn", + "math/rand.Uint32", + "math/rand.Uint64", + "math/rand.ExpFloat64", + "math/rand.Float32", + "math/rand.Float64", + "math/rand.NormFloat64", + "(*math/rand.Rand).Int", + "(*math/rand.Rand).Int31", + "(*math/rand.Rand).Int31n", + "(*math/rand.Rand).Int63", + "(*math/rand.Rand).Int63n", + "(*math/rand.Rand).Intn", + "(*math/rand.Rand).Uint32", + "(*math/rand.Rand).Uint64", + "(*math/rand.Rand).ExpFloat64", + "(*math/rand.Rand).Float32", + "(*math/rand.Rand).Float64", + "(*math/rand.Rand).NormFloat64", + "math/rand/v2.Int", + "math/rand/v2.Int32", + "math/rand/v2.Int32N", + "math/rand/v2.Int64", + "math/rand/v2.Int64N", + "math/rand/v2.IntN", + "math/rand/v2.N", + "math/rand/v2.Uint", + "math/rand/v2.Uint32", + "math/rand/v2.Uint32N", + "math/rand/v2.Uint64", + "math/rand/v2.Uint64N", + "math/rand/v2.UintN", + "math/rand/v2.ExpFloat64", + "math/rand/v2.Float32", + "math/rand/v2.Float64", + "math/rand/v2.NormFloat64", + "(*math/rand/v2.Rand).Int", + "(*math/rand/v2.Rand).Int32", + "(*math/rand/v2.Rand).Int32N", + "(*math/rand/v2.Rand).Int64", + "(*math/rand/v2.Rand).Int64N", + "(*math/rand/v2.Rand).IntN", + "(*math/rand/v2.Rand).N", + "(*math/rand/v2.Rand).Uint", + "(*math/rand/v2.Rand).Uint32", + "(*math/rand/v2.Rand).Uint32N", + "(*math/rand/v2.Rand).Uint64", + "(*math/rand/v2.Rand).Uint64N", + "(*math/rand/v2.Rand).UintN", + "(*math/rand/v2.Rand).ExpFloat64", + "(*math/rand/v2.Rand).Float32", + "(*math/rand/v2.Rand).Float64", + "(*math/rand/v2.Rand).NormFloat64": + continue + } + } + + report.Report(pass, op, fmt.Sprintf("identical expressions on the left and right side of the '%s' operator", op.Op)) + } + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4001/sa4001.go b/vendor/honnef.co/go/tools/staticcheck/sa4001/sa4001.go new file mode 100644 index 0000000..2ca4370 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4001/sa4001.go @@ -0,0 +1,52 @@ +package sa4001 + +import ( + "go/ast" + "regexp" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4001", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `\'&*x\' gets simplified to \'x\', it does not copy \'x\'`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + // cgo produces code like fn(&*_Cvar_kSomeCallbacks) which we don't + // want to flag. + cgoIdent = regexp.MustCompile(`^_C(func|var)_.+$`) + checkIneffectiveCopyQ1 = pattern.MustParse(`(UnaryExpr "&" (StarExpr obj))`) + checkIneffectiveCopyQ2 = pattern.MustParse(`(StarExpr (UnaryExpr "&" _))`) +) + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + if m, ok := code.Match(pass, checkIneffectiveCopyQ1, node); ok { + if ident, ok := m.State["obj"].(*ast.Ident); !ok || !cgoIdent.MatchString(ident.Name) { + report.Report(pass, node, "&*x will be simplified to x. It will not copy x.") + } + } else if _, ok := code.Match(pass, checkIneffectiveCopyQ2, node); ok { + report.Report(pass, node, "*&x will be simplified to x. It will not copy x.") + } + } + code.Preorder(pass, fn, (*ast.UnaryExpr)(nil), (*ast.StarExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go b/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go new file mode 100644 index 0000000..eb36c41 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4003/sa4003.go @@ -0,0 +1,224 @@ +package sa4003 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + "math" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4003", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Comparing unsigned values against negative values is pointless`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + isobj := func(expr ast.Expr, name string) bool { + if name == "" { + return false + } + sel, ok := expr.(*ast.SelectorExpr) + if !ok { + return false + } + return typeutil.IsObject(pass.TypesInfo.ObjectOf(sel.Sel), name) + } + + fn := func(node ast.Node) { + expr := node.(*ast.BinaryExpr) + tx := pass.TypesInfo.TypeOf(expr.X) + tset := typeutil.NewTypeSet(tx) + + // We only check for the math constants and integer literals, not for + // all constant expressions. This is to avoid + // false positives when constant values differ under different build tags. + var ( + maxMathConst string + minMathConst string + maxLiteral constant.Value + minLiteral constant.Value + ) + + allUnsigned := tset.All(func(t *types.Term) bool { + if basic, ok := t.Type().Underlying().(*types.Basic); ok { + return basic.Info()&types.IsUnsigned != 0 + } + return false + }) + + if allUnsigned { + isZeroLiteral := func(expr ast.Expr) bool { + return code.IsIntegerLiteral(pass, expr, constant.MakeInt64(0)) + } + if (expr.Op == token.LSS && isZeroLiteral(expr.Y)) || + (expr.Op == token.GTR && isZeroLiteral(expr.X)) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is less than 0", tx), + report.FilterGenerated(), + ) + } + if expr.Op == token.GEQ && isZeroLiteral(expr.Y) || + expr.Op == token.LEQ && isZeroLiteral(expr.X) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is >= 0", tx), + report.FilterGenerated(), + ) + } + } + + core := tset.CoreType() + if core == nil { + // All remaining checks are only relevant when the type set + // contains a single underlying type. + // + // If we had a 'var x uint8 | uint16', + // then the type checker wouldn't permit a check such as + // 'if x <= math.MaxUint16', because the constant cannot be converted to all + // types in the type set. + return + } + + basic, ok := core.(*types.Basic) + if !ok { + return + } + + switch basic.Kind() { + case types.Uint8: + maxMathConst = "math.MaxUint8" + minLiteral = constant.MakeUint64(0) + maxLiteral = constant.MakeUint64(math.MaxUint8) + case types.Uint16: + maxMathConst = "math.MaxUint16" + minLiteral = constant.MakeUint64(0) + maxLiteral = constant.MakeUint64(math.MaxUint16) + case types.Uint32: + maxMathConst = "math.MaxUint32" + minLiteral = constant.MakeUint64(0) + maxLiteral = constant.MakeUint64(math.MaxUint32) + case types.Uint64: + maxMathConst = "math.MaxUint64" + minLiteral = constant.MakeUint64(0) + maxLiteral = constant.MakeUint64(math.MaxUint64) + case types.Uint: + // TODO(dh): we could chose 32 bit vs 64 bit depending on the + // file's build tags + maxMathConst = "math.MaxUint64" + minLiteral = constant.MakeUint64(0) + maxLiteral = constant.MakeUint64(math.MaxUint64) + + case types.Int8: + minMathConst = "math.MinInt8" + maxMathConst = "math.MaxInt8" + minLiteral = constant.MakeInt64(math.MinInt8) + maxLiteral = constant.MakeInt64(math.MaxInt8) + case types.Int16: + minMathConst = "math.MinInt16" + maxMathConst = "math.MaxInt16" + minLiteral = constant.MakeInt64(math.MinInt16) + maxLiteral = constant.MakeInt64(math.MaxInt16) + case types.Int32: + minMathConst = "math.MinInt32" + maxMathConst = "math.MaxInt32" + minLiteral = constant.MakeInt64(math.MinInt32) + maxLiteral = constant.MakeInt64(math.MaxInt32) + case types.Int64: + minMathConst = "math.MinInt64" + maxMathConst = "math.MaxInt64" + minLiteral = constant.MakeInt64(math.MinInt64) + maxLiteral = constant.MakeInt64(math.MaxInt64) + case types.Int: + // TODO(dh): we could chose 32 bit vs 64 bit depending on the + // file's build tags + minMathConst = "math.MinInt64" + maxMathConst = "math.MaxInt64" + minLiteral = constant.MakeInt64(math.MinInt64) + maxLiteral = constant.MakeInt64(math.MaxInt64) + } + + isLiteral := func(expr ast.Expr, c constant.Value) bool { + if c == nil { + return false + } + return code.IsIntegerLiteral(pass, expr, c) + } + + x, y, op := expr.X, expr.Y, expr.Op + switch op { + case token.GEQ, token.GTR: + case token.LEQ: + x, y = y, x + op = token.GEQ + case token.LSS: + x, y = y, x + op = token.GTR + default: + return + } + + if isobj(y, maxMathConst) || isLiteral(y, maxLiteral) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is greater than %s", tx, maxMathConst), + report.FilterGenerated(), + ) + } + if op == token.GEQ && (isobj(x, maxMathConst) || isLiteral(x, maxLiteral)) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is <= %s", tx, maxMathConst), + report.FilterGenerated(), + ) + } + + if !allUnsigned { + if isobj(x, minMathConst) || isLiteral(x, minLiteral) { + report.Report( + pass, + expr, + fmt.Sprintf("no value of type %s is less than %s", tx, minMathConst), + report.FilterGenerated(), + ) + } + if op == token.GEQ && (isobj(y, minMathConst) || isLiteral(y, minLiteral)) { + report.Report( + pass, + expr, + fmt.Sprintf("every value of type %s is >= %s", tx, minMathConst), + report.FilterGenerated(), + ) + } + } + + } + code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4004/sa4004.go b/vendor/honnef.co/go/tools/staticcheck/sa4004/sa4004.go new file mode 100644 index 0000000..8f20350 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4004/sa4004.go @@ -0,0 +1,159 @@ +package sa4004 + +import ( + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4004", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `The loop exits unconditionally after one iteration`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // This check detects some, but not all unconditional loop exits. + // We give up in the following cases: + // + // - a goto anywhere in the loop. The goto might skip over our + // return, and we don't check that it doesn't. + // + // - any nested, unlabelled continue, even if it is in another + // loop or closure. + fn := func(node ast.Node) { + var body *ast.BlockStmt + switch fn := node.(type) { + case *ast.FuncDecl: + body = fn.Body + case *ast.FuncLit: + body = fn.Body + default: + lint.ExhaustiveTypeSwitch(node) + } + if body == nil { + return + } + labels := map[types.Object]ast.Stmt{} + ast.Inspect(body, func(node ast.Node) bool { + label, ok := node.(*ast.LabeledStmt) + if !ok { + return true + } + labels[pass.TypesInfo.ObjectOf(label.Label)] = label.Stmt + return true + }) + + ast.Inspect(body, func(node ast.Node) bool { + var loop ast.Node + var body *ast.BlockStmt + switch node := node.(type) { + case *ast.ForStmt: + body = node.Body + loop = node + case *ast.RangeStmt: + ok := typeutil.All(pass.TypesInfo.TypeOf(node.X), func(term *types.Term) bool { + switch term.Type().Underlying().(type) { + case *types.Slice, *types.Chan, *types.Basic, *types.Pointer, *types.Array: + return true + case *types.Map: + // looping once over a map is a valid pattern for + // getting an arbitrary element. + return false + case *types.Signature: + // we have no idea what semantics the function implements + return false + default: + lint.ExhaustiveTypeSwitch(term.Type().Underlying()) + return false + } + }) + if !ok { + return true + } + body = node.Body + loop = node + default: + return true + } + if len(body.List) < 2 { + // TODO(dh): is this check needed? when body.List < 2, + // then we can't find both an unconditional exit and a + // branching statement (if, ...). and we don't flag + // unconditional exits if there has been no branching + // in the loop body. + + // avoid flagging the somewhat common pattern of using + // a range loop to get the first element in a slice, + // or the first rune in a string. + return true + } + var unconditionalExit ast.Node + hasBranching := false + for _, stmt := range body.List { + switch stmt := stmt.(type) { + case *ast.BranchStmt: + switch stmt.Tok { + case token.BREAK: + if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { + unconditionalExit = stmt + } + case token.CONTINUE: + if stmt.Label == nil || labels[pass.TypesInfo.ObjectOf(stmt.Label)] == loop { + unconditionalExit = nil + return false + } + } + case *ast.ReturnStmt: + unconditionalExit = stmt + case *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.SwitchStmt, *ast.SelectStmt: + hasBranching = true + } + } + if unconditionalExit == nil || !hasBranching { + return false + } + ast.Inspect(body, func(node ast.Node) bool { + if branch, ok := node.(*ast.BranchStmt); ok { + + switch branch.Tok { + case token.GOTO: + unconditionalExit = nil + return false + case token.CONTINUE: + if branch.Label != nil && labels[pass.TypesInfo.ObjectOf(branch.Label)] != loop { + return true + } + unconditionalExit = nil + return false + } + } + return true + }) + if unconditionalExit != nil { + report.Report(pass, unconditionalExit, "the surrounding loop is unconditionally terminated") + } + return true + }) + } + code.Preorder(pass, fn, (*ast.FuncDecl)(nil), (*ast.FuncLit)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go b/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go new file mode 100644 index 0000000..c46a038 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4005/sa4005.go @@ -0,0 +1,134 @@ +package sa4005 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4005", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Field assignment that will never be observed. Did you mean to use a pointer receiver?`, + Since: "2021.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // The analysis only considers the receiver and its first level + // fields. It doesn't look at other parameters, nor at nested + // fields. + // + // The analysis does not detect all kinds of dead stores, only + // those of fields that are never read after the write. That is, + // we do not flag 'a.x = 1; a.x = 2; _ = a.x'. We might explore + // this again if we add support for SROA to go/ir and implement + // https://github.com/dominikh/go-tools/issues/191. + + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR) +fnLoop: + for _, fn := range irpkg.SrcFuncs { + if recv := fn.Signature.Recv(); recv == nil { + continue + } else if _, ok := recv.Type().Underlying().(*types.Struct); !ok { + continue + } + + recv := fn.Params[0] + refs := *recv.Referrers() + if len(refs) != 1 { + continue + } + store, ok := refs[0].(*ir.Store) + if !ok { + continue + } + alloc, ok := store.Addr.(*ir.Alloc) + if !ok || alloc.Heap { + continue + } + + reads := map[int][]ir.Instruction{} + writes := map[int][]ir.Instruction{} + for _, ref := range *alloc.Referrers() { + switch ref := ref.(type) { + case *ir.FieldAddr: + for _, refref := range *ref.Referrers() { + switch refref.(type) { + case *ir.Store: + writes[ref.Field] = append(writes[ref.Field], refref) + case *ir.Load: + reads[ref.Field] = append(reads[ref.Field], refref) + default: + // this should be safe… if the field address + // escapes, then alloc.Heap will be true. + // there should be no instructions left that, + // given this FieldAddr, without escaping, can + // effect a load or store. + continue + } + } + case *ir.Store: + // we could treat this as a store to every field, but + // we don't want to decide the semantics of partial + // struct initializers. should `v = t{x: 1}` also mark + // v.y as being written to? + if ref != store { + continue fnLoop + } + case *ir.Load: + // a load of the entire struct loads every field + for i := 0; i < recv.Type().Underlying().(*types.Struct).NumFields(); i++ { + reads[i] = append(reads[i], ref) + } + default: + continue fnLoop + } + } + + offset := func(instr ir.Instruction) int { + for i, other := range instr.Block().Instrs { + if instr == other { + return i + } + } + panic("couldn't find instruction in its block") + } + + for field, ws := range writes { + rs := reads[field] + wLoop: + for _, w := range ws { + for _, r := range rs { + if w.Block() == r.Block() { + if offset(r) > offset(w) { + // found a reachable read of our write + continue wLoop + } + } else if irutil.Reachable(w.Block(), r.Block()) { + // found a reachable read of our write + continue wLoop + } + } + fieldName := recv.Type().Underlying().(*types.Struct).Field(field).Name() + report.Report(pass, w, fmt.Sprintf("ineffective assignment to field %s.%s", recv.Type().(interface{ Obj() *types.TypeName }).Obj().Name(), fieldName)) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go b/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go new file mode 100644 index 0000000..0486500 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4006/sa4006.go @@ -0,0 +1,168 @@ +package sa4006 + +import ( + "fmt" + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4006", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + if irutil.IsExample(fn) { + continue + } + node := fn.Source() + if node == nil { + continue + } + if gen, ok := code.Generator(pass, node.Pos()); ok && gen == generated.Goyacc { + // Don't flag unused values in code generated by goyacc. + // There may be hundreds of those due to the way the state + // machine is constructed. + continue + } + + switchTags := map[ir.Value]struct{}{} + ast.Inspect(node, func(node ast.Node) bool { + s, ok := node.(*ast.SwitchStmt) + if !ok { + return true + } + v, _ := fn.ValueForExpr(s.Tag) + switchTags[v] = struct{}{} + return true + }) + + // OPT(dh): don't use a map, possibly use a bitset + var hasUse func(v ir.Value, seen map[ir.Value]struct{}) bool + hasUse = func(v ir.Value, seen map[ir.Value]struct{}) bool { + if _, ok := seen[v]; ok { + return false + } + if _, ok := switchTags[v]; ok { + return true + } + refs := v.Referrers() + if refs == nil { + // TODO investigate why refs can be nil + return true + } + for _, ref := range *refs { + switch ref := ref.(type) { + case *ir.Phi: + if seen == nil { + seen = map[ir.Value]struct{}{} + } + seen[v] = struct{}{} + if hasUse(ref, seen) { + return true + } + default: + return true + } + } + return false + } + + ast.Inspect(node, func(node ast.Node) bool { + inc, ok := node.(*ast.IncDecStmt) + if ok { + val, _ := fn.ValueForExpr(inc.X) + if val == nil { + return true + } + if _, ok := val.(*ir.Const); ok { + // a zero-valued constant, for example in 'foo := []string(nil)' + return true + } + if !hasUse(val, nil) { + report.Report(pass, inc, fmt.Sprintf("this value of %s is never used", inc.X)) + } + return true + } + + assign, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + if len(assign.Lhs) > 1 && len(assign.Rhs) == 1 { + // Either a function call with multiple return values, + // or a comma-ok assignment + + val, _ := fn.ValueForExpr(assign.Rhs[0]) + if val == nil { + return true + } + refs := val.Referrers() + if refs == nil { + return true + } + for _, ref := range *refs { + ex, ok := ref.(*ir.Extract) + if !ok { + continue + } + if !hasUse(ex, nil) { + lhs := assign.Lhs[ex.Index] + if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" { + continue + } + report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs)) + } + } + return true + } + for i, lhs := range assign.Lhs { + rhs := assign.Rhs[i] + if ident, ok := lhs.(*ast.Ident); !ok || ok && ident.Name == "_" { + continue + } + val, _ := fn.ValueForExpr(rhs) + if val == nil { + if assign.Tok != token.ASSIGN { // +=, *=, etc. + val, _ = fn.ValueForExpr(lhs) + } + if val == nil { + continue + } + } + + if _, ok := val.(*ir.Const); ok { + // a zero-valued constant, for example in 'foo := []string(nil)' + continue + } + if !hasUse(val, nil) { + report.Report(pass, assign, fmt.Sprintf("this value of %s is never used", lhs)) + } + } + return true + }) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go b/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go new file mode 100644 index 0000000..f6c89ee --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4008/sa4008.go @@ -0,0 +1,100 @@ +package sa4008 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4008", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `The variable in the loop condition never changes, are you incrementing the wrong variable?`, + Text: `For example: + + for i := 0; i < 10; j++ { ... } + +This may also occur when a loop can only execute once because of unconditional +control flow that terminates the loop. For example, when a loop body contains an +unconditional break, return, or panic: + + func f() { + panic("oops") + } + func g() { + for i := 0; i < 10; i++ { + // f unconditionally calls panic, which means "i" is + // never incremented. + f() + } + }`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + cb := func(node ast.Node) bool { + loop, ok := node.(*ast.ForStmt) + if !ok { + return true + } + if loop.Init == nil || loop.Cond == nil || loop.Post == nil { + return true + } + init, ok := loop.Init.(*ast.AssignStmt) + if !ok || len(init.Lhs) != 1 || len(init.Rhs) != 1 { + return true + } + cond, ok := loop.Cond.(*ast.BinaryExpr) + if !ok { + return true + } + x, ok := cond.X.(*ast.Ident) + if !ok { + return true + } + lhs, ok := init.Lhs[0].(*ast.Ident) + if !ok { + return true + } + if pass.TypesInfo.ObjectOf(x) != pass.TypesInfo.ObjectOf(lhs) { + return true + } + if _, ok := loop.Post.(*ast.IncDecStmt); !ok { + return true + } + + v, isAddr := fn.ValueForExpr(cond.X) + if v == nil || isAddr { + return true + } + switch v.(type) { + case *ir.Phi: + return true + case *ir.Load: + return true + } + report.Report(pass, cond, "variable in loop condition never changes") + + return true + } + if source := fn.Source(); source != nil { + ast.Inspect(source, cb) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go b/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go new file mode 100644 index 0000000..7c94d5e --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4009/sa4009.go @@ -0,0 +1,105 @@ +package sa4009 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4009", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `A function argument is overwritten before its first use`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + cb := func(node ast.Node) bool { + var typ *ast.FuncType + var body *ast.BlockStmt + switch fn := node.(type) { + case *ast.FuncDecl: + typ = fn.Type + body = fn.Body + case *ast.FuncLit: + typ = fn.Type + body = fn.Body + } + if body == nil { + return true + } + if len(typ.Params.List) == 0 { + return true + } + for _, field := range typ.Params.List { + for _, arg := range field.Names { + obj := pass.TypesInfo.ObjectOf(arg) + var irobj *ir.Parameter + for _, param := range fn.Params { + if param.Object() == obj { + irobj = param + break + } + } + if irobj == nil { + continue + } + refs := irobj.Referrers() + if refs == nil { + continue + } + if len(*refs) != 0 { + continue + } + + var assignment ast.Node + ast.Inspect(body, func(node ast.Node) bool { + if assignment != nil { + return false + } + assign, ok := node.(*ast.AssignStmt) + if !ok { + return true + } + for _, lhs := range assign.Lhs { + ident, ok := lhs.(*ast.Ident) + if !ok { + continue + } + if pass.TypesInfo.ObjectOf(ident) == obj { + assignment = assign + return false + } + } + return true + }) + if assignment != nil { + report.Report(pass, arg, fmt.Sprintf("argument %s is overwritten before first use", arg), + report.Related(assignment, fmt.Sprintf("assignment to %s", arg))) + } + } + } + return true + } + if source := fn.Source(); source != nil { + ast.Inspect(source, cb) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go b/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go new file mode 100644 index 0000000..f445aa7 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4010/sa4010.go @@ -0,0 +1,210 @@ +package sa4010 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4010", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `The result of \'append\' will never be observed anywhere`, + Text: `Calls to \'append\' produce a new slice value. When the result of +\'append\' is assigned to a variable that is never subsequently read, the +append operation may have an unintended effect.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + isAppend := func(ins ir.Value) bool { + call, ok := ins.(*ir.Call) + if !ok { + return false + } + if call.Call.IsInvoke() { + return false + } + if builtin, ok := call.Call.Value.(*ir.Builtin); !ok || builtin.Name() != "append" { + return false + } + return true + } + + // We have to be careful about aliasing. + // Multiple slices may refer to the same backing array, + // making appends observable even when we don't see the result of append be used anywhere. + // + // We will have to restrict ourselves to slices that have been allocated within the function, + // haven't been sliced, + // and haven't been passed anywhere that could retain them (such as function calls or memory stores). + // + // We check whether an append should be flagged in two steps. + // + // In the first step, we look at the data flow graph, starting in reverse from the argument to append, till we reach the root. + // This graph must only consist of the following instructions: + // + // - phi + // - slice + // - const nil + // - MakeSlice + // - Alloc + // - calls to append + // + // If this step succeeds, we look at all referrers of the values found in the first step, recursively. + // These referrers must either be in the set of values found in the first step + // or fulfill the same type requirements as step 1, with the exception of appends, which are forbidden. + // + // If both steps succeed then we know that the backing array hasn't been aliased in an observable manner. + // + // We could relax these restrictions by making use of additional information: + // - if we passed the slice to a function that doesn't retain the slice then we can still flag it + // - if a slice has been sliced but is dead afterwards, we can flag appends to the new slice + + // OPT(dh): We could cache the results of both validate functions. + // However, we only use these functions on values that we otherwise want to flag, which are very few. + // Not caching values hasn't increased the runtimes for the standard library nor k8s. + var validateArgument func(v ir.Value, seen map[ir.Value]struct{}) bool + validateArgument = func(v ir.Value, seen map[ir.Value]struct{}) bool { + if _, ok := seen[v]; ok { + // break cycle + return true + } + seen[v] = struct{}{} + switch v := v.(type) { + case *ir.Phi: + for _, edge := range v.Edges { + if !validateArgument(edge, seen) { + return false + } + } + return true + case *ir.Slice: + return validateArgument(v.X, seen) + case *ir.Const: + return true + case *ir.MakeSlice: + return true + case *ir.Alloc: + return true + case *ir.Call: + if isAppend(v) { + return validateArgument(v.Call.Args[0], seen) + } + return false + default: + return false + } + } + + var validateReferrers func(v ir.Value, seen map[ir.Instruction]struct{}) bool + validateReferrers = func(v ir.Value, seen map[ir.Instruction]struct{}) bool { + if refs := v.Referrers(); refs != nil { + for _, ref := range *refs { + if _, ok := seen[ref]; ok { + continue + } + + seen[ref] = struct{}{} + switch ref.(type) { + case *ir.Phi: + case *ir.Slice: + case *ir.MakeSlice: + case *ir.Alloc: + default: + return false + } + + if ref, ok := ref.(ir.Value); ok { + if !validateReferrers(ref, seen) { + return false + } + } + } + } + return true + } + + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + val, ok := ins.(ir.Value) + if !ok || !isAppend(val) { + continue + } + + isUsed := false + visited := map[ir.Instruction]bool{} + var walkRefs func(refs []ir.Instruction) + walkRefs = func(refs []ir.Instruction) { + loop: + for _, ref := range refs { + if visited[ref] { + continue + } + visited[ref] = true + switch ref := ref.(type) { + case *ir.Phi: + walkRefs(*ref.Referrers()) + case ir.Value: + if !isAppend(ref) { + isUsed = true + } else { + walkRefs(*ref.Referrers()) + } + case ir.Instruction: + isUsed = true + break loop + } + } + } + + refs := val.Referrers() + if refs == nil { + continue + } + walkRefs(*refs) + + if isUsed { + continue + } + + seen := map[ir.Value]struct{}{} + if !validateArgument(ins.(*ir.Call).Call.Args[0], seen) { + continue + } + + seen2 := map[ir.Instruction]struct{}{} + for k := range seen { + if k, ok := k.(ir.Instruction); ok { + seen2[k] = struct{}{} + } + } + seen2[ins] = struct{}{} + failed := false + for v := range seen { + if !validateReferrers(v, seen2) { + failed = true + break + } + } + if !failed { + report.Report(pass, ins, "this result of append is never used, except maybe in other appends") + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4011/sa4011.go b/vendor/honnef.co/go/tools/staticcheck/sa4011/sa4011.go new file mode 100644 index 0000000..7cfa2ea --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4011/sa4011.go @@ -0,0 +1,88 @@ +package sa4011 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4011", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Break statement with no effect. Did you mean to break out of an outer loop?`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + var body *ast.BlockStmt + switch node := node.(type) { + case *ast.ForStmt: + body = node.Body + case *ast.RangeStmt: + body = node.Body + default: + lint.ExhaustiveTypeSwitch(node) + } + for _, stmt := range body.List { + var blocks [][]ast.Stmt + switch stmt := stmt.(type) { + case *ast.SwitchStmt: + for _, c := range stmt.Body.List { + blocks = append(blocks, c.(*ast.CaseClause).Body) + } + case *ast.SelectStmt: + for _, c := range stmt.Body.List { + blocks = append(blocks, c.(*ast.CommClause).Body) + } + default: + continue + } + + for _, body := range blocks { + if len(body) == 0 { + continue + } + lasts := []ast.Stmt{body[len(body)-1]} + // TODO(dh): unfold all levels of nested block + // statements, not just a single level if statement + if ifs, ok := lasts[0].(*ast.IfStmt); ok { + if len(ifs.Body.List) == 0 { + continue + } + lasts[0] = ifs.Body.List[len(ifs.Body.List)-1] + + if block, ok := ifs.Else.(*ast.BlockStmt); ok { + if len(block.List) != 0 { + lasts = append(lasts, block.List[len(block.List)-1]) + } + } + } + for _, last := range lasts { + branch, ok := last.(*ast.BranchStmt) + if !ok || branch.Tok != token.BREAK || branch.Label != nil { + continue + } + report.Report(pass, branch, "ineffective break statement. Did you mean to break out of the outer loop?") + } + } + } + } + code.Preorder(pass, fn, (*ast.ForStmt)(nil), (*ast.RangeStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4012/sa4012.go b/vendor/honnef.co/go/tools/staticcheck/sa4012/sa4012.go new file mode 100644 index 0000000..4234a28 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4012/sa4012.go @@ -0,0 +1,51 @@ +package sa4012 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4012", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Comparing a value against NaN even though no value is equal to NaN`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + isNaN := func(v ir.Value) bool { + call, ok := v.(*ir.Call) + if !ok { + return false + } + return irutil.IsCallTo(call.Common(), "math.NaN") + } + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, block := range fn.Blocks { + for _, ins := range block.Instrs { + ins, ok := ins.(*ir.BinOp) + if !ok { + continue + } + if isNaN(irutil.Flatten(ins.X)) || isNaN(irutil.Flatten(ins.Y)) { + report.Report(pass, ins, "no value is equal to NaN, not even NaN itself") + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4013/sa4013.go b/vendor/honnef.co/go/tools/staticcheck/sa4013/sa4013.go new file mode 100644 index 0000000..1bd48f6 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4013/sa4013.go @@ -0,0 +1,40 @@ +package sa4013 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4013", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Negating a boolean twice (\'!!b\') is the same as writing \'b\'. This is either redundant, or a typo.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkDoubleNegationQ = pattern.MustParse(`(UnaryExpr "!" single@(UnaryExpr "!" x))`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkDoubleNegationQ) { + report.Report(pass, node, "negating a boolean twice has no effect; is this a typo?", report.Fixes( + edit.Fix("Turn into single negation", edit.ReplaceWithNode(pass.Fset, node, m.State["single"].(ast.Node))), + edit.Fix("Remove double negation", edit.ReplaceWithNode(pass.Fset, node, m.State["x"].(ast.Node))))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4014/sa4014.go b/vendor/honnef.co/go/tools/staticcheck/sa4014/sa4014.go new file mode 100644 index 0000000..de11648 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4014/sa4014.go @@ -0,0 +1,78 @@ +package sa4014 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4014", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `An if/else if chain has repeated conditions and no side-effects; if the condition didn't match the first time, it won't match the second time, either`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + seen := map[ast.Node]bool{} + + var collectConds func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) + collectConds = func(ifstmt *ast.IfStmt, conds []ast.Expr) ([]ast.Expr, bool) { + seen[ifstmt] = true + // Bail if any if-statement has an Init statement or side effects in its condition + if ifstmt.Init != nil { + return nil, false + } + if code.MayHaveSideEffects(pass, ifstmt.Cond, nil) { + return nil, false + } + + conds = append(conds, ifstmt.Cond) + if elsestmt, ok := ifstmt.Else.(*ast.IfStmt); ok { + return collectConds(elsestmt, conds) + } + return conds, true + } + fn := func(node ast.Node) { + ifstmt := node.(*ast.IfStmt) + if seen[ifstmt] { + // this if-statement is part of an if/else-if chain that we've already processed + return + } + if ifstmt.Else == nil { + // there can be at most one condition + return + } + conds, ok := collectConds(ifstmt, nil) + if !ok { + return + } + if len(conds) < 2 { + return + } + counts := map[string]int{} + for _, cond := range conds { + s := report.Render(pass, cond) + counts[s]++ + if counts[s] == 2 { + report.Report(pass, cond, "this condition occurs multiple times in this if/else if chain") + } + } + } + code.Preorder(pass, fn, (*ast.IfStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4015/sa4015.go b/vendor/honnef.co/go/tools/staticcheck/sa4015/sa4015.go new file mode 100644 index 0000000..d94a89d --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4015/sa4015.go @@ -0,0 +1,56 @@ +package sa4015 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4015", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(checkMathIntRules), + }, + Doc: &lint.RawDocumentation{ + Title: `Calling functions like \'math.Ceil\' on floats converted from integers doesn't do anything useful`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkMathIntRules = map[string]callcheck.Check{ + "math.Ceil": pointlessIntMath, + "math.Floor": pointlessIntMath, + "math.IsNaN": pointlessIntMath, + "math.Trunc": pointlessIntMath, + "math.IsInf": pointlessIntMath, +} + +func pointlessIntMath(call *callcheck.Call) { + if ConvertedFromInt(call.Args[0].Value) { + call.Invalid(fmt.Sprintf("calling %s on a converted integer is pointless", irutil.CallName(call.Instr.Common()))) + } +} + +func ConvertedFromInt(v callcheck.Value) bool { + conv, ok := v.Value.(*ir.Convert) + if !ok { + return false + } + return typeutil.NewTypeSet(conv.X.Type()).All(func(t *types.Term) bool { + b, ok := t.Type().Underlying().(*types.Basic) + return ok && b.Info()&types.IsInteger != 0 + }) +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4016/sa4016.go b/vendor/honnef.co/go/tools/staticcheck/sa4016/sa4016.go new file mode 100644 index 0000000..993ea65 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4016/sa4016.go @@ -0,0 +1,116 @@ +package sa4016 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4016", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Certain bitwise operations, such as \'x ^ 0\', do not do anything useful`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + binop := node.(*ast.BinaryExpr) + if !typeutil.All(pass.TypesInfo.TypeOf(binop), func(term *types.Term) bool { + b, ok := term.Type().Underlying().(*types.Basic) + if !ok { + return false + } + return (b.Info() & types.IsInteger) != 0 + }) { + return + } + switch binop.Op { + case token.AND, token.OR, token.XOR: + default: + // we do not flag shifts because too often, x<<0 is part + // of a pattern, x<<0, x<<8, x<<16, ... + return + } + if y, ok := binop.Y.(*ast.Ident); ok { + obj, ok := pass.TypesInfo.ObjectOf(y).(*types.Const) + if !ok { + return + } + if obj.Pkg() != pass.Pkg { + // identifier was dot-imported + return + } + if v, _ := constant.Int64Val(obj.Val()); v != 0 { + return + } + path, _ := astutil.PathEnclosingInterval(code.File(pass, obj), obj.Pos(), obj.Pos()) + if len(path) < 2 { + return + } + spec, ok := path[1].(*ast.ValueSpec) + if !ok { + return + } + if len(spec.Names) != 1 || len(spec.Values) != 1 { + // TODO(dh): we could support this + return + } + ident, ok := spec.Values[0].(*ast.Ident) + if !ok { + return + } + if !isIota(pass.TypesInfo.ObjectOf(ident)) { + return + } + switch binop.Op { + case token.AND: + report.Report(pass, node, + fmt.Sprintf("%s always equals 0; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.Y), report.Render(pass, binop.Y))) + case token.OR, token.XOR: + report.Report(pass, node, + fmt.Sprintf("%s always equals %s; %s is defined as iota and has value 0, maybe %s is meant to be 1 << iota?", report.Render(pass, binop), report.Render(pass, binop.X), report.Render(pass, binop.Y), report.Render(pass, binop.Y))) + } + } else if code.IsIntegerLiteral(pass, binop.Y, constant.MakeInt64(0)) { + switch binop.Op { + case token.AND: + report.Report(pass, node, fmt.Sprintf("%s always equals 0", report.Render(pass, binop))) + case token.OR, token.XOR: + report.Report(pass, node, fmt.Sprintf("%s always equals %s", report.Render(pass, binop), report.Render(pass, binop.X))) + } + } + } + code.Preorder(pass, fn, (*ast.BinaryExpr)(nil)) + return nil, nil +} + +func isIota(obj types.Object) bool { + if obj.Name() != "iota" { + return false + } + c, ok := obj.(*types.Const) + if !ok { + return false + } + return c.Pkg() == nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go b/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go new file mode 100644 index 0000000..8302249 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4017/sa4017.go @@ -0,0 +1,85 @@ +package sa4017 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/purity" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4017", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, purity.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Discarding the return values of a function without side effects, making the call pointless`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + pure := pass.ResultOf[purity.Analyzer].(purity.Result) + +fnLoop: + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + if code.IsInTest(pass, fn) { + params := fn.Signature.Params() + for param := range params.Variables() { + if typeutil.IsPointerToTypeWithName(param.Type(), "testing.B") { + // Ignore discarded pure functions in code related + // to benchmarks. Instead of matching BenchmarkFoo + // functions, we match any function accepting a + // *testing.B. Benchmarks sometimes call generic + // functions for doing the actual work, and + // checking for the parameter is a lot easier and + // faster than analyzing call trees. + continue fnLoop + } + } + } + + for _, b := range fn.Blocks { + for _, ins := range b.Instrs { + ins, ok := ins.(*ir.Call) + if !ok { + continue + } + refs := ins.Referrers() + if refs == nil || len(*refs) > 0 { + continue + } + + callee := ins.Common().StaticCallee() + if callee == nil { + continue + } + if callee.Object() == nil { + // TODO(dh): support anonymous functions + continue + } + if _, ok := pure[callee.Object().(*types.Func)]; ok { + if pass.Pkg.Path() == "fmt_test" && callee.Object().(*types.Func).FullName() == "fmt.Sprintf" { + // special case for benchmarks in the fmt package + continue + } + report.Report(pass, ins, fmt.Sprintf("%s doesn't have side effects and its return value is ignored", callee.Object().Name())) + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4018/sa4018.go b/vendor/honnef.co/go/tools/staticcheck/sa4018/sa4018.go new file mode 100644 index 0000000..e4c4999 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4018/sa4018.go @@ -0,0 +1,61 @@ +package sa4018 + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/facts/purity" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4018", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, purity.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Self-assignment of variables`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + pure := pass.ResultOf[purity.Analyzer].(purity.Result) + + fn := func(node ast.Node) { + assign := node.(*ast.AssignStmt) + if assign.Tok != token.ASSIGN || len(assign.Lhs) != len(assign.Rhs) { + return + } + for i, lhs := range assign.Lhs { + rhs := assign.Rhs[i] + if reflect.TypeOf(lhs) != reflect.TypeOf(rhs) { + continue + } + if code.MayHaveSideEffects(pass, lhs, pure) || code.MayHaveSideEffects(pass, rhs, pure) { + continue + } + + rlh := report.Render(pass, lhs) + rrh := report.Render(pass, rhs) + if rlh == rrh { + report.Report(pass, assign, fmt.Sprintf("self-assignment of %s to %s", rrh, rlh), report.FilterGenerated()) + } + } + } + code.Preorder(pass, fn, (*ast.AssignStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4019/sa4019.go b/vendor/honnef.co/go/tools/staticcheck/sa4019/sa4019.go new file mode 100644 index 0000000..dd4f0e6 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4019/sa4019.go @@ -0,0 +1,82 @@ +package sa4019 + +import ( + "fmt" + "go/ast" + "sort" + "strings" + + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4019", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Multiple, identical build constraints in the same file`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func buildTagsIdentical(s1, s2 []string) bool { + if len(s1) != len(s2) { + return false + } + s1s := make([]string, len(s1)) + copy(s1s, s1) + sort.Strings(s1s) + s2s := make([]string, len(s2)) + copy(s2s, s2) + sort.Strings(s2s) + for i, s := range s1s { + if s != s2s[i] { + return false + } + } + return true +} + +func run(pass *analysis.Pass) (any, error) { + for _, f := range pass.Files { + constraints := buildTags(f) + for i, constraint1 := range constraints { + for j, constraint2 := range constraints { + if i >= j { + continue + } + if buildTagsIdentical(constraint1, constraint2) { + msg := fmt.Sprintf("identical build constraints %q and %q", + strings.Join(constraint1, " "), + strings.Join(constraint2, " ")) + report.Report(pass, f, msg, report.FilterGenerated(), report.ShortRange()) + } + } + } + } + return nil, nil +} + +func buildTags(f *ast.File) [][]string { + var out [][]string + for line := range strings.SplitSeq(astutil.Preamble(f), "\n") { + if !strings.HasPrefix(line, "+build ") { + continue + } + line = strings.TrimSpace(strings.TrimPrefix(line, "+build ")) + fields := strings.Fields(line) + out = append(out, fields) + } + return out +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4020/sa4020.go b/vendor/honnef.co/go/tools/staticcheck/sa4020/sa4020.go new file mode 100644 index 0000000..8ff2fb3 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4020/sa4020.go @@ -0,0 +1,172 @@ +package sa4020 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/exp/typeparams" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4020", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Unreachable case clause in a type switch`, + Text: `In a type switch like the following + + type T struct{} + func (T) Read(b []byte) (int, error) { return 0, nil } + + var v any = T{} + + switch v.(type) { + case io.Reader: + // ... + case T: + // unreachable + } + +the second case clause can never be reached because \'T\' implements +\'io.Reader\' and case clauses are evaluated in source order. + +Another example: + + type T struct{} + func (T) Read(b []byte) (int, error) { return 0, nil } + func (T) Close() error { return nil } + + var v any = T{} + + switch v.(type) { + case io.Reader: + // ... + case io.ReadCloser: + // unreachable + } + +Even though \'T\' has a \'Close\' method and thus implements \'io.ReadCloser\', +\'io.Reader\' will always match first. The method set of \'io.Reader\' is a +subset of \'io.ReadCloser\'. Thus it is impossible to match the second +case without matching the first case. + + +Structurally equivalent interfaces + +A special case of the previous example are structurally identical +interfaces. Given these declarations + + type T error + type V error + + func doSomething() error { + err, ok := doAnotherThing() + if ok { + return T(err) + } + + return U(err) + } + +the following type switch will have an unreachable case clause: + + switch doSomething().(type) { + case T: + // ... + case V: + // unreachable + } + +\'T\' will always match before V because they are structurally equivalent +and therefore \'doSomething()\''s return value implements both.`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // Check if T subsumes V in a type switch. T subsumes V if T is an interface and T's method set is a subset of V's method set. + subsumes := func(T, V types.Type) bool { + if typeparams.IsTypeParam(T) { + return false + } + tIface, ok := T.Underlying().(*types.Interface) + if !ok { + return false + } + + return types.Implements(V, tIface) + } + + subsumesAny := func(Ts, Vs []types.Type) (types.Type, types.Type, bool) { + for _, T := range Ts { + for _, V := range Vs { + if subsumes(T, V) { + return T, V, true + } + } + } + + return nil, nil, false + } + + fn := func(node ast.Node) { + tsStmt := node.(*ast.TypeSwitchStmt) + + type ccAndTypes struct { + cc *ast.CaseClause + types []types.Type + } + + // All asserted types in the order of case clauses. + ccs := make([]ccAndTypes, 0, len(tsStmt.Body.List)) + for _, stmt := range tsStmt.Body.List { + cc, _ := stmt.(*ast.CaseClause) + + // Exclude the 'default' case. + if len(cc.List) == 0 { + continue + } + + Ts := make([]types.Type, 0, len(cc.List)) + for _, expr := range cc.List { + // Exclude the 'nil' value from any 'case' statement (it is always reachable). + if typ := pass.TypesInfo.TypeOf(expr); typ != types.Typ[types.UntypedNil] { + Ts = append(Ts, typ) + } + } + + ccs = append(ccs, ccAndTypes{cc: cc, types: Ts}) + } + + if len(ccs) <= 1 { + // Zero or one case clauses, nothing to check. + return + } + + // Check if case clauses following cc have types that are subsumed by cc. + for i, cc := range ccs[:len(ccs)-1] { + for _, next := range ccs[i+1:] { + if T, V, yes := subsumesAny(cc.types, next.types); yes { + report.Report(pass, next.cc, fmt.Sprintf("unreachable case clause: %s will always match before %s", T.String(), V.String()), + report.ShortRange()) + } + } + } + } + + code.Preorder(pass, fn, (*ast.TypeSwitchStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4021/sa4021.go b/vendor/honnef.co/go/tools/staticcheck/sa4021/sa4021.go new file mode 100644 index 0000000..6f2133c --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4021/sa4021.go @@ -0,0 +1,36 @@ +package sa4021 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4021", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `\"x = append(y)\" is equivalent to \"x = y\"`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var checkSingleArgAppendQ = pattern.MustParse(`(CallExpr (Builtin "append") [_])`) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, checkSingleArgAppendQ) { + report.Report(pass, node, "x = append(y) is equivalent to x = y", report.FilterGenerated()) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4022/sa4022.go b/vendor/honnef.co/go/tools/staticcheck/sa4022/sa4022.go new file mode 100644 index 0000000..efa32e4 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4022/sa4022.go @@ -0,0 +1,40 @@ +package sa4022 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4022", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Comparing the address of a variable against nil`, + Text: `Code such as \"if &x == nil\" is meaningless, because taking the address of a variable always yields a non-nil pointer.`, + Since: "2020.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var CheckAddressIsNilQ = pattern.MustParse( + `(BinaryExpr + (UnaryExpr "&" _) + (Or "==" "!=") + (Builtin "nil"))`) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, CheckAddressIsNilQ) { + report.Report(pass, node, "the address of a variable cannot be nil") + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go b/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go new file mode 100644 index 0000000..3f1e866 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4023/sa4023.go @@ -0,0 +1,211 @@ +package sa4023 + +import ( + "fmt" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/nilness" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/exp/typeparams" + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4023", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, nilness.Analysis}, + }, + Doc: &lint.RawDocumentation{ + Title: `Impossible comparison of interface value with untyped nil`, + Text: `Under the covers, interfaces are implemented as two elements, a +type T and a value V. V is a concrete value such as an int, +struct or pointer, never an interface itself, and has type T. For +instance, if we store the int value 3 in an interface, the +resulting interface value has, schematically, (T=int, V=3). The +value V is also known as the interface's dynamic value, since a +given interface variable might hold different values V (and +corresponding types T) during the execution of the program. + +An interface value is nil only if the V and T are both +unset, (T=nil, V is not set), In particular, a nil interface will +always hold a nil type. If we store a nil pointer of type *int +inside an interface value, the inner type will be *int regardless +of the value of the pointer: (T=*int, V=nil). Such an interface +value will therefore be non-nil even when the pointer value V +inside is nil. + +This situation can be confusing, and arises when a nil value is +stored inside an interface value such as an error return: + + func returnsError() error { + var p *MyError = nil + if bad() { + p = ErrBad + } + return p // Will always return a non-nil error. + } + +If all goes well, the function returns a nil p, so the return +value is an error interface value holding (T=*MyError, V=nil). +This means that if the caller compares the returned error to nil, +it will always look as if there was an error even if nothing bad +happened. To return a proper nil error to the caller, the +function must return an explicit nil: + + func returnsError() error { + if bad() { + return ErrBad + } + return nil + } + +It's a good idea for functions that return errors always to use +the error type in their signature (as we did above) rather than a +concrete type such as \'*MyError\', to help guarantee the error is +created correctly. As an example, \'os.Open\' returns an error even +though, if not nil, it's always of concrete type *os.PathError. + +Similar situations to those described here can arise whenever +interfaces are used. Just keep in mind that if any concrete value +has been stored in the interface, the interface will not be nil. +For more information, see The Laws of +Reflection at https://golang.org/doc/articles/laws_of_reflection.html. + +This text has been copied from +https://golang.org/doc/faq#nil_error, licensed under the Creative +Commons Attribution 3.0 License.`, + Since: "2020.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // TODO should this be MergeIfAll? + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // The comparison 'fn() == nil' can never be true if fn() returns + // an interface value and only returns typed nils. This is usually + // a mistake in the function itself, but all we can say for + // certain is that the comparison is pointless. + // + // Flag results if no untyped nils are being returned, but either + // known typed nils, or typed unknown nilness are being returned. + + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR) + nilnessRes := pass.ResultOf[nilness.Analysis].(*nilness.Result) + for _, fn := range irpkg.SrcFuncs { + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + binop, ok := instr.(*ir.BinOp) + if !ok || !(binop.Op == token.EQL || binop.Op == token.NEQ) { + continue + } + if !types.IsInterface(binop.X.Type()) || typeparams.IsTypeParam(binop.X.Type()) { + // TODO support swapped X and Y + continue + } + + k, ok := binop.Y.(*ir.Const) + if !ok || !k.IsNil() { + // if binop.X is an interface, then binop.Y can only be a + // Const if its untyped. A typed nil constant would first + // be passed to MakeInterface. + continue + } + + var idx int + var obj *types.Func + switch x := irutil.Flatten(binop.X).(type) { + case *ir.Call: + callee := x.Call.StaticCallee() + if callee == nil { + continue + } + obj, _ = callee.Object().(*types.Func) + idx = 0 + case *ir.Extract: + call, ok := irutil.Flatten(x.Tuple).(*ir.Call) + if !ok { + continue + } + callee := call.Call.StaticCallee() + if callee == nil { + continue + } + obj, _ = callee.Object().(*types.Func) + idx = x.Index + case *ir.MakeInterface: + var qualifier string + switch binop.Op { + case token.EQL: + qualifier = "never" + case token.NEQ: + qualifier = "always" + default: + panic("unreachable") + } + + terms, err := typeparams.NormalTerms(x.X.Type()) + if len(terms) == 0 || err != nil { + // Type is a type parameter with no type terms (or we + // couldn't determine the terms). Such a type _can_ be + // nil when put in an interface value. + continue + } + + if report.HasRange(x.X) { + report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), + report.Related(x.X, "the lhs of the comparison gets its value from here and has a concrete type")) + } else { + // we can't generate related information for this, so + // make the diagnostic itself slightly more useful + report.Report(pass, binop, + fmt.Sprintf("this comparison is %s true; the lhs of the comparison has been assigned a concretely typed value", + qualifier)) + } + continue + } + if obj == nil { + continue + } + + nillity := nilnessRes.Nilness(obj, idx) + if nillity.Outer == nilness.NeverNil && + !code.IsInTest(pass, binop) && + !irutil.IsTrivial(irpkg.Pkg.Prog.FuncValue(obj)) { + // Don't flag these comparisons in tests. Tests may be + // explicitly enforcing the invariant that a value isn't + // nil. + + var qualifier string + switch binop.Op { + case token.EQL: + qualifier = "never" + case token.NEQ: + qualifier = "always" + default: + panic("unreachable") + } + report.Report(pass, binop, fmt.Sprintf("this comparison is %s true", qualifier), + // TODO support swapped X and Y + report.Related(binop.X, + fmt.Sprintf("the lhs of the comparison is the %s return value of this function call", + report.Ordinal(idx+1))), + report.Related(obj, + fmt.Sprintf("%s never returns a nil interface value", typeutil.FuncName(obj)))) + } + } + } + } + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4024/sa4024.go b/vendor/honnef.co/go/tools/staticcheck/sa4024/sa4024.go new file mode 100644 index 0000000..a956050 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4024/sa4024.go @@ -0,0 +1,58 @@ +package sa4024 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4024", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Checking for impossible return value from a builtin function`, + Text: `Return values of the \'len\' and \'cap\' builtins cannot be negative. + +See https://golang.org/pkg/builtin/#len and https://golang.org/pkg/builtin/#cap. + +Example: + + if len(slice) < 0 { + fmt.Println("unreachable code") + }`, + Since: "2021.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var builtinLessThanZeroQ = pattern.MustParse(` + (Or + (BinaryExpr + (IntegerLiteral "0") + ">" + (CallExpr builtin@(Builtin (Or "len" "cap")) _)) + (BinaryExpr + (CallExpr builtin@(Builtin (Or "len" "cap")) _) + "<" + (IntegerLiteral "0"))) +`) + +func run(pass *analysis.Pass) (any, error) { + for node, matcher := range code.Matches(pass, builtinLessThanZeroQ) { + builtin := matcher.State["builtin"].(*ast.Ident) + report.Report(pass, node, fmt.Sprintf("builtin function %s does not return negative values", builtin.Name)) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4025/sa4025.go b/vendor/honnef.co/go/tools/staticcheck/sa4025/sa4025.go new file mode 100644 index 0000000..a8bc2e8 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4025/sa4025.go @@ -0,0 +1,69 @@ +package sa4025 + +import ( + "fmt" + "go/ast" + "go/constant" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4025", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: "Integer division of literals that results in zero", + Text: `When dividing two integer constants, the result will +also be an integer. Thus, a division such as \'2 / 3\' results in \'0\'. +This is true for all of the following examples: + + _ = 2 / 3 + const _ = 2 / 3 + const _ float64 = 2 / 3 + _ = float64(2 / 3) + +Staticcheck will flag such divisions if both sides of the division are +integer literals, as it is highly unlikely that the division was +intended to truncate to zero. Staticcheck will not flag integer +division involving named constants, to avoid noisy positives. +`, + Since: "2021.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var integerDivisionQ = pattern.MustParse(`(BinaryExpr (IntegerLiteral _) "/" (IntegerLiteral _))`) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, integerDivisionQ) { + val := constant.ToInt(pass.TypesInfo.Types[node.(ast.Expr)].Value) + if v, ok := constant.Uint64Val(val); ok && v == 0 { + report.Report(pass, node, fmt.Sprintf("the integer division '%s' results in zero", report.Render(pass, node))) + } + + // TODO: we could offer a suggested fix here, but I am not + // sure what it should be. There are many options to choose + // from. + + // Note: we experimented with flagging divisions that truncate + // (e.g. 4 / 3), but it ran into false positives in Go's + // 'time' package, which does this, deliberately: + // + // unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + // + // The check also found a real bug in other code, but I don't + // think we can outright ban this kind of division. + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4026/sa4026.go b/vendor/honnef.co/go/tools/staticcheck/sa4026/sa4026.go new file mode 100644 index 0000000..31681d1 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4026/sa4026.go @@ -0,0 +1,79 @@ +package sa4026 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4026", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: "Go constants cannot express negative zero", + Text: `In IEEE 754 floating point math, zero has a sign and can be positive +or negative. This can be useful in certain numerical code. + +Go constants, however, cannot express negative zero. This means that +the literals \'-0.0\' and \'0.0\' have the same ideal value (zero) and +will both represent positive zero at runtime. + +To explicitly and reliably create a negative zero, you can use the +\'math.Copysign\' function: \'math.Copysign(0, -1)\'.`, + Since: "2021.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var negativeZeroFloatQ = pattern.MustParse(` + (Or + (UnaryExpr + "-" + (BasicLit "FLOAT" "0.0")) + + (UnaryExpr + "-" + (CallExpr conv@(Object (Or "float32" "float64")) lit@(Or (BasicLit "INT" "0") (BasicLit "FLOAT" "0.0")))) + + (CallExpr + conv@(Object (Or "float32" "float64")) + (UnaryExpr "-" lit@(BasicLit "INT" "0"))))`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, negativeZeroFloatQ) { + if conv, ok := m.State["conv"].(*types.TypeName); ok { + var replacement string + // TODO(dh): how does this handle type aliases? + if conv.Name() == "float32" { + replacement = `float32(math.Copysign(0, -1))` + } else { + replacement = `math.Copysign(0, -1)` + } + report.Report(pass, node, + fmt.Sprintf("in Go, the floating-point expression '%s' is the same as '%s(%s)', it does not produce a negative zero", + report.Render(pass, node), + conv.Name(), + report.Render(pass, m.State["lit"])), + report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement)))) + } else { + const replacement = `math.Copysign(0, -1)` + report.Report(pass, node, + "in Go, the floating-point literal '-0.0' is the same as '0.0', it does not produce a negative zero", + report.Fixes(edit.Fix("Use math.Copysign to create negative zero", edit.ReplaceWithString(node, replacement)))) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4027/sa4027.go b/vendor/honnef.co/go/tools/staticcheck/sa4027/sa4027.go new file mode 100644 index 0000000..cb8c739 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4027/sa4027.go @@ -0,0 +1,57 @@ +package sa4027 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4027", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `\'(*net/url.URL).Query\' returns a copy, modifying it doesn't change the URL`, + Text: `\'(*net/url.URL).Query\' parses the current value of \'net/url.URL.RawQuery\' +and returns it as a map of type \'net/url.Values\'. Subsequent changes to +this map will not affect the URL unless the map gets encoded and +assigned to the URL's \'RawQuery\'. + +As a consequence, the following code pattern is an expensive no-op: +\'u.Query().Add(key, value)\'.`, + Since: "2021.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ineffectiveURLQueryAddQ = pattern.MustParse(`(CallExpr (SelectorExpr (CallExpr (SelectorExpr recv (Ident "Query")) []) (Ident meth)) _)`) + +func run(pass *analysis.Pass) (any, error) { + // TODO(dh): We could make this check more complex and detect + // pointless modifications of net/url.Values in general, but that + // requires us to get the state machine correct, else we'll cause + // false positives. + + for node, m := range code.Matches(pass, ineffectiveURLQueryAddQ) { + if !code.IsOfPointerToTypeWithName(pass, m.State["recv"].(ast.Expr), "net/url.URL") { + continue + } + switch m.State["meth"].(string) { + case "Add", "Del", "Set": + default: + continue + } + report.Report(pass, node, "(*net/url.URL).Query returns a copy, modifying it doesn't change the URL") + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4028/sa4028.go b/vendor/honnef.co/go/tools/staticcheck/sa4028/sa4028.go new file mode 100644 index 0000000..1873d7b --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4028/sa4028.go @@ -0,0 +1,35 @@ +package sa4028 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4028", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `\'x % 1\' is always zero`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, // MergeIfAny if we only flag literals, not named constants + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var moduloOneQ = pattern.MustParse(`(BinaryExpr _ "%" (IntegerLiteral "1"))`) + +func run(pass *analysis.Pass) (any, error) { + for node := range code.Matches(pass, moduloOneQ) { + report.Report(pass, node, "x % 1 is always zero") + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4029/sa4029.go b/vendor/honnef.co/go/tools/staticcheck/sa4029/sa4029.go new file mode 100644 index 0000000..de77e23 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4029/sa4029.go @@ -0,0 +1,80 @@ +package sa4029 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4029", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: "Ineffective attempt at sorting slice", + Text: ` +\'sort.Float64Slice\', \'sort.IntSlice\', and \'sort.StringSlice\' are +types, not functions. Doing \'x = sort.StringSlice(x)\' does nothing, +especially not sort any values. The correct usage is +\'sort.Sort(sort.StringSlice(x))\' or \'sort.StringSlice(x).Sort()\', +but there are more convenient helpers, namely \'sort.Float64s\', +\'sort.Ints\', and \'sort.Strings\'. +`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ineffectiveSortQ = pattern.MustParse(`(AssignStmt target@(Ident _) "=" (CallExpr typ@(Symbol (Or "sort.Float64Slice" "sort.IntSlice" "sort.StringSlice")) [target]))`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, ineffectiveSortQ) { + _, ok := types.Unalias(pass.TypesInfo.TypeOf(m.State["target"].(ast.Expr))).(*types.Slice) + if !ok { + // Avoid flagging 'x = sort.StringSlice(x)' where TypeOf(x) == sort.StringSlice + continue + } + + var alternative string + typeName := types.TypeString(types.Unalias(m.State["typ"].(*types.TypeName).Type()), nil) + switch typeName { + case "sort.Float64Slice": + alternative = "Float64s" + case "sort.IntSlice": + alternative = "Ints" + case "sort.StringSlice": + alternative = "Strings" + default: + panic(fmt.Sprintf("unreachable: %q", typeName)) + } + + r := &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.Ident{Name: "sort"}, + Sel: &ast.Ident{Name: alternative}, + }, + Args: []ast.Expr{m.State["target"].(ast.Expr)}, + } + + report.Report(pass, node, + fmt.Sprintf("%s is a type, not a function, and %s doesn't sort your values; consider using sort.%s instead", + typeName, + report.Render(pass, node.(*ast.AssignStmt).Rhs[0]), + alternative), + report.Fixes(edit.Fix(fmt.Sprintf("Replace with call to sort.%s", alternative), edit.ReplaceWithNode(pass.Fset, node, r)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4030/sa4030.go b/vendor/honnef.co/go/tools/staticcheck/sa4030/sa4030.go new file mode 100644 index 0000000..fad7c5b --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4030/sa4030.go @@ -0,0 +1,70 @@ +package sa4030 + +import ( + "fmt" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4030", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: "Ineffective attempt at generating random number", + Text: ` +Functions in the \'math/rand\' package that accept upper limits, such +as \'Intn\', generate random numbers in the half-open interval [0,n). In +other words, the generated numbers will be \'>= 0\' and \'< n\' – they +don't include \'n\'. \'rand.Intn(1)\' therefore doesn't generate \'0\' +or \'1\', it always generates \'0\'.`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ineffectiveRandIntQ = pattern.MustParse(` + (CallExpr + (Symbol + name@(Or + "math/rand.Int31n" + "math/rand.Int63n" + "math/rand.Intn" + "(*math/rand.Rand).Int31n" + "(*math/rand.Rand).Int63n" + "(*math/rand.Rand).Intn" + + "math/rand/v2.Int32N" + "math/rand/v2.Int64N" + "math/rand/v2.IntN" + "math/rand/v2.N" + "math/rand/v2.Uint32N" + "math/rand/v2.Uint64N" + "math/rand/v2.UintN" + + "(*math/rand/v2.Rand).Int32N" + "(*math/rand/v2.Rand).Int64N" + "(*math/rand/v2.Rand).IntN" + "(*math/rand/v2.Rand).Uint32N" + "(*math/rand/v2.Rand).Uint64N" + "(*math/rand/v2.Rand).UintN")) + [(IntegerLiteral "1")])`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, ineffectiveRandIntQ) { + report.Report(pass, node, + fmt.Sprintf("%s(n) generates a random value 0 <= x < n; that is, the generated values don't include n; %s therefore always returns 0", + m.State["name"], report.Render(pass, node))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go b/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go new file mode 100644 index 0000000..3da5ba0 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4031/sa4031.go @@ -0,0 +1,160 @@ +package sa4031 + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "sort" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/pattern" + "honnef.co/go/tools/staticcheck/sa4022" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4031", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Checking never-nil value against nil`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var allocationNilCheckQ = pattern.MustParse(`(IfStmt _ cond@(BinaryExpr lhs op@(Or "==" "!=") (Builtin "nil")) _ _)`) + +func run(pass *analysis.Pass) (any, error) { + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + + var path []ast.Node + fn := func(node ast.Node, stack []ast.Node) { + m, ok := code.Match(pass, allocationNilCheckQ, node) + if !ok { + return + } + cond := m.State["cond"].(ast.Node) + if _, ok := code.Match(pass, sa4022.CheckAddressIsNilQ, cond); ok { + // Don't duplicate diagnostics reported by SA4022 + return + } + lhs := m.State["lhs"].(ast.Expr) + path = path[:0] + for i := len(stack) - 1; i >= 0; i-- { + path = append(path, stack[i]) + } + irfn := ir.EnclosingFunction(irpkg, path) + if irfn == nil { + // For example for functions named "_", because we don't generate IR for them. + return + } + v, isAddr := irfn.ValueForExpr(lhs) + if isAddr { + return + } + + seen := map[ir.Value]struct{}{} + var values []ir.Value + var neverNil func(v ir.Value, track bool) bool + neverNil = func(v ir.Value, track bool) bool { + if _, ok := seen[v]; ok { + return true + } + seen[v] = struct{}{} + switch v := v.(type) { + case *ir.MakeClosure, *ir.Function: + if track { + values = append(values, v) + } + return true + case *ir.MakeChan, *ir.MakeMap, *ir.MakeSlice, *ir.Alloc: + if track { + values = append(values, v) + } + return true + case *ir.Slice: + if track { + values = append(values, v) + } + return neverNil(v.X, false) + case *ir.FieldAddr: + if track { + values = append(values, v) + } + return neverNil(v.X, false) + case *ir.Phi: + for _, e := range v.Edges { + if !neverNil(e, true) { + return false + } + } + return true + default: + return false + } + } + + if !neverNil(v, true) { + return + } + + var qualifier string + if op := m.State["op"].(token.Token); op == token.EQL { + qualifier = "never" + } else { + qualifier = "always" + } + fallback := fmt.Sprintf("this nil check is %s true", qualifier) + + sort.Slice(values, func(i, j int) bool { return values[i].Pos() < values[j].Pos() }) + + if ident, ok := m.State["lhs"].(*ast.Ident); ok { + if _, ok := pass.TypesInfo.ObjectOf(ident).(*types.Var); ok { + var opts []report.Option + if v.Parent() == irfn { + if len(values) == 1 { + opts = append(opts, report.Related(values[0], fmt.Sprintf("this is the value of %s", ident.Name))) + } else { + for _, vv := range values { + opts = append(opts, report.Related(vv, fmt.Sprintf("this is one of the value of %s", ident.Name))) + } + } + } + + switch v.(type) { + case *ir.MakeClosure, *ir.Function: + report.Report(pass, cond, "the checked variable contains a function and is never nil; did you mean to call it?", opts...) + default: + report.Report(pass, cond, fallback, opts...) + } + } else { + if _, ok := v.(*ir.Function); ok { + report.Report(pass, cond, "functions are never nil; did you mean to call it?") + } else { + report.Report(pass, cond, fallback) + } + } + } else { + if _, ok := v.(*ir.Function); ok { + report.Report(pass, cond, "functions are never nil; did you mean to call it?") + } else { + report.Report(pass, cond, fallback) + } + } + } + code.PreorderStack(pass, fn, (*ast.IfStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa4032/sa4032.go b/vendor/honnef.co/go/tools/staticcheck/sa4032/sa4032.go new file mode 100644 index 0000000..99012da --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa4032/sa4032.go @@ -0,0 +1,221 @@ +package sa4032 + +import ( + "fmt" + "go/ast" + "go/build/constraint" + "go/constant" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/pattern" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA4032", + Run: CheckImpossibleGOOSGOARCH, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Comparing \'runtime.GOOS\' or \'runtime.GOARCH\' against impossible value`, + Since: "2024.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + goosComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOOS") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`) + goarchComparisonQ = pattern.MustParse(`(BinaryExpr (Symbol "runtime.GOARCH") op@(Or "==" "!=") lit@(BasicLit "STRING" _))`) +) + +func CheckImpossibleGOOSGOARCH(pass *analysis.Pass) (any, error) { + // TODO(dh): validate GOOS and GOARCH together. that is, + // given '(linux && amd64) || (windows && mips)', + // flag 'if runtime.GOOS == "linux" && runtime.GOARCH == "mips"' + // + // We can't use our IR for the control flow graph, because go/types constant folds constant comparisons, so + // 'runtime.GOOS == "windows"' will just become 'false'. We can't use the AST-based CFG builder from x/tools, + // because it doesn't model branch conditions. + + if !code.CouldMatchAny(pass, goarchComparisonQ, goosComparisonQ) { + return nil, nil + } + + for _, f := range pass.Files { + expr, ok := code.BuildConstraints(pass, f) + if !ok { + continue + } + + ast.Inspect(f, func(node ast.Node) bool { + if m, ok := code.Match(pass, goosComparisonQ, node); ok { + tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)] + goos := constant.StringVal(tv.Value) + + if _, ok := knowledge.KnownGOOS[goos]; !ok { + // Don't try to reason about GOOS values we don't know about. Maybe the user is using a newer + // version of Go that supports a new target, or maybe they run a fork of Go. + return true + } + sat, ok := validateGOOSComparison(expr, goos) + if !ok { + return true + } + if !sat { + // Note that we do not have to worry about constraints that can never be satisfied, such as 'linux + // && windows'. Packages with such files will not be passed to Staticcheck in the first place, + // precisely because the constraints aren't satisfiable. + report.Report(pass, node, + fmt.Sprintf("due to the file's build constraints, runtime.GOOS will never equal %q", goos)) + } + } else if m, ok := code.Match(pass, goarchComparisonQ, node); ok { + tv := pass.TypesInfo.Types[m.State["lit"].(ast.Expr)] + goarch := constant.StringVal(tv.Value) + + if _, ok := knowledge.KnownGOARCH[goarch]; !ok { + // Don't try to reason about GOARCH values we don't know about. Maybe the user is using a newer + // version of Go that supports a new target, or maybe they run a fork of Go. + return true + } + sat, ok := validateGOARCHComparison(expr, goarch) + if !ok { + return true + } + if !sat { + // Note that we do not have to worry about constraints that can never be satisfied, such as 'amd64 + // && mips'. Packages with such files will not be passed to Staticcheck in the first place, + // precisely because the constraints aren't satisfiable. + report.Report(pass, node, + fmt.Sprintf("due to the file's build constraints, runtime.GOARCH will never equal %q", goarch)) + } + } + return true + }) + } + + return nil, nil +} +func validateGOOSComparison(expr constraint.Expr, goos string) (sat bool, didCheck bool) { + matchGoosTag := func(tag string, goos string) (ok bool, goosTag bool) { + switch tag { + case "aix", + "android", + "dragonfly", + "freebsd", + "hurd", + "illumos", + "ios", + "js", + "netbsd", + "openbsd", + "plan9", + "wasip1", + "windows": + return goos == tag, true + case "darwin": + return (goos == "darwin" || goos == "ios"), true + case "linux": + return (goos == "linux" || goos == "android"), true + case "solaris": + return (goos == "solaris" || goos == "illumos"), true + case "unix": + return (goos == "aix" || + goos == "android" || + goos == "darwin" || + goos == "dragonfly" || + goos == "freebsd" || + goos == "hurd" || + goos == "illumos" || + goos == "ios" || + goos == "linux" || + goos == "netbsd" || + goos == "openbsd" || + goos == "solaris"), true + default: + return false, false + } + } + + return validateTagComparison(expr, func(tag string) (matched bool, special bool) { + return matchGoosTag(tag, goos) + }) +} + +func validateGOARCHComparison(expr constraint.Expr, goarch string) (sat bool, didCheck bool) { + matchGoarchTag := func(tag string, goarch string) (ok bool, goosTag bool) { + switch tag { + case "386", + "amd64", + "arm", + "arm64", + "loong64", + "mips", + "mipsle", + "mips64", + "mips64le", + "ppc64", + "ppc64le", + "riscv64", + "s390x", + "sparc64", + "wasm": + return goarch == tag, true + default: + return false, false + } + } + + return validateTagComparison(expr, func(tag string) (matched bool, special bool) { + return matchGoarchTag(tag, goarch) + }) +} + +func validateTagComparison(expr constraint.Expr, matchSpecialTag func(tag string) (matched bool, special bool)) (sat bool, didCheck bool) { + otherTags := map[string]int{} + // Collect all tags that aren't known architecture-based tags + b := expr.Eval(func(tag string) bool { + ok, special := matchSpecialTag(tag) + if !special { + // Assign an ID to this tag, but only if we haven't seen it before. For the expression 'foo && foo', this + // callback will be called twice for the 'foo' tag. + if _, ok := otherTags[tag]; !ok { + otherTags[tag] = len(otherTags) + } + } + return ok + }) + + if b || len(otherTags) == 0 { + // We're done. Either the formula can be satisfied regardless of the values of non-special tags, if any, + // or there aren't any non-special tags and the formula cannot be satisfied. + return b, true + } + + if len(otherTags) > 10 { + // We have to try 2**len(otherTags) combinations of tags. 2**10 is about the worst we're willing to try. + return false, false + } + + // Try all permutations of otherTags. If any evaluates to true, then the expression is satisfiable. + for bits := 0; bits < 1< 0 && !strings.HasPrefix(tag, ",") { + // For better compatibility with v1, accept almost any unescaped name. + n := len(tag) - len(strings.TrimLeftFunc(tag, func(r rune) bool { + return !strings.ContainsRune(",\\'\"`", r) // reserve comma, backslash, and quotes + })) + name := tag[:n] + + // If the next character is not a comma, then the name is either + // malformed (if n > 0) or a single-quoted name. + // In either case, call consumeTagOption to handle it further. + var err error + if !strings.HasPrefix(tag[n:], ",") && len(name) != len(tag) { + name, n, err = consumeTagOption(tag) + if err != nil { + report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err)) + } + } + if !utf8.ValidString(name) { + report.Report(pass, field.Tag, + fmt.Sprintf("invalid UTF-8 in JSON object name %q", name)) + name = string([]rune(name)) // replace invalid UTF-8 with utf8.RuneError + } + if name == "-" && tag[0] == '-' { + // TODO(dh): offer automatic fix + report.Report(pass, field.Tag, + fmt.Sprintf("should encoding/json ignore this field or name it \"-\"? Either use `json:\"-\"` to ignore the field or use `json:\"'-'%s` to specify %q as the name", + strings.TrimPrefix(strconv.Quote(tagOrig), `"-`), name)) + } + tag = tag[n:] + } + + // Handle any additional tag options (if any). + var wasFormat bool + seenOpts := make(map[string]bool) + for len(tag) > 0 { + // Consume comma delimiter. + if tag[0] != ',' { + report.Report(pass, field.Tag, + fmt.Sprintf("malformed `json` tag: invalid character %q before next option (expecting ',')", + tag[0])) + } else { + tag = tag[len(","):] + if len(tag) == 0 { + report.Report(pass, field.Tag, "malformed `json` tag: invalid trailing ',' character") + break + } + } + + // Consume and process the tag option. + opt, n, err := consumeTagOption(tag) + if err != nil { + report.Report(pass, field.Tag, fmt.Sprintf("malformed `json` tag: %v", err)) + } + rawOpt := tag[:n] + tag = tag[n:] + switch { + case wasFormat: + report.Report(pass, field.Tag, "`format` tag option was not specified last") + case strings.HasPrefix(rawOpt, "'") && strings.TrimFunc(opt, isLetterOrDigit) == "": + // TODO(dh): offer automatic fix + report.Report(pass, field.Tag, + fmt.Sprintf("unnecessarily quoted appearance of `%s` tag option; specify `%s` instead", rawOpt, opt)) + } + switch opt { + case "case": + if !strings.HasPrefix(tag, ":") { + // TODO(dh): offer automatic fix + report.Report(pass, field.Tag, + "missing value for `case` tag option; specify `case:ignore` or `case:strict` instead") + break + } + tag = tag[len(":"):] + opt, n, err := consumeTagOption(tag) + if err != nil { + report.Report(pass, field.Tag, + fmt.Sprintf("malformed value for `case` tag option: %v", err)) + break + } + rawOpt := tag[:n] + tag = tag[n:] + if strings.HasPrefix(rawOpt, "'") { + // TODO(dh): offer automatic fix + report.Report(pass, field.Tag, + fmt.Sprintf("unnecessarily quoted appearance of `case:%s` tag option; specify `case:%s` instead", + rawOpt, opt)) + } + switch opt { + case "ignore": + case "strict": + default: + report.Report(pass, field.Tag, + fmt.Sprintf("invalid appearance of unknown `case:%s` tag value", rawOpt)) + } + case "embed", "inline": + case "unknown": + case "omitzero": + case "omitempty": + case "string": + const msg = "invalid appearance of `string` tag option; it is only intended for fields of numeric types or pointers to those" + tset := typeutil.NewTypeSet(pass.TypesInfo.TypeOf(field.Type)) + if len(tset.Terms) == 0 { + // TODO(dh): improve message, call out the use of type parameters + report.Report(pass, field.Tag, msg) + continue + } + for _, term := range tset.Terms { + T := typeutil.Dereference(term.Type().Underlying()) + for _, term2 := range typeutil.NewTypeSet(T).Terms { + basic, ok := term2.Type().Underlying().(*types.Basic) + // We accept bools and strings because v1 of encoding/json + // supports those. We don't mention that in the message, + // however, because their support is accidental, and v2 + // doesn't support it. + if !ok || (basic.Info()&(types.IsBoolean|types.IsInteger|types.IsFloat|types.IsString)) == 0 { + // TODO(dh): improve message, show how we arrived at the type + report.Report(pass, field.Tag, msg) + } + } + } + case "format": + if !strings.HasPrefix(tag, ":") { + report.Report(pass, field.Tag, "missing value for `format` tag option") + break + } + tag = tag[len(":"):] + _, n, err := consumeTagOption(tag) + if err != nil { + report.Report(pass, field.Tag, + fmt.Sprintf("malformed value for `format` tag option: %v", err)) + break + } + tag = tag[n:] + wasFormat = true + default: + // Reject keys that resemble one of the supported options. + // This catches invalid mutants such as "omitEmpty" or "omit_empty". + normOpt := strings.ReplaceAll(strings.ToLower(opt), "_", "") + switch normOpt { + case "case", "inline", "unknown", "omitzero", "omitempty", "string", "format": + report.Report(pass, field.Tag, + fmt.Sprintf("invalid appearance of `%s` tag option; specify `%s` instead", + opt, normOpt)) + default: + report.Report(pass, field.Tag, + fmt.Sprintf("invalid appearance of unknown `%s` tag option", opt)) + } + } + + // Reject duplicates. + if seenOpts[opt] { + report.Report(pass, field.Tag, + fmt.Sprintf("duplicate appearance of `%s` tag option", rawOpt)) + } + seenOpts[opt] = true + } + + if seenOpts["inline"] && seenOpts["unknown"] { + report.Report(pass, field.Tag, + "field cannot have both `inline` and `unknown` specified") + } + + // TODO(dh): implement more restrictions for types of inlined and unknown + // fields, including recursive restrictions: + // + // - Go struct field %s cannot have any options other than `inline` or `unknown` specified + // - inlined Go struct field %s of type %s with `unknown` tag must be a Go map of string key or a jsontext.Value + // - inlined Go struct field %s is not exported + // - inlined map field %s of type %s must have a string key that does not implement marshal or unmarshal methods + // - inlined Go struct field %s of type %s must be a Go struct, Go map of string key, or jsontext.Value +} + +// consumeTagOption consumes the next option, +// which is either a Go identifier or a single-quoted string. +// If the next option is invalid, it returns all of in until the next comma, +// and reports an error. +func consumeTagOption(in string) (string, int, error) { + // For legacy compatibility with v1, assume options are comma-separated. + i := strings.IndexByte(in, ',') + if i < 0 { + i = len(in) + } + + switch r, _ := utf8.DecodeRuneInString(in); { + // Option as a Go identifier. + case r == '_' || unicode.IsLetter(r): + n := len(in) - len(strings.TrimLeftFunc(in, isLetterOrDigit)) + return in[:n], n, nil + // Option as a single-quoted string. + case r == '\'': + // The grammar is nearly identical to a double-quoted Go string literal, + // but uses single quotes as the terminators. The reason for a custom + // grammar is because both backtick and double quotes cannot be used + // verbatim in a struct tag. + // + // Convert a single-quoted string to a double-quote string and rely on + // strconv.Unquote to handle the rest. + var inEscape bool + b := []byte{'"'} + n := len(`'`) + for len(in) > n { + r, rn := utf8.DecodeRuneInString(in[n:]) + switch { + case inEscape: + if r == '\'' { + b = b[:len(b)-1] // remove escape character: `\'` => `'` + } + inEscape = false + case r == '\\': + inEscape = true + case r == '"': + b = append(b, '\\') // insert escape character: `"` => `\"` + case r == '\'': + b = append(b, '"') + n += len(`'`) + out, err := strconv.Unquote(string(b)) + if err != nil { + return in[:i], i, fmt.Errorf("invalid single-quoted string: %s", in[:n]) + } + return out, n, nil + } + b = append(b, in[n:][:rn]...) + n += rn + } + if n > 10 { + n = 10 // limit the amount of context printed in the error + } + //lint:ignore ST1005 The ellipsis denotes truncated text + return in[:i], i, fmt.Errorf("single-quoted string not terminated: %s...", in[:n]) + case len(in) == 0: + return in[:i], i, io.ErrUnexpectedEOF + default: + return in[:i], i, fmt.Errorf("invalid character %q at start of option (expecting Unicode letter or single quote)", r) + } +} + +func isLetterOrDigit(r rune) bool { + return r == '_' || unicode.IsLetter(r) || unicode.IsNumber(r) +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa5008/sa5008.go b/vendor/honnef.co/go/tools/staticcheck/sa5008/sa5008.go new file mode 100644 index 0000000..2b3453f --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa5008/sa5008.go @@ -0,0 +1,130 @@ +package sa5008 + +import ( + "fmt" + "go/ast" + "go/types" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/staticcheck/fakereflect" + "honnef.co/go/tools/staticcheck/fakexml" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA5008", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid struct tag`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + importsGoFlags := false + + // we use the AST instead of (*types.Package).Imports to work + // around vendored packages in GOPATH mode. A vendored package's + // path will include the vendoring subtree as a prefix. + for _, f := range pass.Files { + for _, imp := range f.Imports { + v := imp.Path.Value + if v[1:len(v)-1] == "github.com/jessevdk/go-flags" { + importsGoFlags = true + break + } + } + } + + fn := func(node ast.Node) { + structNode := node.(*ast.StructType) + T := pass.TypesInfo.Types[structNode].Type.(*types.Struct) + rt := fakereflect.TypeAndCanAddr{ + Type: T, + } + for i, field := range structNode.Fields.List { + if field.Tag == nil { + continue + } + tags, err := parseStructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]) + if err != nil { + report.Report(pass, field.Tag, fmt.Sprintf("unparseable struct tag: %s", err)) + continue + } + for k, v := range tags { + if len(v) > 1 { + isGoFlagsTag := importsGoFlags && + (k == "choice" || k == "optional-value" || k == "default") + if !isGoFlagsTag { + report.Report(pass, field.Tag, fmt.Sprintf("duplicate struct tag %q", k)) + } + } + + switch k { + case "json": + checkJSONTag(pass, field, v[0]) + case "xml": + if _, err := fakexml.StructFieldInfo(rt.Field(i)); err != nil { + report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: %s", err)) + } + checkXMLTag(pass, field, v[0]) + } + } + } + } + code.Preorder(pass, fn, (*ast.StructType)(nil)) + return nil, nil +} + +func checkJSONTag(pass *analysis.Pass, field *ast.Field, tag string) { + if pass.Pkg.Path() == "encoding/json" || + pass.Pkg.Path() == "encoding/json_test" || + pass.Pkg.Path() == "encoding/json/v2" || + pass.Pkg.Path() == "encoding/json/v2_test" { + // don't flag malformed JSON tags in the encoding/json + // package; it knows what it is doing, and it is testing + // itself. + return + } + //lint:ignore SA9003 TODO(dh): should we flag empty tags? + if len(tag) == 0 { + } + + validateJSONTag(pass, field, tag) +} + +func checkXMLTag(pass *analysis.Pass, field *ast.Field, tag string) { + //lint:ignore SA9003 TODO(dh): should we flag empty tags? + if len(tag) == 0 { + } + fields := strings.Split(tag, ",") + counts := map[string]int{} + for _, s := range fields[1:] { + switch s { + case "attr", "chardata", "cdata", "innerxml", "comment": + counts[s]++ + case "omitempty", "any": + counts[s]++ + case "": + default: + report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: unknown option %q", s)) + } + } + for k, v := range counts { + if v > 1 { + report.Report(pass, field.Tag, fmt.Sprintf("invalid XML tag: duplicate option %q", k)) + } + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa5008/structtag.go b/vendor/honnef.co/go/tools/staticcheck/sa5008/structtag.go new file mode 100644 index 0000000..e8121e5 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa5008/structtag.go @@ -0,0 +1,58 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Copyright 2019 Dominik Honnef. All rights reserved. + +package sa5008 + +import "strconv" + +func parseStructTag(tag string) (map[string][]string, error) { + // FIXME(dh): detect missing closing quote + out := map[string][]string{} + + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + value, err := strconv.Unquote(qvalue) + if err != nil { + return nil, err + } + out[name] = append(out[name], value) + } + return out, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go b/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go new file mode 100644 index 0000000..7eaa095 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa5009/sa5009.go @@ -0,0 +1,409 @@ +package sa5009 + +import ( + "fmt" + "go/constant" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + "honnef.co/go/tools/printf" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA5009", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Invalid Printf call`, + Since: "2019.2", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +// TODO(dh): detect printf wrappers +var rules = map[string]callcheck.Check{ + "fmt.Errorf": func(call *callcheck.Call) { check(call, 0, 1) }, + "fmt.Printf": func(call *callcheck.Call) { check(call, 0, 1) }, + "fmt.Sprintf": func(call *callcheck.Call) { check(call, 0, 1) }, + "fmt.Fprintf": func(call *callcheck.Call) { check(call, 1, 2) }, + "golang.org/x/xerrors.Errorf": func(call *callcheck.Call) { check(call, 0, 1) }, +} + +type verbFlag int + +const ( + isInt verbFlag = 1 << iota + isBool + isFP + isString + isPointer + // Verbs that accept "pseudo pointers" will sometimes dereference + // non-nil pointers. For example, %x on a non-nil *struct will print the + // individual fields, but on a nil pointer it will print the address. + isPseudoPointer + isSlice + isAny + noRecurse +) + +var verbs = [...]verbFlag{ + 'b': isPseudoPointer | isInt | isFP, + 'c': isInt, + 'd': isPseudoPointer | isInt, + 'e': isFP, + 'E': isFP, + 'f': isFP, + 'F': isFP, + 'g': isFP, + 'G': isFP, + 'o': isPseudoPointer | isInt, + 'O': isPseudoPointer | isInt, + 'p': isSlice | isPointer | noRecurse, + 'q': isInt | isString, + 's': isString, + 't': isBool, + 'T': isAny, + 'U': isInt, + 'v': isAny, + 'X': isPseudoPointer | isInt | isFP | isString, + 'x': isPseudoPointer | isInt | isFP | isString, +} + +func check(call *callcheck.Call, fIdx, vIdx int) { + f := call.Args[fIdx] + var args []ir.Value + switch v := call.Args[vIdx].Value.Value.(type) { + case *ir.Slice: + var ok bool + args, ok = irutil.Vararg(v) + if !ok { + // We don't know what the actual arguments to the function are + return + } + case *ir.Const: + // nil, i.e. no arguments + default: + // We don't know what the actual arguments to the function are + return + } + checkImpl(f, f.Value.Value, args) +} + +func checkImpl(carg *callcheck.Argument, f ir.Value, args []ir.Value) { + var msCache *typeutil.MethodSetCache + if f.Parent() != nil { + msCache = &f.Parent().Prog.MethodSets + } + + elem := func(T types.Type, verb rune) ([]types.Type, bool) { + if verbs[verb]&noRecurse != 0 { + return []types.Type{T}, false + } + switch T := T.(type) { + case *types.Slice: + if verbs[verb]&isSlice != 0 { + return []types.Type{T}, false + } + if verbs[verb]&isString != 0 && types.Identical(T.Elem().Underlying(), types.Typ[types.Byte]) { + return []types.Type{T}, false + } + return []types.Type{T.Elem()}, true + case *types.Map: + key := T.Key() + val := T.Elem() + return []types.Type{key, val}, true + case *types.Struct: + out := make([]types.Type, 0, T.NumFields()) + for field := range T.Fields() { + out = append(out, field.Type()) + } + return out, true + case *types.Array: + return []types.Type{T.Elem()}, true + default: + return []types.Type{T}, false + } + } + isInfo := func(T types.Type, info types.BasicInfo) bool { + basic, ok := T.Underlying().(*types.Basic) + return ok && basic.Info()&info != 0 + } + + isFormatter := func(T types.Type, ms *types.MethodSet) bool { + sel := ms.Lookup(nil, "Format") + if sel == nil { + return false + } + fn, ok := sel.Obj().(*types.Func) + if !ok { + // should be unreachable + return false + } + sig := fn.Type().(*types.Signature) + if sig.Params().Len() != 2 { + return false + } + // TODO(dh): check the types of the arguments for more + // precision + if sig.Results().Len() != 0 { + return false + } + return true + } + + var seen typeutil.Map[struct{}] + var checkType func(verb rune, T types.Type, top bool) bool + checkType = func(verb rune, T types.Type, top bool) bool { + if top { + seen = typeutil.Map[struct{}]{} + } + if _, ok := seen.At(T); ok { + return true + } + seen.Set(T, struct{}{}) + if int(verb) >= len(verbs) { + // Unknown verb + return true + } + + flags := verbs[verb] + if flags == 0 { + // Unknown verb + return true + } + + ms := msCache.MethodSet(T) + if isFormatter(T, ms) { + // the value is responsible for formatting itself + return true + } + + if flags&isString != 0 && (types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"])) { + // Check for stringer early because we're about to dereference + return true + } + + T = T.Underlying() + if flags&(isPointer|isPseudoPointer) == 0 && top { + T = typeutil.Dereference(T) + } + if flags&isPseudoPointer != 0 && top { + t := typeutil.Dereference(T) + if _, ok := t.Underlying().(*types.Struct); ok { + T = t + } + } + + if _, ok := T.(*types.Interface); ok { + // We don't know what's in the interface + return true + } + + var info types.BasicInfo + if flags&isInt != 0 { + info |= types.IsInteger + } + if flags&isBool != 0 { + info |= types.IsBoolean + } + if flags&isFP != 0 { + info |= types.IsFloat | types.IsComplex + } + if flags&isString != 0 { + info |= types.IsString + } + + if info != 0 && isInfo(T, info) { + return true + } + + if flags&isString != 0 { + isStringyElem := func(typ types.Type) bool { + if typ, ok := typ.Underlying().(*types.Basic); ok { + return typ.Kind() == types.Byte + } + return false + } + switch T := T.(type) { + case *types.Slice: + if isStringyElem(T.Elem()) { + return true + } + case *types.Array: + if isStringyElem(T.Elem()) { + return true + } + } + if types.Implements(T, knowledge.Interfaces["fmt.Stringer"]) || types.Implements(T, knowledge.Interfaces["error"]) { + return true + } + } + + if flags&isPointer != 0 && typeutil.IsPointerLike(T) { + return true + } + if flags&isPseudoPointer != 0 { + switch U := T.Underlying().(type) { + case *types.Pointer: + if !top { + return true + } + + if _, ok := U.Elem().Underlying().(*types.Struct); !ok { + // TODO(dh): can this condition ever be false? For + // *T, if T is a struct, we'll already have + // dereferenced it, meaning the *types.Pointer + // branch couldn't have been taken. For T that + // aren't structs, this condition will always + // evaluate to true. + return true + } + case *types.Chan, *types.Signature: + // Channels and functions are always treated as + // pointers and never recursed into. + return true + case *types.Basic: + if U.Kind() == types.UnsafePointer { + return true + } + case *types.Interface: + // we will already have bailed if the type is an + // interface. + panic("unreachable") + default: + // other pointer-like types, such as maps or slices, + // will be printed element-wise. + } + } + + if flags&isSlice != 0 { + if _, ok := T.(*types.Slice); ok { + return true + } + } + + if flags&isAny != 0 { + return true + } + + elems, ok := elem(T.Underlying(), verb) + if !ok { + return false + } + for _, elem := range elems { + if !checkType(verb, elem, false) { + return false + } + } + + return true + } + + k, ok := irutil.Flatten(f).(*ir.Const) + if !ok { + return + } + actions, err := printf.Parse(constant.StringVal(k.Value)) + if err != nil { + carg.Invalid("couldn't parse format string") + return + } + + ptr := 1 + hasExplicit := false + + checkStar := func(verb printf.Verb, star printf.Argument) bool { + if star, ok := star.(printf.Star); ok { + idx := 0 + if star.Index == -1 { + idx = ptr + ptr++ + } else { + hasExplicit = true + idx = star.Index + ptr = star.Index + 1 + } + if idx == 0 { + carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw)) + return false + } + if idx > len(args) { + carg.Invalid( + fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args", + verb.Raw, idx, len(args))) + return false + } + if arg, ok := args[idx-1].(*ir.MakeInterface); ok { + if !isInfo(arg.X.Type(), types.IsInteger) { + carg.Invalid(fmt.Sprintf("Printf format %s reads non-int arg #%d as argument of *", verb.Raw, idx)) + } + } + } + return true + } + + // We only report one problem per format string. Making a + // mistake with an index tends to invalidate all future + // implicit indices. + for _, action := range actions { + verb, ok := action.(printf.Verb) + if !ok { + continue + } + + if !checkStar(verb, verb.Width) || !checkStar(verb, verb.Precision) { + return + } + + off := ptr + if verb.Value != -1 { + hasExplicit = true + off = verb.Value + } + if off > len(args) { + carg.Invalid( + fmt.Sprintf("Printf format %s reads arg #%d, but call has only %d args", + verb.Raw, off, len(args))) + return + } else if verb.Value == 0 && verb.Letter != '%' { + carg.Invalid(fmt.Sprintf("Printf format %s reads invalid arg 0; indices are 1-based", verb.Raw)) + return + } else if off != 0 { + arg, ok := args[off-1].(*ir.MakeInterface) + if ok { + if !checkType(verb.Letter, arg.X.Type(), true) { + carg.Invalid(fmt.Sprintf("Printf format %s has arg #%d of wrong type %s", + verb.Raw, ptr, args[ptr-1].(*ir.MakeInterface).X.Type())) + return + } + } + } + + switch verb.Value { + case -1: + // Consume next argument + ptr++ + case 0: + // Don't consume any arguments + default: + ptr = verb.Value + 1 + } + } + + if !hasExplicit && ptr <= len(args) { + carg.Invalid(fmt.Sprintf("Printf call needs %d args but has %d args", ptr-1, len(args))) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa5010/sa5010.go b/vendor/honnef.co/go/tools/staticcheck/sa5010/sa5010.go new file mode 100644 index 0000000..684d530 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa5010/sa5010.go @@ -0,0 +1,115 @@ +package sa5010 + +import ( + "fmt" + "go/types" + "strings" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA5010", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Impossible type assertion`, + + Text: `Some type assertions can be statically proven to be +impossible. This is the case when the method sets of both +arguments of the type assertion conflict with each other, for +example by containing the same method with different +signatures. + +The Go compiler already applies this check when asserting from an +interface value to a concrete type. If the concrete type misses +methods from the interface, or if function signatures don't match, +then the type assertion can never succeed. + +This check applies the same logic when asserting from one interface to +another. If both interface types contain the same method but with +different signatures, then the type assertion can never succeed, +either.`, + + Since: "2020.1", + Severity: lint.SeverityWarning, + // Technically this should be MergeIfAll, but the Go compiler + // already flags some impossible type assertions, so + // MergeIfAny is consistent with the compiler. + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + type entry struct { + l, r *types.Func + } + + msc := &pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg.Prog.MethodSets + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, b := range fn.Blocks { + instrLoop: + for _, instr := range b.Instrs { + assert, ok := instr.(*ir.TypeAssert) + if !ok { + continue + } + var wrong []entry + left := assert.X.Type() + right := assert.AssertedType + righti, ok := right.Underlying().(*types.Interface) + + if !ok { + // We only care about interface->interface + // assertions. The Go compiler already catches + // impossible interface->concrete assertions. + continue + } + + ms := msc.MethodSet(left) + for mr := range righti.Methods() { + sel := ms.Lookup(mr.Pkg(), mr.Name()) + if sel == nil { + continue + } + ml := sel.Obj().(*types.Func) + if ml.Origin() != ml || mr.Origin() != mr { + // Give up when we see generics. + // + // TODO(dh): support generics once go/types gets an + // exported API for type unification. + continue instrLoop + } + if types.AssignableTo(ml.Type(), mr.Type()) { + continue + } + + wrong = append(wrong, entry{ml, mr}) + } + + if len(wrong) != 0 { + var s strings.Builder + s.WriteString(fmt.Sprintf("impossible type assertion; %s and %s contradict each other:", + types.TypeString(left, types.RelativeTo(pass.Pkg)), + types.TypeString(right, types.RelativeTo(pass.Pkg)))) + for _, e := range wrong { + s.WriteString(fmt.Sprintf("\n\twrong type for %s method", e.l.Name())) + s.WriteString(fmt.Sprintf("\n\t\thave %s", e.l.Type())) + s.WriteString(fmt.Sprintf("\n\t\twant %s", e.r.Type())) + } + report.Report(pass, assert, s.String()) + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go b/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go new file mode 100644 index 0000000..51dc39f --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa5012/sa5012.go @@ -0,0 +1,287 @@ +package sa5012 + +import ( + "fmt" + "go/ast" + "go/constant" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/iterutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA5012", + Run: run, + FactTypes: []analysis.Fact{new(evenElements)}, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Passing odd-sized slice to function expecting even size", + Text: `Some functions that take slices as parameters expect the slices to have an even number of elements. +Often, these functions treat elements in a slice as pairs. +For example, \'strings.NewReplacer\' takes pairs of old and new strings, +and calling it with an odd number of elements would be an error.`, + Since: "2020.2", + Severity: lint.SeverityError, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +type evenElements struct{} + +func (evenElements) AFact() {} + +func (evenElements) String() string { return "needs even elements" } + +func findSliceLength(v ir.Value) int { + // TODO(dh): VRP would help here + + v = irutil.Flatten(v) + val := func(v ir.Value) int { + if v, ok := v.(*ir.Const); ok { + return int(v.Int64()) + } + return -1 + } + switch v := v.(type) { + case *ir.Slice: + low := 0 + high := -1 + if v.Low != nil { + low = val(v.Low) + } + if v.High != nil { + high = val(v.High) + } else { + switch vv := v.X.(type) { + case *ir.Alloc: + high = int(typeutil.Dereference(vv.Type()).Underlying().(*types.Array).Len()) + case *ir.Slice: + high = findSliceLength(vv) + } + } + if low == -1 || high == -1 { + return -1 + } + return high - low + default: + return -1 + } +} + +func flagSliceLens(pass *analysis.Pass) { + var tag evenElements + + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + call, ok := instr.(ir.CallInstruction) + if !ok { + continue + } + callee := call.Common().StaticCallee() + if callee == nil { + continue + } + for argi, arg := range call.Common().Args { + if callee.Signature.Recv() != nil { + if argi == 0 { + continue + } + argi-- + } + + _, ok := arg.Type().Underlying().(*types.Slice) + if !ok { + continue + } + param := callee.Signature.Params().At(argi) + if !pass.ImportObjectFact(param, &tag) { + continue + } + + // TODO handle stubs + + // we know the argument has to have even length. + // now let's try to find its length + if n := findSliceLength(arg); n > -1 && n%2 != 0 { + src := call.Source().(*ast.CallExpr).Args[argi] + sig := call.Common().Signature() + var label string + if argi == sig.Params().Len()-1 && sig.Variadic() { + label = "variadic argument" + } else { + label = "argument" + } + // Note that param.Name() is guaranteed to not + // be empty, otherwise the function couldn't + // have enforced its length. + report.Report(pass, src, fmt.Sprintf("%s %q is expected to have even number of elements, but has %d elements", label, param.Name(), n)) + } + } + } + } + } +} + +func findSliceLenChecks(pass *analysis.Pass) { + // mark all function parameters that have to be of even length + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, b := range fn.Blocks { + // all paths go through this block + if !iterutil.All(fn.Returns(), b.Dominates) { + continue + } + + // if foo % 2 != 0 + ifi, ok := b.Control().(*ir.If) + if !ok { + continue + } + cmp, ok := ifi.Cond.(*ir.BinOp) + if !ok { + continue + } + var needle uint64 + switch cmp.Op { + case token.NEQ: + // look for != 0 + needle = 0 + case token.EQL: + // look for == 1 + needle = 1 + default: + continue + } + + rem, ok1 := cmp.X.(*ir.BinOp) + k, ok2 := cmp.Y.(*ir.Const) + if ok1 != ok2 { + continue + } + if !ok1 { + rem, ok1 = cmp.Y.(*ir.BinOp) + k, ok2 = cmp.X.(*ir.Const) + } + if !ok1 || !ok2 || rem.Op != token.REM || k.Value.Kind() != constant.Int || k.Uint64() != needle { + continue + } + k, ok = rem.Y.(*ir.Const) + if !ok || k.Value.Kind() != constant.Int || k.Uint64() != 2 { + continue + } + + // if len(foo) % 2 != 0 + call, ok := rem.X.(*ir.Call) + if !ok || !irutil.IsCallTo(call.Common(), "len") { + continue + } + + // we're checking the length of a parameter that is a slice + // TODO(dh): support parameters that have flown through sigmas and phis + param, ok := call.Call.Args[0].(*ir.Parameter) + if !ok { + continue + } + if !typeutil.All(param.Type(), typeutil.IsSlice) { + continue + } + + // if len(foo) % 2 != 0 then panic + if _, ok := b.Succs[0].Control().(*ir.Panic); !ok { + continue + } + + pass.ExportObjectFact(param.Object(), new(evenElements)) + } + } +} + +func findIndirectSliceLenChecks(pass *analysis.Pass) { + seen := map[*ir.Function]struct{}{} + + var doFunction func(fn *ir.Function) + doFunction = func(fn *ir.Function) { + if _, ok := seen[fn]; ok { + return + } + seen[fn] = struct{}{} + + for _, b := range fn.Blocks { + // all paths go through this block + if !iterutil.All(fn.Returns(), b.Dominates) { + continue + } + + for _, instr := range b.Instrs { + call, ok := instr.(*ir.Call) + if !ok { + continue + } + callee := call.Call.StaticCallee() + if callee == nil { + continue + } + + if callee.Pkg == fn.Pkg || callee.Pkg == nil { + doFunction(callee) + } + + for argi, arg := range call.Call.Args { + if callee.Signature.Recv() != nil { + if argi == 0 { + continue + } + argi-- + } + + // TODO(dh): support parameters that have flown through length-preserving instructions + param, ok := arg.(*ir.Parameter) + if !ok { + continue + } + if !typeutil.All(param.Type(), typeutil.IsSlice) { + continue + } + + // We can't use callee.Params to look up the + // parameter, because Params is not populated for + // external functions. In our modular analysis. + // any function in any package that isn't the + // current package is considered "external", as it + // has been loaded from export data only. + sigParams := callee.Signature.Params() + + if !pass.ImportObjectFact(sigParams.At(argi), new(evenElements)) { + continue + } + pass.ExportObjectFact(param.Object(), new(evenElements)) + } + } + } + } + + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + doFunction(fn) + } +} + +func run(pass *analysis.Pass) (any, error) { + findSliceLenChecks(pass) + findIndirectSliceLenChecks(pass) + flagSliceLens(pass) + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6000/sa6000.go b/vendor/honnef.co/go/tools/staticcheck/sa6000/sa6000.go new file mode 100644 index 0000000..958cc8f --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6000/sa6000.go @@ -0,0 +1,57 @@ +package sa6000 + +import ( + "fmt" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6000", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Using \'regexp.Match\' or related in a loop, should use \'regexp.Compile\'`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "regexp.Match": check("regexp.Match"), + "regexp.MatchReader": check("regexp.MatchReader"), + "regexp.MatchString": check("regexp.MatchString"), +} + +func check(name string) callcheck.Check { + return func(call *callcheck.Call) { + if callcheck.ExtractConst(call.Args[0].Value) == nil { + return + } + if !isInLoop(call.Instr.Block()) { + return + } + call.Invalid(fmt.Sprintf("calling %s in a loop has poor performance, consider using regexp.Compile", name)) + } +} + +func isInLoop(b *ir.BasicBlock) bool { + sets := irutil.FindLoops(b.Parent()) + for _, set := range sets { + if set.Has(b) { + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go b/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go new file mode 100644 index 0000000..c164967 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6001/sa6001.go @@ -0,0 +1,115 @@ +package sa6001 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6001", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Missing an optimization opportunity when indexing maps by byte slices`, + + Text: `Map keys must be comparable, which precludes the use of byte slices. +This usually leads to using string keys and converting byte slices to +strings. + +Normally, a conversion of a byte slice to a string needs to copy the data and +causes allocations. The compiler, however, recognizes \'m[string(b)]\' and +uses the data of \'b\' directly, without copying it, because it knows that +the data can't change during the map lookup. This leads to the +counter-intuitive situation that + + k := string(b) + println(m[k]) + println(m[k]) + +will be less efficient than + + println(m[string(b)]) + println(m[string(b)]) + +because the first version needs to copy and allocate, while the second +one does not. + +For some history on this optimization, check out commit +f5f5a8b6209f84961687d993b93ea0d397f5d5bf in the Go repository.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, b := range fn.Blocks { + insLoop: + for _, ins := range b.Instrs { + var fromType types.Type + var toType types.Type + + // find []byte -> string conversions + switch ins := ins.(type) { + case *ir.Convert: + fromType = ins.X.Type() + toType = ins.Type() + case *ir.MultiConvert: + fromType = ins.X.Type() + toType = ins.Type() + default: + continue + } + if toType != types.Universe.Lookup("string").Type() { + continue + } + tset := typeutil.NewTypeSet(fromType) + // If at least one of the types is []byte, then it's more efficient to inline the conversion + if !tset.Any(func(term *types.Term) bool { + s, ok := term.Type().Underlying().(*types.Slice) + return ok && s.Elem().Underlying() == types.Universe.Lookup("byte").Type() + }) { + continue + } + refs := ins.Referrers() + anyUses := false + for _, ref := range *refs { + switch ref := ref.(type) { + case *ir.MapLookup: + anyUses = true + if src, ok := ref.Source().(*ast.IndexExpr); ok { + if _, ok := src.Index.(*ast.Ident); !ok { + continue insLoop + } + } else { + continue insLoop + } + default: + // the string is used somewhere else than a + // map lookup + continue insLoop + } + } + + if !anyUses { + continue + } + report.Report(pass, ins, "m[string(key)] would be more efficient than k := string(key); m[k]") + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go b/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go new file mode 100644 index 0000000..91ec730 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6002/sa6002.go @@ -0,0 +1,52 @@ +package sa6002 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6002", + Requires: []*analysis.Analyzer{buildir.Analyzer}, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Storing non-pointer values in \'sync.Pool\' allocates memory`, + Text: `A \'sync.Pool\' is used to avoid unnecessary allocations and reduce the +amount of work the garbage collector has to do. + +When passing a value that is not a pointer to a function that accepts +an interface, the value needs to be placed on the heap, which means an +additional allocation. Slices are a common thing to put in sync.Pools, +and they're structs with 3 fields (length, capacity, and a pointer to +an array). In order to avoid the extra allocation, one should store a +pointer to the slice instead. + +See the comments on https://go-review.googlesource.com/c/go/+/24371 +that discuss this problem.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + "(*sync.Pool).Put": func(call *callcheck.Call) { + arg := call.Args[knowledge.Arg("(*sync.Pool).Put.x")] + typ := arg.Value.Value.Type() + _, isSlice := typ.Underlying().(*types.Slice) + if !typeutil.IsPointerLike(typ) || isSlice { + arg.Invalid("argument should be pointer-like to avoid allocations") + } + }, +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6003/sa6003.go b/vendor/honnef.co/go/tools/staticcheck/sa6003/sa6003.go new file mode 100644 index 0000000..271ee9f --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6003/sa6003.go @@ -0,0 +1,42 @@ +package sa6003 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/internal/sharedcheck" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6003", + Run: sharedcheck.CheckRangeStringRunes, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Converting a string to a slice of runes before ranging over it`, + Text: `You may want to loop over the runes in a string. Instead of converting +the string to a slice of runes and looping over that, you can loop +over the string itself. That is, + + for _, r := range s {} + +and + + for _, r := range []rune(s) {} + +will yield the same values. The first version, however, will be faster +and avoid unnecessary memory allocations. + +Do note that if you are interested in the indices, ranging over a +string and over a slice of runes will yield different indices. The +first one yields byte offsets, while the second one yields indices in +the slice of runes.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go b/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go new file mode 100644 index 0000000..05cfc1c --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6005/sa6005.go @@ -0,0 +1,78 @@ +package sa6005 + +import ( + "fmt" + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6005", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Inefficient string comparison with \'strings.ToLower\' or \'strings.ToUpper\'`, + Text: `Converting two strings to the same case and comparing them like so + + if strings.ToLower(s1) == strings.ToLower(s2) { + ... + } + +is significantly more expensive than comparing them with +\'strings.EqualFold(s1, s2)\'. This is due to memory usage as well as +computational complexity. + +\'strings.ToLower\' will have to allocate memory for the new strings, as +well as convert both strings fully, even if they differ on the very +first byte. strings.EqualFold, on the other hand, compares the strings +one character at a time. It doesn't need to create two intermediate +strings and can return as soon as the first non-matching character has +been found. + +For a more in-depth explanation of this issue, see +https://blog.digitalocean.com/how-to-efficiently-compare-strings-in-go/`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkToLowerToUpperComparisonQ = pattern.MustParse(` + (BinaryExpr + (CallExpr fun@(Symbol (Or "strings.ToLower" "strings.ToUpper")) [a]) + tok@(Or "==" "!=") + (CallExpr fun [b]))`) + checkToLowerToUpperComparisonR = pattern.MustParse(`(CallExpr (SelectorExpr (Ident "strings") (Ident "EqualFold")) [a b])`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkToLowerToUpperComparisonQ) { + rn := pattern.NodeToAST(checkToLowerToUpperComparisonR.Root, m.State).(ast.Expr) + method := "strings.EqualFold" + if m.State["tok"].(token.Token) == token.NEQ { + rn = &ast.UnaryExpr{ + Op: token.NOT, + X: rn, + } + method = "!" + method + } + + report.Report(pass, node, + fmt.Sprintf("should use %s instead", method), + report.Fixes(edit.Fix("replace with "+method, edit.ReplaceWithNode(pass.Fset, node, rn)))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa6006/sa6006.go b/vendor/honnef.co/go/tools/staticcheck/sa6006/sa6006.go new file mode 100644 index 0000000..bc56848 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa6006/sa6006.go @@ -0,0 +1,46 @@ +package sa6006 + +import ( + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA6006", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Using io.WriteString to write \'[]byte\'`, + Text: `Using io.WriteString to write a slice of bytes, as in + + io.WriteString(w, string(b)) + +is both unnecessary and inefficient. Converting from \'[]byte\' to \'string\' +has to allocate and copy the data, and we could simply use \'w.Write(b)\' +instead.`, + + Since: "2024.1", + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ioWriteStringConversion = pattern.MustParse(`(CallExpr (Symbol "io.WriteString") [_ (CallExpr (Builtin "string") [arg])])`) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, ioWriteStringConversion) { + if !code.IsOfStringConvertibleByteSlice(pass, m.State["arg"].(ast.Expr)) { + continue + } + report.Report(pass, node, "use io.Writer.Write instead of converting from []byte to string to use io.WriteString") + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9001/sa9001.go b/vendor/honnef.co/go/tools/staticcheck/sa9001/sa9001.go new file mode 100644 index 0000000..2be35c0 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9001/sa9001.go @@ -0,0 +1,69 @@ +package sa9001 + +import ( + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9001", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Defers in range loops may not run when you expect them to`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + loop := node.(*ast.RangeStmt) + typ := pass.TypesInfo.TypeOf(loop.X) + _, ok := typeutil.CoreType(typ).(*types.Chan) + if !ok { + return + } + + stmts := []*ast.DeferStmt{} + exits := false + fn2 := func(node ast.Node) bool { + switch stmt := node.(type) { + case *ast.DeferStmt: + stmts = append(stmts, stmt) + case *ast.FuncLit: + // Don't look into function bodies + return false + case *ast.ReturnStmt: + exits = true + case *ast.BranchStmt: + exits = node.(*ast.BranchStmt).Tok == token.BREAK + } + return true + } + ast.Inspect(loop.Body, fn2) + + if exits { + return + } + for _, stmt := range stmts { + report.Report(pass, stmt, "defers in this range loop won't run unless the channel gets closed") + } + } + code.Preorder(pass, fn, (*ast.RangeStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9002/sa9002.go b/vendor/honnef.co/go/tools/staticcheck/sa9002/sa9002.go new file mode 100644 index 0000000..4923bae --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9002/sa9002.go @@ -0,0 +1,63 @@ +package sa9002 + +import ( + "fmt" + "go/ast" + "strconv" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9002", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Using a non-octal \'os.FileMode\' that looks like it was meant to be in octal.`, + Since: "2017.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + call := node.(*ast.CallExpr) + for _, arg := range call.Args { + lit, ok := arg.(*ast.BasicLit) + if !ok { + continue + } + if !typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "os.FileMode") && + !typeutil.IsTypeWithName(pass.TypesInfo.TypeOf(lit), "io/fs.FileMode") { + continue + } + if len(lit.Value) == 3 && + lit.Value[0] != '0' && + lit.Value[0] >= '0' && lit.Value[0] <= '7' && + lit.Value[1] >= '0' && lit.Value[1] <= '7' && + lit.Value[2] >= '0' && lit.Value[2] <= '7' { + + v, err := strconv.ParseInt(lit.Value, 10, 64) + if err != nil { + continue + } + report.Report(pass, arg, fmt.Sprintf("file mode '%s' evaluates to %#o; did you mean '0%s'?", lit.Value, v, lit.Value), + report.Fixes(edit.Fix("Fix octal literal", edit.ReplaceWithString(arg, "0"+lit.Value)))) + } + } + } + code.Preorder(pass, fn, (*ast.CallExpr)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9003/sa9003.go b/vendor/honnef.co/go/tools/staticcheck/sa9003/sa9003.go new file mode 100644 index 0000000..0824eb4 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9003/sa9003.go @@ -0,0 +1,62 @@ +package sa9003 + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9003", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Empty body in an if or else branch`, + Since: "2017.1", + NonDefault: true, + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + if fn.Source() == nil { + continue + } + if irutil.IsExample(fn) { + continue + } + cb := func(node ast.Node) bool { + ifstmt, ok := node.(*ast.IfStmt) + if !ok { + return true + } + if ifstmt.Else != nil { + b, ok := ifstmt.Else.(*ast.BlockStmt) + if !ok || len(b.List) != 0 { + return true + } + report.Report(pass, ifstmt.Else, "empty branch", report.FilterGenerated(), report.ShortRange()) + } + if len(ifstmt.Body.List) != 0 { + return true + } + report.Report(pass, ifstmt, "empty branch", report.FilterGenerated(), report.ShortRange()) + return true + } + if source := fn.Source(); source != nil { + ast.Inspect(source, cb) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9004/sa9004.go b/vendor/honnef.co/go/tools/staticcheck/sa9004/sa9004.go new file mode 100644 index 0000000..acf242e --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9004/sa9004.go @@ -0,0 +1,185 @@ +package sa9004 + +import ( + "go/ast" + "go/token" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9004", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Only the first constant has an explicit type`, + + Text: `In a constant declaration such as the following: + + const ( + First byte = 1 + Second = 2 + ) + +the constant Second does not have the same type as the constant First. +This construct shouldn't be confused with + + const ( + First byte = iota + Second + ) + +where \'First\' and \'Second\' do indeed have the same type. The type is only +passed on when no explicit value is assigned to the constant. + +When declaring enumerations with explicit values it is therefore +important not to write + + const ( + EnumFirst EnumType = 1 + EnumSecond = 2 + EnumThird = 3 + ) + +This discrepancy in types can cause various confusing behaviors and +bugs. + + +Wrong type in variable declarations + +The most obvious issue with such incorrect enumerations expresses +itself as a compile error: + + package pkg + + const ( + EnumFirst uint8 = 1 + EnumSecond = 2 + ) + + func fn(useFirst bool) { + x := EnumSecond + if useFirst { + x = EnumFirst + } + } + +fails to compile with + + ./const.go:11:5: cannot use EnumFirst (type uint8) as type int in assignment + + +Losing method sets + +A more subtle issue occurs with types that have methods and optional +interfaces. Consider the following: + + package main + + import "fmt" + + type Enum int + + func (e Enum) String() string { + return "an enum" + } + + const ( + EnumFirst Enum = 1 + EnumSecond = 2 + ) + + func main() { + fmt.Println(EnumFirst) + fmt.Println(EnumSecond) + } + +This code will output + + an enum + 2 + +as \'EnumSecond\' has no explicit type, and thus defaults to \'int\'.`, + Since: "2019.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + decl := node.(*ast.GenDecl) + if !decl.Lparen.IsValid() { + return + } + if decl.Tok != token.CONST { + return + } + + groups := astutil.GroupSpecs(pass.Fset, decl.Specs) + groupLoop: + for _, group := range groups { + if len(group) < 2 { + continue + } + if group[0].(*ast.ValueSpec).Type == nil { + // first constant doesn't have a type + continue groupLoop + } + + firstType := pass.TypesInfo.TypeOf(group[0].(*ast.ValueSpec).Values[0]) + for i, spec := range group { + spec := spec.(*ast.ValueSpec) + if i > 0 && spec.Type != nil { + continue groupLoop + } + if len(spec.Names) != 1 || len(spec.Values) != 1 { + continue groupLoop + } + + if !types.ConvertibleTo(pass.TypesInfo.TypeOf(spec.Values[0]), firstType) { + continue groupLoop + } + + switch v := spec.Values[0].(type) { + case *ast.BasicLit: + case *ast.UnaryExpr: + if _, ok := v.X.(*ast.BasicLit); !ok { + continue groupLoop + } + default: + // if it's not a literal it might be typed, such as + // time.Microsecond = 1000 * Nanosecond + continue groupLoop + } + } + var edits []analysis.TextEdit + typ := group[0].(*ast.ValueSpec).Type + for _, spec := range group[1:] { + nspec := *spec.(*ast.ValueSpec) + nspec.Type = typ + // The position of `spec` node excludes comments (if any). + // However, on generating the source back from the node, the comments are included. Setting `Comment` to nil ensures deduplication of comments. + nspec.Comment = nil + edits = append(edits, edit.ReplaceWithNode(pass.Fset, spec, &nspec)) + } + report.Report(pass, group[0], + "only the first constant in this group has an explicit type", + report.Fixes(edit.Fix("Add type to all constants in group", edits...))) + } + } + code.Preorder(pass, fn, (*ast.GenDecl)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go b/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go new file mode 100644 index 0000000..d367ecd --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9005/sa9005.go @@ -0,0 +1,96 @@ +package sa9005 + +import ( + "fmt" + "go/types" + + "honnef.co/go/tools/analysis/callcheck" + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/knowledge" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9005", + Requires: []*analysis.Analyzer{ + buildir.Analyzer, + // Filtering generated code because it may include empty structs generated from data models. + generated.Analyzer, + }, + Run: callcheck.Analyzer(rules), + }, + Doc: &lint.RawDocumentation{ + Title: `Trying to marshal a struct with no public fields nor custom marshaling`, + Text: ` +The \'encoding/json\' and \'encoding/xml\' packages only operate on exported +fields in structs, not unexported ones. It is usually an error to try +to (un)marshal structs that only consist of unexported fields. + +This check will not flag calls involving types that define custom +marshaling behavior, e.g. via \'MarshalJSON\' methods. It will also not +flag empty structs.`, + Since: "2019.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var rules = map[string]callcheck.Check{ + // TODO(dh): should we really flag XML? Even an empty struct + // produces a non-zero amount of data, namely its type name. + // Let's see if we encounter any false positives. + // + // Also, should we flag gob? + "encoding/json.Marshal": check(knowledge.Arg("json.Marshal.v"), "MarshalJSON", "MarshalText"), + "encoding/json.MarshalIndent": check(knowledge.Arg("json.MarshalIndent.v"), "MarshalJSON", "MarshalText"), + "encoding/xml.Marshal": check(knowledge.Arg("xml.Marshal.v"), "MarshalXML", "MarshalText"), + "encoding/xml.MarshalIndent": check(knowledge.Arg("xml.MarshalIndent.v"), "MarshalXML", "MarshalText"), + "(*encoding/json.Encoder).Encode": check(knowledge.Arg("(*encoding/json.Encoder).Encode.v"), "MarshalJSON", "MarshalText"), + "(*encoding/xml.Encoder).Encode": check(knowledge.Arg("(*encoding/xml.Encoder).Encode.v"), "MarshalXML", "MarshalText"), + + "encoding/json.Unmarshal": check(knowledge.Arg("json.Unmarshal.v"), "UnmarshalJSON", "UnmarshalText"), + "encoding/xml.Unmarshal": check(knowledge.Arg("xml.Unmarshal.v"), "UnmarshalXML", "UnmarshalText"), + "(*encoding/json.Decoder).Decode": check(knowledge.Arg("(*encoding/json.Decoder).Decode.v"), "UnmarshalJSON", "UnmarshalText"), + "(*encoding/xml.Decoder).Decode": check(knowledge.Arg("(*encoding/xml.Decoder).Decode.v"), "UnmarshalXML", "UnmarshalText"), +} + +func check(argN int, meths ...string) callcheck.Check { + return func(call *callcheck.Call) { + if code.IsGenerated(call.Pass, call.Instr.Pos()) { + return + } + arg := call.Args[argN] + T := arg.Value.Value.Type() + Ts, ok := typeutil.Dereference(T).Underlying().(*types.Struct) + if !ok { + return + } + if Ts.NumFields() == 0 { + return + } + fields := typeutil.FlattenFields(Ts) + for _, field := range fields { + if field.Var.Exported() { + return + } + } + // OPT(dh): we could use a method set cache here + ms := call.Instr.Parent().Prog.MethodSets.MethodSet(T) + // TODO(dh): we're not checking the signature, which can cause false negatives. + // This isn't a huge problem, however, since vet complains about incorrect signatures. + for _, meth := range meths { + if ms.Lookup(nil, meth) != nil { + return + } + } + arg.Invalid(fmt.Sprintf("struct type '%s' doesn't have any exported fields, nor custom marshaling", typeutil.Dereference(T))) + } +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9006/sa9006.go b/vendor/honnef.co/go/tools/staticcheck/sa9006/sa9006.go new file mode 100644 index 0000000..4f92ee1 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9006/sa9006.go @@ -0,0 +1,100 @@ +package sa9006 + +import ( + "fmt" + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9006", + Run: run, + Requires: code.RequiredAnalyzers, + }, + Doc: &lint.RawDocumentation{ + Title: `Dubious bit shifting of a fixed size integer value`, + Text: `Bit shifting a value past its size will always clear the value. + +For instance: + + v := int8(42) + v >>= 8 + +will always result in 0. + +This check flags bit shifting operations on fixed size integer values only. +That is, int, uint and uintptr are never flagged to avoid potential false +positives in somewhat exotic but valid bit twiddling tricks: + + // Clear any value above 32 bits if integers are more than 32 bits. + func f(i int) int { + v := i >> 32 + v = v << 32 + return i-v + }`, + Since: "2020.2", + Severity: lint.SeverityWarning, + // Technically this should be MergeIfAll, because the type of + // v might be different for different build tags. Practically, + // don't write code that depends on that. + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer +var ( + checkFixedLengthTypeShiftQ = pattern.MustParse(` + (Or + (AssignStmt _ (Or ">>=" "<<=") _) + (BinaryExpr _ (Or ">>" "<<") _)) + `) +) + +func run(pass *analysis.Pass) (any, error) { + isDubiousShift := func(x, y ast.Expr) (int64, int64, bool) { + typ, ok := pass.TypesInfo.TypeOf(x).Underlying().(*types.Basic) + if !ok { + return 0, 0, false + } + switch typ.Kind() { + case types.Int8, types.Int16, types.Int32, types.Int64, + types.Uint8, types.Uint16, types.Uint32, types.Uint64: + // We're only interested in fixed–size types. + default: + return 0, 0, false + } + + const bitsInByte = 8 + typeBits := pass.TypesSizes.Sizeof(typ) * bitsInByte + + shiftLength, ok := code.ExprToInt(pass, y) + if !ok { + return 0, 0, false + } + + return typeBits, shiftLength, shiftLength >= typeBits + } + + for node := range code.Matches(pass, checkFixedLengthTypeShiftQ) { + switch e := node.(type) { + case *ast.AssignStmt: + if size, shift, yes := isDubiousShift(e.Lhs[0], e.Rhs[0]); yes { + report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift)) + } + case *ast.BinaryExpr: + if size, shift, yes := isDubiousShift(e.X, e.Y); yes { + report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift)) + } + } + } + + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9007/sa9007.go b/vendor/honnef.co/go/tools/staticcheck/sa9007/sa9007.go new file mode 100644 index 0000000..4522590 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9007/sa9007.go @@ -0,0 +1,114 @@ +package sa9007 + +import ( + "fmt" + "os" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9007", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "Deleting a directory that shouldn't be deleted", + Text: ` +It is virtually never correct to delete system directories such as +/tmp or the user's home directory. However, it can be fairly easy to +do by mistake, for example by mistakenly using \'os.TempDir\' instead +of \'ioutil.TempDir\', or by forgetting to add a suffix to the result +of \'os.UserHomeDir\'. + +Writing + + d := os.TempDir() + defer os.RemoveAll(d) + +in your unit tests will have a devastating effect on the stability of your system. + +This check flags attempts at deleting the following directories: + +- os.TempDir +- os.UserCacheDir +- os.UserConfigDir +- os.UserHomeDir +`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + for _, b := range fn.Blocks { + for _, instr := range b.Instrs { + call, ok := instr.(ir.CallInstruction) + if !ok { + continue + } + if !irutil.IsCallTo(call.Common(), "os.RemoveAll") { + continue + } + + kind := "" + ex := "" + callName := "" + arg := irutil.Flatten(call.Common().Args[0]) + switch arg := arg.(type) { + case *ir.Call: + callName = irutil.CallName(&arg.Call) + if callName != "os.TempDir" { + continue + } + kind = "temporary" + ex = os.TempDir() + case *ir.Extract: + if arg.Index != 0 { + continue + } + first, ok := arg.Tuple.(*ir.Call) + if !ok { + continue + } + callName = irutil.CallName(&first.Call) + switch callName { + case "os.UserCacheDir": + kind = "cache" + ex, _ = os.UserCacheDir() + case "os.UserConfigDir": + kind = "config" + ex, _ = os.UserConfigDir() + case "os.UserHomeDir": + kind = "home" + ex, _ = os.UserHomeDir() + default: + continue + } + default: + continue + } + + if ex == "" { + report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind), + report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory", callName, kind))) + } else { + report.Report(pass, call, fmt.Sprintf("this call to os.RemoveAll deletes the user's entire %s directory, not a subdirectory therein", kind), + report.Related(arg, fmt.Sprintf("this call to %s returns the user's %s directory, for example %s", callName, kind, ex))) + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9008/sa9008.go b/vendor/honnef.co/go/tools/staticcheck/sa9008/sa9008.go new file mode 100644 index 0000000..b565ab6 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9008/sa9008.go @@ -0,0 +1,124 @@ +package sa9008 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9008", + Run: run, + Requires: append([]*analysis.Analyzer{buildir.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `\'else\' branch of a type assertion is probably not reading the right value`, + Text: ` +When declaring variables as part of an \'if\' statement (like in \"if +foo := ...; foo {\"), the same variables will also be in the scope of +the \'else\' branch. This means that in the following example + + if x, ok := x.(int); ok { + // ... + } else { + fmt.Printf("unexpected type %T", x) + } + +\'x\' in the \'else\' branch will refer to the \'x\' from \'x, ok +:=\'; it will not refer to the \'x\' that is being type-asserted. The +result of a failed type assertion is the zero value of the type that +is being asserted to, so \'x\' in the else branch will always have the +value \'0\' and the type \'int\'. +`, + Since: "2022.1", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var typeAssertionShadowingElseQ = pattern.MustParse(`(IfStmt (AssignStmt [obj@(Ident _) ok@(Ident _)] ":=" assert@(TypeAssertExpr obj _)) ok _ elseBranch)`) + +func run(pass *analysis.Pass) (any, error) { + // TODO(dh): without the IR-based verification, this check is able + // to find more bugs, but also more prone to false positives. It + // would be a good candidate for the 'codereview' category of + // checks. + + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + for _, m := range code.Matches(pass, typeAssertionShadowingElseQ) { + shadow := pass.TypesInfo.ObjectOf(m.State["obj"].(*ast.Ident)) + shadowed := m.State["assert"].(*ast.TypeAssertExpr).X + + path, exact := astutil.PathEnclosingInterval(code.File(pass, shadow), shadow.Pos(), shadow.Pos()) + if !exact { + // TODO(dh): when can this happen? + continue + } + irfn := ir.EnclosingFunction(irpkg, path) + if irfn == nil { + // For example for functions named "_", because we don't generate IR for them. + continue + } + + shadoweeIR, isAddr := irfn.ValueForExpr(m.State["obj"].(*ast.Ident)) + if shadoweeIR == nil || isAddr { + // TODO(dh): is this possible? + continue + } + + var branch ast.Node + switch br := m.State["elseBranch"].(type) { + case ast.Node: + branch = br + case []ast.Stmt: + branch = &ast.BlockStmt{List: br} + case nil: + continue + default: + panic(fmt.Sprintf("unexpected type %T", br)) + } + + ast.Inspect(branch, func(node ast.Node) bool { + ident, ok := node.(*ast.Ident) + if !ok { + return true + } + if pass.TypesInfo.ObjectOf(ident) != shadow { + return true + } + + v, isAddr := irfn.ValueForExpr(ident) + if v == nil || isAddr { + return true + } + if irutil.Flatten(v) != shadoweeIR { + // Same types.Object, but different IR value. This + // either means that the variable has been + // assigned to since the type assertion, or that + // the variable has escaped to the heap. Either + // way, we shouldn't flag reads of it. + return true + } + + report.Report(pass, ident, + fmt.Sprintf("%s refers to the result of a failed type assertion and is a zero value, not the value that was being type-asserted", report.Render(pass, ident)), + report.Related(shadow, "this is the variable being read"), + report.Related(shadowed, "this is the variable being shadowed")) + return true + }) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9009/sa9009.go b/vendor/honnef.co/go/tools/staticcheck/sa9009/sa9009.go new file mode 100644 index 0000000..f98db5a --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9009/sa9009.go @@ -0,0 +1,68 @@ +package sa9009 + +import ( + "fmt" + "strings" + + "golang.org/x/tools/go/analysis" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9009", + Run: run, + Requires: []*analysis.Analyzer{}, + }, + Doc: &lint.RawDocumentation{ + Title: "Ineffectual Go compiler directive", + Text: ` +A potential Go compiler directive was found, but is ineffectual as it begins +with whitespace.`, + Since: "2024.1", + Severity: lint.SeverityWarning, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, f := range pass.Files { + for _, cg := range f.Comments { + for _, c := range cg.List { + // Compiler directives have to be // comments + if !strings.HasPrefix(c.Text, "//") { + continue + } + if pass.Fset.PositionFor(c.Pos(), false).Column != 1 { + // Compiler directives have to be top-level. This also + // avoids a false positive for + // 'import _ "unsafe" // go:linkname' + continue + } + text := strings.TrimLeft(c.Text[2:], " \t") + if len(text) == len(c.Text[2:]) { + // There was no leading whitespace + continue + } + if !strings.HasPrefix(text, "go:") { + // Not an attempted compiler directive + continue + } + text = text[3:] + if len(text) == 0 || text[0] < 'a' || text[0] > 'z' { + // A letter other than a-z after "go:", so unlikely to be an + // attempted compiler directive + continue + } + report.Report(pass, c, + fmt.Sprintf( + "ineffectual compiler directive due to extraneous space: %q", + c.Text)) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go b/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go new file mode 100644 index 0000000..c171ba1 --- /dev/null +++ b/vendor/honnef.co/go/tools/staticcheck/sa9010/sa9010.go @@ -0,0 +1,60 @@ +package sa9010 + +import ( + "go/ast" + "go/types" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "SA9010", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Returned function should be called in defer`, + Text: ` +If you have a function such as: + + func f() func() { + // Do something. + return func() { + // Do something. + } + } + +Then calling that in defer: + + defer f() + +Is almost always a mistake, since you typically want to call the returned +function: + + defer f()() +`, + Since: "2026.2", + Severity: lint.SeverityWarning, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(n ast.Node) { + def := n.(*ast.DeferStmt) + if _, ok := pass.TypesInfo.TypeOf(def.Call).Underlying().(*types.Signature); ok { + report.Report(pass, def, "deferred return function not called") + } + } + + code.Preorder(pass, fn, (*ast.DeferStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/analysis.go b/vendor/honnef.co/go/tools/stylecheck/analysis.go new file mode 100644 index 0000000..7171bef --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/analysis.go @@ -0,0 +1,46 @@ +// Code generated by generate.go. DO NOT EDIT. + +package stylecheck + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/stylecheck/st1000" + "honnef.co/go/tools/stylecheck/st1001" + "honnef.co/go/tools/stylecheck/st1003" + "honnef.co/go/tools/stylecheck/st1005" + "honnef.co/go/tools/stylecheck/st1006" + "honnef.co/go/tools/stylecheck/st1008" + "honnef.co/go/tools/stylecheck/st1011" + "honnef.co/go/tools/stylecheck/st1012" + "honnef.co/go/tools/stylecheck/st1013" + "honnef.co/go/tools/stylecheck/st1015" + "honnef.co/go/tools/stylecheck/st1016" + "honnef.co/go/tools/stylecheck/st1017" + "honnef.co/go/tools/stylecheck/st1018" + "honnef.co/go/tools/stylecheck/st1019" + "honnef.co/go/tools/stylecheck/st1020" + "honnef.co/go/tools/stylecheck/st1021" + "honnef.co/go/tools/stylecheck/st1022" + "honnef.co/go/tools/stylecheck/st1023" +) + +var Analyzers = []*lint.Analyzer{ + st1000.SCAnalyzer, + st1001.SCAnalyzer, + st1003.SCAnalyzer, + st1005.SCAnalyzer, + st1006.SCAnalyzer, + st1008.SCAnalyzer, + st1011.SCAnalyzer, + st1012.SCAnalyzer, + st1013.SCAnalyzer, + st1015.SCAnalyzer, + st1016.SCAnalyzer, + st1017.SCAnalyzer, + st1018.SCAnalyzer, + st1019.SCAnalyzer, + st1020.SCAnalyzer, + st1021.SCAnalyzer, + st1022.SCAnalyzer, + st1023.SCAnalyzer, +} diff --git a/vendor/honnef.co/go/tools/stylecheck/doc.go b/vendor/honnef.co/go/tools/stylecheck/doc.go new file mode 100644 index 0000000..2964990 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/doc.go @@ -0,0 +1,10 @@ +//go:generate go run ../generate.go + +// Package stylecheck contains analyzes that enforce style rules. +// Most of the recommendations made are universally agreed upon by the wider Go community. +// Some analyzes, however, implement stricter rules that not everyone will agree with. +// In the context of Staticcheck, these analyzes are not enabled by default. +// +// For the most part it is recommended to follow the advice given by the analyzers that are enabled by default, +// but you may want to disable additional analyzes on a case by case basis. +package stylecheck diff --git a/vendor/honnef.co/go/tools/stylecheck/st1000/st1000.go b/vendor/honnef.co/go/tools/stylecheck/st1000/st1000.go new file mode 100644 index 0000000..a3cf8c3 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1000/st1000.go @@ -0,0 +1,83 @@ +package st1000 + +import ( + "fmt" + "go/ast" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1000", + Run: run, + }, + Doc: &lint.RawDocumentation{ + Title: `Incorrect or missing package comment`, + Text: `Packages must have a package comment that is formatted according to +the guidelines laid out in +https://go.dev/wiki/CodeReviewComments#package-comments.`, + Since: "2019.1", + NonDefault: true, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + // - At least one file in a non-main package should have a package comment + // + // - The comment should be of the form + // "Package x ...". This has a slight potential for false + // positives, as multiple files can have package comments, in + // which case they get appended. But that doesn't happen a lot in + // the real world. + + if pass.Pkg.Name() == "main" { + return nil, nil + } + hasDocs := false + for _, f := range pass.Files { + if code.IsInTest(pass, f) { + continue + } + text, ok := docText(f.Doc) + if ok { + hasDocs = true + prefix := "Package " + f.Name.Name + isNonAlpha := func(b byte) bool { + // This check only considers ASCII, which can lead to false negatives for some malformed package + // comments. + return !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')) + } + if !strings.HasPrefix(text, prefix) || (len(text) > len(prefix) && !isNonAlpha(text[len(prefix)])) { + report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix)) + } + } + } + + if !hasDocs { + for _, f := range pass.Files { + if code.IsInTest(pass, f) { + continue + } + report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange()) + } + } + return nil, nil +} + +func docText(doc *ast.CommentGroup) (string, bool) { + if doc == nil { + return "", false + } + // We trim spaces primarily because of /**/ style comments, which often have leading space. + text := strings.TrimSpace(doc.Text()) + return text, text != "" +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1001/st1001.go b/vendor/honnef.co/go/tools/stylecheck/st1001/st1001.go new file mode 100644 index 0000000..b4fd06c --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1001/st1001.go @@ -0,0 +1,71 @@ +package st1001 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/config" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1001", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer, config.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Dot imports are discouraged`, + Text: `Dot imports that aren't in external test packages are discouraged. + +The \'dot_import_whitelist\' option can be used to whitelist certain +imports. + +Quoting Go Code Review Comments: + +> The \'import .\' form can be useful in tests that, due to circular +> dependencies, cannot be made part of the package being tested: +> +> package foo_test +> +> import ( +> "bar/testutil" // also imports "foo" +> . "foo" +> ) +> +> In this case, the test file cannot be in package foo because it +> uses \'bar/testutil\', which imports \'foo\'. So we use the \'import .\' +> form to let the file pretend to be part of package foo even though +> it is not. Except for this one case, do not use \'import .\' in your +> programs. It makes the programs much harder to read because it is +> unclear whether a name like \'Quux\' is a top-level identifier in the +> current package or in an imported package.`, + Since: "2019.1", + Options: []string{"dot_import_whitelist"}, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, f := range pass.Files { + imports: + for _, imp := range f.Imports { + path := imp.Path.Value + path = path[1 : len(path)-1] + for _, w := range config.For(pass).DotImportWhitelist { + if w == path { + continue imports + } + } + + if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) { + report.Report(pass, imp, "should not use dot imports", report.FilterGenerated()) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1003/st1003.go b/vendor/honnef.co/go/tools/stylecheck/st1003/st1003.go new file mode 100644 index 0000000..a8c26e7 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1003/st1003.go @@ -0,0 +1,310 @@ +package st1003 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + "unicode" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/config" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1003", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, config.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Poorly chosen identifier`, + Text: `Identifiers, such as variable and package names, follow certain rules. + +See the following links for details: + +- https://go.dev/doc/effective_go#package-names +- https://go.dev/doc/effective_go#mixed-caps +- https://go.dev/wiki/CodeReviewComments#initialisms +- https://go.dev/wiki/CodeReviewComments#variable-names`, + Since: "2019.1", + NonDefault: true, + Options: []string{"initialisms"}, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +// knownNameExceptions is a set of names that are known to be exempt from naming checks. +// This is usually because they are constrained by having to match names in the +// standard library. +var knownNameExceptions = map[string]bool{ + "LastInsertId": true, // must match database/sql + "kWh": true, +} + +func run(pass *analysis.Pass) (any, error) { + // A large part of this function is copied from + // github.com/golang/lint, Copyright (c) 2013 The Go Authors, + // licensed under the BSD 3-clause license. + + allCaps := func(s string) bool { + hasUppercaseLetters := false + for _, r := range s { + if !hasUppercaseLetters && r >= 'A' && r <= 'Z' { + hasUppercaseLetters = true + } + if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') { + return false + } + } + return hasUppercaseLetters + } + + check := func(id *ast.Ident, thing string, initialisms map[string]bool) { + if id.Name == "_" { + return + } + if knownNameExceptions[id.Name] { + return + } + + // Handle two common styles from other languages that don't belong in Go. + if len(id.Name) >= 5 && allCaps(id.Name) && strings.Contains(id.Name, "_") { + report.Report(pass, id, "should not use ALL_CAPS in Go names; use CamelCase instead", report.FilterGenerated()) + return + } + + should := lintName(id.Name, initialisms) + if id.Name == should { + return + } + + if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") { + report.Report(pass, id, fmt.Sprintf("should not use underscores in Go names; %s %s should be %s", thing, id.Name, should), report.FilterGenerated()) + return + } + report.Report(pass, id, fmt.Sprintf("%s %s should be %s", thing, id.Name, should), report.FilterGenerated()) + } + checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) { + if fl == nil { + return + } + for _, f := range fl.List { + for _, id := range f.Names { + check(id, thing, initialisms) + } + } + } + + il := config.For(pass).Initialisms + initialisms := make(map[string]bool, len(il)) + for _, word := range il { + initialisms[word] = true + } + for _, f := range pass.Files { + // Package names need slightly different handling than other names. + if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") { + report.Report(pass, f.Name, "should not use underscores in package names", report.FilterGenerated()) + } + if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 { + report.Report(pass, f.Name, fmt.Sprintf("should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name)), report.FilterGenerated()) + } + } + + fn := func(node ast.Node) { + switch v := node.(type) { + case *ast.AssignStmt: + if v.Tok != token.DEFINE { + return + } + for _, exp := range v.Lhs { + if id, ok := exp.(*ast.Ident); ok { + check(id, "var", initialisms) + } + } + case *ast.FuncDecl: + // Functions with no body are defined elsewhere (in + // assembly, or via go:linkname). These are likely to + // be something very low level (such as the runtime), + // where our rules don't apply. + if v.Body == nil { + return + } + + if code.IsInTest(pass, v) && + (strings.HasPrefix(v.Name.Name, "Example") || + strings.HasPrefix(v.Name.Name, "Test") || + strings.HasPrefix(v.Name.Name, "Benchmark") || + strings.HasPrefix(v.Name.Name, "Fuzz")) { + return + } + + thing := "func" + if v.Recv != nil { + thing = "method" + } + + if !isTechnicallyExported(v) { + check(v.Name, thing, initialisms) + } + + checkList(v.Type.Params, thing+" parameter", initialisms) + checkList(v.Type.Results, thing+" result", initialisms) + case *ast.GenDecl: + if v.Tok == token.IMPORT { + return + } + var thing string + switch v.Tok { + case token.CONST: + thing = "const" + case token.TYPE: + thing = "type" + case token.VAR: + thing = "var" + } + for _, spec := range v.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + check(s.Name, thing, initialisms) + case *ast.ValueSpec: + for _, id := range s.Names { + check(id, thing, initialisms) + } + } + } + case *ast.InterfaceType: + // Do not check interface method names. + // They are often constrained by the method names of concrete types. + for _, x := range v.Methods.List { + ft, ok := x.Type.(*ast.FuncType) + if !ok { // might be an embedded interface name + continue + } + checkList(ft.Params, "interface method parameter", initialisms) + checkList(ft.Results, "interface method result", initialisms) + } + case *ast.RangeStmt: + if v.Tok == token.ASSIGN { + return + } + if id, ok := v.Key.(*ast.Ident); ok { + check(id, "range var", initialisms) + } + if id, ok := v.Value.(*ast.Ident); ok { + check(id, "range var", initialisms) + } + case *ast.StructType: + for _, f := range v.Fields.List { + for _, id := range f.Names { + check(id, "struct field", initialisms) + } + } + } + } + + needle := []ast.Node{ + (*ast.AssignStmt)(nil), + (*ast.FuncDecl)(nil), + (*ast.GenDecl)(nil), + (*ast.InterfaceType)(nil), + (*ast.RangeStmt)(nil), + (*ast.StructType)(nil), + } + + code.Preorder(pass, fn, needle...) + return nil, nil +} + +// lintName returns a different name if it should be different. +func lintName(name string, initialisms map[string]bool) (should string) { + // A large part of this function is copied from + // github.com/golang/lint, Copyright (c) 2013 The Go Authors, + // licensed under the BSD 3-clause license. + + // Fast path for simple cases: "_" and all lowercase. + if name == "_" { + return name + } + if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 { + return name + } + + // Split camelCase at any lower->upper transition, and split on underscores. + // Check each word for common initialisms. + runes := []rune(name) + w, i := 0, 0 // index of start of word, scan + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + if i+1 == len(runes) { + eow = true + } else if runes[i+1] == '_' && i+1 != len(runes)-1 { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && runes[i+n+1] == '_' { + n++ + } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- + } + + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true + } + i++ + if !eow { + continue + } + + // [w,i) is a word. + word := string(runes[w:i]) + if u := strings.ToUpper(word); initialisms[u] { + // Keep consistent case, which is lowercase only at the start. + if w == 0 && unicode.IsLower(runes[w]) { + u = strings.ToLower(u) + } + // All the common initialisms are ASCII, + // so we can replace the bytes exactly. + // TODO(dh): this won't be true once we allow custom initialisms + copy(runes[w:], []rune(u)) + } else if w > 0 && strings.ToLower(word) == word { + // already all lowercase, and not the first word, so uppercase the first character. + runes[w] = unicode.ToUpper(runes[w]) + } + w = i + } + return string(runes) +} + +func isTechnicallyExported(f *ast.FuncDecl) bool { + if f.Recv != nil || f.Doc == nil { + return false + } + + const export = "//export " + const linkname = "//go:linkname " + for _, c := range f.Doc.List { + if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name { + return true + } + + if strings.HasPrefix(c.Text, linkname) { + return true + } + } + return false +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1005/st1005.go b/vendor/honnef.co/go/tools/stylecheck/st1005/st1005.go new file mode 100644 index 0000000..477c0a1 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1005/st1005.go @@ -0,0 +1,130 @@ +package st1005 + +import ( + "go/constant" + "strings" + "unicode" + "unicode/utf8" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ir" + "honnef.co/go/tools/go/ir/irutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1005", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Incorrectly formatted error string`, + Text: `Error strings follow a set of guidelines to ensure uniformity and good +composability. + +Quoting Go Code Review Comments: + +> Error strings should not be capitalized (unless beginning with +> proper nouns or acronyms) or end with punctuation, since they are +> usually printed following other context. That is, use +> \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so +> that \'log.Printf("Reading %s: %v", filename, err)\' formats without a +> spurious capital letter mid-message.`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + objNames := map[*ir.Package]map[string]bool{} + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + objNames[irpkg] = map[string]bool{} + for _, m := range irpkg.Members { + if typ, ok := m.(*ir.Type); ok { + objNames[irpkg][typ.Name()] = true + } + } + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + objNames[fn.Package()][fn.Name()] = true + } + + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + if code.IsInTest(pass, fn) { + // We don't care about malformed error messages in tests; + // they're usually for direct human consumption, not part + // of an API + continue + } + for _, block := range fn.Blocks { + instrLoop: + for _, ins := range block.Instrs { + call, ok := ins.(*ir.Call) + if !ok { + continue + } + if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") { + continue + } + + k, ok := call.Common().Args[0].(*ir.Const) + if !ok { + continue + } + + s := constant.StringVal(k.Value) + if len(s) == 0 { + continue + } + switch s[len(s)-1] { + case '.', ':', '!', '\n': + report.Report(pass, call, "error strings should not end with punctuation or newlines") + } + before, _, ok0 := strings.Cut(s, " ") + if !ok0 { + // single word error message, probably not a real + // error but something used in tests or during + // debugging + continue + } + word := before + first, n := utf8.DecodeRuneInString(word) + if !unicode.IsUpper(first) { + continue + } + for _, c := range word[n:] { + if unicode.IsUpper(c) || unicode.IsDigit(c) { + // Word is probably an initialism or multi-word function name. Digits cover elliptic curves like + // P384. + continue instrLoop + } + } + + if strings.ContainsRune(word, '(') { + // Might be a function call + continue instrLoop + } + word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) }) + if objNames[fn.Package()][word] { + // Word is probably the name of a function or type in this package + continue + } + // First word in error starts with a capital + // letter, and the word doesn't contain any other + // capitals, making it unlikely to be an + // initialism or multi-word function name. + // + // It could still be a proper noun, though. + + report.Report(pass, call, "error strings should not be capitalized") + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1006/st1006.go b/vendor/honnef.co/go/tools/stylecheck/st1006/st1006.go new file mode 100644 index 0000000..30b0dc8 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1006/st1006.go @@ -0,0 +1,65 @@ +package st1006 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1006", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Poorly chosen receiver name`, + Text: `Quoting Go Code Review Comments: + +> The name of a method's receiver should be a reflection of its +> identity; often a one or two letter abbreviation of its type +> suffices (such as "c" or "cl" for "Client"). Don't use generic +> names such as "me", "this" or "self", identifiers typical of +> object-oriented languages that place more emphasis on methods as +> opposed to functions. The name need not be as descriptive as that +> of a method argument, as its role is obvious and serves no +> documentary purpose. It can be very short as it will appear on +> almost every line of every method of the type; familiarity admits +> brevity. Be consistent, too: if you call the receiver "c" in one +> method, don't call it "cl" in another.`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + for _, m := range irpkg.Members { + if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() { + ms := typeutil.IntuitiveMethodSet(T.Type(), nil) + for _, sel := range ms { + fn := sel.Obj().(*types.Func) + recv := fn.Type().(*types.Signature).Recv() + if typeutil.Dereference(recv.Type()) != T.Type() { + // skip embedded methods + continue + } + if recv.Name() == "self" || recv.Name() == "this" { + report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated()) + } + if recv.Name() == "_" { + report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated()) + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1008/st1008.go b/vendor/honnef.co/go/tools/stylecheck/st1008/st1008.go new file mode 100644 index 0000000..8c9f326 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1008/st1008.go @@ -0,0 +1,58 @@ +package st1008 + +import ( + "go/types" + + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1008", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `A function's error value should be its last return value`, + Text: `A function's error value should be its last return value.`, + Since: `2019.1`, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { +fnLoop: + for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs { + sig := fn.Type().(*types.Signature) + rets := sig.Results() + if rets == nil || rets.Len() < 2 { + continue + } + + if types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("error").Type() { + // Last return type is error. If the function also returns + // errors in other positions, that's fine. + continue + } + + if rets.Len() >= 2 && + types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("bool").Type() && + types.Unalias(rets.At(rets.Len()-2).Type()) == types.Universe.Lookup("error").Type() { + // Accept (..., error, bool) and assume it's a comma-ok function. It's not clear whether the bool should come last or not for these kinds of functions. + continue + } + for i := rets.Len() - 2; i >= 0; i-- { + if types.Unalias(rets.At(i).Type()) == types.Universe.Lookup("error").Type() { + report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange()) + continue fnLoop + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1011/st1011.go b/vendor/honnef.co/go/tools/stylecheck/st1011/st1011.go new file mode 100644 index 0000000..cb43bba --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1011/st1011.go @@ -0,0 +1,88 @@ +package st1011 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1011", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Poorly chosen name for variable of type \'time.Duration\'`, + Text: `\'time.Duration\' values represent an amount of time, which is represented +as a count of nanoseconds. An expression like \'5 * time.Microsecond\' +yields the value \'5000\'. It is therefore not appropriate to suffix a +variable of type \'time.Duration\' with any time unit, such as \'Msec\' or +\'Milli\'.`, + Since: `2019.1`, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + suffixes := []string{ + "Sec", "Secs", "Seconds", + "Msec", "Msecs", + "Milli", "Millis", "Milliseconds", + "Usec", "Usecs", "Microseconds", + "MS", "Ms", + } + fn := func(names []*ast.Ident) { + for _, name := range names { + if _, ok := pass.TypesInfo.Defs[name]; !ok { + continue + } + T := pass.TypesInfo.TypeOf(name) + if !typeutil.IsTypeWithName(T, "time.Duration") && !typeutil.IsPointerToTypeWithName(T, "time.Duration") { + continue + } + for _, suffix := range suffixes { + if strings.HasSuffix(name.Name, suffix) { + report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix)) + break + } + } + } + } + + fn2 := func(node ast.Node) { + switch node := node.(type) { + case *ast.ValueSpec: + fn(node.Names) + case *ast.FieldList: + for _, field := range node.List { + fn(field.Names) + } + case *ast.AssignStmt: + if node.Tok != token.DEFINE { + break + } + var names []*ast.Ident + for _, lhs := range node.Lhs { + if lhs, ok := lhs.(*ast.Ident); ok { + names = append(names, lhs) + } + } + fn(names) + } + } + + code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1012/st1012.go b/vendor/honnef.co/go/tools/stylecheck/st1012/st1012.go new file mode 100644 index 0000000..cf921fe --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1012/st1012.go @@ -0,0 +1,68 @@ +package st1012 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1012", + Run: run, + }, + Doc: &lint.RawDocumentation{ + Title: `Poorly chosen name for error variable`, + Text: `Error variables that are part of an API should be called \'errFoo\' or +\'ErrFoo\'.`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, f := range pass.Files { + for _, decl := range f.Decls { + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.VAR { + continue + } + for _, spec := range gen.Specs { + spec := spec.(*ast.ValueSpec) + if len(spec.Names) != len(spec.Values) { + continue + } + + for i, name := range spec.Names { + val := spec.Values[i] + if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") { + continue + } + + if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") { + // special case for internal variable names of + // bundled HTTP 2 code in net/http + continue + } + prefix := "err" + if name.IsExported() { + prefix = "Err" + } + if !strings.HasPrefix(name.Name, prefix) { + report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix)) + } + } + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1013/st1013.go b/vendor/honnef.co/go/tools/stylecheck/st1013/st1013.go new file mode 100644 index 0000000..01fe71c --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1013/st1013.go @@ -0,0 +1,159 @@ +package st1013 + +import ( + "fmt" + "go/ast" + "go/constant" + "strconv" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/config" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1013", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer, config.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Should use constants for HTTP error codes, not magic numbers`, + Text: `HTTP has a tremendous number of status codes. While some of those are +well known (200, 400, 404, 500), most of them are not. The \'net/http\' +package provides constants for all status codes that are part of the +various specifications. It is recommended to use these constants +instead of hard-coding magic numbers, to vastly improve the +readability of your code.`, + Since: "2019.1", + Options: []string{"http_status_code_whitelist"}, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var query = pattern.MustParse(` + (CallExpr + (Symbol + name@(Or + "net/http.Error" + "net/http.Redirect" + "net/http.StatusText" + "net/http.RedirectHandler")) + args)`) + +func run(pass *analysis.Pass) (any, error) { + whitelist := map[string]bool{} + for _, code := range config.For(pass).HTTPStatusCodeWhitelist { + whitelist[code] = true + } + for _, m := range code.Matches(pass, query) { + var arg int + switch m.State["name"].(string) { + case "net/http.Error": + arg = 2 + case "net/http.Redirect": + arg = 3 + case "net/http.StatusText": + arg = 0 + case "net/http.RedirectHandler": + arg = 1 + default: + continue + } + args := m.State["args"].([]ast.Expr) + if arg >= len(args) { + continue + } + tv, ok := code.IntegerLiteral(pass, args[arg]) + if !ok { + continue + } + n, ok := constant.Int64Val(tv.Value) + if !ok { + continue + } + if whitelist[strconv.FormatInt(n, 10)] { + continue + } + + s, ok := httpStatusCodes[n] + if !ok { + continue + } + lit := args[arg] + report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n), + report.FilterGenerated(), + report.Fixes(edit.Fix(fmt.Sprintf("Use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s)))) + } + return nil, nil +} + +var httpStatusCodes = map[int64]string{ + 100: "StatusContinue", + 101: "StatusSwitchingProtocols", + 102: "StatusProcessing", + 200: "StatusOK", + 201: "StatusCreated", + 202: "StatusAccepted", + 203: "StatusNonAuthoritativeInfo", + 204: "StatusNoContent", + 205: "StatusResetContent", + 206: "StatusPartialContent", + 207: "StatusMultiStatus", + 208: "StatusAlreadyReported", + 226: "StatusIMUsed", + 300: "StatusMultipleChoices", + 301: "StatusMovedPermanently", + 302: "StatusFound", + 303: "StatusSeeOther", + 304: "StatusNotModified", + 305: "StatusUseProxy", + 307: "StatusTemporaryRedirect", + 308: "StatusPermanentRedirect", + 400: "StatusBadRequest", + 401: "StatusUnauthorized", + 402: "StatusPaymentRequired", + 403: "StatusForbidden", + 404: "StatusNotFound", + 405: "StatusMethodNotAllowed", + 406: "StatusNotAcceptable", + 407: "StatusProxyAuthRequired", + 408: "StatusRequestTimeout", + 409: "StatusConflict", + 410: "StatusGone", + 411: "StatusLengthRequired", + 412: "StatusPreconditionFailed", + 413: "StatusRequestEntityTooLarge", + 414: "StatusRequestURITooLong", + 415: "StatusUnsupportedMediaType", + 416: "StatusRequestedRangeNotSatisfiable", + 417: "StatusExpectationFailed", + 418: "StatusTeapot", + 422: "StatusUnprocessableEntity", + 423: "StatusLocked", + 424: "StatusFailedDependency", + 426: "StatusUpgradeRequired", + 428: "StatusPreconditionRequired", + 429: "StatusTooManyRequests", + 431: "StatusRequestHeaderFieldsTooLarge", + 451: "StatusUnavailableForLegalReasons", + 500: "StatusInternalServerError", + 501: "StatusNotImplemented", + 502: "StatusBadGateway", + 503: "StatusServiceUnavailable", + 504: "StatusGatewayTimeout", + 505: "StatusHTTPVersionNotSupported", + 506: "StatusVariantAlsoNegotiates", + 507: "StatusInsufficientStorage", + 508: "StatusLoopDetected", + 510: "StatusNotExtended", + 511: "StatusNetworkAuthenticationRequired", +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1015/st1015.go b/vendor/honnef.co/go/tools/stylecheck/st1015/st1015.go new file mode 100644 index 0000000..18d0e20 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1015/st1015.go @@ -0,0 +1,77 @@ +package st1015 + +import ( + "go/ast" + "go/token" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1015", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `A switch's default case should be the first or last case`, + Since: "2019.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + hasFallthrough := func(clause ast.Stmt) bool { + // A valid fallthrough statement may be used only as the final non-empty statement in a case clause. Thus we can + // easily avoid falsely matching fallthroughs in nested switches by not descending into blocks. + + body := clause.(*ast.CaseClause).Body + for i := len(body) - 1; i >= 0; i-- { + last := body[i] + switch stmt := last.(type) { + case *ast.EmptyStmt: + // Fallthrough may be followed by empty statements + case *ast.BranchStmt: + return stmt.Tok == token.FALLTHROUGH + default: + return false + } + } + + return false + } + + fn := func(node ast.Node) { + stmt := node.(*ast.SwitchStmt) + list := stmt.Body.List + defaultIdx := -1 + for i, c := range list { + if c.(*ast.CaseClause).List == nil { + defaultIdx = i + break + } + } + + if defaultIdx == -1 || defaultIdx == 0 || defaultIdx == len(list)-1 { + // No default case, or it's the first or last case + return + } + + if hasFallthrough(list[defaultIdx-1]) || hasFallthrough(list[defaultIdx]) { + // We either fall into or out of this case; don't mess with the order + return + } + + report.Report(pass, list[defaultIdx], "default case should be first or last in switch statement", report.FilterGenerated()) + } + code.Preorder(pass, fn, (*ast.SwitchStmt)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1016/st1016.go b/vendor/honnef.co/go/tools/stylecheck/st1016/st1016.go new file mode 100644 index 0000000..56aa2ea --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1016/st1016.go @@ -0,0 +1,74 @@ +package st1016 + +import ( + "fmt" + "go/types" + "sort" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/types/typeutil" + "honnef.co/go/tools/internal/passes/buildir" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1016", + Run: run, + Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Use consistent method receiver names`, + Since: "2019.1", + NonDefault: true, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg + for _, m := range irpkg.Members { + names := map[string]int{} + + var firstFn *types.Func + if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() { + ms := typeutil.IntuitiveMethodSet(T.Type(), nil) + for _, sel := range ms { + fn := sel.Obj().(*types.Func) + recv := fn.Type().(*types.Signature).Recv() + if code.IsGenerated(pass, recv.Pos()) { + // Don't concern ourselves with methods in generated code + continue + } + if typeutil.Dereference(recv.Type()) != T.Type() { + // skip embedded methods + continue + } + if firstFn == nil { + firstFn = fn + } + if recv.Name() != "" && recv.Name() != "_" { + names[recv.Name()]++ + } + } + } + + if len(names) > 1 { + var seen []string + for name, count := range names { + seen = append(seen, fmt.Sprintf("%dx %q", count, name)) + } + sort.Strings(seen) + + report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", "))) + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1017/st1017.go b/vendor/honnef.co/go/tools/stylecheck/st1017/st1017.go new file mode 100644 index 0000000..d279808 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1017/st1017.go @@ -0,0 +1,47 @@ +package st1017 + +import ( + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/edit" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/pattern" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1017", + Run: run, + Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...), + }, + Doc: &lint.RawDocumentation{ + Title: `Don't use Yoda conditions`, + Text: `Yoda conditions are conditions of the kind \"if 42 == x\", where the +literal is on the left side of the comparison. These are a common +idiom in languages in which assignment is an expression, to avoid bugs +of the kind \"if (x = 42)\". In Go, which doesn't allow for this kind of +bug, we prefer the more idiomatic \"if x == 42\".`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +var ( + checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(TrulyConstantExpression _) tok@(Or "==" "!=") right@(Not (TrulyConstantExpression _)))`) + checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`) +) + +func run(pass *analysis.Pass) (any, error) { + for node, m := range code.Matches(pass, checkYodaConditionsQ) { + edits := code.EditMatch(pass, node, m, checkYodaConditionsR) + report.Report(pass, node, "don't use Yoda conditions", + report.FilterGenerated(), + report.Fixes(edit.Fix("Un-Yoda-fy", edits...))) + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1018/st1018.go b/vendor/honnef.co/go/tools/stylecheck/st1018/st1018.go new file mode 100644 index 0000000..a479c8d --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1018/st1018.go @@ -0,0 +1,157 @@ +package st1018 + +import ( + "fmt" + "go/ast" + "go/token" + "strconv" + "unicode" + "unicode/utf8" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1018", + Run: run, + Requires: []*analysis.Analyzer{inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Avoid zero-width and control characters in string literals`, + Since: "2019.2", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + lit := node.(*ast.BasicLit) + if lit.Kind != token.STRING { + return + } + + type invalid struct { + r rune + off int + } + var invalids []invalid + hasFormat := false + hasControl := false + prev := rune(-1) + const zwj = '\u200d' + for off, r := range lit.Value { + if unicode.Is(unicode.Cf, r) { + if r >= '\U000e0020' && r <= '\U000e007f' { + // These are used for spelling out country codes for flag emoji + } else if unicode.Is(unicode.Variation_Selector, r) { + // Always allow variation selectors + } else if r == zwj && (unicode.Is(unicode.S, prev) || unicode.Is(unicode.Variation_Selector, prev)) { + // Allow zero-width joiner in emoji, including those that use variation selectors. + + // Technically some foreign scripts make valid use of zero-width joiners, too, but for now we'll err + // on the side of flagging all non-emoji uses of ZWJ. + } else { + switch r { + case '\u0600', '\u0601', '\u0602', '\u0603', '\u0604', '\u0605', '\u0890', '\u0891', '\u08e2': + // Arabic characters that are not actually invisible. If anyone knows why these are in the + // Other, Format category please let me know. + case '\u061c', '\u202A', '\u202B', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u202C', '\u2069': + // Bidirectional formatting characters. At best they will render confusingly, at worst they're used + // to cause confusion. + fallthrough + default: + invalids = append(invalids, invalid{r, off}) + hasFormat = true + } + } + } else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' { + invalids = append(invalids, invalid{r, off}) + hasControl = true + } + prev = r + } + + switch len(invalids) { + case 0: + return + case 1: + var kind string + if hasFormat { + kind = "format" + } else if hasControl { + kind = "control" + } else { + panic("unreachable") + } + + r := invalids[0] + msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r) + + replacement := strconv.QuoteRune(r.r) + replacement = replacement[1 : len(replacement)-1] + edit := analysis.SuggestedFix{ + Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r), + TextEdits: []analysis.TextEdit{{ + Pos: lit.Pos() + token.Pos(r.off), + End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)), + NewText: []byte(replacement), + }}, + } + delete := analysis.SuggestedFix{ + Message: fmt.Sprintf("delete %s character %U", kind, r.r), + TextEdits: []analysis.TextEdit{{ + Pos: lit.Pos() + token.Pos(r.off), + End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)), + }}, + } + report.Report(pass, lit, msg, report.Fixes(edit, delete)) + default: + var kind string + if hasFormat && hasControl { + kind = "format and control" + } else if hasFormat { + kind = "format" + } else if hasControl { + kind = "control" + } else { + panic("unreachable") + } + + msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind) + var edits []analysis.TextEdit + var deletions []analysis.TextEdit + for _, r := range invalids { + replacement := strconv.QuoteRune(r.r) + replacement = replacement[1 : len(replacement)-1] + edits = append(edits, analysis.TextEdit{ + Pos: lit.Pos() + token.Pos(r.off), + End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)), + NewText: []byte(replacement), + }) + deletions = append(deletions, analysis.TextEdit{ + Pos: lit.Pos() + token.Pos(r.off), + End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)), + }) + } + edit := analysis.SuggestedFix{ + Message: fmt.Sprintf("replace all %s characters with escape sequences", kind), + TextEdits: edits, + } + delete := analysis.SuggestedFix{ + Message: fmt.Sprintf("delete all %s characters", kind), + TextEdits: deletions, + } + report.Report(pass, lit, msg, report.Fixes(edit, delete)) + } + } + code.Preorder(pass, fn, (*ast.BasicLit)(nil)) + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1019/st1019.go b/vendor/honnef.co/go/tools/stylecheck/st1019/st1019.go new file mode 100644 index 0000000..d43c818 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1019/st1019.go @@ -0,0 +1,87 @@ +package st1019 + +import ( + "fmt" + "go/ast" + + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1019", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: `Importing the same package multiple times`, + Text: `Go allows importing the same package multiple times, as long as +different import aliases are being used. That is, the following +bit of code is valid: + + import ( + "fmt" + fumpt "fmt" + format "fmt" + ) + +However, this is very rarely done on purpose. Usually, it is a +sign of code that got refactored, accidentally adding duplicate +import statements. It is also a rarely known feature, which may +contribute to confusion. + +Do note that sometimes, this feature may be used +intentionally (see for example +https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d) +– if you want to allow this pattern in your code base, you're +advised to disable this check. + +It is acceptable to import the same package twice if one of the imports +uses the blank identifier. This is allowed in order to increase +resilience against erroneous changes when using the same package for its +side effects as well as its exported API.`, + Since: "2020.1", + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + for _, f := range pass.Files { + // Collect all imports by their import path + imports := make(map[string][]*ast.ImportSpec, len(f.Imports)) + for _, imp := range f.Imports { + if imp.Name != nil && imp.Name.Name == "_" { + // Allow blank imports to coexist with one normal import. + // + // We don't have to count the number of blank imports, + // goimports removes duplicates. + continue + } + imports[imp.Path.Value] = append(imports[imp.Path.Value], imp) + } + + for path, value := range imports { + if path[1:len(path)-1] == "unsafe" { + // Don't flag unsafe. Cgo generated code imports + // unsafe as _cgo_unsafe, in addition to the user's import. + continue + } + // If there's more than one import per path, we flag that + if len(value) > 1 { + s := fmt.Sprintf("package %s is being imported more than once", path) + opts := []report.Option{report.FilterGenerated()} + for _, imp := range value[1:] { + opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path))) + } + report.Report(pass, value[0], s, opts...) + } + } + } + return nil, nil +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1020/st1020.go b/vendor/honnef.co/go/tools/stylecheck/st1020/st1020.go new file mode 100644 index 0000000..b395f2c --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1020/st1020.go @@ -0,0 +1,101 @@ +package st1020 + +import ( + "fmt" + "go/ast" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1020", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "The documentation of an exported function should start with the function's name", + Text: `Doc comments work best as complete sentences, which +allow a wide variety of automated presentations. The first sentence +should be a one-sentence summary that starts with the name being +declared. + +If every doc comment begins with the name of the item it describes, +you can use the \'doc\' subcommand of the \'go\' tool and run the output +through grep. + +See https://go.dev/doc/effective_go#commentary for more +information on how to write good documentation.`, + Since: "2020.1", + NonDefault: true, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + fn := func(node ast.Node) { + if code.IsInTest(pass, node) { + return + } + + decl := node.(*ast.FuncDecl) + text, ok := docText(decl.Doc) + if !ok { + return + } + if !ast.IsExported(decl.Name.Name) { + return + } + if strings.HasPrefix(text, "Deprecated: ") { + return + } + + kind := "function" + if decl.Recv != nil { + kind = "method" + var ident *ast.Ident + T := decl.Recv.List[0].Type + if T_, ok := T.(*ast.StarExpr); ok { + T = T_.X + } + switch T := T.(type) { + case *ast.IndexExpr: + ident = T.X.(*ast.Ident) + case *ast.IndexListExpr: + ident = T.X.(*ast.Ident) + case *ast.Ident: + ident = T + default: + lint.ExhaustiveTypeSwitch(T) + } + if !ast.IsExported(ident.Name) { + return + } + } + prefix := decl.Name.Name + " " + if !strings.HasPrefix(text, prefix) { + report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated()) + } + } + + code.Preorder(pass, fn, (*ast.FuncDecl)(nil)) + return nil, nil +} + +func docText(doc *ast.CommentGroup) (string, bool) { + if doc == nil { + return "", false + } + // We trim spaces primarily because of /**/ style comments, which often have leading space. + text := strings.TrimSpace(doc.Text()) + return text, text != "" +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1021/st1021.go b/vendor/honnef.co/go/tools/stylecheck/st1021/st1021.go new file mode 100644 index 0000000..9a43c81 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1021/st1021.go @@ -0,0 +1,124 @@ +package st1021 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1021", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "The documentation of an exported type should start with type's name", + Text: `Doc comments work best as complete sentences, which +allow a wide variety of automated presentations. The first sentence +should be a one-sentence summary that starts with the name being +declared. + +If every doc comment begins with the name of the item it describes, +you can use the \'doc\' subcommand of the \'go\' tool and run the output +through grep. + +See https://go.dev/doc/effective_go#commentary for more +information on how to write good documentation.`, + Since: "2020.1", + NonDefault: true, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + var genDecl *ast.GenDecl + fn := func(node ast.Node, push bool) bool { + if !push { + genDecl = nil + return false + } + if code.IsInTest(pass, node) { + return false + } + + switch node := node.(type) { + case *ast.GenDecl: + if node.Tok == token.IMPORT { + return false + } + genDecl = node + return true + case *ast.TypeSpec: + if !ast.IsExported(node.Name.Name) { + return false + } + + doc := node.Doc + text, ok := docText(doc) + if !ok { + if len(genDecl.Specs) != 1 { + // more than one spec in the GenDecl, don't validate the + // docstring + return false + } + if genDecl.Lparen.IsValid() { + // 'type ( T )' is weird, don't guess the user's intention + return false + } + doc = genDecl.Doc + text, ok = docText(doc) + if !ok { + return false + } + } + + // Check comment before we strip articles in case the type's name is an article. + if strings.HasPrefix(text, node.Name.Name+" ") { + return false + } + + s := text + articles := [...]string{"A", "An", "The"} + for _, a := range articles { + if strings.HasPrefix(s, a+" ") { + s = s[len(a)+1:] + break + } + } + if !strings.HasPrefix(s, node.Name.Name+" ") { + report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated()) + } + return false + case *ast.FuncLit, *ast.FuncDecl: + return false + default: + lint.ExhaustiveTypeSwitch(node) + return false + } + } + + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn) + return nil, nil +} + +func docText(doc *ast.CommentGroup) (string, bool) { + if doc == nil { + return "", false + } + // We trim spaces primarily because of /**/ style comments, which often have leading space. + text := strings.TrimSpace(doc.Text()) + return text, text != "" +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1022/st1022.go b/vendor/honnef.co/go/tools/stylecheck/st1022/st1022.go new file mode 100644 index 0000000..2813632 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1022/st1022.go @@ -0,0 +1,105 @@ +package st1022 + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "honnef.co/go/tools/analysis/code" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/inspect" + "golang.org/x/tools/go/ast/inspector" +) + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: &analysis.Analyzer{ + Name: "ST1022", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer}, + }, + Doc: &lint.RawDocumentation{ + Title: "The documentation of an exported variable or constant should start with variable's name", + Text: `Doc comments work best as complete sentences, which +allow a wide variety of automated presentations. The first sentence +should be a one-sentence summary that starts with the name being +declared. + +If every doc comment begins with the name of the item it describes, +you can use the \'doc\' subcommand of the \'go\' tool and run the output +through grep. + +See https://go.dev/doc/effective_go#commentary for more +information on how to write good documentation.`, + Since: "2020.1", + NonDefault: true, + MergeIf: lint.MergeIfAny, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer + +func run(pass *analysis.Pass) (any, error) { + var genDecl *ast.GenDecl + fn := func(node ast.Node, push bool) bool { + if !push { + genDecl = nil + return false + } + if code.IsInTest(pass, node) { + return false + } + + switch node := node.(type) { + case *ast.GenDecl: + if node.Tok == token.IMPORT { + return false + } + genDecl = node + return true + case *ast.ValueSpec: + if genDecl.Lparen.IsValid() || len(node.Names) > 1 { + // Don't try to guess the user's intention + return false + } + name := node.Names[0].Name + if !ast.IsExported(name) { + return false + } + text, ok := docText(genDecl.Doc) + if !ok { + return false + } + prefix := name + " " + if !strings.HasPrefix(text, prefix) { + kind := "var" + if genDecl.Tok == token.CONST { + kind = "const" + } + report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated()) + } + return false + case *ast.FuncLit, *ast.FuncDecl: + return false + default: + lint.ExhaustiveTypeSwitch(node) + return false + } + } + + pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn) + return nil, nil +} + +func docText(doc *ast.CommentGroup) (string, bool) { + if doc == nil { + return "", false + } + // We trim spaces primarily because of /**/ style comments, which often have leading space. + text := strings.TrimSpace(doc.Text()) + return text, text != "" +} diff --git a/vendor/honnef.co/go/tools/stylecheck/st1023/st1023.go b/vendor/honnef.co/go/tools/stylecheck/st1023/st1023.go new file mode 100644 index 0000000..c1ab2c0 --- /dev/null +++ b/vendor/honnef.co/go/tools/stylecheck/st1023/st1023.go @@ -0,0 +1,22 @@ +package st1023 + +import ( + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/internal/sharedcheck" +) + +func init() { + SCAnalyzer.Analyzer.Name = "ST1023" +} + +var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ + Analyzer: sharedcheck.RedundantTypeInDeclarationChecker("should", false), + Doc: &lint.RawDocumentation{ + Title: "Redundant type in variable declaration", + Since: "2021.1", + NonDefault: true, + MergeIf: lint.MergeIfAll, + }, +}) + +var Analyzer = SCAnalyzer.Analyzer diff --git a/vendor/honnef.co/go/tools/unused/implements.go b/vendor/honnef.co/go/tools/unused/implements.go new file mode 100644 index 0000000..f7c3796 --- /dev/null +++ b/vendor/honnef.co/go/tools/unused/implements.go @@ -0,0 +1,102 @@ +package unused + +import ( + "go/types" + + "honnef.co/go/tools/go/types/typeutil" +) + +// lookupMethod returns the index of and method with matching package and name, or (-1, nil). +func lookupMethod(T *types.Interface, pkg *types.Package, name string) (int, *types.Func) { + if name != "_" { + for i := 0; i < T.NumMethods(); i++ { + m := T.Method(i) + if sameId(m, pkg, name) { + return i, m + } + } + } + return -1, nil +} + +func sameId(obj types.Object, pkg *types.Package, name string) bool { + // spec: + // "Two identifiers are different if they are spelled differently, + // or if they appear in different packages and are not exported. + // Otherwise, they are the same." + if name != obj.Name() { + return false + } + // obj.Name == name + if obj.Exported() { + return true + } + // not exported, so packages must be the same (pkg == nil for + // fields in Universe scope; this can only happen for types + // introduced via Eval) + if pkg == nil || obj.Pkg() == nil { + return pkg == obj.Pkg() + } + // pkg != nil && obj.pkg != nil + return pkg.Path() == obj.Pkg().Path() +} + +func implements(V types.Type, T *types.Interface, msV *types.MethodSet) ([]*types.Selection, bool) { + // fast path for common case + if T.Empty() { + return nil, true + } + + if ityp, _ := V.Underlying().(*types.Interface); ityp != nil { + // TODO(dh): is this code reachable? + for m := range T.Methods() { + _, obj := lookupMethod(ityp, m.Pkg(), m.Name()) + switch { + case obj == nil: + return nil, false + case !types.Identical(obj.Type(), m.Type()): + return nil, false + } + } + return nil, true + } + + // A concrete type implements T if it implements all methods of T. + var sels []*types.Selection + + mapping := map[*types.TypeParam]types.Type{} + for m := range T.Methods() { + sel := msV.Lookup(m.Pkg(), m.Name()) + if sel == nil { + return nil, false + } + + f, _ := sel.Obj().(*types.Func) + if f == nil { + return nil, false + } + + if ok := typeutil.Unify(f.Type(), m.Type(), mapping); !ok { + return nil, false + } + + sels = append(sels, sel) + } + for tparam, targ := range mapping { + // This checks constraints on a best-effort basis, erring on the side + // of accepting too many types. + if !satisfiesConstraint(targ, tparam) { + return nil, false + } + } + return sels, true +} + +func satisfiesConstraint(t types.Type, tp *types.TypeParam) bool { + if t == nil { + // t is nil when we unify two type parameters. + return true + } + bound := tp.Constraint().Underlying().(*types.Interface) + return types.Satisfies(t, bound) +} diff --git a/vendor/honnef.co/go/tools/unused/runtime.go b/vendor/honnef.co/go/tools/unused/runtime.go new file mode 100644 index 0000000..11be4a3 --- /dev/null +++ b/vendor/honnef.co/go/tools/unused/runtime.go @@ -0,0 +1,331 @@ +package unused + +// Functions defined in the Go runtime that may be called through +// compiler magic or via assembly. +var runtimeFuncs = map[string]bool{ + // Copied from cmd/compile/internal/typecheck/builtin.go, var runtimeDecls + "newobject": true, + "panicindex": true, + "panicslice": true, + "panicdivide": true, + "panicmakeslicelen": true, + "throwinit": true, + "panicwrap": true, + "gopanic": true, + "gorecover": true, + "goschedguarded": true, + "printbool": true, + "printfloat": true, + "printint": true, + "printhex": true, + "printuint": true, + "printcomplex": true, + "printstring": true, + "printpointer": true, + "printiface": true, + "printeface": true, + "printslice": true, + "printnl": true, + "printsp": true, + "printlock": true, + "printunlock": true, + "concatstring2": true, + "concatstring3": true, + "concatstring4": true, + "concatstring5": true, + "concatstrings": true, + "cmpstring": true, + "intstring": true, + "slicebytetostring": true, + "slicebytetostringtmp": true, + "slicerunetostring": true, + "stringtoslicebyte": true, + "stringtoslicerune": true, + "slicecopy": true, + "slicestringcopy": true, + "decoderune": true, + "countrunes": true, + "convI2I": true, + "convT16": true, + "convT32": true, + "convT64": true, + "convTstring": true, + "convTslice": true, + "convT2E": true, + "convT2Enoptr": true, + "convT2I": true, + "convT2Inoptr": true, + "assertE2I": true, + "assertE2I2": true, + "assertI2I": true, + "assertI2I2": true, + "panicdottypeE": true, + "panicdottypeI": true, + "panicnildottype": true, + "ifaceeq": true, + "efaceeq": true, + "fastrand": true, + "makemap64": true, + "makemap": true, + "makemap_small": true, + "mapaccess1": true, + "mapaccess1_fast32": true, + "mapaccess1_fast64": true, + "mapaccess1_faststr": true, + "mapaccess1_fat": true, + "mapaccess2": true, + "mapaccess2_fast32": true, + "mapaccess2_fast64": true, + "mapaccess2_faststr": true, + "mapaccess2_fat": true, + "mapassign": true, + "mapassign_fast32": true, + "mapassign_fast32ptr": true, + "mapassign_fast64": true, + "mapassign_fast64ptr": true, + "mapassign_faststr": true, + "mapiterinit": true, + "mapdelete": true, + "mapdelete_fast32": true, + "mapdelete_fast64": true, + "mapdelete_faststr": true, + "mapiternext": true, + "mapclear": true, + "makechan64": true, + "makechan": true, + "chanrecv1": true, + "chanrecv2": true, + "chansend1": true, + "closechan": true, + "writeBarrier": true, + "typedmemmove": true, + "typedmemclr": true, + "typedslicecopy": true, + "selectnbsend": true, + "selectnbrecv": true, + "selectnbrecv2": true, + "selectsetpc": true, + "selectgo": true, + "block": true, + "makeslice": true, + "makeslice64": true, + "growslice": true, + "memmove": true, + "memclrNoHeapPointers": true, + "memclrHasPointers": true, + "memequal": true, + "memequal8": true, + "memequal16": true, + "memequal32": true, + "memequal64": true, + "memequal128": true, + "int64div": true, + "uint64div": true, + "int64mod": true, + "uint64mod": true, + "float64toint64": true, + "float64touint64": true, + "float64touint32": true, + "int64tofloat64": true, + "uint64tofloat64": true, + "uint32tofloat64": true, + "complex128div": true, + "racefuncenter": true, + "racefuncenterfp": true, + "racefuncexit": true, + "raceread": true, + "racewrite": true, + "racereadrange": true, + "racewriterange": true, + "msanread": true, + "msanwrite": true, + "x86HasPOPCNT": true, + "x86HasSSE41": true, + "arm64HasATOMICS": true, + "mallocgc": true, + "panicshift": true, + "panicmakeslicecap": true, + "goPanicIndex": true, + "goPanicIndexU": true, + "goPanicSliceAlen": true, + "goPanicSliceAlenU": true, + "goPanicSliceAcap": true, + "goPanicSliceAcapU": true, + "goPanicSliceB": true, + "goPanicSliceBU": true, + "goPanicSlice3Alen": true, + "goPanicSlice3AlenU": true, + "goPanicSlice3Acap": true, + "goPanicSlice3AcapU": true, + "goPanicSlice3B": true, + "goPanicSlice3BU": true, + "goPanicSlice3C": true, + "goPanicSlice3CU": true, + "goPanicSliceConvert": true, + "printuintptr": true, + "convT": true, + "convTnoptr": true, + "makeslicecopy": true, + "unsafeslicecheckptr": true, + "panicunsafeslicelen": true, + "panicunsafeslicenilptr": true, + "unsafestringcheckptr": true, + "panicunsafestringlen": true, + "panicunsafestringnilptr": true, + "mulUintptr": true, + "memequal0": true, + "f32equal": true, + "f64equal": true, + "c64equal": true, + "c128equal": true, + "strequal": true, + "interequal": true, + "nilinterequal": true, + "memhash": true, + "memhash0": true, + "memhash8": true, + "memhash16": true, + "memhash32": true, + "memhash64": true, + "memhash128": true, + "f32hash": true, + "f64hash": true, + "c64hash": true, + "c128hash": true, + "strhash": true, + "interhash": true, + "nilinterhash": true, + "int64tofloat32": true, + "uint64tofloat32": true, + "getcallerpc": true, + "getcallersp": true, + "msanmove": true, + "asanread": true, + "asanwrite": true, + "checkptrAlignment": true, + "checkptrArithmetic": true, + "libfuzzerTraceCmp1": true, + "libfuzzerTraceCmp2": true, + "libfuzzerTraceCmp4": true, + "libfuzzerTraceCmp8": true, + "libfuzzerTraceConstCmp1": true, + "libfuzzerTraceConstCmp2": true, + "libfuzzerTraceConstCmp4": true, + "libfuzzerTraceConstCmp8": true, + "libfuzzerHookStrCmp": true, + "libfuzzerHookEqualFold": true, + "addCovMeta": true, + "x86HasFMA": true, + "armHasVFPv4": true, + + // Extracted from assembly code in the standard library, with the exception of the runtime package itself + "abort": true, + "aeshashbody": true, + "args": true, + "asminit": true, + "badctxt": true, + "badmcall2": true, + "badmcall": true, + "badmorestackg0": true, + "badmorestackgsignal": true, + "badsignal2": true, + "callbackasm1": true, + "callCfunction": true, + "cgocallback_gofunc": true, + "cgocallbackg": true, + "checkgoarm": true, + "check": true, + "debugCallCheck": true, + "debugCallWrap": true, + "emptyfunc": true, + "entersyscall": true, + "exit": true, + "exits": true, + "exitsyscall": true, + "externalthreadhandler": true, + "findnull": true, + "goexit1": true, + "gostring": true, + "i386_set_ldt": true, + "_initcgo": true, + "init_thread_tls": true, + "ldt0setup": true, + "libpreinit": true, + "load_g": true, + "morestack": true, + "mstart": true, + "nacl_sysinfo": true, + "nanotimeQPC": true, + "nanotime": true, + "newosproc0": true, + "newproc": true, + "newstack": true, + "noted": true, + "nowQPC": true, + "osinit": true, + "printf": true, + "racecallback": true, + "reflectcallmove": true, + "reginit": true, + "rt0_go": true, + "save_g": true, + "schedinit": true, + "setldt": true, + "settls": true, + "sighandler": true, + "sigprofNonGo": true, + "sigtrampgo": true, + "_sigtramp": true, + "sigtramp": true, + "stackcheck": true, + "syscall_chdir": true, + "syscall_chroot": true, + "syscall_close": true, + "syscall_dup2": true, + "syscall_execve": true, + "syscall_exit": true, + "syscall_fcntl": true, + "syscall_forkx": true, + "syscall_gethostname": true, + "syscall_getpid": true, + "syscall_ioctl": true, + "syscall_pipe": true, + "syscall_rawsyscall6": true, + "syscall_rawSyscall6": true, + "syscall_rawsyscall": true, + "syscall_RawSyscall": true, + "syscall_rawsysvicall6": true, + "syscall_setgid": true, + "syscall_setgroups": true, + "syscall_setpgid": true, + "syscall_setsid": true, + "syscall_setuid": true, + "syscall_syscall6": true, + "syscall_syscall": true, + "syscall_Syscall": true, + "syscall_sysvicall6": true, + "syscall_wait4": true, + "syscall_write": true, + "traceback": true, + "tstart": true, + "usplitR0": true, + "wbBufFlush": true, + "write": true, + + // Other runtime functions that can get called in non-standard ways + "bgsweep": true, + "memhash_varlen": true, + "strhashFallback": true, + "asanregisterglobals": true, + "cgoUse": true, + "cgoCheckPointer": true, + "cgoCheckResult": true, + "_cgo_panic_internal": true, + "addExitHook": true, +} + +var runtimeCoverageFuncs = map[string]bool{ + "initHook": true, + "markProfileEmitted": true, + "processCoverTestDir": true, +} diff --git a/vendor/honnef.co/go/tools/unused/serialize.go b/vendor/honnef.co/go/tools/unused/serialize.go new file mode 100644 index 0000000..e971541 --- /dev/null +++ b/vendor/honnef.co/go/tools/unused/serialize.go @@ -0,0 +1,99 @@ +package unused + +import ( + "fmt" + "go/token" + "os" + + "golang.org/x/tools/go/types/objectpath" +) + +type ObjectPath struct { + PkgPath string + ObjPath objectpath.Path +} + +// XXX make sure that node 0 always exists and is always the root + +type SerializedGraph struct { + nodes []Node + nodesByPath map[ObjectPath]NodeID + // XXX deduplicating on position is dubious for `switch x := foo.(type)`, where x will be declared many times for + // the different types, but all at the same position. On the other hand, merging these nodes is probably fine. + nodesByPosition map[token.Position]NodeID +} + +func trace(f string, args ...any) { + fmt.Fprintf(os.Stderr, f, args...) + fmt.Fprintln(os.Stderr) +} + +func (g *SerializedGraph) Merge(nodes []Node) { + if g.nodesByPath == nil { + g.nodesByPath = map[ObjectPath]NodeID{} + } + if g.nodesByPosition == nil { + g.nodesByPosition = map[token.Position]NodeID{} + } + if len(g.nodes) == 0 { + // Seed nodes with a root node + g.nodes = append(g.nodes, Node{}) + } + // OPT(dh): reuse storage between calls to Merge + remapping := make([]NodeID, len(nodes)) + + // First pass: compute remapping of IDs of to-be-merged nodes + for _, n := range nodes { + // XXX Column is never 0. it's 1 if there is no column information in the export data. which sucks, because + // objects can also genuinely be in column 1. + if n.id != 0 && n.obj.Path == (ObjectPath{}) && n.obj.Position.Column == 0 { + // If the object has no path, then it couldn't have come from export data, which means it needs to have full + // position information including a column. + panic(fmt.Sprintf("object %q has no path but also no column information", n.obj.Name)) + } + + if orig, ok := g.nodesByPath[n.obj.Path]; ok { + // We already have a node for this object + trace("deduplicating %d -> %d based on path %s", n.id, orig, n.obj.Path) + remapping[n.id] = orig + } else if orig, ok := g.nodesByPosition[n.obj.Position]; ok && n.obj.Position.Column != 0 { + // We already have a node for this object + trace("deduplicating %d -> %d based on position %s", n.id, orig, n.obj.Position) + remapping[n.id] = orig + } else { + // This object is new to us; change ID to avoid collision + newID := NodeID(len(g.nodes)) + trace("new node, remapping %d -> %d", n.id, newID) + remapping[n.id] = newID + g.nodes = append(g.nodes, Node{ + id: newID, + obj: n.obj, + uses: make([]NodeID, 0, len(n.uses)), + owns: make([]NodeID, 0, len(n.owns)), + }) + if n.id == 0 { + // Our root uses all the roots of the subgraphs + g.nodes[0].uses = append(g.nodes[0].uses, newID) + } + if n.obj.Path != (ObjectPath{}) { + g.nodesByPath[n.obj.Path] = newID + } + if n.obj.Position.Column != 0 { + g.nodesByPosition[n.obj.Position] = newID + } + } + } + + // Second step: apply remapping + for _, n := range nodes { + n.id = remapping[n.id] + for i := range n.uses { + n.uses[i] = remapping[n.uses[i]] + } + for i := range n.owns { + n.owns[i] = remapping[n.owns[i]] + } + g.nodes[n.id].uses = append(g.nodes[n.id].uses, n.uses...) + g.nodes[n.id].owns = append(g.nodes[n.id].owns, n.owns...) + } +} diff --git a/vendor/honnef.co/go/tools/unused/unused.go b/vendor/honnef.co/go/tools/unused/unused.go new file mode 100644 index 0000000..fd7b5c0 --- /dev/null +++ b/vendor/honnef.co/go/tools/unused/unused.go @@ -0,0 +1,1739 @@ +// Package unused contains code for finding unused code. +package unused + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + "io" + "reflect" + "slices" + "strings" + + "honnef.co/go/tools/analysis/facts/directives" + "honnef.co/go/tools/analysis/facts/generated" + "honnef.co/go/tools/analysis/lint" + "honnef.co/go/tools/analysis/report" + "honnef.co/go/tools/go/ast/astutil" + "honnef.co/go/tools/go/types/typeutil" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/types/objectpath" +) + +// OPT(dh): don't track local variables that can't have any interesting outgoing edges. For example, using a local +// variable of type int is meaningless; we don't care if `int` is used or not. +// +// Note that we do have to track variables with for example array types, because the array type could have involved a +// named constant. +// +// We probably have different culling needs depending on the mode of operation, too. If we analyze multiple packages in +// one graph (unused's "whole program" mode), we could remove further useless edges (e.g. into nodes that themselves +// have no outgoing edges and aren't meaningful objects on their own) after having analyzed a package, to keep the +// in-memory representation small on average. If we only analyze a single package, that step would just waste cycles, as +// we're about to throw the entire graph away, anyway. + +// TODO(dh): currently, types use methods that implement interfaces. However, this makes a method used even if the +// relevant interface is never used. What if instead interfaces used those methods? Right now we cannot do that, because +// methods use their receivers, so using a method uses the type. But do we need that edge? Is there a way to refer to a +// method without explicitly mentioning the type somewhere? If not, the edge from method to receiver is superfluous. + +// XXX vet all code for proper use of core types + +// TODO(dh): we cannot observe function calls in assembly files. + +/* + +This overview is true when using the default options. Different options may change individual behaviors. + +- packages use: + - (1.1) exported named types + - (1.2) exported functions (but not methods!) + - (1.3) exported variables + - (1.4) exported constants + - (1.5) init functions + - (1.6) functions exported to cgo + - (1.7) the main function iff in the main package + - (1.8) symbols linked via go:linkname + - (1.9) objects in generated files + +- named types use: + - (2.1) exported methods + - (2.2) the type they're based on + - (2.5) all their type parameters. Unused type parameters are probably useless, but they're a brand new feature and we + don't want to introduce false positives because we couldn't anticipate some novel use-case. + - (2.6) all their type arguments + +- functions use: + - (4.1) all their arguments, return parameters and receivers + - (4.2) anonymous functions defined beneath them + - (4.3) closures and bound methods. + this implements a simplified model where a function is used merely by being referenced, even if it is never called. + that way we don't have to keep track of closures escaping functions. + - (4.4) functions they return. we assume that someone else will call the returned function + - (4.5) functions/interface methods they call + - (4.6) types they instantiate or convert to + - (4.7) fields they access + - (4.9) package-level variables they assign to iff in tests (sinks for benchmarks) + - (4.10) all their type parameters. See 2.5 for reasoning. + - (4.11) local variables + - Note that the majority of this is handled implicitly by seeing idents be used. In particular, unlike the old + IR-based implementation, the AST-based one doesn't care about closures, bound methods or anonymous functions. + They're all just additional nodes in the AST. + +- conversions use: + - (5.1) when converting between two equivalent structs, the fields in + either struct use each other. the fields are relevant for the + conversion, but only if the fields are also accessed outside the + conversion. + - (5.2) when converting to or from unsafe.Pointer, mark all fields as used. + +- structs use: + - (6.1) fields of type NoCopy sentinel + - (6.2) exported fields + - (6.3) embedded fields that help implement interfaces (either fully implements it, or contributes required methods) (recursively) + - (6.4) embedded fields that have exported methods (recursively) + - (6.5) embedded structs that have exported fields (recursively) + - (6.6) all fields if they have a structs.HostLayout field + +- (7.1) field accesses use fields +- (7.2) fields use their types + +- (8.0) How we handle interfaces: + - (8.1) We do not technically care about interfaces that only consist of + exported methods. Exported methods on concrete types are always + marked as used. + - (8.2) Any concrete type implements all known interfaces. Even if it isn't + assigned to any interfaces in our code, the user may receive a value + of the type and expect to pass it back to us through an interface. + + Concrete types use their methods that implement interfaces. If the + type is used, it uses those methods. Otherwise, it doesn't. This + way, types aren't incorrectly marked reachable through the edge + from method to type. + + - (8.3) All interface methods are marked as used, even if they never get + called. This is to accommodate sum types (unexported interface + method that must exist but never gets called.) + + - (8.4) All embedded interfaces are marked as used. This is an + extension of 8.3, but we have to explicitly track embedded + interfaces because in a chain C->B->A, B wouldn't be marked as + used by 8.3 just because it contributes A's methods to C. + +- Inherent uses: + - (9.2) variables use their types + - (9.3) types use their underlying and element types + - (9.4) conversions use the type they convert to + - (9.7) variable _reads_ use variables, writes do not, except in tests + - (9.8) runtime functions that may be called from user code via the compiler + - (9.9) objects named the blank identifier are used. They cannot be referred to and are usually used explicitly to + use something that would otherwise be unused. + - The majority of idents get marked as read by virtue of being in the AST. + +- const groups: + - (10.1) if one constant out of a block of constants is used, mark all + of them used. a lot of the time, unused constants exist for the sake + of completeness. See also + https://github.com/dominikh/go-tools/issues/365 + + Do not, however, include constants named _ in constant groups. + + +- (11.1) anonymous struct types use all their fields. we cannot + deduplicate struct types, as that leads to order-dependent + reports. we can't not deduplicate struct types while still + tracking fields, because then each instance of the unnamed type in + the data flow chain will get its own fields, causing false + positives. Thus, we only accurately track fields of named struct + types, and assume that unnamed struct types use all their fields. + +- type parameters use: + - (12.1) their constraint type + +*/ + +var Debug io.Writer + +func assert(b bool) { + if !b { + panic("failed assertion") + } +} + +// TODO(dh): should we return a map instead of two slices? +type Result struct { + Used []Object + Unused []Object + Quiet []Object +} + +var Analyzer = &lint.Analyzer{ + Doc: &lint.RawDocumentation{ + Title: "Unused code", + }, + Analyzer: &analysis.Analyzer{ + Name: "U1000", + Doc: "Unused code", + Run: run, + Requires: []*analysis.Analyzer{generated.Analyzer, directives.Analyzer}, + ResultType: reflect.TypeFor[Result](), + }, +} + +func newGraph( + fset *token.FileSet, + files []*ast.File, + pkg *types.Package, + info *types.Info, + directives []lint.Directive, + generated map[string]generated.Generator, + opts Options, +) *graph { + g := graph{ + pkg: pkg, + info: info, + files: files, + directives: directives, + generated: generated, + fset: fset, + nodes: []Node{{}}, + edges: map[edge]struct{}{}, + objects: map[types.Object]NodeID{}, + opts: opts, + } + + return &g +} + +func run(pass *analysis.Pass) (any, error) { + g := newGraph( + pass.Fset, + pass.Files, + pass.Pkg, + pass.TypesInfo, + pass.ResultOf[directives.Analyzer].([]lint.Directive), + pass.ResultOf[generated.Analyzer].(map[string]generated.Generator), + DefaultOptions, + ) + g.entry() + + sg := &SerializedGraph{ + nodes: g.nodes, + } + + if Debug != nil { + Debug.Write([]byte(sg.Dot())) + } + + return sg.Results(), nil +} + +type Options struct { + FieldWritesAreUses bool + PostStatementsAreReads bool + ExportedIsUsed bool + ExportedFieldsAreUsed bool + ParametersAreUsed bool + LocalVariablesAreUsed bool + GeneratedIsUsed bool +} + +var DefaultOptions = Options{ + FieldWritesAreUses: true, + PostStatementsAreReads: false, + ExportedIsUsed: true, + ExportedFieldsAreUsed: true, + ParametersAreUsed: true, + LocalVariablesAreUsed: true, + GeneratedIsUsed: true, +} + +type edgeKind uint8 + +const ( + edgeKindUse = iota + 1 + edgeKindOwn +) + +type edge struct { + from, to NodeID + kind edgeKind +} + +type graph struct { + pkg *types.Package + info *types.Info + files []*ast.File + fset *token.FileSet + directives []lint.Directive + generated map[string]generated.Generator + + opts Options + + // edges tracks all edges between nodes (uses and owns relationships). This data is also present in the Node struct, + // but there it can't be accessed in O(1) time. edges is used to deduplicate edges. + edges map[edge]struct{} + nodes []Node + objects map[types.Object]NodeID + + // package-level named types + namedTypes []*types.TypeName + interfaceTypes []*types.Interface +} + +type nodeState uint8 + +//gcassert:inline +func (ns nodeState) seen() bool { return ns&nodeStateSeen != 0 } + +//gcassert:inline +func (ns nodeState) quiet() bool { return ns&nodeStateQuiet != 0 } + +const ( + nodeStateSeen nodeState = 1 << iota + nodeStateQuiet +) + +// OPT(dh): 32 bits would be plenty, but the Node struct would end up with padding, anyway. +type NodeID uint64 + +type Node struct { + id NodeID + obj Object + + // using slices instead of maps here helps make merging of graphs simpler and more efficient, because we can rewrite + // IDs in place instead of having to build new maps. + uses []NodeID + owns []NodeID +} + +func (g *graph) objectToObject(obj types.Object) Object { + // OPT(dh): I think we only need object paths in whole-program mode. In other cases, position-based node merging + // should suffice. + + // objectpath.For is an expensive function and we'd like to avoid calling it when we know that there cannot be a + // path, or when the path doesn't matter. + // + // Unexported global objects don't have paths. Local variables may have paths when they're parameters or return + // parameters, but we do not care about those, because they're not API that other packages can refer to directly. We + // do have to track fields, because they may be part of an anonymous type declared in a parameter or return + // parameter. We cannot categorically ignore unexported identifiers, because an exported field might have been + // embedded via an unexported field, which will be referred to. + + var relevant bool + switch obj := obj.(type) { + case *types.Var: + // If it's a field or it's an exported top-level variable, we care about it. Otherwise, we don't. + // OPT(dh): same question as posed in the default branch + relevant = obj.IsField() || token.IsExported(obj.Name()) + default: + // OPT(dh): See if it's worth checking that the object is actually in package scope, and doesn't just have a + // capitalized name. + relevant = token.IsExported(obj.Name()) + } + + var path ObjectPath + if relevant { + objPath, _ := objectpath.For(obj) + if objPath != "" { + path = ObjectPath{ + PkgPath: obj.Pkg().Path(), + ObjPath: objPath, + } + } + } + name := obj.Name() + if sig, ok := obj.Type().(*types.Signature); ok && sig.Recv() != nil { + switch types.Unalias(sig.Recv().Type()).(type) { + case *types.Named, *types.Pointer: + typ := types.TypeString(sig.Recv().Type(), func(*types.Package) string { return "" }) + if len(typ) > 0 && typ[0] == '*' { + name = fmt.Sprintf("(%s).%s", typ, obj.Name()) + } else if len(typ) > 0 { + name = fmt.Sprintf("%s.%s", typ, obj.Name()) + } + } + } + return Object{ + Name: name, + ShortName: obj.Name(), + Kind: typString(obj), + Path: path, + Position: g.fset.PositionFor(obj.Pos(), false), + DisplayPosition: report.DisplayPosition(g.fset, obj.Pos()), + } +} + +func typString(obj types.Object) string { + switch obj := obj.(type) { + case *types.Func: + return "func" + case *types.Var: + if obj.IsField() { + return "field" + } + return "var" + case *types.Const: + return "const" + case *types.TypeName: + if _, ok := obj.Type().(*types.TypeParam); ok { + return "type param" + } else { + return "type" + } + default: + return "identifier" + } +} + +func (g *graph) newNode(obj types.Object) NodeID { + id := NodeID(len(g.nodes)) + n := Node{ + id: id, + obj: g.objectToObject(obj), + } + g.nodes = append(g.nodes, n) + if _, ok := g.objects[obj]; ok { + panic(fmt.Sprintf("already had a node for %s", obj)) + } + g.objects[obj] = id + return id +} + +func (g *graph) node(obj types.Object) NodeID { + if obj == nil { + return 0 + } + obj = origin(obj) + if n, ok := g.objects[obj]; ok { + return n + } + n := g.newNode(obj) + return n +} + +func origin(obj types.Object) types.Object { + switch obj := obj.(type) { + case *types.Var: + return obj.Origin() + case *types.Func: + return obj.Origin() + default: + return obj + } +} + +func (g *graph) addEdge(e edge) bool { + if _, ok := g.edges[e]; ok { + return false + } + g.edges[e] = struct{}{} + return true +} + +func (g *graph) addOwned(owner, owned NodeID) { + e := edge{owner, owned, edgeKindOwn} + if !g.addEdge(e) { + return + } + n := &g.nodes[owner] + n.owns = append(n.owns, owned) +} + +func (g *graph) addUse(by, used NodeID) { + e := edge{by, used, edgeKindUse} + if !g.addEdge(e) { + return + } + nBy := &g.nodes[by] + nBy.uses = append(nBy.uses, used) +} + +func (g *graph) see(obj, owner types.Object) { + if obj == nil { + panic("saw nil object") + } + + if g.opts.ExportedIsUsed && obj.Pkg() != g.pkg || obj.Pkg() == nil { + return + } + + nObj := g.node(obj) + if owner != nil { + nOwner := g.node(owner) + g.addOwned(nOwner, nObj) + } +} + +func isIrrelevant(obj types.Object) bool { + switch obj.(type) { + case *types.PkgName: + return true + default: + return false + } +} + +func (g *graph) use(used, by types.Object) { + if g.opts.ExportedIsUsed { + if used.Pkg() != g.pkg || used.Pkg() == nil { + return + } + if by != nil && by.Pkg() != g.pkg { + return + } + } + + if isIrrelevant(used) { + return + } + + nUsed := g.node(used) + nBy := g.node(by) + g.addUse(nBy, nUsed) +} + +func (g *graph) entry() { + for _, f := range g.files { + for _, cg := range f.Comments { + for _, c := range cg.List { + if strings.HasPrefix(c.Text, "//go:linkname ") { + // FIXME(dh): we're looking at all comments. The + // compiler only looks at comments in the + // left-most column. The intention probably is to + // only look at top-level comments. + + // (1.8) packages use symbols linked via go:linkname + fields := strings.Fields(c.Text) + if len(fields) == 3 { + obj := g.pkg.Scope().Lookup(fields[1]) + if obj == nil { + continue + } + g.use(obj, nil) + } + } + } + } + } + + for _, f := range g.files { + for _, decl := range f.Decls { + g.decl(decl, nil) + } + } + + if g.opts.GeneratedIsUsed { + // OPT(dh): depending on the options used, we do not need to track all objects. For example, if local variables + // are always used, then it is enough to use their surrounding function. + for obj := range g.objects { + path := g.fset.PositionFor(obj.Pos(), false).Filename + if _, ok := g.generated[path]; ok { + g.use(obj, nil) + } + } + } + + // We use a normal map instead of a typeutil.Map because we deduplicate + // these on a best effort basis, as an optimization. + allInterfaces := make(map[*types.Interface]struct{}) + for _, typ := range g.interfaceTypes { + allInterfaces[typ] = struct{}{} + } + for _, ins := range g.info.Instances { + if typ, ok := ins.Type.(*types.Named); ok && typ.Obj().Pkg() == g.pkg { + if iface, ok := typ.Underlying().(*types.Interface); ok { + allInterfaces[iface] = struct{}{} + } + } + } + processMethodSet := func(named *types.TypeName, ms *types.MethodSet) { + if g.opts.ExportedIsUsed { + for m := range ms.Methods() { + if token.IsExported(m.Obj().Name()) { + // (2.1) named types use exported methods + // (6.4) structs use embedded fields that have exported methods + // + // By reading the selection, we read all embedded fields that are part of the path + g.readSelection(m, named) + } + } + } + + if _, ok := named.Type().Underlying().(*types.Interface); !ok { + // (8.0) handle interfaces + // + // We don't care about interfaces implementing interfaces; all their methods are already used, anyway + for iface := range allInterfaces { + if sels, ok := implements(named.Type(), iface, ms); ok { + for _, sel := range sels { + // (8.2) any concrete type implements all known interfaces + // (6.3) structs use embedded fields that help implement interfaces + g.readSelection(sel, named) + } + } + } + } + } + + for _, named := range g.namedTypes { + // OPT(dh): do we already have the method set available? + processMethodSet(named, types.NewMethodSet(named.Type())) + processMethodSet(named, types.NewMethodSet(types.NewPointer(named.Type()))) + + } + + type ignoredKey struct { + file string + line int + } + ignores := map[ignoredKey]struct{}{} + for _, dir := range g.directives { + if dir.Command != "ignore" && dir.Command != "file-ignore" { + continue + } + if len(dir.Arguments) == 0 { + continue + } + if slices.Contains(strings.Split(dir.Arguments[0], ","), "U1000") { + pos := g.fset.PositionFor(dir.Node.Pos(), false) + var key ignoredKey + switch dir.Command { + case "ignore": + key = ignoredKey{ + pos.Filename, + pos.Line, + } + case "file-ignore": + key = ignoredKey{ + pos.Filename, + -1, + } + } + + ignores[key] = struct{}{} + } + } + + if len(ignores) > 0 { + // all objects annotated with a //lint:ignore U1000 are considered used + for obj := range g.objects { + pos := g.fset.PositionFor(obj.Pos(), false) + key1 := ignoredKey{ + pos.Filename, + pos.Line, + } + key2 := ignoredKey{ + pos.Filename, + -1, + } + _, ok := ignores[key1] + if !ok { + _, ok = ignores[key2] + } + if ok { + g.use(obj, nil) + + // use methods and fields of ignored types + if obj, ok := obj.(*types.TypeName); ok { + if obj.IsAlias() { + if typ, ok := types.Unalias(obj.Type()).(*types.Named); ok && (g.opts.ExportedIsUsed && typ.Obj().Pkg() != obj.Pkg() || typ.Obj().Pkg() == nil) { + // This is an alias of a named type in another package. + // Don't walk its fields or methods; we don't have to. + // + // For aliases to types in the same package, we do want to ignore the fields and methods, + // because ignoring the alias should ignore the aliased type. + continue + } + } + if typ, ok := types.Unalias(obj.Type()).(*types.Named); ok { + for method := range typ.Methods() { + g.use(method, nil) + } + } + if typ, ok := obj.Type().Underlying().(*types.Struct); ok { + for field := range typ.Fields() { + g.use(field, nil) + } + } + } + } + } + } +} + +func isOfType[T any](x any) bool { + _, ok := x.(T) + return ok +} + +func (g *graph) read(node ast.Node, by types.Object) { + if node == nil { + return + } + + switch node := node.(type) { + case *ast.Ident: + // Among many other things, this handles + // (7.1) field accesses use fields + + obj := g.info.ObjectOf(node) + g.use(obj, by) + + case *ast.BasicLit: + // Nothing to do + + case *ast.SliceExpr: + g.read(node.X, by) + g.read(node.Low, by) + g.read(node.High, by) + g.read(node.Max, by) + + case *ast.UnaryExpr: + g.read(node.X, by) + + case *ast.ParenExpr: + g.read(node.X, by) + + case *ast.ArrayType: + g.read(node.Len, by) + g.read(node.Elt, by) + + case *ast.SelectorExpr: + g.readSelectorExpr(node, by) + + case *ast.IndexExpr: + // Among many other things, this handles + // (2.6) named types use all their type arguments + g.read(node.X, by) + g.read(node.Index, by) + + case *ast.IndexListExpr: + // Among many other things, this handles + // (2.6) named types use all their type arguments + g.read(node.X, by) + for _, index := range node.Indices { + g.read(index, by) + } + + case *ast.BinaryExpr: + g.read(node.X, by) + g.read(node.Y, by) + + case *ast.CompositeLit: + g.read(node.Type, by) + // We get the type of the node itself, not of node.Type, to handle nested composite literals of the kind + // T{{...}} + typ, isStruct := typeutil.CoreType(g.info.TypeOf(node)).(*types.Struct) + + if isStruct { + unkeyed := len(node.Elts) != 0 && !isOfType[*ast.KeyValueExpr](node.Elts[0]) + if g.opts.FieldWritesAreUses && unkeyed { + // Untagged struct literal that specifies all fields. We have to manually use the fields in the type, + // because the unkeyd literal doesn't contain any nodes referring to the fields. + for field := range typ.Fields() { + g.use(field, by) + } + } + if g.opts.FieldWritesAreUses || unkeyed { + for _, elt := range node.Elts { + g.read(elt, by) + } + } else { + for _, elt := range node.Elts { + kv := elt.(*ast.KeyValueExpr) + g.write(kv.Key, by) + g.read(kv.Value, by) + } + } + if g.opts.FieldWritesAreUses && !unkeyed { + for _, elt := range node.Elts { + kv := elt.(*ast.KeyValueExpr) + fname := kv.Key.(*ast.Ident).Name + _, index, _ := types.LookupFieldOrMethod(typ, true, g.pkg, fname) + + cur := typ + for _, step := range index[:len(index)-1] { + field := cur.Field(step) + g.use(field, by) + cur = typeutil.CoreType(field.Type()).(*types.Struct) + } + } + } + } else { + for _, elt := range node.Elts { + g.read(elt, by) + } + } + + case *ast.KeyValueExpr: + g.read(node.Key, by) + g.read(node.Value, by) + + case *ast.StarExpr: + g.read(node.X, by) + + case *ast.MapType: + g.read(node.Key, by) + g.read(node.Value, by) + + case *ast.FuncLit: + g.read(node.Type, by) + + // See graph.decl's handling of ast.FuncDecl for why this bit of code is necessary. + fn := g.info.TypeOf(node).(*types.Signature) + for params, i := fn.Params(), 0; i < params.Len(); i++ { + g.see(params.At(i), by) + if params.At(i).Name() == "" { + g.use(params.At(i), by) + } + } + + g.block(node.Body, by) + + case *ast.FuncType: + m := map[*types.Var]struct{}{} + if !g.opts.ParametersAreUsed { + m = map[*types.Var]struct{}{} + // seeScope marks all local variables in the scope as used, but we don't want to unconditionally use + // parameters, as this is controlled by Options.ParametersAreUsed. Pass seeScope a list of variables it + // should skip. + for _, f := range node.Params.List { + for _, name := range f.Names { + m[g.info.ObjectOf(name).(*types.Var)] = struct{}{} + } + } + } + g.seeScope(node, by, m) + + // (4.1) functions use all their arguments, return parameters and receivers + // (12.1) type parameters use their constraint type + g.read(node.TypeParams, by) + if g.opts.ParametersAreUsed { + g.read(node.Params, by) + } + g.read(node.Results, by) + + case *ast.FieldList: + if node == nil { + return + } + + // This branch is only hit for field lists enclosed by parentheses or square brackets, i.e. parameters. Fields + // (for structs) and method lists (for interfaces) are handled elsewhere. + + for _, field := range node.List { + if len(field.Names) == 0 { + g.read(field.Type, by) + } else { + for _, name := range field.Names { + // OPT(dh): instead of by -> name -> type, we could just emit by -> type. We don't care about the + // (un)usedness of parameters of any kind. + obj := g.info.ObjectOf(name) + g.use(obj, by) + g.read(field.Type, obj) + } + } + } + + case *ast.ChanType: + g.read(node.Value, by) + + case *ast.StructType: + // This is only used for anonymous struct types, not named ones. + + for _, field := range node.Fields.List { + if len(field.Names) == 0 { + // embedded field + + f := g.embeddedField(field.Type, by) + g.use(f, by) + } else { + for _, name := range field.Names { + // (11.1) anonymous struct types use all their fields + // OPT(dh): instead of by -> name -> type, we could just emit by -> type. If the type is used, then the fields are used. + obj := g.info.ObjectOf(name) + g.see(obj, by) + g.use(obj, by) + g.read(field.Type, g.info.ObjectOf(name)) + } + } + } + + case *ast.TypeAssertExpr: + g.read(node.X, by) + g.read(node.Type, by) + + case *ast.InterfaceType: + if len(node.Methods.List) != 0 { + g.interfaceTypes = append(g.interfaceTypes, g.info.TypeOf(node).(*types.Interface)) + } + for _, meth := range node.Methods.List { + switch len(meth.Names) { + case 0: + // Embedded type or type union + // (8.4) all embedded interfaces are marked as used + // (this also covers type sets) + + g.read(meth.Type, by) + case 1: + // Method + // (8.3) all interface methods are marked as used + obj := g.info.ObjectOf(meth.Names[0]) + g.see(obj, by) + g.use(obj, by) + g.read(meth.Type, obj) + default: + panic(fmt.Sprintf("unexpected number of names: %d", len(meth.Names))) + } + } + + case *ast.Ellipsis: + g.read(node.Elt, by) + + case *ast.CallExpr: + g.read(node.Fun, by) + for _, arg := range node.Args { + g.read(arg, by) + } + + // Handle conversions + conv := node + if len(conv.Args) != 1 || conv.Ellipsis.IsValid() { + return + } + + dst := g.info.TypeOf(conv.Fun) + src := g.info.TypeOf(conv.Args[0]) + + // XXX use DereferenceR instead + // XXX guard against infinite recursion in DereferenceR + tSrc := typeutil.CoreType(typeutil.Dereference(src)) + tDst := typeutil.CoreType(typeutil.Dereference(dst)) + stSrc, okSrc := tSrc.(*types.Struct) + stDst, okDst := tDst.(*types.Struct) + if okDst && okSrc { + // Converting between two structs. The fields are + // relevant for the conversion, but only if the + // fields are also used outside of the conversion. + // Mark fields as used by each other. + + assert(stDst.NumFields() == stSrc.NumFields()) + for i := 0; i < stDst.NumFields(); i++ { + // (5.1) when converting between two equivalent structs, the fields in + // either struct use each other. the fields are relevant for the + // conversion, but only if the fields are also accessed outside the + // conversion. + g.use(stDst.Field(i), stSrc.Field(i)) + g.use(stSrc.Field(i), stDst.Field(i)) + } + } else if okSrc && tDst == types.Typ[types.UnsafePointer] { + // (5.2) when converting to or from unsafe.Pointer, mark all fields as used. + g.useAllFieldsRecursively(stSrc, by) + } else if okDst && tSrc == types.Typ[types.UnsafePointer] { + // (5.2) when converting to or from unsafe.Pointer, mark all fields as used. + g.useAllFieldsRecursively(stDst, by) + } + + default: + lint.ExhaustiveTypeSwitch(node) + } +} + +func (g *graph) useAllFieldsRecursively(typ types.Type, by types.Object) { + switch typ := typ.Underlying().(type) { + case *types.Struct: + for field := range typ.Fields() { + g.use(field, by) + g.useAllFieldsRecursively(field.Type(), by) + } + case *types.Array: + g.useAllFieldsRecursively(typ.Elem(), by) + default: + return + } +} + +func (g *graph) write(node ast.Node, by types.Object) { + if node == nil { + return + } + + switch node := node.(type) { + case *ast.Ident: + obj := g.info.ObjectOf(node) + if obj == nil { + // This can happen for `switch x := v.(type)`, where that x doesn't have an object + return + } + + // (4.9) functions use package-level variables they assign to iff in tests (sinks for benchmarks) + // (9.7) variable _reads_ use variables, writes do not, except in tests + path := g.fset.File(obj.Pos()).Name() + if strings.HasSuffix(path, "_test.go") { + if isGlobal(obj) { + g.use(obj, by) + } + } + + case *ast.IndexExpr: + g.read(node.X, by) + g.read(node.Index, by) + + case *ast.SelectorExpr: + if g.opts.FieldWritesAreUses { + // Writing to a field constitutes a use. See https://staticcheck.dev/issues/288 for some discussion on that. + // + // This code can also get triggered by qualified package variables, in which case it doesn't matter what we do, + // because the object is in another package. + // + // FIXME(dh): ^ isn't true if we track usedness of exported identifiers + g.readSelectorExpr(node, by) + } else { + g.read(node.X, by) + g.write(node.Sel, by) + } + + case *ast.StarExpr: + g.read(node.X, by) + + case *ast.ParenExpr: + g.write(node.X, by) + + default: + lint.ExhaustiveTypeSwitch(node) + } +} + +// readSelectorExpr reads all elements of a selector expression, including implicit fields. +func (g *graph) readSelectorExpr(sel *ast.SelectorExpr, by types.Object) { + // cover AST-based accesses + g.read(sel.X, by) + g.read(sel.Sel, by) + + tsel, ok := g.info.Selections[sel] + if !ok { + return + } + g.readSelection(tsel, by) +} + +func (g *graph) readSelection(sel *types.Selection, by types.Object) { + indices := sel.Index() + base := sel.Recv() + for _, idx := range indices[:len(indices)-1] { + // XXX do we need core types here? + field := typeutil.Dereference(base.Underlying()).Underlying().(*types.Struct).Field(idx) + g.use(field, by) + base = field.Type() + } + + g.use(sel.Obj(), by) +} + +func (g *graph) block(block *ast.BlockStmt, by types.Object) { + if block == nil { + return + } + + g.seeScope(block, by, nil) + for _, stmt := range block.List { + g.stmt(stmt, by) + } +} + +func isGlobal(obj types.Object) bool { + return obj.Parent() == obj.Pkg().Scope() +} + +func (g *graph) decl(decl ast.Decl, by types.Object) { + switch decl := decl.(type) { + case *ast.GenDecl: + switch decl.Tok { + case token.IMPORT: + // Nothing to do + + case token.CONST: + for _, spec := range decl.Specs { + vspec := spec.(*ast.ValueSpec) + assert(len(vspec.Values) == 0 || len(vspec.Values) == len(vspec.Names)) + for i, name := range vspec.Names { + obj := g.info.ObjectOf(name) + g.see(obj, by) + g.read(vspec.Type, obj) + + if len(vspec.Values) != 0 { + g.read(vspec.Values[i], obj) + } + + if name.Name == "_" { + // (9.9) objects named the blank identifier are used + g.use(obj, by) + } else if token.IsExported(name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed { + g.use(obj, nil) + } + } + } + + groups := astutil.GroupSpecs(g.fset, decl.Specs) + for _, group := range groups { + // (10.1) if one constant out of a block of constants is used, mark all of them used + // + // We encode this as a ring. If we have a constant group 'const ( a; b; c )', then we'll produce the + // following graph: a -> b -> c -> a. + + var first, prev, last types.Object + for _, spec := range group { + for _, name := range spec.(*ast.ValueSpec).Names { + if name.Name == "_" { + // Having a blank constant in a group doesn't mark the whole group as used + continue + } + + obj := g.info.ObjectOf(name) + if first == nil { + first = obj + } else { + g.use(obj, prev) + } + prev = obj + last = obj + } + } + if first != nil && first != last { + g.use(first, last) + } + } + + case token.TYPE: + for _, spec := range decl.Specs { + tspec := spec.(*ast.TypeSpec) + obj := g.info.ObjectOf(tspec.Name).(*types.TypeName) + g.see(obj, by) + g.seeScope(tspec, obj, nil) + if !tspec.Assign.IsValid() { + g.namedTypes = append(g.namedTypes, obj) + } + if token.IsExported(tspec.Name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed { + // (1.1) packages use exported named types + g.use(g.info.ObjectOf(tspec.Name), nil) + } + + // (2.5) named types use all their type parameters + g.read(tspec.TypeParams, obj) + + g.namedType(obj, tspec.Type) + + if tspec.Name.Name == "_" { + // (9.9) objects named the blank identifier are used + g.use(obj, by) + } + } + + case token.VAR: + // We cannot rely on types.Initializer for package-level variables because + // - initializers are only tracked for variables that are actually initialized + // - we want to see the AST of the type, if specified, not just the rhs + + for _, spec := range decl.Specs { + vspec := spec.(*ast.ValueSpec) + for i, name := range vspec.Names { + obj := g.info.ObjectOf(name) + g.see(obj, by) + // variables and constants use their types + g.read(vspec.Type, obj) + + if len(vspec.Names) == len(vspec.Values) { + // One value per variable + g.read(vspec.Values[i], obj) + } else if len(vspec.Values) != 0 { + // Multiple variables initialized with a single rhs + // assert(len(vspec.Values) == 1) + if len(vspec.Values) != 1 { + panic(g.fset.PositionFor(vspec.Pos(), false)) + } + g.read(vspec.Values[0], obj) + } + + if token.IsExported(name.Name) && isGlobal(obj) && g.opts.ExportedIsUsed { + // (1.3) packages use exported variables + g.use(obj, nil) + } + + if name.Name == "_" { + // (9.9) objects named the blank identifier are used + g.use(obj, by) + } + } + } + + default: + panic(fmt.Sprintf("unexpected token %s", decl.Tok)) + } + + case *ast.FuncDecl: + obj := g.info.ObjectOf(decl.Name).(*types.Func).Origin() + g.see(obj, nil) + + if token.IsExported(decl.Name.Name) && g.opts.ExportedIsUsed { + if decl.Recv == nil { + // (1.2) packages use exported functions + g.use(obj, nil) + } + } else if decl.Name.Name == "init" { + // (1.5) packages use init functions + g.use(obj, nil) + } else if decl.Name.Name == "main" && g.pkg.Name() == "main" { + // (1.7) packages use the main function iff in the main package + g.use(obj, nil) + } else if g.pkg.Path() == "runtime" && runtimeFuncs[decl.Name.Name] { + // (9.8) runtime functions that may be called from user code via the compiler + g.use(obj, nil) + } else if g.pkg.Path() == "runtime/coverage" && runtimeCoverageFuncs[decl.Name.Name] { + // (9.8) runtime functions that may be called from user code via the compiler + g.use(obj, nil) + } + + // (4.1) functions use their receivers + g.read(decl.Recv, obj) + g.read(decl.Type, obj) + g.block(decl.Body, obj) + + // g.read(decl.Type) will ultimately call g.seeScopes and see parameters that way. But because it relies + // entirely on the AST, it cannot resolve unnamed parameters to types.Object. For that reason we explicitly + // handle arguments here, as well as for FuncLits elsewhere. + // + // g.seeScopes can't get to the types.Signature for this function because there is no mapping from ast.FuncType to + // types.Signature, only from ast.Ident to types.Signature. + // + // This code is only really relevant when Options.ParametersAreUsed is false. Otherwise, all parameters are + // considered used, and if we never see a parameter then no harm done (we still see its type separately). + fn := g.info.TypeOf(decl.Name).(*types.Signature) + for params, i := fn.Params(), 0; i < params.Len(); i++ { + g.see(params.At(i), obj) + if params.At(i).Name() == "" { + g.use(params.At(i), obj) + } + } + + if decl.Name.Name == "_" { + // (9.9) objects named the blank identifier are used + g.use(obj, nil) + } + + if decl.Doc != nil { + for _, cmt := range decl.Doc.List { + if strings.HasPrefix(cmt.Text, "//go:cgo_export_") { + // (1.6) packages use functions exported to cgo + g.use(obj, nil) + } + } + } + + default: + // We do not cover BadDecl, but we shouldn't ever see one of those + lint.ExhaustiveTypeSwitch(decl) + } +} + +// seeScope sees all objects in node's scope. If Options.LocalVariablesAreUsed is true, all objects that aren't fields +// are marked as used. Variables set in skipLvars will not be marked as used. +func (g *graph) seeScope(node ast.Node, by types.Object, skipLvars map[*types.Var]struct{}) { + // A note on functions and scopes: for a function declaration, the body's BlockStmt can't be found in + // types.Info.Scopes. Instead, the FuncType can, and that scope will contain receivers, parameters, return + // parameters and immediate local variables. + + scope := g.info.Scopes[node] + if scope == nil { + return + } + for _, name := range scope.Names() { + obj := scope.Lookup(name) + g.see(obj, by) + + if g.opts.LocalVariablesAreUsed { + if obj, ok := obj.(*types.Var); ok && !obj.IsField() { + if _, ok := skipLvars[obj]; !ok { + g.use(obj, by) + } + } + } + } +} + +func (g *graph) stmt(stmt ast.Stmt, by types.Object) { + if stmt == nil { + return + } + + for { + // We don't care about labels, so unwrap LabeledStmts. Note that a label can itself be labeled. + if labeled, ok := stmt.(*ast.LabeledStmt); ok { + stmt = labeled.Stmt + } else { + break + } + } + + switch stmt := stmt.(type) { + case *ast.AssignStmt: + for _, lhs := range stmt.Lhs { + g.write(lhs, by) + } + for _, rhs := range stmt.Rhs { + // Note: it would be more accurate to have the rhs used by the lhs, but it ultimately doesn't matter, + // because local variables always end up used, anyway. + // + // TODO(dh): we'll have to change that once we allow tracking the usedness of parameters + g.read(rhs, by) + } + + case *ast.BlockStmt: + g.block(stmt, by) + + case *ast.BranchStmt: + // Nothing to do + + case *ast.DeclStmt: + g.decl(stmt.Decl, by) + + case *ast.DeferStmt: + g.read(stmt.Call, by) + + case *ast.ExprStmt: + g.read(stmt.X, by) + + case *ast.ForStmt: + g.seeScope(stmt, by, nil) + g.stmt(stmt.Init, by) + g.read(stmt.Cond, by) + g.stmt(stmt.Post, by) + g.block(stmt.Body, by) + + case *ast.GoStmt: + g.read(stmt.Call, by) + + case *ast.IfStmt: + g.seeScope(stmt, by, nil) + g.stmt(stmt.Init, by) + g.read(stmt.Cond, by) + g.block(stmt.Body, by) + g.stmt(stmt.Else, by) + + case *ast.IncDecStmt: + if g.opts.PostStatementsAreReads { + g.read(stmt.X, by) + g.write(stmt.X, by) + } else { + // We treat post-increment as a write only. This ends up using fields, and sinks in tests, but not other + // variables. + g.write(stmt.X, by) + } + + case *ast.RangeStmt: + g.seeScope(stmt, by, nil) + + g.write(stmt.Key, by) + g.write(stmt.Value, by) + g.read(stmt.X, by) + g.block(stmt.Body, by) + + case *ast.ReturnStmt: + for _, ret := range stmt.Results { + g.read(ret, by) + } + + case *ast.SelectStmt: + for _, clause_ := range stmt.Body.List { + clause := clause_.(*ast.CommClause) + g.seeScope(clause, by, nil) + switch comm := clause.Comm.(type) { + case *ast.SendStmt: + g.read(comm.Chan, by) + g.read(comm.Value, by) + case *ast.ExprStmt: + g.read(ast.Unparen(comm.X).(*ast.UnaryExpr).X, by) + case *ast.AssignStmt: + for _, lhs := range comm.Lhs { + g.write(lhs, by) + } + for _, rhs := range comm.Rhs { + g.read(rhs, by) + } + case nil: + default: + lint.ExhaustiveTypeSwitch(comm) + } + for _, body := range clause.Body { + g.stmt(body, by) + } + } + + case *ast.SendStmt: + g.read(stmt.Chan, by) + g.read(stmt.Value, by) + + case *ast.SwitchStmt: + g.seeScope(stmt, by, nil) + g.stmt(stmt.Init, by) + g.read(stmt.Tag, by) + for _, clause_ := range stmt.Body.List { + clause := clause_.(*ast.CaseClause) + g.seeScope(clause, by, nil) + for _, expr := range clause.List { + g.read(expr, by) + } + for _, body := range clause.Body { + g.stmt(body, by) + } + } + + case *ast.TypeSwitchStmt: + g.seeScope(stmt, by, nil) + g.stmt(stmt.Init, by) + g.stmt(stmt.Assign, by) + for _, clause_ := range stmt.Body.List { + clause := clause_.(*ast.CaseClause) + g.seeScope(clause, by, nil) + for _, expr := range clause.List { + g.read(expr, by) + } + for _, body := range clause.Body { + g.stmt(body, by) + } + } + + case *ast.EmptyStmt: + // Nothing to do + + default: + lint.ExhaustiveTypeSwitch(stmt) + } +} + +// embeddedField sees the field declared by the embedded field node, and marks the type as used by the field. +// +// Embedded fields are special in two ways: they don't have names, so we don't have immediate access to an ast.Ident to +// resolve to the field's types.Var and need to instead walk the AST, and we cannot use g.read on the type because +// eventually we do get to an ast.Ident, and ObjectOf resolves embedded fields to the field they declare, not the type. +// That's why we have code specially for handling embedded fields. +func (g *graph) embeddedField(node ast.Node, by types.Object) *types.Var { + // We need to traverse the tree to find the ast.Ident, but all the nodes we traverse should be used by the object we + // get once we resolve the ident. Collect the nodes and process them once we've found the ident. + nodes := make([]ast.Node, 0, 4) + for { + switch node_ := node.(type) { + case *ast.Ident: + // obj is the field + obj := g.info.ObjectOf(node_).(*types.Var) + // the field is declared by the enclosing type + g.see(obj, by) + for _, n := range nodes { + g.read(n, obj) + } + + if tname, ok := g.info.Uses[node_].(*types.TypeName); ok && tname.IsAlias() { + // When embedding an alias we want to use the alias, not what the alias points to. + g.use(tname, obj) + } else { + switch typ := typeutil.Dereference(g.info.TypeOf(node_)).(type) { + case *types.Named: + // (7.2) fields use their types + g.use(typ.Obj(), obj) + case *types.Basic: + // Nothing to do + default: + // Other types are only possible for aliases, which we've already handled + lint.ExhaustiveTypeSwitch(typ) + } + } + return obj + case *ast.StarExpr: + node = node_.X + case *ast.SelectorExpr: + node = node_.Sel + nodes = append(nodes, node_.X) + case *ast.IndexExpr: + node = node_.X + nodes = append(nodes, node_.Index) + case *ast.IndexListExpr: + node = node_.X + default: + lint.ExhaustiveTypeSwitch(node_) + } + } +} + +// isNoCopyType reports whether a type represents the NoCopy sentinel +// type. The NoCopy type is a named struct with no fields and exactly +// one method `func Lock()` that is empty. +// +// FIXME(dh): currently we're not checking that the function body is +// empty. +func isNoCopyType(typ types.Type) bool { + st, ok := typ.Underlying().(*types.Struct) + if !ok { + return false + } + if st.NumFields() != 0 { + return false + } + + named, ok := types.Unalias(typ).(*types.Named) + if !ok { + return false + } + switch num := named.NumMethods(); num { + case 1, 2: + for i := range num { + meth := named.Method(i) + if meth.Name() != "Lock" && meth.Name() != "Unlock" { + return false + } + sig := meth.Type().(*types.Signature) + if sig.Params().Len() != 0 || sig.Results().Len() != 0 { + return false + } + } + default: + return false + } + return true +} + +func (g *graph) namedType(typ *types.TypeName, spec ast.Expr) { + // (2.2) named types use the type they're based on + + if st, ok := spec.(*ast.StructType); ok { + var hasHostLayout bool + + // Named structs are special in that their unexported fields are only + // used if they're being written to. That is, the fields are not used by + // the named type itself, nor are the types of the fields. + for _, field := range st.Fields.List { + seen := map[*types.Struct]struct{}{} + // For `type x struct { *x; F int }`, don't visit the embedded x + seen[g.info.TypeOf(st).(*types.Struct)] = struct{}{} + var hasExportedField func(t types.Type) bool + hasExportedField = func(T types.Type) bool { + t, ok := typeutil.Dereference(T).Underlying().(*types.Struct) + if !ok { + return false + } + if _, ok := seen[t]; ok { + return false + } + seen[t] = struct{}{} + for field := range t.Fields() { + if field.Exported() { + return true + } + if field.Embedded() && hasExportedField(field.Type()) { + return true + } + } + return false + } + + if len(field.Names) == 0 { + fieldVar := g.embeddedField(field.Type, typ) + if token.IsExported(fieldVar.Name()) && g.opts.ExportedIsUsed { + // (6.2) structs use exported fields + g.use(fieldVar, typ) + } + if g.opts.ExportedIsUsed && g.opts.ExportedFieldsAreUsed && hasExportedField(fieldVar.Type()) { + // (6.5) structs use embedded structs that have exported fields (recursively) + g.use(fieldVar, typ) + } + } else { + for _, name := range field.Names { + obj := g.info.ObjectOf(name) + g.see(obj, typ) + // (7.2) fields use their types + // + // This handles aliases correctly because ObjectOf(alias) returns the TypeName of the alias, not + // what the alias points to. + g.read(field.Type, obj) + if name.Name == "_" { + // (9.9) objects named the blank identifier are used + g.use(obj, typ) + } else if token.IsExported(name.Name) && g.opts.ExportedIsUsed { + // (6.2) structs use exported fields + g.use(obj, typ) + } + + if isNoCopyType(obj.Type()) { + // (6.1) structs use fields of type NoCopy sentinel + g.use(obj, typ) + } + } + } + + // (6.6) if the struct has a field of type structs.HostLayout, then + // this signals that all fields are relevant to match some + // externally specified memory layout. + // + // This augments the 5.2 heuristic of using all fields when + // converting via unsafe.Pointer. For example, 5.2 doesn't currently + // handle conversions involving more than one level of pointer + // indirection (although it probably should). Another example that + // doesn't involve the use of unsafe at all is exporting symbols for + // use by C libraries. + // + // The actual requirements for the use of structs.HostLayout fields + // haven't been determined yet. It's an open question whether named + // types of underlying type structs.HostLayout, aliases of it, + // generic instantiations, or embedding structs that themselves + // contain a HostLayout field count as valid uses of the marker (see + // https://golang.org/issues/66408#issuecomment-2120644459) + // + // For now, we require a struct to have a field of type + // structs.HostLayout or an alias of it, where the field itself may + // be embedded. We don't handle fields whose types are type + // parameters. + fieldType := types.Unalias(g.info.TypeOf(field.Type)) + if fieldType, ok := fieldType.(*types.Named); ok { + obj := fieldType.Obj() + if obj.Name() == "HostLayout" && obj.Pkg().Path() == "structs" { + hasHostLayout = true + } + } + } + + // For 6.6. + if hasHostLayout { + g.useAllFieldsRecursively(typ.Type(), typ) + } + } else { + g.read(spec, typ) + } +} + +func (g *SerializedGraph) color(rootID NodeID, states []nodeState) { + root := g.nodes[rootID] + if states[rootID].seen() { + return + } + states[rootID] |= nodeStateSeen + for _, n := range root.uses { + g.color(n, states) + } +} + +type Object struct { + Name string + ShortName string + // OPT(dh): use an enum for the kind + Kind string + Path ObjectPath + Position token.Position + DisplayPosition token.Position +} + +func (g *SerializedGraph) Results() Result { + // XXX objectpath does not return paths for unexported objects, which means that if we analyze the same code twice + // (e.g. normal and test variant), then some objects will appear multiple times, but may not be used identically. we + // have to deduplicate based on the token.Position. Actually we have to do that, anyway, because we may flag types + // local to functions. Those are probably always both used or both unused, but we don't want to flag them twice, + // either. + // + // Note, however, that we still need objectpaths to deduplicate exported identifiers when analyzing independent + // packages in whole-program mode, because if package A uses an object from package B, B will have been imported + // from export data, and we will not have column information. + // + // XXX ^ document that design requirement. + + states := g.colorAndQuieten() + + var res Result + // OPT(dh): can we find meaningful initial capacities for the used and unused slices? + for _, n := range g.nodes[1:] { + state := states[n.id] + if state.seen() { + res.Used = append(res.Used, n.obj) + } else if state.quiet() { + res.Quiet = append(res.Quiet, n.obj) + } else { + res.Unused = append(res.Unused, n.obj) + } + } + + return res +} + +func (g *SerializedGraph) colorAndQuieten() []nodeState { + states := make([]nodeState, len(g.nodes)+1) + g.color(0, states) + + var quieten func(id NodeID) + quieten = func(id NodeID) { + states[id] |= nodeStateQuiet + for _, owned := range g.nodes[id].owns { + quieten(owned) + } + } + + for _, n := range g.nodes { + if states[n.id].seen() { + continue + } + for _, owned := range n.owns { + quieten(owned) + } + } + + return states +} + +// Dot formats a graph in Graphviz dot format. +func (g *SerializedGraph) Dot() string { + b := &strings.Builder{} + states := g.colorAndQuieten() + // Note: We use addresses in our node names. This only works as long as Go's garbage collector doesn't move + // memory around in the middle of our debug printing. + debugNode := func(n Node) { + if n.id == 0 { + fmt.Fprintf(b, "n%d [label=\"Root\"];\n", n.id) + } else { + color := "red" + if states[n.id].seen() { + color = "green" + } else if states[n.id].quiet() { + color = "grey" + } + label := fmt.Sprintf("%s %s\n%s", n.obj.Kind, n.obj.Name, n.obj.Position) + fmt.Fprintf(b, "n%d [label=%q, color=%q];\n", n.id, label, color) + } + for _, e := range n.uses { + fmt.Fprintf(b, "n%d -> n%d;\n", n.id, e) + } + + for _, owned := range n.owns { + fmt.Fprintf(b, "n%d -> n%d [style=dashed];\n", n.id, owned) + } + } + + fmt.Fprintf(b, "digraph{\n") + for _, v := range g.nodes { + debugNode(v) + } + + fmt.Fprintf(b, "}\n") + + return b.String() +} + +func Graph(fset *token.FileSet, + files []*ast.File, + pkg *types.Package, + info *types.Info, + directives []lint.Directive, + generated map[string]generated.Generator, + opts Options, +) []Node { + g := newGraph(fset, files, pkg, info, directives, generated, opts) + g.entry() + return g.nodes +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 93d033c..c6b850a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,8 @@ -# github.com/emersion/go-imap/v2 v2.0.0-beta.7 +# github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c +## explicit; go 1.18 +github.com/BurntSushi/toml +github.com/BurntSushi/toml/internal +# github.com/emersion/go-imap/v2 v2.0.0-beta.8 ## explicit; go 1.18 github.com/emersion/go-imap/v2 github.com/emersion/go-imap/v2/imapclient @@ -15,24 +19,27 @@ github.com/emersion/go-message/textproto # github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 ## explicit; go 1.12 github.com/emersion/go-sasl -# github.com/sashabaranov/go-openai v1.41.2 +# github.com/sashabaranov/go-openai v1.42.0 ## explicit; go 1.18 github.com/sashabaranov/go-openai github.com/sashabaranov/go-openai/internal github.com/sashabaranov/go-openai/jsonschema -# golang.org/x/mod v0.31.0 -## explicit; go 1.24.0 +# golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 +## explicit; go 1.18 +golang.org/x/exp/typeparams +# golang.org/x/mod v0.39.0 +## explicit; go 1.25.0 golang.org/x/mod/internal/lazyregexp golang.org/x/mod/module golang.org/x/mod/semver -# golang.org/x/sync v0.19.0 -## explicit; go 1.24.0 +# golang.org/x/sync v0.22.0 +## explicit; go 1.25.0 golang.org/x/sync/errgroup -# golang.org/x/sys v0.39.0 -## explicit; go 1.24.0 +# golang.org/x/sys v0.47.0 +## explicit; go 1.25.0 golang.org/x/sys/windows -# golang.org/x/telemetry v0.0.0-20251203150158-8fff8a5912fc -## explicit; go 1.24.0 +# golang.org/x/telemetry v0.0.0-20260811182544-a038080d80e5 +## explicit; go 1.25.0 golang.org/x/telemetry golang.org/x/telemetry/counter golang.org/x/telemetry/internal/config @@ -42,8 +49,8 @@ golang.org/x/telemetry/internal/crashmonitor golang.org/x/telemetry/internal/mmap golang.org/x/telemetry/internal/telemetry golang.org/x/telemetry/internal/upload -# golang.org/x/text v0.32.0 -## explicit; go 1.24.0 +# golang.org/x/text v0.41.0 +## explicit; go 1.25.0 golang.org/x/text/encoding golang.org/x/text/encoding/charmap golang.org/x/text/encoding/htmlindex @@ -62,10 +69,13 @@ golang.org/x/text/internal/utf8internal golang.org/x/text/language golang.org/x/text/runes golang.org/x/text/transform -# golang.org/x/tools v0.40.0 -## explicit; go 1.24.0 +# golang.org/x/tools v0.49.0 +## explicit; go 1.25.0 golang.org/x/tools/cmd/deadcode golang.org/x/tools/cmd/goimports +golang.org/x/tools/go/analysis +golang.org/x/tools/go/analysis/passes/ctrlflow +golang.org/x/tools/go/analysis/passes/inspect golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/ast/edge golang.org/x/tools/go/ast/inspector @@ -76,6 +86,7 @@ golang.org/x/tools/go/callgraph/internal/chautil golang.org/x/tools/go/callgraph/rta golang.org/x/tools/go/callgraph/vta golang.org/x/tools/go/callgraph/vta/internal/trie +golang.org/x/tools/go/cfg golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/cgo golang.org/x/tools/go/loader @@ -94,17 +105,15 @@ golang.org/x/tools/internal/gocommand golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/imports golang.org/x/tools/internal/modindex +golang.org/x/tools/internal/moremaps golang.org/x/tools/internal/packagesinternal golang.org/x/tools/internal/pkgbits -golang.org/x/tools/internal/ssainternal golang.org/x/tools/internal/stdlib golang.org/x/tools/internal/typeparams golang.org/x/tools/internal/typesinternal golang.org/x/tools/internal/versions -# golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated -## explicit; go 1.23.0 -# golang.org/x/vuln v1.1.4 -## explicit; go 1.22.0 +# golang.org/x/vuln v1.7.0 +## explicit; go 1.25.0 golang.org/x/vuln/cmd/govulncheck golang.org/x/vuln/internal golang.org/x/vuln/internal/buildinfo @@ -125,3 +134,218 @@ golang.org/x/vuln/scan # gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 +# honnef.co/go/tools v0.8.0 +## explicit; go 1.26.0 +honnef.co/go/tools/analysis/callcheck +honnef.co/go/tools/analysis/code +honnef.co/go/tools/analysis/dfa +honnef.co/go/tools/analysis/dfa/dense +honnef.co/go/tools/analysis/edit +honnef.co/go/tools/analysis/facts/deprecated +honnef.co/go/tools/analysis/facts/directives +honnef.co/go/tools/analysis/facts/generated +honnef.co/go/tools/analysis/facts/nilness +honnef.co/go/tools/analysis/facts/purity +honnef.co/go/tools/analysis/facts/tokenfile +honnef.co/go/tools/analysis/lint +honnef.co/go/tools/analysis/report +honnef.co/go/tools/cmd/staticcheck +honnef.co/go/tools/config +honnef.co/go/tools/go/ast/astutil +honnef.co/go/tools/go/buildid +honnef.co/go/tools/go/ir +honnef.co/go/tools/go/ir/irutil +honnef.co/go/tools/go/loader +honnef.co/go/tools/go/types/typeutil +honnef.co/go/tools/internal/iterutil +honnef.co/go/tools/internal/passes/buildir +honnef.co/go/tools/internal/renameio +honnef.co/go/tools/internal/robustio +honnef.co/go/tools/internal/sharedcheck +honnef.co/go/tools/internal/sync +honnef.co/go/tools/internal/xtools-internal/aliases +honnef.co/go/tools/internal/xtools-internal/analysis/typeindex +honnef.co/go/tools/internal/xtools-internal/astutil +honnef.co/go/tools/internal/xtools-internal/graph +honnef.co/go/tools/internal/xtools-internal/moreiters +honnef.co/go/tools/internal/xtools-internal/typeparams +honnef.co/go/tools/internal/xtools-internal/typesinternal +honnef.co/go/tools/internal/xtools-internal/typesinternal/typeindex +honnef.co/go/tools/internal/xtools-internal/versions +honnef.co/go/tools/knowledge +honnef.co/go/tools/lintcmd +honnef.co/go/tools/lintcmd/cache +honnef.co/go/tools/lintcmd/cache/cacheprog +honnef.co/go/tools/lintcmd/runner +honnef.co/go/tools/lintcmd/version +honnef.co/go/tools/pattern +honnef.co/go/tools/printf +honnef.co/go/tools/quickfix +honnef.co/go/tools/quickfix/qf1001 +honnef.co/go/tools/quickfix/qf1002 +honnef.co/go/tools/quickfix/qf1003 +honnef.co/go/tools/quickfix/qf1004 +honnef.co/go/tools/quickfix/qf1005 +honnef.co/go/tools/quickfix/qf1006 +honnef.co/go/tools/quickfix/qf1007 +honnef.co/go/tools/quickfix/qf1008 +honnef.co/go/tools/quickfix/qf1009 +honnef.co/go/tools/quickfix/qf1010 +honnef.co/go/tools/quickfix/qf1011 +honnef.co/go/tools/quickfix/qf1012 +honnef.co/go/tools/sarif +honnef.co/go/tools/simple +honnef.co/go/tools/simple/s1000 +honnef.co/go/tools/simple/s1001 +honnef.co/go/tools/simple/s1002 +honnef.co/go/tools/simple/s1003 +honnef.co/go/tools/simple/s1004 +honnef.co/go/tools/simple/s1005 +honnef.co/go/tools/simple/s1006 +honnef.co/go/tools/simple/s1007 +honnef.co/go/tools/simple/s1008 +honnef.co/go/tools/simple/s1009 +honnef.co/go/tools/simple/s1010 +honnef.co/go/tools/simple/s1011 +honnef.co/go/tools/simple/s1012 +honnef.co/go/tools/simple/s1016 +honnef.co/go/tools/simple/s1017 +honnef.co/go/tools/simple/s1018 +honnef.co/go/tools/simple/s1019 +honnef.co/go/tools/simple/s1020 +honnef.co/go/tools/simple/s1021 +honnef.co/go/tools/simple/s1023 +honnef.co/go/tools/simple/s1024 +honnef.co/go/tools/simple/s1025 +honnef.co/go/tools/simple/s1028 +honnef.co/go/tools/simple/s1029 +honnef.co/go/tools/simple/s1030 +honnef.co/go/tools/simple/s1031 +honnef.co/go/tools/simple/s1032 +honnef.co/go/tools/simple/s1033 +honnef.co/go/tools/simple/s1034 +honnef.co/go/tools/simple/s1035 +honnef.co/go/tools/simple/s1036 +honnef.co/go/tools/simple/s1037 +honnef.co/go/tools/simple/s1038 +honnef.co/go/tools/simple/s1039 +honnef.co/go/tools/simple/s1040 +honnef.co/go/tools/staticcheck +honnef.co/go/tools/staticcheck/fakejson +honnef.co/go/tools/staticcheck/fakereflect +honnef.co/go/tools/staticcheck/fakexml +honnef.co/go/tools/staticcheck/sa1000 +honnef.co/go/tools/staticcheck/sa1001 +honnef.co/go/tools/staticcheck/sa1002 +honnef.co/go/tools/staticcheck/sa1003 +honnef.co/go/tools/staticcheck/sa1004 +honnef.co/go/tools/staticcheck/sa1005 +honnef.co/go/tools/staticcheck/sa1006 +honnef.co/go/tools/staticcheck/sa1007 +honnef.co/go/tools/staticcheck/sa1008 +honnef.co/go/tools/staticcheck/sa1010 +honnef.co/go/tools/staticcheck/sa1011 +honnef.co/go/tools/staticcheck/sa1012 +honnef.co/go/tools/staticcheck/sa1013 +honnef.co/go/tools/staticcheck/sa1014 +honnef.co/go/tools/staticcheck/sa1015 +honnef.co/go/tools/staticcheck/sa1016 +honnef.co/go/tools/staticcheck/sa1017 +honnef.co/go/tools/staticcheck/sa1018 +honnef.co/go/tools/staticcheck/sa1019 +honnef.co/go/tools/staticcheck/sa1020 +honnef.co/go/tools/staticcheck/sa1021 +honnef.co/go/tools/staticcheck/sa1023 +honnef.co/go/tools/staticcheck/sa1024 +honnef.co/go/tools/staticcheck/sa1025 +honnef.co/go/tools/staticcheck/sa1026 +honnef.co/go/tools/staticcheck/sa1027 +honnef.co/go/tools/staticcheck/sa1028 +honnef.co/go/tools/staticcheck/sa1029 +honnef.co/go/tools/staticcheck/sa1030 +honnef.co/go/tools/staticcheck/sa1031 +honnef.co/go/tools/staticcheck/sa1032 +honnef.co/go/tools/staticcheck/sa2000 +honnef.co/go/tools/staticcheck/sa2001 +honnef.co/go/tools/staticcheck/sa2002 +honnef.co/go/tools/staticcheck/sa2003 +honnef.co/go/tools/staticcheck/sa3000 +honnef.co/go/tools/staticcheck/sa3001 +honnef.co/go/tools/staticcheck/sa4000 +honnef.co/go/tools/staticcheck/sa4001 +honnef.co/go/tools/staticcheck/sa4003 +honnef.co/go/tools/staticcheck/sa4004 +honnef.co/go/tools/staticcheck/sa4005 +honnef.co/go/tools/staticcheck/sa4006 +honnef.co/go/tools/staticcheck/sa4008 +honnef.co/go/tools/staticcheck/sa4009 +honnef.co/go/tools/staticcheck/sa4010 +honnef.co/go/tools/staticcheck/sa4011 +honnef.co/go/tools/staticcheck/sa4012 +honnef.co/go/tools/staticcheck/sa4013 +honnef.co/go/tools/staticcheck/sa4014 +honnef.co/go/tools/staticcheck/sa4015 +honnef.co/go/tools/staticcheck/sa4016 +honnef.co/go/tools/staticcheck/sa4017 +honnef.co/go/tools/staticcheck/sa4018 +honnef.co/go/tools/staticcheck/sa4019 +honnef.co/go/tools/staticcheck/sa4020 +honnef.co/go/tools/staticcheck/sa4021 +honnef.co/go/tools/staticcheck/sa4022 +honnef.co/go/tools/staticcheck/sa4023 +honnef.co/go/tools/staticcheck/sa4024 +honnef.co/go/tools/staticcheck/sa4025 +honnef.co/go/tools/staticcheck/sa4026 +honnef.co/go/tools/staticcheck/sa4027 +honnef.co/go/tools/staticcheck/sa4028 +honnef.co/go/tools/staticcheck/sa4029 +honnef.co/go/tools/staticcheck/sa4030 +honnef.co/go/tools/staticcheck/sa4031 +honnef.co/go/tools/staticcheck/sa4032 +honnef.co/go/tools/staticcheck/sa5000 +honnef.co/go/tools/staticcheck/sa5001 +honnef.co/go/tools/staticcheck/sa5002 +honnef.co/go/tools/staticcheck/sa5003 +honnef.co/go/tools/staticcheck/sa5004 +honnef.co/go/tools/staticcheck/sa5005 +honnef.co/go/tools/staticcheck/sa5007 +honnef.co/go/tools/staticcheck/sa5008 +honnef.co/go/tools/staticcheck/sa5009 +honnef.co/go/tools/staticcheck/sa5010 +honnef.co/go/tools/staticcheck/sa5012 +honnef.co/go/tools/staticcheck/sa6000 +honnef.co/go/tools/staticcheck/sa6001 +honnef.co/go/tools/staticcheck/sa6002 +honnef.co/go/tools/staticcheck/sa6003 +honnef.co/go/tools/staticcheck/sa6005 +honnef.co/go/tools/staticcheck/sa6006 +honnef.co/go/tools/staticcheck/sa9001 +honnef.co/go/tools/staticcheck/sa9002 +honnef.co/go/tools/staticcheck/sa9003 +honnef.co/go/tools/staticcheck/sa9004 +honnef.co/go/tools/staticcheck/sa9005 +honnef.co/go/tools/staticcheck/sa9006 +honnef.co/go/tools/staticcheck/sa9007 +honnef.co/go/tools/staticcheck/sa9008 +honnef.co/go/tools/staticcheck/sa9009 +honnef.co/go/tools/staticcheck/sa9010 +honnef.co/go/tools/stylecheck +honnef.co/go/tools/stylecheck/st1000 +honnef.co/go/tools/stylecheck/st1001 +honnef.co/go/tools/stylecheck/st1003 +honnef.co/go/tools/stylecheck/st1005 +honnef.co/go/tools/stylecheck/st1006 +honnef.co/go/tools/stylecheck/st1008 +honnef.co/go/tools/stylecheck/st1011 +honnef.co/go/tools/stylecheck/st1012 +honnef.co/go/tools/stylecheck/st1013 +honnef.co/go/tools/stylecheck/st1015 +honnef.co/go/tools/stylecheck/st1016 +honnef.co/go/tools/stylecheck/st1017 +honnef.co/go/tools/stylecheck/st1018 +honnef.co/go/tools/stylecheck/st1019 +honnef.co/go/tools/stylecheck/st1020 +honnef.co/go/tools/stylecheck/st1021 +honnef.co/go/tools/stylecheck/st1022 +honnef.co/go/tools/stylecheck/st1023 +honnef.co/go/tools/unused